controller-api-model.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. /*
  2. * A JavaScript class based model for the ZeroTier controller microservice API
  3. * Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. * --
  19. *
  20. * You can be released from the requirements of the license by purchasing
  21. * a commercial license. Buying such a license is mandatory as soon as you
  22. * develop commercial closed-source software that incorporates or links
  23. * directly against ZeroTier software without disclosing the source code
  24. * of your own application.
  25. */
  26. 'use strict';
  27. /**
  28. * Goes through a rule set array and makes sure it's valid, returning a canonicalized version
  29. *
  30. * @param {array[object]} rules Array of ZeroTier rules
  31. * @return New array of canonicalized rules
  32. * @throws {Error} Rule set is invalid
  33. */
  34. function formatRuleSetArray(rules)
  35. {
  36. }
  37. exports.formatRuleSetArray = formatRuleSetArray;
  38. /**
  39. * @param {string} IP with optional /netmask|port section
  40. * @return 4, 6, or 0 if invalid
  41. */
  42. function ipClassify(ip)
  43. {
  44. if ((!ip)||(typeof ip !== 'string'))
  45. return 0;
  46. let ips = ip.split('/');
  47. if (ips.length > 0) {
  48. if (ips.length > 1) {
  49. if (ips[1].length === 0)
  50. return 0;
  51. for(let i=0;i<ips[1].length;++i) {
  52. if ('0123456789'.indexOf(ips[1].charAt(i)) < 0)
  53. return 0;
  54. }
  55. }
  56. if (ips[0].indexOf(':') > 0) {
  57. for(let i=0;i<ips[0].length;++i) {
  58. if ('0123456789abcdefABCDEF:'.indexOf(ips[0].charAt(i)) < 0)
  59. return 0;
  60. }
  61. return 6;
  62. } else if (ips[0].indexOf('.') > 0) {
  63. for(let i=0;i<ips[0].length;++i) {
  64. if ('0123456789.'.indexOf(ips[0].charAt(i)) < 0)
  65. return 0;
  66. }
  67. return 4;
  68. }
  69. }
  70. return 0;
  71. }
  72. exports.ipClassify = ipClassify;
  73. /**
  74. * Make sure a string is lower case hex and optionally left pad
  75. *
  76. * @param x {string} String to format/canonicalize
  77. * @param l {number} Length of desired string or 0/null to not left pad
  78. * @return Padded string
  79. */
  80. function formatZeroTierIdentifier(x,l)
  81. {
  82. x = (x) ? x.toString().toLowerCase() : '';
  83. l = ((typeof l !== 'number')||(l < 0)) ? 0 : l;
  84. let r = '';
  85. for(let i=0;i<x.length;++i) {
  86. let c = x.charAt(i);
  87. if ('0123456789abcdef'.indexOf(c) >= 0) {
  88. r += c;
  89. if (r.length === l)
  90. break;
  91. }
  92. }
  93. while (r.length < l)
  94. r = '0' + r;
  95. return r;
  96. };
  97. exports.formatZeroTierIdentifier = formatZeroTierIdentifier;
  98. // Internal container classes
  99. class _V4AssignMode
  100. {
  101. get zt() { return (this._zt)||false; }
  102. set zt(b) { this._zt = !!b; }
  103. toJSON()
  104. {
  105. return { zt: this.zt };
  106. }
  107. };
  108. class _v6AssignMode
  109. {
  110. get ['6plane'] { return (this._6plane)||false; }
  111. set ['6plane'](b) { this._6plane = !!b; }
  112. get zt() { return (this._zt)||false; }
  113. set zt(b) { this._zt = !!b; }
  114. get rfc4193() { return (this._rfc4193)||false; }
  115. set rfc4193(b) { this._rfc4193 = !!b; }
  116. toJSON()
  117. {
  118. return {
  119. zt: this.zt,
  120. rfc4193: this.rfc4193,
  121. '6plane': this['6plane']
  122. };
  123. }
  124. }
  125. class Network
  126. {
  127. constructor(obj)
  128. {
  129. this.clear();
  130. this.patch(obj);
  131. }
  132. get objtype() { return 'network'; }
  133. get id() { return this._id; }
  134. set id(x) { return (this._id = formatZeroTierIdentifier(x,16)); }
  135. get nwid() { return this._id; } // legacy
  136. get authTokens() { return this._authTokens; }
  137. set authTokens(at)
  138. {
  139. this._authTokens = {};
  140. if ((at)&&(typeof at === 'object')&&(!Array.isArray(at))) {
  141. for(let k in at) {
  142. let exp = parseInt(at[k])||0;
  143. if (exp >= 0)
  144. this._authTokens[k] = exp;
  145. }
  146. }
  147. return this._authTokens;
  148. }
  149. get capabilities() { return this._capabilities; }
  150. set capabilities(c)
  151. {
  152. let ca = [];
  153. let ids = {};
  154. if ((c)&&(Array.isArray(c))) {
  155. for(let a=0;a<c.length;++a) {
  156. let cap = c[a];
  157. if ((cap)&&(typeof cap === 'object')&&(!Array.isArray(cap))) {
  158. let capId = parseInt(cap.id)||-1;
  159. if ((capId >= 0)&&(capId <= 0xffffffff)&&(!ids[capId])) {
  160. ids[capId] = true;
  161. let capDefault = !!cap['default'];
  162. let capRules = formatRuleSetArray(cap.rules);
  163. ca.push({
  164. id: capId,
  165. 'default': capDefault,
  166. rules: capRules
  167. });
  168. }
  169. }
  170. }
  171. }
  172. ca.sort(function(a,b) {
  173. a = a.id;
  174. b = b.id;
  175. return ((a > b) ? 1 : ((a < b) ? -1 : 0));
  176. });
  177. this._capabilities = ca;
  178. return ca;
  179. }
  180. get ipAssignmentPools() return { this._ipAssignmentPools; }
  181. set ipAssignmentPools(ipp)
  182. {
  183. let pa = [];
  184. let ranges = {};
  185. if ((ipp)&&(Array.isArray(ipp))) {
  186. for(let a=0;a<ipp.length;++a) {
  187. let range = ipp[a];
  188. if ((range)&&(typeof range === 'object')&&(!Array.isArray(range))) {
  189. let start = range.ipRangeStart;
  190. let end = range.ipRangeEnd;
  191. if ((start)&&(end)) {
  192. let stype = ipClassify(start);
  193. if ((stype > 0)&&(stype === ipClassify(end))&&(!ranges[start+'_'+end])) {
  194. ranges[start+'_'+end] = true;
  195. pa.push({ ipRangeStart: start, ipRangeEnd: end });
  196. }
  197. }
  198. }
  199. }
  200. }
  201. pa.sort(function(a,b) { return a.ipRangeStart.localeCompare(b.ipRangeStart); });
  202. this._ipAssignmentPools = pa;
  203. return pa;
  204. }
  205. get multicastLimit() return { this._multicastLimit; }
  206. set multicastLimit(n)
  207. {
  208. try {
  209. let nn = parseInt(n)||0;
  210. this._multicastLimit = (nn >= 0) ? nn : 0;
  211. } catch (e) {
  212. this._multicastLimit = 0;
  213. }
  214. return this._multicastLimit;
  215. }
  216. get routes() return { this._routes; }
  217. set routes(r)
  218. {
  219. let ra = [];
  220. let targets = {};
  221. if ((r)&&(Array.isArray(r))) {
  222. for(let a=0;a<r.length;++a) {
  223. let route = r[a];
  224. if ((route)&&(typeof route === 'object')&&(!Array.isArray(route))) {
  225. let routeTarget = route.target;
  226. let routeVia = route.via||null;
  227. let rtt = ipClassify(routeTarget);
  228. if ((rtt > 0)&&((routeVia === null)||(ipClassify(routeVia) === rtt))&&(!targets[routeTarget])) {
  229. targets[routeTarget] = true;
  230. ra.push({ target: routeTarget, via: routeVia });
  231. }
  232. }
  233. }
  234. }
  235. ra.sort(function(a,b) { return a.routeTarget.localeCompare(b.routeTarget); });
  236. this._routes = ra;
  237. return ra;
  238. }
  239. get tags() return { this._tags; }
  240. set tags(t)
  241. {
  242. let ta = [];
  243. if ((t)&&(Array.isArray(t))) {
  244. for(let a=0;a<t.length;++a) {
  245. let tag = t[a];
  246. if ((tag)&&(typeof tag === 'object')&&(!Array.isArray(tag))) {
  247. let tagId = parseInt(tag.id)||-1;
  248. if ((tagId >= 0)||(tagId <= 0xffffffff)) {
  249. let tagDefault = tag.default;
  250. if (typeof tagDefault !== 'number')
  251. tagDefault = parseInt(tagDefault)||null;
  252. if ((tagDefault < 0)||(tagDefault > 0xffffffff))
  253. tagDefault = null;
  254. ta.push({ 'id': tagId, 'default': tagDefault });
  255. }
  256. }
  257. }
  258. }
  259. ta.sort(function(a,b) {
  260. a = a.id;
  261. b = b.id;
  262. return ((a > b) ? 1 : ((a < b) ? -1 : 0));
  263. });
  264. this._tags = ta;
  265. return ta;
  266. }
  267. get v4AssignMode() return { this._v4AssignMode; }
  268. set v4AssignMode(m)
  269. {
  270. if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) {
  271. this._v4AssignMode.zt = m.zt;
  272. } else if (m === 'zt') { // legacy
  273. this._v4AssignMode.zt = true;
  274. } else {
  275. this._v4AssignMode.zt = false;
  276. }
  277. }
  278. get v6AssignMode() return { this._v6AssignMode; }
  279. set v6AssignMode(m)
  280. {
  281. if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) {
  282. this._v6AssignMode.zt = m.zt;
  283. this._v6AssignMode.rfc4193 = m.rfc4193;
  284. this._v6AssignMode['6plane'] = m['6plane'];
  285. } else if (typeof m === 'string') { // legacy
  286. let ms = m.split(',');
  287. this._v6AssignMode.zt = false;
  288. this._v6AssignMode.rfc4193 = false;
  289. this._v6AssignMode['6plane'] = false;
  290. for(let i=0;i<ms.length;++i) {
  291. switch(ms[i]) {
  292. case 'zt':
  293. this._v6AssignMode.zt = true;
  294. break;
  295. case 'rfc4193':
  296. this._v6AssignMode.rfc4193 = true;
  297. break;
  298. case '6plane':
  299. this._v6AssignMode['6plane'] = true;
  300. break;
  301. }
  302. }
  303. } else {
  304. this._v6AssignMode.zt = false;
  305. this._v6AssignMode.rfc4193 = false;
  306. this._v6AssignMode['6plane'] = false;
  307. }
  308. }
  309. get rules() { return this._rules; }
  310. set rules(r) { this._rules = formatRuleSetArray(r); }
  311. get enableBroadcast() { return this._enableBroadcast; }
  312. set enableBroadcast(b) { this._enableBroadcast = !!b; }
  313. get mtu() { return this._mtu; }
  314. set mtu(n)
  315. {
  316. let mtu = parseInt(n)||0;
  317. if (mtu <= 1280) mtu = 1280; // minimum as per IPv6 spec
  318. if (mtu >= 10000) mtu = 10000; // maximum as per ZT spec
  319. this._mtu = mtu;
  320. }
  321. get name() { return this._name; }
  322. set name(n)
  323. {
  324. if (typeof n === 'string')
  325. this._name = n;
  326. else if (typeof n === 'number')
  327. this._name = n.toString();
  328. else this._name = '';
  329. }
  330. get private() { return this._private; }
  331. set private(b)
  332. {
  333. // This is really meaningful for security, so make true unless explicitly set to false.
  334. this._private = (b !== false);
  335. }
  336. get activeMemberCount() { return this.__activeMemberCount; }
  337. get authorizedMemberCount() { return this.__authorizedMemberCount; }
  338. get totalMemberCount() { return this.__totalMemberCount; }
  339. get clock() { return this.__clock; }
  340. get creationTime() { return this.__creationTime; }
  341. get revision() { return this.__revision; }
  342. toJSONExcludeControllerGenerated()
  343. {
  344. return {
  345. id: this.id,
  346. objtype: 'network',
  347. nwid: this.nwid,
  348. authTokens: this.authTokens,
  349. capabilities: this.capabilities,
  350. ipAssignmentPools: this.ipAssignmentPools,
  351. multicastLimit: this.multicastLimit,
  352. routes: this.routes,
  353. tags: this.tags,
  354. v4AssignMode: this._v4AssignMode.toJSON(),
  355. v6AssignMode: this._v6AssignMode.toJSON(),
  356. rules: this.rules,
  357. enableBroadcast: this.enableBroadcast,
  358. mtu: this.mtu,
  359. name: this.name,
  360. 'private': this['private']
  361. };
  362. }
  363. toJSON()
  364. {
  365. var j = this.toJSONExcludeControllerGenerated();
  366. j.activeMemberCount = this.activeMemberCount;
  367. j.authorizedMemberCount = this.authorizedMemberCount;
  368. j.totalMemberCount = this.totalMemberCount;
  369. j.clock = this.clock;
  370. j.creationTime = this.creationTime;
  371. j.revision = this.revision;
  372. return j;
  373. }
  374. clear()
  375. {
  376. this._id = '';
  377. this._authTokens = {};
  378. this._capabilities = [];
  379. this._ipAssignmentPools = [];
  380. this._multicastLimit = 32;
  381. this._routes = [];
  382. this._tags = [];
  383. this._v4AssignMode = new _V4AssignMode();
  384. this._v6AssignMode = new _v6AssignMode();
  385. this._rules = [];
  386. this._enableBroadcast = true;
  387. this._mtu = 2800;
  388. this._name = '';
  389. this._private = true;
  390. this.__activeMemberCount = 0;
  391. this.__authorizedMemberCount = 0;
  392. this.__totalMemberCount = 0;
  393. this.__clock = 0;
  394. this.__creationTime = 0;
  395. this.__revision = 0;
  396. }
  397. patch(obj)
  398. {
  399. if (obj instanceof Network)
  400. obj = obj.toJSON();
  401. if ((obj)&&(typeof obj === 'object')&&(!Array.isArray(obj))) {
  402. for(var k in obj) {
  403. try {
  404. switch(k) {
  405. case 'id':
  406. case 'authTokens':
  407. case 'capabilities':
  408. case 'ipAssignmentPools':
  409. case 'multicastLimit':
  410. case 'routes':
  411. case 'tags':
  412. case 'rules':
  413. case 'enableBroadcast':
  414. case 'mtu':
  415. case 'name':
  416. case 'private':
  417. case 'v4AssignMode':
  418. case 'v6AssignMode':
  419. this[k] = obj[k];
  420. break;
  421. case 'activeMemberCount':
  422. case 'authorizedMemberCount':
  423. case 'totalMemberCount':
  424. case 'clock':
  425. case 'creationTime':
  426. case 'revision':
  427. this['__'+k] = parseInt(obj[k])||0;
  428. break;
  429. }
  430. } catch (e) {}
  431. }
  432. }
  433. }
  434. };
  435. exports.Network = Network;
  436. class Member
  437. {
  438. constructor(obj)
  439. {
  440. this.clear();
  441. this.patch(obj);
  442. }
  443. get objtype() { return 'member'; }
  444. get id() { return this._id; }
  445. set id(x) { this._id = formatZeroTierIdentifier((typeof x === 'number') ? x.toString(16) : x,10); }
  446. get address() { return this._id; } // legacy
  447. get nwid() { return this._nwid; }
  448. set nwid(x) { this._nwid = formatZeroTierIdentifier(x,16); }
  449. get controllerId() { return this.nwid.substr(0,10); }
  450. get authorized() { return this._authorized; }
  451. set authorized(b) { this._authorized = (b === true); } // security critical so require explicit set to true
  452. get activeBridge() { return this._activeBridge; }
  453. set activeBridge(b) { this._activeBridge = !!b; }
  454. get capabilities() { return this._capabilities; }
  455. set capabilities(c)
  456. {
  457. }
  458. get identity() { return this._identity; }
  459. set identity(istr)
  460. {
  461. if ((istr)&&(typeof istr === 'string'))
  462. this._identity = istr;
  463. else this._identity = null;
  464. }
  465. get ipAssignments() { return this._ipAssignments; }
  466. set ipAssignments(ipa)
  467. {
  468. }
  469. get noAutoAssignIps() { return this._noAutoAssignIps; }
  470. set noAutoAssignIps(b) { this._noAutoAssignIps = !!b; }
  471. get tags() { return this._tags; }
  472. set tags(t)
  473. {
  474. }
  475. clear()
  476. {
  477. this._id = '';
  478. this._nwid = '';
  479. this._authorized = false;
  480. this._activeBridge = false;
  481. this._capabilities = [];
  482. this._identity = '';
  483. this._ipAssignments = [];
  484. this._noAutoAssignIps = false;
  485. this._tags = [];
  486. this.__creationTime = 0;
  487. this.__lastAuthorizedTime = 0;
  488. this.__lastAuthorizedCredentialType = null;
  489. this.__lastAuthorizedCredential = null;
  490. this.__lastDeauthorizedTime = 0;
  491. this.__physicalAddr = '';
  492. this.__revision = 0;
  493. this.__vMajor = 0;
  494. this.__vMinor = 0;
  495. this.__vRev = 0;
  496. this.__vProto = 0;
  497. }
  498. };
  499. exports.Member = Member;