diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docs/images/rethink-arch.png b/docs/images/rethink-arch.png new file mode 100644 index 0000000..b9ed0fb Binary files /dev/null and b/docs/images/rethink-arch.png differ diff --git a/package.json b/package.json index ddc803a..c517fa7 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,10 @@ "webRTC" ], "author": "reThink Project", - "contributors": [ - "Frédéric Luart ", + "contributors": [ "Nicolas Daviaud ", + "Jamal Boulmal ", + "Frédéric Luart ", "Arnaud Vallee ", "Tuo Zhang ", "Hao Jiang " @@ -97,6 +98,7 @@ "jspm": "^0.16.15", "log4js": "^0.6.38", "moment": "^2.17.1", + "node-cache": "^4.1.1", "passport": "^0.3.2", "passport-local": "^1.0.0", "passport.socketio": "^3.6.1", diff --git a/src/configs/server-settings.js b/src/configs/server-settings.js index c34e57b..7c81059 100644 --- a/src/configs/server-settings.js +++ b/src/configs/server-settings.js @@ -33,7 +33,12 @@ module.exports = { enabled: false } }, - redisURL:process.env.redisURL || 'localhost', + redisConfig: { + url:process.env.redisURL || 'localhost', + port: 6379, + policyDB: 15, + persisDB: 0, + }, port: process.env.PORT || '9090', ioConfig: { transports: [ @@ -50,5 +55,9 @@ module.exports = { sessionCookieSecret: '88HpNxVxm5k94AY2', useSSL: process.env.useSSL || false, sslCertificate: process.env.sslCertificate || path.resolve(path.join(__dirname, '../../', '/') + '/server.crt'), - sslPKey: process.env.sslPKey || path.resolve(path.join(__dirname, '../../', '/') + '/key.pem') + sslPKey: process.env.sslPKey || path.resolve(path.join(__dirname, '../../', '/') + '/key.pem'), + policyConfig: { + development: false, + defaultBehavior: false + } }; diff --git a/src/main/MsgNode.js b/src/main/MsgNode.js index a01711a..70572d2 100644 --- a/src/main/MsgNode.js +++ b/src/main/MsgNode.js @@ -42,7 +42,7 @@ let FileStore = require('session-file-store')(expressSession); let Client = require('./components/Client'); let Registry = require('./components/Registry'); let Message = require('./components/Message'); -let PEP = require('./components/policyEngine/pep/Pep'); +let PolicyEngine = require('./components/policyEngine/PolicyEngine'); let NodejsCtx = require('./components/policyEngine/context/NodejsCtx'); let MessageBus = require('./components/MessageBus'); let SessionManager = require('./components/SessionManager'); @@ -66,7 +66,8 @@ class MsgNode { this.config = config; this.config.domainRegistryUrl = this.config.domainRegistryConfig.url.replace(/\/$/, '') + '/'; // redis.createClient(port, host); e.g host=this.config.redisURL - this.storage = redis.createClient(6379, this.config.redisURL.slice(7)); + let redisConfig = this.config.redisConfig; + this.storage = redis.createClient(redisConfig.port, redisConfig.url.slice(7), {db: redisConfig.persisDB}); this.domain = this.config.MNdomain; // define logger configuration @@ -123,8 +124,8 @@ class MsgNode { this.registry.registerComponent(MNpersistm); let bus = new MessageBus('MessageBus', this.registry); this.registry.registerComponent(bus); - let pep = new PEP('PEP', new NodejsCtx(this.registry)); - this.registry.registerComponent(pep); + let pe = new PolicyEngine('PolicyEngine', new NodejsCtx(this.registry)); + this.registry.registerComponent(pe); let sm = new SessionManager('mn:/session', this.registry); this.registry.registerComponent(sm); let alm = new AddressAllocationManager('domain://msg-node.' + this.registry.getDomain() + '/address-allocation', this.registry); diff --git a/src/main/components/ClientMessage.js b/src/main/components/ClientMessage.js index c21f1b7..f118079 100644 --- a/src/main/components/ClientMessage.js +++ b/src/main/components/ClientMessage.js @@ -33,6 +33,7 @@ class ClientMessage { this.msg = msg; this.logger = registry.getLogger(); this.name = 'ClientMessage'; + this.pe = this.registry.getComponent('PolicyEngine'); } getMessage() { @@ -57,11 +58,11 @@ class ClientMessage { } dispatch() { - let response = this.registry.getComponent('PEP').analyse(this.msg.msg); + let response = this.pe.getPEP().analyse(this); if (response.result) { this.msg.msg = response.msg; } else { - this.replyError('PEP', response.getInfo()); + this.replyError('PolicyEngine', response.getInfo()); return; } let comp = this.registry.getComponent(this.msg.getTo()); @@ -85,6 +86,7 @@ class ClientMessage { replyDomain(msg) { // msg.setType('response'); + this.pe.getPIP().setRegistryCacheEntry(this.msg.msg, msg); this.client.replyDomain(msg); } diff --git a/src/main/components/policyEngine/AttributeCondition.js b/src/main/components/policyEngine/AttributeCondition.js index 5208305..0eed3a8 100644 --- a/src/main/components/policyEngine/AttributeCondition.js +++ b/src/main/components/policyEngine/AttributeCondition.js @@ -6,11 +6,11 @@ let Operators = require('./Operators'); class AttributeCondition { - constructor(context, condition) { + constructor(context, attribute, expression) { this.context = context; this.logger = this.context.registry.getLogger(); - this.attribute = Object.keys(condition)[0]; - this.expression = condition[Object.keys(condition)[0]]; + this.attribute = attribute; + this.expression = expression; this.operators = new Operators(); this.name = "PDP AttrCond"; } @@ -23,8 +23,8 @@ class AttributeCondition { * by executing the operator implementation. * @param {Object} message */ - this.context[this.attribute] = {message: message}; - let value = this.context[this.attribute]; + + let value = this.getAttributeValue(message); let final = false; if (expression.constructor === Object) { let results = []; @@ -37,16 +37,22 @@ class AttributeCondition { params = Array.isArray(params)?params:[params]; result = this.operators[operator](params.map(param=>{return this.isApplicable(message, param)})); } - // otherwise it is comparative operator - // if params is an array - else if (operator !== "in" && params.constructor === Array) { - result = params.some(param => { - return this.operators[operator](value, param, this.attribute); - }); - } - // otherwise it is a value + // otherwise it is comparative operator, and params is really params else { - result = this.operators[operator](value, params, this.attribute); + // read attribute value + params = Array.isArray(params) ? + params.map(param=>{return this.getAttributeValue(message, param)}) : + this.getAttributeValue(message, params); + // if params is an array + if (operator !== "in" && params.constructor === Array) { + result = params.some(param => { + return this.operators[operator](value, param, this.attribute); + }); + } + // otherwise it is a value + else { + result = this.operators[operator](value, params, this.attribute); + } } results.push(result); } @@ -56,10 +62,23 @@ class AttributeCondition { return this.isApplicable(message, express); }); } else { - throw new Error(`Unsupported condition format`); + throw new Error(`[${this.name}] Unsupported condition format`); } return final; } + + getAttributeValue(msg, attribute = this.attribute){ + if (attribute !== this.attribute) { + let attr = this.context.isAttributed(attribute); + if (attr) { + return this.context.getValueOfAttribute(attr, msg) + } else { + return attribute; + } + } else { + return this.context.getValueOfAttribute(attribute, msg); + } + } } module.exports = AttributeCondition; \ No newline at end of file diff --git a/src/main/components/policyEngine/Attributes.js b/src/main/components/policyEngine/Attributes.js new file mode 100644 index 0000000..d64a4b9 --- /dev/null +++ b/src/main/components/policyEngine/Attributes.js @@ -0,0 +1,286 @@ +/** + * Created by hao on 20/04/17. + */ +let divideEmail = require('./context/Utils').divideEmail; +let divideURL = require('./context/Utils').divideURL; +let removePathFromURL = require('./context/Utils').removePathFromURL; +let getUserEmailFromURL = require('./context/Utils').getUserEmailFromURL; +let moment = require('moment'); +let config = require('../../../configs/server-settings'); + +let msgTypes = { + registration:(msg)=>{ + let result = false; + let urlPattern = "domain://registry."; + if (msg.from.startsWith(urlPattern) || msg.to.startsWith(urlPattern)) { + result = true; + } + return result; + }, + addressAllocation:(msg)=>{ + let result = false; + let urlPattern = "/address-allocation"; + if (msg.from.endsWith(urlPattern) && msg.to.endsWith(urlPattern)) { + result = true; + } + return result; + }, + discovery:(msg)=>{ + let result = false; + let urlPattern = "/discovery/"; + if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { + result = true; + } + return result; + }, + globalRegistry:(msg)=>{ + let result = false; + let urlPattern = "/graph-connector"; + if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { + result = true; + } + return result; + }, + identityManagement:(msg)=>{ + let result = false; + let urlPattern = "/idm"; + if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { + result = true; + } + return result; + }, + dataSync:(msg)=>{ + let result = false; + let urlPattern = "/sm"; + if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { + result = true; + } + return result; + }, + p2pConnection:(msg)=>{ + let result = false; + if (msg.from.endsWith("/ua") || msg.to.endsWith("/ua")) { + result = true; + } else if (msg.from.endsWith("/sm") || msg.to.endsWith("/sm")) { + result = true; + } + return result; + } +}; + +class Attributes { + // ============================== environment attributes ============================= + date(msg, context, newValue = null){ + return moment().format("YYYY-MM-DD"); + } + time(msg, context, newValue = null){ + return moment().format("HH:mm:ss"); + } + weekday(msg, context, newValue = null){ + return moment().format("dddd"); + } + // ============================== subject attributes ================================= + srcIDPDomain(msg, context, newValue = null){ + if (msg.body.identity !== undefined) { + return divideEmail(msg.body.identity.userProfile.username).domain; + } else { + return undefined; + } + } + srcUsername(msg, context, newValue = null){ + if (msg.body.identity !== undefined) { + return msg.body.identity.userProfile.username; + } else if (msg.body.value && msg.body.value.user){ + return getUserEmailFromURL(msg.body.value.user); + } else if (msg.from.startsWith('hyperty://'|| msg.from.startsWith('runtime://'))){ + let infoList = context.policyEngine.pip.getRegistryCacheEntry(removePathFromURL(msg.from)); + if (infoList.length) { + return getUserEmailFromURL(infoList[0].userID); + } else { + return undefined; + } + } else { + return undefined; + } + } + srcRuntime(msg, context, newValue = null){ + if (msg.from.startsWith('runtime://')) { + return removePathFromURL(msg.from); + } else if (msg.from.startsWith('hyperty://')) { + let infoList = context.policyEngine.pip.getRegistryCacheEntry(removePathFromURL(msg.from)); + if (infoList.length) { + return infoList[0].runtime; + } else { + return undefined; + } + } else { + return undefined; + } + } + srcHyperty(msg, context, newValue = null){ + if (msg.from.startsWith('hyperty://')) { + return removePathFromURL(msg.from); + } else { + return undefined; + } + } + srcIDP(msg, context, newValue = null){ + if (msg.body.identity !== undefined) { + return msg.body.identity.idp; + } else { + return undefined; + } + } + srcSPDomain(msg, context, newValue = null){ + let from = msg.from; + return divideURL(from).domain; + } + srcScheme(msg, context, newValue = null){ + let from = msg.from; + return divideURL(from).type; + } + msgFrom(msg, context, newValue = null){ + return msg.from; + } + // ==================================== object attributes ========================================= + msgTo(msg, context, newValue = null){ + return msg.to; + } + dstScheme(msg, context, newValue = null){ + let to = msg.to; + return divideURL(to).type; + } + dstSPDomain(msg, context, newValue = null){ + let to = msg.to; + return divideURL(to).domain; + } + dstUsername(msg, context, newValue = null){ + if (msg.to.startsWith('hyperty://'|| msg.to.startsWith('runtime://'))){ + let infoList = context.policyEngine.pip.getRegistryCacheEntry(removePathFromURL(msg.to)); + if (infoList.length) { + return getUserEmailFromURL(infoList[0].userID); + } else { + return undefined; + } + } else { + return undefined; + } + } + dstRuntime(msg, context, newValue = null){ + if (msg.to.startsWith('runtime://')) { + return removePathFromURL(msg.to); + } else if (msg.to.startsWith('hyperty://')) { + let infoList = context.policyEngine.pip.getRegistryCacheEntry(removePathFromURL(msg.to)); + if (infoList.length) { + return infoList[0].runtime; + } else { + return undefined; + } + } else { + return undefined; + } + } + dstHyperty(msg, context, newValue = null){ + if (msg.to.startsWith('hyperty://')) { + return removePathFromURL(msg.to); + } else { + return undefined; + } + } + // ==================================== resource & action attributes ========================================== + actType(msg, context, newValue = null){ + return msg.type; + } + resource(msg, context, newValue = null){ + if (msg.body !== undefined) { + return msg.body.resource; + } else { + return undefined; + } + } + valueResources(msg, context, newValue = null) { + if (msg.body.value !== undefined) { + return msg.body.value.resources; + } else { + return undefined; + } + } + // ===================================== system attributes ========================================= + msgId(msg, context, newValue = null){ + return msg.id; + } + msgType(msg, context, newValue = null){ + for (let key in msgTypes) { + if (msgTypes.hasOwnProperty(key) && msgTypes[key](msg)) { + return key; + } + } + return undefined; + } + auth(msg, context, newValue = null){ + if (msg.body !== undefined) { + return msg.body.auth; + } else { + return undefined; + } + } + domain(msg, context, newValue = null){ + return config.MNdomain; + } + + domainRegistry(msg, context, newValue = null){ + return config.domainRegistryConfig.url; + } + port(msg, context, newValue = null){ + return config.port; + } + useSSL(msg, context, newValue = null){ + return config.useSSL; + } + // ===================================== message specific attributes ================================== + // e.g., address allocation messages + valueNumber(msg, context, newValue = null){ + if (newValue){ + if (msg.body.value !== undefined) { + msg.body.value.number = newValue; + return msg; + } + } else { + if (msg.body.value !== undefined) { + return msg.body.value.number; + } else { + return undefined; + } + } + } + valueAllocated(msg, context, newValue = null){ + if (newValue) { + if (msg.body.value !== undefined) { + msg.body.value.allocated = newValue; + return msg; + } + } else { + if (msg.body.value !== undefined) { + return msg.body.value.allocated; + } else { + return undefined; + } + } + } + valueExpires(msg, context, newValue = null) { + if (msg.body.value && msg.body.value.expires){ + return msg.body.value.expires; + } else { + return undefined; + } + } + // ====================================== others ========================================== + userRegistries(msg, context, newValue = null) { + if (msg.body.value && msg.body.value.user){ + return context.policyEngine.pip.getRegistryCacheEntry(msg.body.value.user); + } else { + return undefined; + } + } +} +module.exports = Attributes; \ No newline at end of file diff --git a/src/main/components/policyEngine/Condition.js b/src/main/components/policyEngine/Condition.js index 898957c..c277b00 100644 --- a/src/main/components/policyEngine/Condition.js +++ b/src/main/components/policyEngine/Condition.js @@ -8,14 +8,12 @@ let Operators = require("./Operators"); class Condition { constructor(owner, context, condition, usedFor = "Condition"){ - this.name = owner; - this.usedFor = usedFor; + this.name = owner+' '+usedFor; + this.develop = context.devMode; this.context = context; this.logger = this.context.registry.getLogger(); this.operators = new Operators(); - this.toString = JSON.stringify(condition); this.condition = this._buildCondition(condition); - } _buildCondition(condition){ @@ -43,18 +41,19 @@ class Condition { }); } // if the key is an attribute - else if (key in this.context){ - condition = new AttributeCondition(this.context, condition); - } - // else invalid else { - this.logger.info(`[${this.name}] warning: unrecognized key when building condition: ${key}`); + key = this.context.isAttributed(key); + if (key) { + condition = new AttributeCondition(this.context, key, value); + } else { + this.logger.error(`[${this.name}] unrecognized key when building condition: ${key}!`); + } } } // else the map is empty - else { + // else { // this.logger.info(`[${this.name}] warning: empty ${this.usedFor} implies global applicability`); - } + // } } // if the condition is of ARRAY type, which contains subConditions with OR relations else { @@ -67,16 +66,20 @@ class Condition { isApplicable(message, condition = this.condition){ if (condition instanceof AttributeCondition){ - return condition.isApplicable(message); + let _applicable = condition.isApplicable(message); + if (this.develop){ + this.logger.info(`[${this.name}] ${condition.attribute} ${condition.getAttributeValue(message)} ${JSON.stringify(condition.expression).slice(1,-1)}: ${_applicable}`); + } + return _applicable; } // if the condition is not of Object type else if (condition.constructor !== Object) { - throw new Error(`[${this.name}] syntax error: compiled condition should only be of Object type`); + throw new Error(`[${this.name}] syntax error: compiled condition should only be of Object type!`); } // the condition is not an instance of AttributeCondition // the condition contains multiple keys else if (Object.keys(condition).length > 1){ - throw new Error(`[${this.name}] syntax error: compiled condition contains multiple keys in a map`); + throw new Error(`[${this.name}] syntax error: compiled condition contains multiple keys in a map!`); } // the condition contains only one key else if (Object.keys(condition).length === 1) { @@ -88,14 +91,17 @@ class Condition { return this.isApplicable(message, subCondition); }) ); - } else if (key in this.context){ - throw new Error(`[${this.name}] syntax error: attribute is failed to build attribute condition object: ${key}`); + } else if (this.context.isAttributed(key)){ + throw new Error(`[${this.name}] syntax error: attribute is failed to build attribute condition object: ${key}!`); } else { - throw new Error(`[${this.name}] syntax error: unrecognized key in condition field: ${key}`); + throw new Error(`[${this.name}] syntax error: unrecognized key in condition field: ${key}!`); } } // empty condition else { + if (this.develop){ + this.logger.info(`[${this.name}] is globally applicable`); + } return true; } } diff --git a/src/main/components/policyEngine/Operators.js b/src/main/components/policyEngine/Operators.js index 3c424a2..5528e23 100644 --- a/src/main/components/policyEngine/Operators.js +++ b/src/main/components/policyEngine/Operators.js @@ -66,7 +66,6 @@ class Operators { value = parseInt(value); start = parseInt(start); end = parseInt(end); - console.log(`${attribute} is not explicitly specified for between operation.`); } return (value > start && value < end); } @@ -75,6 +74,17 @@ class Operators { /** * @param [number/string] a parameter for comparison * */ + if (Array.isArray(value) && Array.isArray(param)) { + if (value.length === param.length) { + return value.every((u, i)=>{ + return u === param[i]; + }); + } else { + return false; + } + } else if(Array.isArray(value) && typeof param === 'number'){ + return value.length === param; + } return String(param)==='*' || String(param)===String(value); } @@ -82,6 +92,11 @@ class Operators { /** * @param [number/string] a parameter for comparison * */ + if (Array.isArray(value) && Array.isArray(param)) { + return value.length > param.length; + } else if(Array.isArray(value) && typeof param === 'number'){ + return value.length > param; + } return parseInt(value) > parseInt(param); } @@ -89,6 +104,11 @@ class Operators { /** * @param [number/string] a parameter for comparison * */ + if (Array.isArray(value) && Array.isArray(param)) { + return value.length < param.length; + } else if(Array.isArray(value) && typeof param === 'number'){ + return value.length < param; + } return parseInt(value) < parseInt(param); } diff --git a/src/main/components/policyEngine/Policy.js b/src/main/components/policyEngine/Policy.js index 1266fd9..41f8136 100644 --- a/src/main/components/policyEngine/Policy.js +++ b/src/main/components/policyEngine/Policy.js @@ -10,7 +10,7 @@ let FirstApplicable = require('./algorithm/FirstApplicable'); class Policy { - constructor(context, policyObj) { + constructor(owner, context, policyObj) { if (!("id" in policyObj)) throw new Error("id is not defined."); if (!("priority" in policyObj)) throw new Error("priority is not defined."); if (!("target" in policyObj)) throw new Error("target is not defined."); @@ -20,7 +20,7 @@ class Policy { this.context = context; this.logger = this.context.registry.getLogger(); this.id = policyObj.id; - this.name = `PDP Policy ${this.id}`; + this.name = owner + ` | Policy ${this.id}`; this.priority = policyObj.priority; this.target = new Target(this.name, this.context, policyObj.target, 'Target'); this.obligations = policyObj.obligations; @@ -41,7 +41,7 @@ class Policy { } let response = this.ruleCombiningAlgorithm.combine(results); let obligations = this.obligations[response.effect]; - if (obligations) response.addObligations(obligations); + if (obligations) response.addObligations(new Map().set(this.name.substr(4), obligations)); return response; } @@ -54,7 +54,7 @@ class Policy { rule.priority = this._getLastPriority() + 1; } if (!(rule instanceof Rule)) { - rule = new Rule(this.context, rule); + rule = new Rule(this.name, this.context, rule); } nrules.push(rule); } diff --git a/src/main/components/policyEngine/PolicyEngine.js b/src/main/components/policyEngine/PolicyEngine.js new file mode 100644 index 0000000..a8642ca --- /dev/null +++ b/src/main/components/policyEngine/PolicyEngine.js @@ -0,0 +1,44 @@ +/** + * Created by hao on 18/04/17. + */ +let pdp = require('./pdp/Pdp'); +let pep = require('./pep/Pep'); +let pip = require('./pip/Pip'); +let prp = require('./prp/Prp'); + +class PolicyEngine { + constructor(name, context) { + this.name = name; + context.policyEngine = this; + this.context = context; + this.logger = context.registry.getLogger(); + this.pip = new pip(context); + this.prp = new prp(context); + this.pdp = new pdp(context, this.prp); + this.pep = new pep(context, this.pdp); + this.logger.info(`[${this.name}] started ${context.devMode? "in development mode": ""}`); + } + + getName(){ + return this.name + } + + getPDP(){ + return this.pdp; + } + + getPEP(){ + return this.pep; + } + + getPRP(){ + return this.prp; + } + + getPIP(){ + return this.pip; + } + +} + +module.exports = PolicyEngine; \ No newline at end of file diff --git a/src/main/components/policyEngine/PolicySet.js b/src/main/components/policyEngine/PolicySet.js index d2cebaa..c84f72f 100644 --- a/src/main/components/policyEngine/PolicySet.js +++ b/src/main/components/policyEngine/PolicySet.js @@ -46,7 +46,7 @@ class PolicySet { } let response = this.policyCombiningAlgorithm.combine(results); let obligations = this.obligations[response.effect]; - if (obligations) response.addObligations(obligations); + if (obligations) response.addObligations(new Map().set(this.name.substr(4), obligations)); return response; } @@ -68,7 +68,7 @@ class PolicySet { if (!policies.hasOwnProperty(i)) continue; let policy = policies[i]; if (!(policy instanceof Policy)) { - policy = new Policy(this.context, policy); + policy = new Policy(this.name, this.context, policy); } npolicies.push(policy); } diff --git a/src/main/components/policyEngine/Request.js b/src/main/components/policyEngine/Request.js new file mode 100644 index 0000000..9ab0c53 --- /dev/null +++ b/src/main/components/policyEngine/Request.js @@ -0,0 +1,26 @@ +/** + * Created by hao on 17/04/17. + */ +let Moment = require('moment'); + +class Request { + constructor (message, context) { + this.msg = message; + this.timeStamp = Moment(); + this.context = context; + } + + getMessage(){ + return this.msg; + } + + getTimeStamp(){ + return this.timeStamp; + } + + getContext(){ + return this.context; + } +} + +module.exports = Request; \ No newline at end of file diff --git a/src/main/components/policyEngine/Response.js b/src/main/components/policyEngine/Response.js index 9e3f571..1ea0b0f 100644 --- a/src/main/components/policyEngine/Response.js +++ b/src/main/components/policyEngine/Response.js @@ -9,20 +9,13 @@ class Response { this.obligations = new Map(); this.info = info; this.source = source; + this.msg = null; } addObligations(obligations) { - if (Object.keys(obligations).length){ - for (let key in obligations) { - if (!obligations.hasOwnProperty(key)) continue; - this.obligations.set(key, obligations[key]); - } + for (let [key,value] of obligations) { + this.obligations.set(key, value); } - - } - - appendSource(child){ - this.source = this.source + '/' + child.substr(3); } setEffect(effect){ @@ -38,9 +31,18 @@ class Response { } getInfo(){ - return `${this.source} determined to ${this.effect}. ${this.info}` + return `[${this.source}] determined to ${this.effect}. ${this.info}` + } + + getMessage(){ + return this.msg; + } + + setMessage(msg){ + this.msg = msg; } + } module.exports = Response; \ No newline at end of file diff --git a/src/main/components/policyEngine/Rule.js b/src/main/components/policyEngine/Rule.js index 299ad10..a28e57c 100644 --- a/src/main/components/policyEngine/Rule.js +++ b/src/main/components/policyEngine/Rule.js @@ -6,7 +6,7 @@ let Condition = require("./Condition"); let Response = require("./Response"); class Rule { - constructor(context, rule) { + constructor(owner, context, rule) { if (!("id" in rule)) throw new Error("id is not defined."); if (!("target" in rule)) throw new Error("target is not defined."); if (!("condition" in rule)) throw new Error("condition is not defined."); @@ -15,7 +15,7 @@ class Rule { if (!("priority" in rule)) throw new Error("priority is not defined."); this.context = context; this.id = rule.id; - this.name = `PDP Rule ${this.id}`; + this.name = owner+` | Rule ${this.id}`; this.logger = this.context.registry.getLogger(); this.target = new Condition(this.name, this.context, rule.target, 'Target'); this.condition = new Condition(this.name, this.context, rule.condition); @@ -34,7 +34,7 @@ class Rule { let isApplicable = this.condition.isApplicable(message); if (isApplicable) { res.setEffect(this.effect); - res.addObligations(this.obligations); + res.addObligations(new Map().set(this.name.substr(4),this.obligations)); return res; } return res; diff --git a/src/main/components/policyEngine/algorithm/AllowOverrides.js b/src/main/components/policyEngine/algorithm/AllowOverrides.js index 097a9cb..6ff8128 100644 --- a/src/main/components/policyEngine/algorithm/AllowOverrides.js +++ b/src/main/components/policyEngine/algorithm/AllowOverrides.js @@ -33,6 +33,7 @@ class AllowOverrides { constructor (context, name){ this.name = name; + this.develop = context.devMode; this.context = context; this.logger = this.context.registry.getLogger(); } @@ -43,20 +44,23 @@ class AllowOverrides { * @returns {Response} */ combine(responses) { - let response = new Response(this.name, `resulted from allow-overrides algorithm of ${this.name}`); - for (let i in responses){ - let res = responses[i]; - response.addObligations(res.obligations); - } - let decisions = responses.map(res=>{return res.effect}); + let response = new Response(this.name); + let decisions = responses.map(res=>{ + if (this.develop){ + this.logger.info(`[${res.source}] evaluated to ${res.effect}`); + } + return res.effect + }); let idxPer = decisions.indexOf("permit"); + let idxDen = decisions.indexOf("deny"); if (idxPer !== -1) { - response.setEffect("permit"); - response.appendSource(responses[idxPer].source); - } else if (decisions.indexOf("deny") !== -1) { - response.setEffect("deny"); - } else { - response.setInfo("not applicable to the targeted message"); + response = responses[idxPer]; + } else if (idxDen !== -1) { + response = responses[idxDen]; + } + response.setInfo(`resulted from allow-overrides algorithm`); + if (this.develop){ + this.logger.info(`${response.getInfo()}`); } return response; } diff --git a/src/main/components/policyEngine/algorithm/BlockOverrides.js b/src/main/components/policyEngine/algorithm/BlockOverrides.js index cb0f1ee..8fee6d8 100644 --- a/src/main/components/policyEngine/algorithm/BlockOverrides.js +++ b/src/main/components/policyEngine/algorithm/BlockOverrides.js @@ -30,32 +30,36 @@ let Response = require("../Response"); class BlockOverrides { - constructor (context, name){ this.name = name; + this.develop = context.devMode; this.context = context; this.logger = this.context.registry.getLogger(); } + /** * Given an array of individual authorization decisions, prioritizes a positive one. * @param {boolean[]} responses * @returns {Response} */ combine(responses) { - let response = new Response(this.name, `resulted from block-overrides algorithm of ${this.name}`); - for (let i in responses){ - let res = responses[i]; - response.addObligations(res.obligations); + let response = new Response(this.name); + let decisions = responses.map(res=>{ + if (this.develop){ + this.logger.info(`[${res.source}] evaluated to ${res.effect}`); + } + return res.effect + }); + let idxDen = decisions.indexOf("deny"); + let idxPer = decisions.indexOf("permit"); + if (idxDen !== -1) { + response = responses[idxDen]; + } else if (idxPer !== -1) { + response = responses[idxPer]; } - let decisions = responses.map(res=>{return res.effect}); - let idxDeny = decisions.indexOf("deny"); - if (idxDeny !== -1) { - response.setEffect("deny"); - response.appendSource(responses[idxDeny].source); - } else if (decisions.indexOf("permit") !== -1) { - response.setEffect("permit"); - } else { - response.setInfo("not applicable to the targeted message"); + response.setInfo(`resulted from block-overrides algorithm`); + if (this.develop){ + this.logger.info(`${response.getInfo()}`); } return response; } diff --git a/src/main/components/policyEngine/algorithm/FirstApplicable.js b/src/main/components/policyEngine/algorithm/FirstApplicable.js index 7af7a44..006d70a 100644 --- a/src/main/components/policyEngine/algorithm/FirstApplicable.js +++ b/src/main/components/policyEngine/algorithm/FirstApplicable.js @@ -32,6 +32,7 @@ class FirstApplicable { constructor (context, name){ this.name = name; + this.develop = context.devMode; this.context = context; this.logger = this.context.registry.getLogger(); } @@ -41,15 +42,19 @@ class FirstApplicable { * @returns {Response} */ combine(responses) { - let response = new Response(this.name, `${this.name} not applicable to the targeted message`); + let response = new Response(this.name); for (let i in responses) { if (responses[i].effect !== 'notApplicable') { response = responses[i]; + break; } } + response.setInfo(`resulted from first-applicable algorithm`); + if (this.develop){ + this.logger.info(`${response.getInfo()}`); + } return response; } - } module.exports = FirstApplicable; diff --git a/src/main/components/policyEngine/context/NodejsCtx.js b/src/main/components/policyEngine/context/NodejsCtx.js index 50c8979..87d0302 100644 --- a/src/main/components/policyEngine/context/NodejsCtx.js +++ b/src/main/components/policyEngine/context/NodejsCtx.js @@ -2,6 +2,7 @@ * Created by hjiang on 3/9/17. */ let ReThinkCtx = require('./ReThinkCtx'); +let Attributes = require('./../Attributes'); class NodejsCtx extends ReThinkCtx { @@ -9,94 +10,49 @@ class NodejsCtx extends ReThinkCtx { super(); this.name = 'PDP'; this.registry = registry; - this.msg = null; - this.msgTypes = { - registration: function (msg) { - let result = false; - let urlPattern = "domain://registry."; - if (msg.from.startsWith(urlPattern) || msg.to.startsWith(urlPattern)) { - result = true; - } - return result; - }, - addressAllocation: function (msg) { - let result = false; - let urlPattern = "/address-allocation"; - if (msg.from.endsWith(urlPattern) && msg.to.endsWith(urlPattern)) { - result = true; - } - return result; - }, - discovery: function (msg) { - let result = false; - let urlPattern = "/discovery/"; - if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { - result = true; - } - return result; - }, - globalRegistry: function (msg) { - let result = false; - let urlPattern = "/graph-connector"; - if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { - result = true; - } - return result; - }, - identityManagement: function (msg) { - let result = false; - let urlPattern = "/idm"; - if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { - result = true; - } - return result; - }, - dataSync: function (msg) { - let result = false; - let urlPattern = "/sm"; - if (msg.from.endsWith(urlPattern) || msg.to.endsWith(urlPattern)) { - result = true; - } - return result; - }, - p2pConnection: function (msg) { - let result = false; - if (msg.from.endsWith("/ua") || msg.to.endsWith("/ua")) { - result = true; - } else if (msg.from.endsWith("/sm") || msg.to.endsWith("/sm")) { - result = true; - } - return result; - } - } + this.devMode = registry.config.policyConfig.development; + this.logger = this.registry.getLogger(); + this.attri = new Attributes(); } - get domainUrl(){ - return this.registry.getDomain(); + setValueOfAttribute(attr, msg, newValue){ + // todo: currently only valueNumber and valueAllocated support to set new values. Enable others if needed. + if (attr in this.attri) { + return this.attri[attr](msg, this, newValue); + } else { + this.logger.error(`[${this.name}] attribute ${attr} is not valid`); + } } - get domainRegistryUrl(){ - return this.registry.getConfig().domainRegistryConfig.url; + getValueOfAttribute(attr, msg) { + if (attr in this.attri) { + return this.attri[attr](msg, this); + } else { + this.logger.error(`[${this.name}] attribute ${attr} is not valid`); + } } - get port(){ - return this.registry.getConfig().port; + isAttribute(attr) { + return Object.getOwnPropertyNames(this.attri.__proto__).slice(1).includes(attr); } - get useSSL(){ - return this.registry.getConfig().useSSL; + isAttributed(attr){ + if (attr.startsWith("<") && attr.endsWith(">") && this.isAttribute(attr.slice(1,-1))){ + return attr.slice(1,-1); + } else { + return null; + } } - get msgType() { - return this._msgType; - } + getAllAttributeValues(msg){ - set msgType(params){ - for (let key in this.msgTypes) { - if (this.msgTypes.hasOwnProperty(key) && this.msgTypes[key](params.message)) { - this._msgType = key; - } + let [attributes, attributeValues] = [Object.getOwnPropertyNames(this.attri.__proto__).slice(1), new Map()]; + for (let index in attributes) { + let attribute = attributes[index]; + attributeValues.set(attribute, this.attri[attribute](msg, this)); } + return attributeValues; } + } module.exports = NodejsCtx; \ No newline at end of file diff --git a/src/main/components/policyEngine/context/ReThinkCtx.js b/src/main/components/policyEngine/context/ReThinkCtx.js index 6b742b0..b88356f 100644 --- a/src/main/components/policyEngine/context/ReThinkCtx.js +++ b/src/main/components/policyEngine/context/ReThinkCtx.js @@ -1,178 +1,10 @@ /** * Created by hjiang on 3/9/17. */ -let divideEmail = require('./Utils').divideEmail; -let divideURL = require('./Utils').divideURL; -let isDataObjectURL = require('./Utils').isDataObjectURL; -let moment = require('moment'); -let operators = require('../Operators'); class ReThinkCtx { - constructor(){ - this.defaultBehaviour = true; - this.operators = new operators(); - this.groups = {}; - } - - // ==================================== environment attributes ====================================== - - get date() { - return this._date; - } - - set date(now) { - this._date = moment().format("YYYY-MM-DD"); - } - - get time() { - return this._time; - } - - set time(now) { - this._time = moment().format("HH:mm:ss"); - } - - get weekday() { - return this._weekday; - } - - set weekday(now) { - this._weekday = moment().format("dddd"); - } - - // ==================================== subject attributes ========================================= - - get srcIDPDomain() { - return this._srcIDPDomain; - } - - set srcIDPDomain(params) { - if (params.message.body.identity !== undefined) { - this._srcIDPDomain = divideEmail(params.message.body.identity.userProfile.username).domain; - } - } - - get srcUsername() { - return this._srcUsername; - } - - set srcUsername(params) { - if (params.message.body.identity !== undefined) { - this._srcUsername = params.message.body.identity.userProfile.username; - } - } - - get srcIDP() { - return this._srcIDP; - } - - set srcIDP(params) { - if (params.message.body.identity !== undefined) { - this._srcIDP = params.message.body.identity.idp; - } - } - - get srcSPDomain() { - return this._srcSPDomain; - } - - set srcSPDomain(params) { - let from = params.message.from; - this._srcSPDomain = divideURL(from).domain; - } - - get srcScheme() { - return this._srcScheme; - } - - set srcScheme(params) { - let from = params.message.from; - this._srcScheme = divideURL(from).type; - } - - get msgFrom() { - return this._msgFrom; - } - - set msgFrom(params) { - this._msgFrom = params.message.from; - } - - // ==================================== object attributes ========================================= - get msgTo() { - return this._msgTo; - } - - set msgTo(params) { - this._msgTo = params.message.to; - } - - get dstScheme() { - return this._dstScheme; - } - - set dstScheme(params) { - let to = params.message.to; - this._dstScheme = divideURL(to).type; - } - - get dstSPDomain() { - return this._dstSPDomain; - } - - set dstSPDomain(params) { - let to = params.message.to; - this._dstSPDomain = divideURL(to).domain; - } - - // ==================================== action attribute ========================================== - - get actionType() { - return this._actionType; - } - - set actionType(params) { - this._actionType = params.message.type; - } - - // ===================================== resource attribute ======================================= - get resource() { - return this._resource; - } - - set resource(params) { - if (params.message.body.resource !== undefined) { - this._resource = params.message.body.resource; - } - } - - // ===================================== system attribute ========================================= - get msgId() { - return this._msgId; - } - - set msgId(params) { - this._msgId = params.message.id; - } - - get msgType() { - throw new Error("method must be implemented"); - } - - set msgType(params) { - throw new Error("method must be implemented"); - } - - get auth() { - return this._auth; - } - - set auth(params) { - if (params.message.body.auth !== undefined) { - this._auth = params.message.body.auth; - } - } + constructor(){} } module.exports = ReThinkCtx; \ No newline at end of file diff --git a/src/main/components/policyEngine/context/Utils.js b/src/main/components/policyEngine/context/Utils.js index 4e7351e..48a9c10 100644 --- a/src/main/components/policyEngine/context/Utils.js +++ b/src/main/components/policyEngine/context/Utils.js @@ -121,7 +121,7 @@ getUserURLFromEmail: function (userEmail) { */ getUserEmailFromURL: function (userURL) { - let url = divideURL(userURL); + let url = module.exports.divideURL(userURL); return url.identity.replace('/', '') + '@' + url.domain; // identity field has '/exampleID' instead of 'exampleID' }, @@ -135,7 +135,7 @@ convertToUserURL: function (identifier) { // check if the identifier is already in the url format if (identifier.substring(0, 7) === 'user://') { - let dividedURL = divideURL(identifier); + let dividedURL = module.exports.divideURL(identifier); //check if the url is well formated if (dividedURL.domain && dividedURL.identity) { diff --git a/src/main/components/policyEngine/pdp/Pdp.js b/src/main/components/policyEngine/pdp/Pdp.js index dfa55a2..9e9d03c 100644 --- a/src/main/components/policyEngine/pdp/Pdp.js +++ b/src/main/components/policyEngine/pdp/Pdp.js @@ -12,40 +12,45 @@ let PRP = require("../prp/Prp"); let Response = require("../Response"); class PDP { - constructor(context) { + constructor(context, prp) { this.name = 'PDP'; - context.pdp = this; this.context = context; this.logger = this.context.registry.getLogger(); - this.prp = new PRP(this.context); + this.prp = prp; this.logger.info(`[${this.name}] new instance`); + this.develop = context.devMode; } // ========================= public ============================= - authorize(authorizationRequest) { - - authorizationRequest = this._validate(authorizationRequest); - - let policySet = this.prp.getPolicySet(authorizationRequest); - - let response = policySet ? policySet.evaluatePolicies(authorizationRequest) : new Response(this.name, 'No policies could be found from PRP'); - - return this._respond(response); - + authorize(request) { + let policySet = null; + let msg = this._validate(request); + if (msg) { + if (this.develop){ + this.logger.info(`[${this.name}] message attributes`, this.context.getAllAttributeValues(msg)); + } + policySet = this.prp.getPolicySet(msg); + } + return this._respond(policySet, msg); } // ========================= private ============================ - _validate(authorizationRequest) { - - // Todo: Identify fields to check for request validation. - - return authorizationRequest; + _validate(request) { + let msg = request.getMessage(); + if (!(msg && msg.id && msg.from && msg.to && msg.type)){ + this.logger.error(`[${this.name}] Invalid message`); + return null; + } + return msg; } - _respond(response) { - // Todo + _respond(policySet, msg) { + let response = new Response(this.name, 'No policies could be found from PRP'); + if (policySet) { + response = policySet.evaluatePolicies(msg); + } return response; } } diff --git a/src/main/components/policyEngine/pep/ContextHandler.js b/src/main/components/policyEngine/pep/ContextHandler.js index a6beac6..98b9d4a 100644 --- a/src/main/components/policyEngine/pep/ContextHandler.js +++ b/src/main/components/policyEngine/pep/ContextHandler.js @@ -1,42 +1,38 @@ /** * Created by hjiang on 3/2/17. */ - +let Request = require('../Request'); class ContextHandler { constructor(context) { this.name = "PEP"; this.context = context; this.logger = this.context.registry.getLogger(); + this.develop = context.devMode; } - parseToAuthzRequest(msg) { - - // Todo: add derived attributes and instant attributes to the request. - // PEPs can send any number of attributes to the PDP. At the very minimum it needs to send "key" attributes - // i.e. the user identity, the resource identity and type and the action identity. This creates the minimal viable PEP request. - // The PEP can also send derived attributes - attributes that are derived from the key attributes. - // For instance a user's job title, department, and clearance are all derived from the user's username or ID. - // PEPs can also send "instant" attributes - attributes that the PEP alone knows such as the IP of the requesting user, - // the device type, HTTP headers... These are attributes that are most commonly (easily) sent by the PEP. + parseToAuthzRequest(clientMsg) { + let msg = clientMsg.getMessage().msg; + msg.body = msg.body || {}; - return msg; + return new Request(msg, this.context); } parseToAuthzResponse(response) { - // Todo: explore more capabilities and add more options. Translate into a set of actions for example. if (response.effect === "permit"){ response.msg.body.auth = true; response.result = true; } else if (response.effect === "notApplicable") { - response.result = this.context.defaultBehaviour; + response.result = this.context.registry.config.policyConfig.defaultBehavior; response.msg.body.auth = false; } else if (response.effect === "deny"){ response.msg.body.auth = false; response.result = false; } - this.logger.info(`[${this.name}] ${response.getInfo()}`); + if (!this.develop){ + this.logger.info(response.getInfo()); + } return response; } diff --git a/src/main/components/policyEngine/pep/Obligator.js b/src/main/components/policyEngine/pep/Obligator.js new file mode 100644 index 0000000..2265d1d --- /dev/null +++ b/src/main/components/policyEngine/pep/Obligator.js @@ -0,0 +1,30 @@ +/** + * Created by hjiang on 25/04/2017. + */ +class Obligator { + constructor(context){ + this.name = 'PEP'; + this.context = context; + this.logger = context.registry.getLogger(); + this.develop = context.devMode; + } + + obligate(response){ + response.obligations.forEach((params, key)=>{ + let msg = response.getMessage(); + switch (key) { + case 'limitNumber': + let attri = Object.keys(params)[0]; + let number= params[attri]; + let value = this.context.getValueOfAttribute(attri, msg); + let newValue = value.slice(0, number); + response.setMessage(this.context.setValueOfAttribute(attri, msg, newValue)); + break; + default: + break; + } + }); + return response; + } +} +module.exports = Obligator; \ No newline at end of file diff --git a/src/main/components/policyEngine/pep/Pep.js b/src/main/components/policyEngine/pep/Pep.js index ad0bbb0..2921af3 100644 --- a/src/main/components/policyEngine/pep/Pep.js +++ b/src/main/components/policyEngine/pep/Pep.js @@ -10,66 +10,60 @@ * */ let ContextHandler = require("./ContextHandler"); let PDP = require("../pdp/Pdp"); +let Obligator = require("./Obligator"); class PEP { - constructor(name, context) { - this.name = name; - context.pep = this; + constructor(context, pdp) { + this.name = 'PEP'; this.context = context; + this.develop = context.devMode; this.logger = this.context.registry.getLogger(); - this.pdp = new PDP(this.context); - this.contextHandler = new ContextHandler(this.context); + this.pdp = pdp; + this.contextHandler = new ContextHandler(context); + this.obligator = new Obligator(context); this.logger.info(`[${this.name}] new instance`); } - /** - * @param {Object} msg - * @param {String} msg.id - * @param {String} msg.type - * @param {String} msg.from - * @param {String} msg.to - * @param {Object} msg.body - * @returns {denied:Boolean,error:Object} - */ + // ======================== public ======================== getName(){ return this.name } - analyse(msg) { - - msg = this._validate(msg); - let authorizationRequest = this.contextHandler.parseToAuthzRequest(msg); - let response = this.pdp.authorize(authorizationRequest); - response.msg = msg; - let authorizationResponse = this.contextHandler.parseToAuthzResponse(response); - return this._enforce(authorizationResponse); + analyse(clientMsg) { + let msg = this._validate(clientMsg.msg.msg); + if (msg) { + let request = this.contextHandler.parseToAuthzRequest(clientMsg); + let response = this.pdp.authorize(request); + response.setMessage(msg); + let authorizationResponse = this.contextHandler.parseToAuthzResponse(response); + return this._enforce(authorizationResponse); + } else { + return { + result: false, + getInfo: ()=>{return 'invalid message'} + }; + } } // ======================== private ======================= _validate(msg) { - - // Todo: enrich with more means appropriate to check the validity of the message - - if (!msg) throw new Error('message is not defined'); - if (!msg.id) throw new Error('message.id is not defined'); - if (!msg.from) throw new Error('message.from is not defined'); - if (!msg.to) throw new Error('message.to is not defined'); - if (!msg.type) throw new Error('message.type is not defined'); - msg.body = msg.body || {}; + if (!(msg && msg.id && msg.from && msg.to && msg.type)){ + this.logger.info(`[${this.name}] Invalid message`); + return null; + } return msg; } _enforce(response) { - - // Todo: take actions according to/specified in the decision from PDP - if (Object.keys(response.obligations).length){ - for (let key in response.obligations) { - if (!response.obligations.hasOwnProperty(key)) continue; - this.logger.info(`[${this.name}] ${key} ${response.obligations[key]}`); + if (response.obligations.size){ + if (this.develop) { + this.logger.info(`[${this.name}] Obligation`, response.obligations); } + return this.obligator.obligate(response); + } else { + return response; } - return response; } } diff --git a/src/main/components/policyEngine/pip/Pip.js b/src/main/components/policyEngine/pip/Pip.js new file mode 100644 index 0000000..1b265cf --- /dev/null +++ b/src/main/components/policyEngine/pip/Pip.js @@ -0,0 +1,98 @@ +/** + * Created by hao on 13/04/17. + */ + +let removePathFromURL = require('../context/Utils').removePathFromURL; +let NodeCache = require('node-cache'); +const RegistryDomainConnector = require('dev-registry-domain/connector'); + +class PIP { + constructor (context) { + this.name = "PIP"; + this.registry = context.registry; + this.develop = context.devMode; + this.cacheTTL = 3600; + this.logger = this.registry.getLogger(); + this.registryConnector = new RegistryDomainConnector(this.registry.getConfig().domainRegistryConfig); + this.cache = new NodeCache({ stdTTL: this.cacheTTL, checkperiod: 600 }); + this.logger.info(`[${this.name}] new instance`); + } + + setRegistryCacheEntry(msg, res){ + let value = null; + if (msg.type === 'create' && msg.body.value.url.startsWith('hyperty') && res.body.code === 200) { + value = { + userID: msg.body.value.user, + descriptor: msg.body.value.descriptor, + hypertyID: msg.body.value.url, + resources: msg.body.value.resources, + expires: msg.body.value.expires, + dataSchemes: msg.body.value.dataSchemes, + status: msg.body.value.status, + runtime: msg.body.value.runtime, + }; + } else if (msg.type === 'update' && msg.body.value && msg.body.value.user && msg.body.resource.startsWith('hyperty') && res.body.code === 200) { + value = { + userID: msg.body.value.user, + descriptor: msg.body.value.descriptor, + hypertyID: msg.body.resource, + resources: msg.body.value.resources, + expires: msg.body.value.expires || this.cacheTTL, + dataSchemes: msg.body.value.dataSchemes, + status: msg.body.value.status, + runtime: msg.from.startsWith('runtime')? removePathFromURL(msg.from): undefined, + }; + } + if (value) { + if (this.develop){ + this.logger.info(`[${this.name}] setting registry cache entry`, value); + } + this.cache.set(value.userID+ ' ' +value.hypertyID+ ' ' +value.runtime, value, value.expires); + } + } + + getRegistryCacheEntry(objURL){ + let [results, keys] = [[], this.cache.keys()]; + for (let index in keys) { + let key = keys[index]; + if (key.split(' ').includes(objURL)) { + results.push(this.cache.get(key)); + } + } + if (this.develop){ + this.logger.info(`[${this.name}] getting registry cache entry`, results); + } + return results; + } + + getDomainRegistryEntry(objURL){ + let _this = this; + let query = { + "type" : "read", + "body" : { "resource": objURL} + }; + let parseObj = (obj)=> { + if (obj.constructor === Object) { + for (let i in Object.keys(obj)) { + let key = Object.keys(obj)[i]; + obj[key] = JSON.parse(obj[key]); + } + return obj; + } + throw new Error(`invalid response from domain registry: ${obj}`); + }; + return new Promise((resolve, reject)=>{ + try { + _this.registryConnector.processMessage(query, (res) => { + resolve(parseObj(res)); + }); + } catch (e) { + _this.logger.error(`[${_this.name}] error while registryConnector processing message. ${e}`); + reject(e); + } + }); + } + +} + +module.exports = PIP; \ No newline at end of file diff --git a/src/main/components/policyEngine/prp/Prp.js b/src/main/components/policyEngine/prp/Prp.js index 4eb8b8d..fae2724 100644 --- a/src/main/components/policyEngine/prp/Prp.js +++ b/src/main/components/policyEngine/prp/Prp.js @@ -10,6 +10,7 @@ class PRP { constructor(context) { this.name = "PRP"; this.context = context; + this.develop = context.devMode; this.logger = this.context.registry.getLogger(); this.fsStore = new FS(this.context); this.redisStore = new Redis(this.context); diff --git a/src/main/components/policyEngine/prp/policy/policy.json b/src/main/components/policyEngine/prp/policy/policy.json index c97baff..8b6db97 100644 --- a/src/main/components/policyEngine/prp/policy/policy.json +++ b/src/main/components/policyEngine/prp/policy/policy.json @@ -4,7 +4,7 @@ "update" : "2017-03-14 17:18:31", "target": {}, "policyCombiningAlgorithm": "allowOverrides", - "obligations": {}, + "obligations": {"permit": {"info": "determines to permit"}}, "priority": 0, "policies": [ { @@ -12,20 +12,20 @@ "target": {}, "ruleCombiningAlgorithm": "blockOverrides", "priority": 0, - "obligations": {"permit": {"emailto": "admin@imt-atlantic.fr"}}, + "obligations": {"deny": {"info": "determines to deny"}}, "rules": [ { "id": 1, "target": {}, - "condition": {"srcIDP":{"equals":"google.com"}}, + "condition": {"":{"equals":"google.com"}}, "effect": "permit", - "obligations": {"emailto": "admin@imt-atlantic.fr"}, + "obligations": {"info": "determines to permit"}, "priority": 0 }, { "id": 2, "target":{}, - "condition": {"srcScheme":{"equals": "hello"}}, + "condition": {"":{"equals": ["hello","runtime"]}}, "effect": "deny", - "obligations": {"emailto": "admin@imt-atlantic.fr"}, + "obligations": {"info": "determines to deny"}, "priority": 0 } ] @@ -35,46 +35,53 @@ "target": {}, "ruleCombiningAlgorithm": "allowOverrides", "priority": 0, - "obligations": {"permit": {"emailto": "admin@imt-atlantic.fr"}}, + "obligations": {"permit": {"info": "determines to permit"}}, "rules": [ { "id": 1, - "target": {"srcIDPDomain": {"equals": "gmail.com"}}, + "target": {"": {"equals": "gmail.com"}}, "condition": [ { - "weekday": {"not": {"in": ["saturday", "sunday"]}}, - "time": {"between": ["06:00:00 12:30:00", "13:00:00 23:00:00"]} + "": {"not": {"in": ["saturday", "sunday"]}}, + "