فهرست منبع

Small controller revisions, first run of controller API model JavaScript.

Adam Ierymenko 8 سال پیش
والد
کامیت
2c682b4d1c
3فایلهای تغییر یافته به همراه577 افزوده شده و 75 حذف شده
  1. 28 73
      controller/EmbeddedNetworkController.cpp
  2. 3 2
      controller/EmbeddedNetworkController.hpp
  3. 546 0
      controller/controller-api-model.js

+ 28 - 73
controller/EmbeddedNetworkController.cpp

@@ -638,14 +638,10 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 							if (newAuth != OSUtils::jsonBool(member["authorized"],false)) {
 								member["authorized"] = newAuth;
 								member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now;
-
-								json ah;
-								ah["a"] = newAuth;
-								ah["by"] = "api";
-								ah["ts"] = now;
-								ah["ct"] = json();
-								ah["c"] = json();
-								member["authHistory"].push_back(ah);
+								if (newAuth) {
+									member["lastAuthorizedCredentialType"] = "api";
+									member["lastAuthorizedCredential"] = json();
+								}
 
 								// Member is being de-authorized, so spray Revocation objects to all online members
 								if (!newAuth) {
@@ -896,22 +892,15 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 
 					if (b.count("authTokens")) {
 						json &authTokens = b["authTokens"];
-						if (authTokens.is_array()) {
-							json nat = json::array();
-							for(unsigned long i=0;i<authTokens.size();++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"] = OSUtils::jsonInt(token["expires"],0ULL);
-										t["maxUsesPerMember"] = OSUtils::jsonInt(token["maxUsesPerMember"],0ULL);
-										nat.push_back(t);
-									}
-								}
+						if (authTokens.is_object()) {
+							json nat;
+							for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) {
+								if ((t.value().is_number())&&(t.value() >= 0))
+									nat[t.key()] = t.value();
 							}
 							network["authTokens"] = nat;
+						} else {
+							network["authTokens"] = {{}};
 						}
 					}
 
@@ -1268,56 +1257,29 @@ void EmbeddedNetworkController::_request(
 	}
 
 	// Determine whether and how member is authorized
-	const char *authorizedBy = (const char *)0;
+	bool authorized = false;
 	bool autoAuthorized = false;
 	json autoAuthCredentialType,autoAuthCredential;
 	if (OSUtils::jsonBool(member["authorized"],false)) {
-		authorizedBy = "memberIsAuthorized";
+		authorized = true;
 	} else if (!OSUtils::jsonBool(network["private"],true)) {
-		authorizedBy = "networkIsPublic";
-		json &ahist = member["authHistory"];
-		if ((!ahist.is_array())||(ahist.size() == 0))
-			autoAuthorized = true;
+		authorized = true;
+		autoAuthorized = true;
+		autoAuthCredentialType = "public";
 	} else {
 		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) {
-						json &token = authTokens[i];
-						if (token.is_object()) {
-							const uint64_t expires = OSUtils::jsonInt(token["expires"],0ULL);
-							const uint64_t maxUses = OSUtils::jsonInt(token["maxUsesPerMember"],0ULL);
-							std::string tstr = OSUtils::jsonString(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 ((OSUtils::jsonString(ah["ct"],"") == "token")&&(OSUtils::jsonString(ah["c"],"") == tstr)&&(OSUtils::jsonBool(ah["a"],false)))
-												++useCount;
-										}
-									}
-									usable = (useCount < maxUses);
-								}
-								if (usable) {
-									authorizedBy = "token";
-									autoAuthorized = true;
-									autoAuthCredentialType = "token";
-									autoAuthCredential = tstr;
-								}
-							}
-						}
+				json authTokens(network["authTokens"]);
+				json &tokenExpires = authTokens[presentedToken];
+				if (tokenExpires.is_number()) {
+					if ((tokenExpires == 0)||(tokenExpires > now)) {
+						authorized = true;
+						autoAuthorized = true;
+						autoAuthCredentialType = "token";
+						autoAuthCredential = presentedToken;
 					}
 				}
 			}
