From 68e549233ddba17ec686a0462e2630580ee3d2a7 Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@gmail.com>
Date: Thu, 15 Sep 2016 13:17:37 -0700
Subject: Revise bearer token code in controller, and add relay policy as a
 meta-data item presented to controller by nodes (to facilitate future
 meshiness).

---
 controller/EmbeddedNetworkController.cpp | 103 +++++++++++++++++++------------
 controller/EmbeddedNetworkController.hpp |   4 +-
 controller/README.md                     |   4 +-
 node/Network.cpp                         |   2 +
 node/NetworkConfig.hpp                   |  10 ++-
 5 files changed, 77 insertions(+), 46 deletions(-)

diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp
index 861792ed..53b345b4 100644
--- a/controller/EmbeddedNetworkController.cpp
+++ b/controller/EmbeddedNetworkController.cpp
@@ -566,42 +566,69 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest(
 
 	// Determine whether and how member is authorized
 	const char *authorizedBy = (const char *)0;
-	if (!_jB(network["private"],true)) {
+	if (_jB(member["authorized"],false)) {
+		authorizedBy = "memberIsAuthorized";
+	} else if (!_jB(network["private"],true)) {
 		authorizedBy = "networkIsPublic";
-		// If member already has an authorized field, leave it alone. That way its state is
-		// preserved if the user toggles the network back to private. Otherwise set it to
-		// true by default for new members of public nets.
 		if (!member.count("authorized")) {
 			member["authorized"] = true;
-			member["lastAuthorizedTime"] = now;
-			member["lastAuthorizedBy"] = authorizedBy;
+			json ah;
+			ah["a"] = true;
+			ah["by"] = authorizedBy;
+			ah["ts"] = now;
+			ah["ct"] = json();
+			ah["c"] = json();
+			member["authHistory"].push_back(ah);
 			member["lastModified"] = now;
-			auto revj = member["revision"];
+			json &revj = member["revision"];
 			member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
 		}
-	} else if (_jB(member["authorized"],false)) {
-		authorizedBy = "memberIsAuthorized";
 	} else {
-		char atok[256];
-		if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH_TOKEN,atok,sizeof(atok)) > 0) {
-			atok[255] = (char)0; // not necessary but YDIFLO
-			if (strlen(atok) > 0) { // extra sanity check since we never want to compare a null token on either side
-				auto authTokens = network["authTokens"];
+		char presentedAuth[512];
+		if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) {
+			presentedAuth[511] = (char)0; // sanity check
+
+			// Check for bearer token presented by member
+			if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) {
+				const char *const presentedToken = presentedAuth + 6;
+
+				json &authTokens = network["authTokens"];
 				if (authTokens.is_array()) {
 					for(unsigned long i=0;i<authTokens.size();++i) {
-						auto at = authTokens[i];
-						if (at.is_object()) {
-							const uint64_t expires = _jI(at["expires"],0ULL);
-							std::string tok = _jS(at["token"],"");
-							if ( ((expires == 0ULL)||(expires > now)) && (tok.length() > 0) && (tok == atok) ) {
-								authorizedBy = "token";
-								member["authorized"] = true; // tokens actually change member authorization state
-								member["lastAuthorizedTime"] = now;
-								member["lastAuthorizedBy"] = authorizedBy;
-								member["lastModified"] = now;
-								auto revj = member["revision"];
-								member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
-								break;
+						json &token = authTokens[i];
+						if (token.is_object()) {
+							const uint64_t expires = _jI(token["expires"],0ULL);
+							const uint64_t maxUses = _jI(token["maxUsesPerMember"],0ULL);
+							std::string tstr = _jS(token["token"],"");
+
+							if (((expires == 0ULL)||(expires > now))&&(tstr == presentedToken)) {
+								bool usable = (maxUses == 0);
+								if (!usable) {
+									uint64_t useCount = 0;
+									json &ahist = member["authHistory"];
+									if (ahist.is_array()) {
+										for(unsigned long j=0;j<ahist.size();++j) {
+											json &ah = ahist[j];
+											if ((_jS(ah["ct"],"") == "token")&&(_jS(ah["c"],"") == tstr)&&(_jB(ah["a"],false)))
+												++useCount;
+										}
+									}
+									usable = (useCount < maxUses);
+								}
+								if (usable) {
+									authorizedBy = "token";
+									member["authorized"] = true;
+									json ah;
+									ah["a"] = true;
+									ah["by"] = authorizedBy;
+									ah["ts"] = now;
+									ah["ct"] = "token";
+									ah["c"] = tstr;
+									member["authHistory"].push_back(ah);
+									member["lastModified"] = now;
+									json &revj = member["revision"];
+									member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
+								}
 							}
 						}
 					}
@@ -1137,16 +1164,15 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 
 						if (b.count("authorized")) {
 							const bool newAuth = _jB(b["authorized"],false);
-							const bool oldAuth = _jB(member["authorized"],false);
-							if (newAuth != oldAuth) {
-								if (newAuth) {
-									member["authorized"] = true;
-									member["lastAuthorizedTime"] = now;
-									member["lastAuthorizedBy"] = "user";
-								} else {
-									member["authorized"] = false;
-									member["lastDeauthorizedTime"] = now;
-								}
+							if (newAuth != _jB(member["authorized"],false)) {
+								member["authorized"] = newAuth;
+								json ah;
+								ah["a"] = newAuth;
+								ah["by"] = "api";
+								ah["ts"] = now;
+								ah["ct"] = json();
+								ah["c"] = json();
+								member["authHistory"].push_back(ah);
 							}
 						}
 
@@ -1427,13 +1453,14 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 						if (authTokens.is_array()) {
 							json nat = json::array();
 							for(unsigned long i=0;i<authTokens.size();++i) {
-								auto token = authTokens[i];
+								json &token = authTokens[i];
 								if (token.is_object()) {
 									std::string tstr = token["token"];
 									if (tstr.length() > 0) {
 										json t = json::object();
 										t["token"] = tstr;
 										t["expires"] = _jI(token["expires"],0ULL);
+										t["maxUsesPerMember"] = _jI(token["maxUsesPerMember"],0ULL);
 										nat.push_back(t);
 									}
 								}
diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp
index 303a5355..1bfd9577 100644
--- a/controller/EmbeddedNetworkController.hpp
+++ b/controller/EmbeddedNetworkController.hpp
@@ -143,9 +143,7 @@ private:
 	inline void _initMember(nlohmann::json &member)
 	{
 		if (!member.count("authorized")) member["authorized"] = false;
-		if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
-		if (!member.count("lastAuthorizedBy")) member["lastAuthorizedBy"] = "";
-		if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
+		if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array();
  		if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array();
 		if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array();
 		if (!member.count("activeBridge")) member["activeBridge"] = false;
diff --git a/controller/README.md b/controller/README.md
index 1eb0ca0d..805641d9 100644
--- a/controller/README.md
+++ b/controller/README.md
@@ -229,9 +229,7 @@ This returns an object containing all currently online members and the most rece
 | nwid                  | string        | 16-digit network ID                               | no       |
 | clock                 | integer       | Current clock, ms since epoch                     | no       |
 | authorized            | boolean       | Is member authorized? (for private networks)      | YES      |
-| lastAuthorizedTime    | integer       | Time 'authorized' was last set to 'true'          | no       |
-| lastAuthorizedBy      | string        | What last set 'authorized' to 'true'?             | no       |
-| lastDeauthorizedTime  | integer       | Time 'authorized' was last set to 'false'         | no       |
+| authHistory           | array[object] | History of auth changes, latest at end            | no       |
 | activeBridge          | boolean       | Member is able to bridge to other Ethernet nets   | YES      |
 | identity              | string        | Member's public ZeroTier identity (if known)      | no       |
 | ipAssignments         | array[string] | Managed IP address assignments                    | YES      |
diff --git a/node/Network.cpp b/node/Network.cpp
index 22aca0d8..197841d9 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -1001,6 +1001,7 @@ void Network::requestConfiguration()
 
 	Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
 	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR,(uint64_t)ZT_VENDOR_ZEROTIER);
 	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,(uint64_t)ZT_PROTO_VERSION);
 	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MAJOR);
 	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MINOR);
@@ -1011,6 +1012,7 @@ void Network::requestConfiguration()
 	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS,(uint64_t)ZT_MAX_NETWORK_TAGS);
 	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS,(uint64_t)0);
 	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,(uint64_t)ZT_RULES_ENGINE_REVISION);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_RELAY_POLICY,(uint64_t)RR->node->relayPolicy());
 
 	if (ctrl == RR->identity.address()) {
 		if (RR->localNetworkController) {
diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp
index ad1cafa5..5ad86855 100644
--- a/node/NetworkConfig.hpp
+++ b/node/NetworkConfig.hpp
@@ -108,9 +108,13 @@ namespace ZeroTier {
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION "v"
 // Protocol version (see Packet.hpp)
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION "pv"
-// Software major, minor, revision
+// Software vendor
+#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR "vend"
+// Software major version
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION "majv"
+// Software minor version
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION "minv"
+// Software revision
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION "revv"
 // Rules engine revision
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV "revr"
@@ -123,9 +127,11 @@ namespace ZeroTier {
 // Maximum number of tags this node can accept
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS "mt"
 // Network join authorization token (if any)
-#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH_TOKEN "atok"
+#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH "a"
 // Network configuration meta-data flags
 #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS "f"
+// Relay policy for this node
+#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_RELAY_POLICY "rp"
 
 // These dictionary keys are short so they don't take up much room.
 // By convention we use upper case for binary blobs, but it doesn't really matter.
-- 
cgit v1.2.3