|
@@ -1,5 +1,6 @@
|
|
|
const debug = require('debug')('growi:service:PassportService');
|
|
const debug = require('debug')('growi:service:PassportService');
|
|
|
const urljoin = require('url-join');
|
|
const urljoin = require('url-join');
|
|
|
|
|
+const luceneQueryParser = require('lucene-query-parser');
|
|
|
const passport = require('passport');
|
|
const passport = require('passport');
|
|
|
const LocalStrategy = require('passport-local').Strategy;
|
|
const LocalStrategy = require('passport-local').Strategy;
|
|
|
const LdapStrategy = require('passport-ldapauth');
|
|
const LdapStrategy = require('passport-ldapauth');
|
|
@@ -674,73 +675,67 @@ class PassportService {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const expr = this.parseABLCRule(rule);
|
|
|
|
|
- if (expr == null) {
|
|
|
|
|
|
|
+ // parse with lucene-query-parser
|
|
|
|
|
+ // see https://github.com/thoward/lucene-query-parser.js/wiki
|
|
|
|
|
+ const luceneRule = luceneQueryParser.parse(rule);
|
|
|
|
|
+ if (luceneRule == null) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- debug({ 'Parsed Rule': JSON.stringify(expr, null, 2) });
|
|
|
|
|
|
|
+ debug({ 'Parsed Rule': JSON.stringify(luceneRule, null, 2) });
|
|
|
|
|
|
|
|
const attributes = this.extractAttributesFromSAMLResponse(response);
|
|
const attributes = this.extractAttributesFromSAMLResponse(response);
|
|
|
debug({ 'Extracted Attributes': JSON.stringify(attributes, null, 2) });
|
|
debug({ 'Extracted Attributes': JSON.stringify(attributes, null, 2) });
|
|
|
|
|
|
|
|
- let evaluatedExpr = false;
|
|
|
|
|
- for (const orOp of expr) {
|
|
|
|
|
- let evaluatedOrOp = true;
|
|
|
|
|
- for (const andOp of orOp) {
|
|
|
|
|
- if (attributes[andOp[0]] == null) {
|
|
|
|
|
- evaluatedOrOp = false;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- evaluatedOrOp = evaluatedOrOp && attributes[andOp[0]].includes(andOp[1]);
|
|
|
|
|
- }
|
|
|
|
|
- evaluatedExpr = evaluatedExpr || evaluatedOrOp;
|
|
|
|
|
|
|
+ return this.evaluateRuleForSamlAttributes(attributes, luceneRule);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Evaluate whether the specified rule is satisfied under the specified attributes
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {object} attributes results by extractAttributesFromSAMLResponse
|
|
|
|
|
+ * @param {object} luceneRule Expression Tree Structure generated by lucene-query-parser
|
|
|
|
|
+ * @see https://github.com/thoward/lucene-query-parser.js/wiki
|
|
|
|
|
+ */
|
|
|
|
|
+ evaluateRuleForSamlAttributes(attributes, luceneRule) {
|
|
|
|
|
+ const { left, right, operator } = luceneRule;
|
|
|
|
|
+
|
|
|
|
|
+ // when combined rules
|
|
|
|
|
+ if (right != null) {
|
|
|
|
|
+ return this.evaluateCombinedRulesForSamlAttributes(attributes, left, right, operator);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (left != null) {
|
|
|
|
|
+ return this.evaluateRuleForSamlAttributes(attributes, left);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return evaluatedExpr;
|
|
|
|
|
|
|
+ const { field, term } = luceneRule;
|
|
|
|
|
+ if (field === '<implicit>') {
|
|
|
|
|
+ return attributes[term] != null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return attributes[field].includes(term);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Parse a rule string for the attribute-based login control
|
|
|
|
|
|
|
+ * Evaluate whether the specified two rules are satisfied under the specified attributes
|
|
|
*
|
|
*
|
|
|
- * The syntax rules are as follows.
|
|
|
|
|
- * <attr> and <value> are any characters except "|", "&", "=".
|
|
|
|
|
- *
|
|
|
|
|
- * ## Syntax
|
|
|
|
|
- * <expr> ::= <or_op> | <or_op> "|" <expr>
|
|
|
|
|
- * <or_op> ::= <and_op> | <and_op> "&" <or_op>
|
|
|
|
|
- * <and_op> ::= <attr> "=" <value>
|
|
|
|
|
- *
|
|
|
|
|
- * ## Example
|
|
|
|
|
- * In: "Department = A | Department = B & Position = Leader"
|
|
|
|
|
- * Out:
|
|
|
|
|
- * [
|
|
|
|
|
- * [
|
|
|
|
|
- * ["Department", "A"]
|
|
|
|
|
- * ],
|
|
|
|
|
- * [
|
|
|
|
|
- * ["Department","B"],
|
|
|
|
|
- * ["Position","Leader"]
|
|
|
|
|
- * ]
|
|
|
|
|
- * ]
|
|
|
|
|
- *
|
|
|
|
|
- * In: Invalid syntax string like a "This is a & bad & rule string."
|
|
|
|
|
- * Out: null
|
|
|
|
|
|
|
+ * @param {object} attributes results by extractAttributesFromSAMLResponse
|
|
|
|
|
+ * @param {object} luceneRuleLeft Expression Tree Structure generated by lucene-query-parser
|
|
|
|
|
+ * @param {object} luceneRuleRight Expression Tree Structure generated by lucene-query-parser
|
|
|
|
|
+ * @param {string} luceneOperator operator string expression
|
|
|
|
|
+ * @see https://github.com/thoward/lucene-query-parser.js/wiki
|
|
|
*/
|
|
*/
|
|
|
- parseABLCRule(rule) {
|
|
|
|
|
- let expr = rule.split('|');
|
|
|
|
|
- expr = expr.map(orOp => orOp.trim().split('&'));
|
|
|
|
|
- expr = expr.map(orOp => orOp.map(andOp => andOp.trim().split('=')));
|
|
|
|
|
- expr = expr.map(orOp => orOp.map(andOp => andOp.map(v => v.trim())));
|
|
|
|
|
- for (const orOp of expr) {
|
|
|
|
|
- for (const andOp of orOp) {
|
|
|
|
|
- if (andOp.length !== 2) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ evaluateCombinedRulesForSamlAttributes(attributes, luceneRuleLeft, luceneRuleRight, luceneOperator) {
|
|
|
|
|
+ if (luceneOperator === 'OR') {
|
|
|
|
|
+ return this.evaluateRuleForSamlAttributes(attributes, luceneRuleLeft) || this.evaluateRuleForSamlAttributes(attributes, luceneRuleRight);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (luceneOperator === 'AND') {
|
|
|
|
|
+ return this.evaluateRuleForSamlAttributes(attributes, luceneRuleLeft) && this.evaluateRuleForSamlAttributes(attributes, luceneRuleRight);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (luceneOperator === 'NOT') {
|
|
|
|
|
+ return this.evaluateRuleForSamlAttributes(attributes, luceneRuleLeft) && !this.evaluateRuleForSamlAttributes(attributes, luceneRuleRight);
|
|
|
}
|
|
}
|
|
|
- return expr;
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
|
|
+ throw new Error(`Unsupported operator: ${luceneOperator}`);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Extract attributes from a SAML response
|
|
* Extract attributes from a SAML response
|