@@ -1325,23 +1287,16 @@ void EmbeddedNetworkController::_request(
 	}
 
 	// If we auto-authorized, update member record
-	if ((autoAuthorized)&&(authorizedBy)) {
+	if ((autoAuthorized)&&(authorized)) {
 		member["authorized"] = true;
 		member["lastAuthorizedTime"] = now;
-
-		json ah;
-		ah["a"] = true;
-		ah["by"] = authorizedBy;
-		ah["ts"] = now;
-		ah["ct"] = autoAuthCredentialType;
-		ah["c"] = autoAuthCredential;
-		member["authHistory"].push_back(ah);
-
+		member["lastAuthorizedCredentialType"] = autoAuthCredentialType;
+		member["lastAuthorizedCredential"] = autoAuthCredential;
 		json &revj = member["revision"];
 		member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
 	}
 
-	if (authorizedBy) {
+	if (authorized) {
 		// Update version info and meta-data if authorized and if this is a genuine request
 		if (requestPacketId) {
 			const uint64_t vMajor = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0);

+ 3 - 2
controller/EmbeddedNetworkController.hpp

@@ -129,7 +129,6 @@ private:
 	inline void _initMember(nlohmann::json &member)
 	{
 		if (!member.count("authorized")) member["authorized"] = false;
-		if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array();
  		if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array();
 		if (!member.count("activeBridge")) member["activeBridge"] = false;
 		if (!member.count("tags")) member["tags"] = nlohmann::json::array();
@@ -139,6 +138,8 @@ private:
 		if (!member.count("revision")) member["revision"] = 0ULL;
 		if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
 		if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
+		if (!member.count("lastAuthorizedCredentialType")) member["lastAuthorizedCredentialType"] = nlohmann::json();
+		if (!member.count("lastAuthorizedCredential")) member["lastAuthorizedCredential"] = nlohmann::json();
 		if (!member.count("vMajor")) member["vMajor"] = -1;
 		if (!member.count("vMinor")) member["vMinor"] = -1;
 		if (!member.count("vRev")) member["vRev"] = -1;
@@ -156,7 +157,7 @@ private:
 		if (!network.count("enableBroadcast")) network["enableBroadcast"] = true;
 		if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}};
 		if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}};
-		if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array();
+		if (!network.count("authTokens")) network["authTokens"] = {{}};
 		if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array();
 		if (!network.count("tags")) network["tags"] = nlohmann::json::array();
 		if (!network.count("routes")) network["routes"] = nlohmann::json::array();

+ 546 - 0
controller/controller-api-model.js

