|
@@ -598,6 +598,119 @@ class PassportService {
|
|
|
return missingRequireds;
|
|
return missingRequireds;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Verify that a SAML response meets the attribute-base login control rule
|
|
|
|
|
+ */
|
|
|
|
|
+ verifySAMLResponseByABLCRule(response) {
|
|
|
|
|
+ const rule = this.crowi.configManager.getConfig('crowi', 'security:passport-saml:ABLCRule');
|
|
|
|
|
+ if (rule == null) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const expr = this.parseABLCRule(rule);
|
|
|
|
|
+ if (expr === null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ debug({"Parsed Rule": JSON.stringify(expr, null, 2)});
|
|
|
|
|
+
|
|
|
|
|
+ const attributes = this.extractAttributesFromSAMLResponse(response);
|
|
|
|
|
+ debug({"Extracted Attributes": JSON.stringify(attributes, null, 2)});
|
|
|
|
|
+
|
|
|
|
|
+ let evaluated_expr = false
|
|
|
|
|
+ for (const or_op of expr) {
|
|
|
|
|
+ let evaluated_or_op = true;
|
|
|
|
|
+ for (const and_op of or_op) {
|
|
|
|
|
+ if (attributes[and_op[0]] == null) {
|
|
|
|
|
+ evaluated_or_op = false
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ evaluated_or_op = evaluated_or_op && attributes[and_op[0]].includes(and_op[1])
|
|
|
|
|
+ }
|
|
|
|
|
+ evaluated_expr = evaluated_expr || evaluated_or_op;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return evaluated_expr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Parse a rule string for the attribute-based login control
|
|
|
|
|
+ *
|
|
|
|
|
+ * 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
|
|
|
|
|
+ */
|
|
|
|
|
+ parseABLCRule(rule) {
|
|
|
|
|
+ let expr = rule.split("|");
|
|
|
|
|
+ expr = expr.map(or_op => or_op.trim().split("&"));
|
|
|
|
|
+ expr = expr.map(or_op => or_op.map(and_op => and_op.trim().split("=")));
|
|
|
|
|
+ expr = expr.map(or_op => or_op.map(and_op => and_op.map(v => v.trim())));
|
|
|
|
|
+ for (const or_op of expr) {
|
|
|
|
|
+ for (const and_op of or_op) {
|
|
|
|
|
+ if (and_op.length !== 2) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return expr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Extract attributes from a SAML response
|
|
|
|
|
+ *
|
|
|
|
|
+ * The format of extracted attributes is the following.
|
|
|
|
|
+ *
|
|
|
|
|
+ * {
|
|
|
|
|
+ * "attribute_name1": ["value1", "value2", ...],
|
|
|
|
|
+ * "attribute_name2": ["value1", "value2", ...],
|
|
|
|
|
+ * ...
|
|
|
|
|
+ * }
|
|
|
|
|
+ */
|
|
|
|
|
+ extractAttributesFromSAMLResponse(response) {
|
|
|
|
|
+ const attributeStatement = response.getAssertion().Assertion.AttributeStatement;
|
|
|
|
|
+ if (attributeStatement == null || attributeStatement[0] == null) {
|
|
|
|
|
+ return {};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const attributes = attributeStatement[0].Attribute;
|
|
|
|
|
+ if (attributes == null) {
|
|
|
|
|
+ return {};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const result = {};
|
|
|
|
|
+ for (const attribute of attributes) {
|
|
|
|
|
+ const name = attribute.$.Name;
|
|
|
|
|
+ const attributeValues = attribute.AttributeValue.map(v => v._)
|
|
|
|
|
+ if (result[name] == null) {
|
|
|
|
|
+ result[name] = attributeValues;
|
|
|
|
|
+ }else {
|
|
|
|
|
+ result[name] = result[name].concat(attributeValues)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* reset BasicStrategy
|
|
* reset BasicStrategy
|
|
|
*
|
|
*
|