Merge "Authorization rules: support YAML nested dictionaries"
This commit is contained in:
commit
1729696c81
|
@ -355,7 +355,9 @@ Below are some examples of how access rules can be defined:
|
||||||
- admin-rule:
|
- admin-rule:
|
||||||
name: affiliate_or_admin
|
name: affiliate_or_admin
|
||||||
conditions:
|
conditions:
|
||||||
- resources_access.account.roles: "affiliate"
|
- resources_access:
|
||||||
|
account:
|
||||||
|
roles: "affiliate"
|
||||||
iss: external_institution
|
iss: external_institution
|
||||||
- resources_access.account.roles: "admin"
|
- resources_access.account.roles: "admin"
|
||||||
- admin-rule:
|
- admin-rule:
|
||||||
|
@ -381,7 +383,8 @@ Below are some examples of how access rules can be defined:
|
||||||
This is the list of conditions that define a rule. A JWT must match **at
|
This is the list of conditions that define a rule. A JWT must match **at
|
||||||
least one** of the conditions for the rule to apply. A condition is a
|
least one** of the conditions for the rule to apply. A condition is a
|
||||||
dictionary where keys are claims. **All** the associated values must
|
dictionary where keys are claims. **All** the associated values must
|
||||||
match the claims in the user's token.
|
match the claims in the user's token; in other words the condition dictionary
|
||||||
|
must be a "sub-dictionary" of the user's JWT.
|
||||||
|
|
||||||
Zuul's authorization engine will adapt matching tests depending on the
|
Zuul's authorization engine will adapt matching tests depending on the
|
||||||
nature of the claim in the token, eg:
|
nature of the claim in the token, eg:
|
||||||
|
@ -391,45 +394,57 @@ Below are some examples of how access rules can be defined:
|
||||||
* if the claim is a string, check that the condition value is equal to
|
* if the claim is a string, check that the condition value is equal to
|
||||||
the claim's value
|
the claim's value
|
||||||
|
|
||||||
In order to allow the parsing of claims with complex structures like
|
The claim names can also be written in the XPath format for clarity: the
|
||||||
dictionaries, claim names can be written in the XPath format.
|
condition
|
||||||
|
|
||||||
The special ``zuul_uid`` claim refers to the ``uid_claim`` setting in an
|
.. code-block:: yaml
|
||||||
authenticator's configuration. By default it refers to the ``sub`` claim
|
|
||||||
of a token. For more details see the :ref:`configuration section
|
|
||||||
<web-server-tenant-scoped-api>` for Zuul web server.
|
|
||||||
|
|
||||||
Under the above example, the following token would match rules
|
resources_access:
|
||||||
``affiliate_or_admin`` and ``alice_or_bob``:
|
account:
|
||||||
|
roles: "affiliate"
|
||||||
|
|
||||||
.. code-block:: javascript
|
is equivalent to the condition
|
||||||
|
|
||||||
{
|
.. code-block:: yaml
|
||||||
'iss': 'external_institution',
|
|
||||||
'aud': 'my_zuul_deployment',
|
|
||||||
'exp': 1234567890,
|
|
||||||
'iat': 1234556780,
|
|
||||||
'sub': 'alice',
|
|
||||||
'resources_access': {
|
|
||||||
'account': {
|
|
||||||
'roles': ['affiliate', 'other_role']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
And this token would only match rule ``affiliate_or_admin``:
|
resources_access.account.roles: "affiliate"
|
||||||
|
|
||||||
.. code-block:: javascript
|
The special ``zuul_uid`` claim refers to the ``uid_claim`` setting in an
|
||||||
|
authenticator's configuration. By default it refers to the ``sub`` claim
|
||||||
|
of a token. For more details see the :ref:`configuration section
|
||||||
|
<web-server-tenant-scoped-api>` for Zuul web server.
|
||||||
|
|
||||||
{
|
Under the above example, the following token would match rules
|
||||||
'iss': 'some_other_institution',
|
``affiliate_or_admin`` and ``alice_or_bob``:
|
||||||
'aud': 'my_zuul_deployment',
|
|
||||||
'exp': 1234567890,
|
.. code-block:: javascript
|
||||||
'sub': 'carol',
|
|
||||||
'iat': 1234556780,
|
{
|
||||||
'resources_access': {
|
'iss': 'external_institution',
|
||||||
'account': {
|
'aud': 'my_zuul_deployment',
|
||||||
'roles': ['admin', 'other_role']
|
'exp': 1234567890,
|
||||||
}
|
'iat': 1234556780,
|
||||||
},
|
'sub': 'alice',
|
||||||
}
|
'resources_access': {
|
||||||
|
'account': {
|
||||||
|
'roles': ['affiliate', 'other_role']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
And this token would only match rule ``affiliate_or_admin``:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
'iss': 'some_other_institution',
|
||||||
|
'aud': 'my_zuul_deployment',
|
||||||
|
'exp': 1234567890,
|
||||||
|
'sub': 'carol',
|
||||||
|
'iat': 1234556780,
|
||||||
|
'resources_access': {
|
||||||
|
'account': {
|
||||||
|
'roles': ['admin', 'other_role']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -512,7 +512,7 @@ class TestAuthorizationRuleParser(ZuulTestCase):
|
||||||
'groups': ['admin', 'ghostbusters']}
|
'groups': ['admin', 'ghostbusters']}
|
||||||
self.assertTrue(rule(claims))
|
self.assertTrue(rule(claims))
|
||||||
|
|
||||||
def test_check_complex_rule_from_yaml(self):
|
def test_check_complex_rule_from_yaml_jsonpath(self):
|
||||||
rule_d = {'name': 'my-rule',
|
rule_d = {'name': 'my-rule',
|
||||||
'conditions': [{'hello.this.is': 'a complex value'},
|
'conditions': [{'hello.this.is': 'a complex value'},
|
||||||
],
|
],
|
||||||
|
@ -523,6 +523,31 @@ class TestAuthorizationRuleParser(ZuulTestCase):
|
||||||
'hello': {
|
'hello': {
|
||||||
'this': {
|
'this': {
|
||||||
'is': 'a complex value'
|
'is': 'a complex value'
|
||||||
|
},
|
||||||
|
'and': {
|
||||||
|
'this one': 'too'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.assertTrue(rule(claims))
|
||||||
|
|
||||||
|
def test_check_complex_rule_from_yaml_nested_dict(self):
|
||||||
|
rule_d = {'name': 'my-rule',
|
||||||
|
'conditions': [{'hello': {'this': {'is': 'a complex value'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
rule = AuthorizationRuleParser().fromYaml(rule_d)
|
||||||
|
self.assertEqual('my-rule', rule.name)
|
||||||
|
claims = {'iss': 'my-idp',
|
||||||
|
'hello': {
|
||||||
|
'this': {
|
||||||
|
'is': 'a complex value'
|
||||||
|
},
|
||||||
|
'and': {
|
||||||
|
'this one': 'too'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4821,7 +4821,7 @@ class ClaimRule(AuthZRule):
|
||||||
self.claim = claim or 'sub'
|
self.claim = claim or 'sub'
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def __call__(self, claims):
|
def _match_jsonpath(self, claims):
|
||||||
matches = [match.value
|
matches = [match.value
|
||||||
for match in jsonpath_rw.parse(self.claim).find(claims)]
|
for match in jsonpath_rw.parse(self.claim).find(claims)]
|
||||||
if len(matches) == 1:
|
if len(matches) == 1:
|
||||||
|
@ -4837,6 +4837,36 @@ class ClaimRule(AuthZRule):
|
||||||
# TODO we should differentiate no match and 2+ matches
|
# TODO we should differentiate no match and 2+ matches
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _match_dict(self, claims):
|
||||||
|
def _compare(value, claim):
|
||||||
|
if isinstance(value, list):
|
||||||
|
if isinstance(claim, list):
|
||||||
|
# if the claim is empty, the value must be empty too:
|
||||||
|
if claim == []:
|
||||||
|
return value == []
|
||||||
|
else:
|
||||||
|
return (set(claim) <= set(value))
|
||||||
|
else:
|
||||||
|
return claim in value
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
if not isinstance(claim, dict):
|
||||||
|
return False
|
||||||
|
elif value == {}:
|
||||||
|
return claim == {}
|
||||||
|
else:
|
||||||
|
return all(_compare(value[x], claim.get(x, {}))
|
||||||
|
for x in value.keys())
|
||||||
|
else:
|
||||||
|
return value == claim
|
||||||
|
|
||||||
|
return _compare(self.value, claims.get(self.claim, {}))
|
||||||
|
|
||||||
|
def __call__(self, claims):
|
||||||
|
if isinstance(self.value, dict):
|
||||||
|
return self._match_dict(claims)
|
||||||
|
else:
|
||||||
|
return self._match_jsonpath(claims)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, ClaimRule):
|
if not isinstance(other, ClaimRule):
|
||||||
return False
|
return False
|
||||||
|
|
Loading…
Reference in New Issue