@@ -0,0 +1,546 @@
+/*
+ * A JavaScript class based model for the ZeroTier controller microservice API
+ * Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
+ */
+
+'use strict';
+
+/**
+ * Goes through a rule set array and makes sure it's valid, returning a canonicalized version
+ *
+ * @param {array[object]} rules Array of ZeroTier rules
+ * @return New array of canonicalized rules
+ * @throws {Error} Rule set is invalid
+ */
+function formatRuleSetArray(rules)
+{
+}
+exports.formatRuleSetArray = formatRuleSetArray;
+
+/**
+ * @param {string} IP with optional /netmask|port section
+ * @return 4, 6, or 0 if invalid
+ */
+function ipClassify(ip)
+{
+	if ((!ip)||(typeof ip !== 'string'))
+		return 0;
+	let ips = ip.split('/');
+	if (ips.length > 0) {
+		if (ips.length > 1) {
+			if (ips[1].length === 0)
+				return 0;
+			for(let i=0;i<ips[1].length;++i) {
+				if ('0123456789'.indexOf(ips[1].charAt(i)) < 0)
+					return 0;
+			}
+		}
+		if (ips[0].indexOf(':') > 0) {
+			for(let i=0;i<ips[0].length;++i) {
+				if ('0123456789abcdefABCDEF:'.indexOf(ips[0].charAt(i)) < 0)
+					return 0;
+			}
+			return 6;
+		} else if (ips[0].indexOf('.') > 0) {
+			for(let i=0;i<ips[0].length;++i) {
+				if ('0123456789.'.indexOf(ips[0].charAt(i)) < 0)
+					return 0;
+			}
+			return 4;
+		}
+	}
+	return 0;
+}
+exports.ipClassify = ipClassify;
+
+/**
+ * Make sure a string is lower case hex and optionally left pad
+ *
+ * @param x {string} String to format/canonicalize
+ * @param l {number} Length of desired string or 0/null to not left pad
+ * @return Padded string
+ */
+function formatZeroTierIdentifier(x,l)
+{
+	x = (x) ? x.toString().toLowerCase() : '';
+	l = ((typeof l !== 'number')||(l < 0)) ? 0 : l;
+
+	let r = '';
+	for(let i=0;i<x.length;++i) {
+		let c = x.charAt(i);
+		if ('0123456789abcdef'.indexOf(c) >= 0) {
+			r += c;
+			if (r.length === l)
+				break;
+		}
+	}
+
+	while (r.length < l)
+		r = '0' + r;
+
+	return r;
+};
+exports.formatZeroTierIdentifier = formatZeroTierIdentifier;
+
+// Internal container classes
+class _V4AssignMode
+{
+	get zt() { return (this._zt)||false; }
+	set zt(b) { this._zt = !!b; }
+	toJSON()
+	{
+		return { zt: this.zt };
+	}
+};
+class _v6AssignMode
+{
+	get ['6plane'] { return (this._6plane)||false; }
+	set ['6plane'](b) { this._6plane = !!b; }
+	get zt() { return (this._zt)||false; }
+	set zt(b) { this._zt = !!b; }
+	get rfc4193() { return (this._rfc4193)||false; }
+	set rfc4193(b) { this._rfc4193 = !!b; }
+	toJSON()
+	{
+		return {
+			zt: this.zt,
+			rfc4193: this.rfc4193,
+			'6plane': this['6plane']
+		};
+	}
+}
+
+class Network
+{
+	constructor(obj)
+	{
+		this.clear();
+		this.patch(obj);
+	}
+
+	get objtype() { return 'network'; }
+
+	get id() { return this._id; }
+	set id(x) { return (this._id = formatZeroTierIdentifier(x,16)); }
+
+	get nwid() { return this._id; } // legacy
+
+	get authTokens() { return this._authTokens; }
+	set authTokens(at)
+	{
+		this._authTokens = {};
+		if ((at)&&(typeof at === 'object')&&(!Array.isArray(at))) {
+			for(let k in at) {
+				let exp = parseInt(at[k])||0;
+				if (exp >= 0)
+					this._authTokens[k] = exp;
+			}
+		}
+		return this._authTokens;
+	}
+
+	get capabilities() { return this._capabilities; }
+	set capabilities(c)
+	{
+		let ca = [];
+		let ids = {};
+		if ((c)&&(Array.isArray(c))) {
+			for(let a=0;a<c.length;++a) {
+				let cap = c[a];
+				if ((cap)&&(typeof cap === 'object')&&(!Array.isArray(cap))) {
+					let capId = parseInt(cap.id)||-1;
+					if ((capId >= 0)&&(capId <= 0xffffffff)&&(!ids[capId])) {
+						ids[capId] = true;
+						let capDefault = !!cap['default'];
+						let capRules = formatRuleSetArray(cap.rules);
+						ca.push({
+							id: capId,
+							'default': capDefault,
+							rules: capRules
+						});
+					}
+				}
+			}
+		}
+		ca.sort(function(a,b) {
+			a = a.id;
+			b = b.id;
+			return ((a > b) ? 1 : ((a < b) ? -1 : 0));
+		});
+		this._capabilities = ca;
+		return ca;
+	}
+
+	get ipAssignmentPools() return { this._ipAssignmentPools; }
+	set ipAssignmentPools(ipp)
+	{
+		let pa = [];
+		let ranges = {};
+		if ((ipp)&&(Array.isArray(ipp))) {
+			for(let a=0;a<ipp.length;++a) {
+				let range = ipp[a];
+				if ((range)&&(typeof range === 'object')&&(!Array.isArray(range))) {
+					let start = range.ipRangeStart;
+					let end = range.ipRangeEnd;
+					if ((start)&&(end)) {
+						let stype = ipClassify(start);
+						if ((stype > 0)&&(stype === ipClassify(end))&&(!ranges[start+'_'+end])) {
+							ranges[start+'_'+end] = true;
+							pa.push({ ipRangeStart: start, ipRangeEnd: end });
+						}
+					}
+				}
+			}
+		}
+		pa.sort(function(a,b) { return a.ipRangeStart.localeCompare(b.ipRangeStart); });
+		this._ipAssignmentPools = pa;
+		return pa;
+	}
+
+	get multicastLimit() return { this._multicastLimit; }
+	set multicastLimit(n)
+	{
+		try {
+			let nn = parseInt(n)||0;
+			this._multicastLimit = (nn >= 0) ? nn : 0;
+		} catch (e) {
+			this._multicastLimit = 0;
+		}
+		return this._multicastLimit;
+	}
+
+	get routes() return { this._routes; }
+	set routes(r)
+	{
+		let ra = [];
+		let targets = {};
+		if ((r)&&(Array.isArray(r))) {
+			for(let a=0;a<r.length;++a) {
+				let route = r[a];
+				if ((route)&&(typeof route === 'object')&&(!Array.isArray(route))) {
+					let routeTarget = route.target;
+					let routeVia = route.via||null;
+					let rtt = ipClassify(routeTarget);
+					if ((rtt > 0)&&((routeVia === null)||(ipClassify(routeVia) === rtt))&&(!targets[routeTarget])) {
+						targets[routeTarget] = true;
+						ra.push({ target: routeTarget, via: routeVia });
+					}
+				}
+			}
+		}
+		ra.sort(function(a,b) { return a.routeTarget.localeCompare(b.routeTarget); });
+		this._routes = ra;
+		return ra;
+	}
+
+	get tags() return { this._tags; }
+	set tags(t)
+	{
+		let ta = [];
+		if ((t)&&(Array.isArray(t))) {
+			for(let a=0;a<t.length;++a) {
+				let tag = t[a];
+				if ((tag)&&(typeof tag === 'object')&&(!Array.isArray(tag))) {
+					let tagId = parseInt(tag.id)||-1;
+					if ((tagId >= 0)||(tagId <= 0xffffffff)) {
+						let tagDefault = tag.default;
+						if (typeof tagDefault !== 'number')
+							tagDefault = parseInt(tagDefault)||null;
+						if ((tagDefault < 0)||(tagDefault > 0xffffffff))
+							tagDefault = null;
+						ta.push({ 'id': tagId, 'default': tagDefault });
+					}
+				}
+			}
+		}
+		ta.sort(function(a,b) {
+			a = a.id;
+			b = b.id;
+			return ((a > b) ? 1 : ((a < b) ? -1 : 0));
+		});
+		this._tags = ta;
+		return ta;
+	}
+
+	get v4AssignMode() return { this._v4AssignMode; }
+	set v4AssignMode(m)
+	{
+		if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) {
+			this._v4AssignMode.zt = m.zt;
+		} else if (m === 'zt') { // legacy
+			this._v4AssignMode.zt = true;
+		} else {
+			this._v4AssignMode.zt = false;
+		}
+	}
+
+	get v6AssignMode() return { this._v6AssignMode; }
+	set v6AssignMode(m)
+	{
+		if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) {
+			this._v6AssignMode.zt = m.zt;
+			this._v6AssignMode.rfc4193 = m.rfc4193;
+			this._v6AssignMode['6plane'] = m['6plane'];
+		} else if (typeof m === 'string') { // legacy
+			let ms = m.split(',');
+			this._v6AssignMode.zt = false;
+			this._v6AssignMode.rfc4193 = false;
+			this._v6AssignMode['6plane'] = false;
+			for(let i=0;i<ms.length;++i) {
+				switch(ms[i]) {
+					case 'zt':
+						this._v6AssignMode.zt = true;
+						break;
+					case 'rfc4193':
+						this._v6AssignMode.rfc4193 = true;
+						break;
+					case '6plane':
+						this._v6AssignMode['6plane'] = true;
+						break;
+				}
+			}
+		} else {
+			this._v6AssignMode.zt = false;
+			this._v6AssignMode.rfc4193 = false;
+			this._v6AssignMode['6plane'] = false;
+		}
+	}
+
+	get rules() { return this._rules; }
+	set rules(r) { this._rules = formatRuleSetArray(r); }
+
+	get enableBroadcast() { return this._enableBroadcast; }
+	set enableBroadcast(b) { this._enableBroadcast = !!b; }
+
+	get mtu() { return this._mtu; }
+	set mtu(n)
+	{
+		let mtu = parseInt(n)||0;
+		if (mtu <= 1280) mtu = 1280; // minimum as per IPv6 spec
+		if (mtu >= 10000) mtu = 10000; // maximum as per ZT spec
+		this._mtu = mtu;
+	}
+
+	get name() { return this._name; }
+	set name(n)
+	{
+		if (typeof n === 'string')
+			this._name = n;
+		else if (typeof n === 'number')
+			this._name = n.toString();
+		else this._name = '';
+	}
+
+	get private() { return this._private; }
+	set private(b)
+	{
+		// This is really meaningful for security, so make true unless explicitly set to false.
+		this._private = (b !== false);
+	}
+
+	get activeMemberCount() { return this.__activeMemberCount; }
+	get authorizedMemberCount() { return this.__authorizedMemberCount; }
+	get totalMemberCount() { return this.__totalMemberCount; }
+	get clock() { return this.__clock; }
+	get creationTime() { return this.__creationTime; }
+	get revision() { return this.__revision; }
+
+	toJSONExcludeControllerGenerated()
+	{
+		return {
+			id: this.id,
+			objtype: 'network',
+			nwid: this.nwid,
+			authTokens: this.authTokens,
+			capabilities: this.capabilities,
+			ipAssignmentPools: this.ipAssignmentPools,
+			multicastLimit: this.multicastLimit,
+			routes: this.routes,
+			tags: this.tags,
+			v4AssignMode: this._v4AssignMode.toJSON(),
+			v6AssignMode: this._v6AssignMode.toJSON(),
+			rules: this.rules,
+			enableBroadcast: this.enableBroadcast,
+			mtu: this.mtu,
+			name: this.name,
+			'private': this['private']
+		};
+	}
+
+	toJSON()
+	{
+		var j = this.toJSONExcludeControllerGenerated();
+		j.activeMemberCount = this.activeMemberCount;
+		j.authorizedMemberCount = this.authorizedMemberCount;
+		j.totalMemberCount = this.totalMemberCount;
+		j.clock = this.clock;
+		j.creationTime = this.creationTime;
+		j.revision = this.revision;
+		return j;
+	}
+
+	clear()
+	{
+		this._id = '';
+		this._authTokens = {};
+		this._capabilities = [];
+		this._ipAssignmentPools = [];
+		this._multicastLimit = 32;
+		this._routes = [];
+		this._tags = [];
+		this._v4AssignMode = new _V4AssignMode();
+		this._v6AssignMode = new _v6AssignMode();
+		this._rules = [];
+		this._enableBroadcast = true;
+		this._mtu = 2800;
+		this._name = '';
+		this._private = true;
+
+		this.__activeMemberCount = 0;
+		this.__authorizedMemberCount = 0;
+		this.__totalMemberCount = 0;
+		this.__clock = 0;
+		this.__creationTime = 0;
+		this.__revision = 0;
+	}
+
+	patch(obj)
+	{
+		if (obj instanceof Network)
+			obj = obj.toJSON();
+		if ((obj)&&(typeof obj === 'object')&&(!Array.isArray(obj))) {
+			for(var k in obj) {
+				try {
+					switch(k) {
+						case 'id':
+						case 'authTokens':
+						case 'capabilities':
+						case 'ipAssignmentPools':
+						case 'multicastLimit':
+						case 'routes':
+						case 'tags':
+						case 'rules':
+						case 'enableBroadcast':
+						case 'mtu':
+						case 'name':
+						case 'private':
+						case 'v4AssignMode':
+						case 'v6AssignMode':
+							this[k] = obj[k];
+							break;
+
+						case 'activeMemberCount':
+						case 'authorizedMemberCount':
+						case 'totalMemberCount':
+						case 'clock':
+						case 'creationTime':
+						case 'revision':
+							this['__'+k] = parseInt(obj[k])||0;
+							break;
+					}
+				} catch (e) {}
+			}
+		}
+	}
+};
+exports.Network = Network;
+
+class Member
+{
+	constructor(obj)
+	{
+		this.clear();
+		this.patch(obj);
+	}
+
+	get objtype() { return 'member'; }
+
+	get id() { return this._id; }
+	set id(x) { this._id = formatZeroTierIdentifier((typeof x === 'number') ? x.toString(16) : x,10); }
+
+	get address() { return this._id; } // legacy
+
+	get nwid() { return this._nwid; }
+	set nwid(x) { this._nwid = formatZeroTierIdentifier(x,16); }
+
+	get controllerId() { return this.nwid.substr(0,10); }
+
+	get authorized() { return this._authorized; }
+	set authorized(b) { this._authorized = (b === true); } // security critical so require explicit set to true
+
+	get activeBridge() { return this._activeBridge; }
+	set activeBridge(b) { this._activeBridge = !!b; }
+
+	get capabilities() { return this._capabilities; }
+	set capabilities(c)
+	{
+	}
+
+	get identity() { return this._identity; }
+	set identity(istr)
+	{
+		if ((istr)&&(typeof istr === 'string'))
+			this._identity = istr;
+		else this._identity = null;
+	}
+
+	get ipAssignments() { return this._ipAssignments; }
+	set ipAssignments(ipa)
+	{
+	}
+
+	get noAutoAssignIps() { return this._noAutoAssignIps; }
+	set noAutoAssignIps(b) { this._noAutoAssignIps = !!b; }
+
+	get tags() { return this._tags; }
+	set tags(t)
+	{
+	}
+
+	clear()
+	{
+		this._id = '';
+		this._nwid = '';
+		this._authorized = false;
+		this._activeBridge = false;
+		this._capabilities = [];
+		this._identity = '';
+		this._ipAssignments = [];
+		this._noAutoAssignIps = false;
+		this._tags = [];
+
+		this.__creationTime = 0;
+		this.__lastAuthorizedTime = 0;
+		this.__lastAuthorizedCredentialType = null;
+		this.__lastAuthorizedCredential = null;
+		this.__lastDeauthorizedTime = 0;
+		this.__physicalAddr = '';
+		this.__revision = 0;
+		this.__vMajor = 0;
+		this.__vMinor = 0;
+		this.__vRev = 0;
+		this.__vProto = 0;
+	}
+};
+exports.Member = Member;