From 6552e773d66d8946cd2465d7e59788f756ab1c24 Mon Sep 17 00:00:00 2001 From: jsmolar Date: Wed, 12 Oct 2022 14:48:02 +0200 Subject: [PATCH 1/2] Added generic auth rule method for authconfig --- testsuite/objects/__init__.py | 8 +++ testsuite/openshift/objects/auth_config.py | 66 +++++++++++++++------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/testsuite/objects/__init__.py b/testsuite/objects/__init__.py index eb5a37cc..0e7ab09d 100644 --- a/testsuite/objects/__init__.py +++ b/testsuite/objects/__init__.py @@ -72,6 +72,10 @@ def add_external_opa_policy(self, name, endpoint, ttl): def add_response(self, response): """Add response to AuthConfig""" + @abc.abstractmethod + def add_auth_rule(self, name, rule, when, metrics, priority): + """Adds JSON pattern-matching authorization rule (authorization.json)""" + @abc.abstractmethod def add_role_rule(self, name: str, role: str, path: str, metrics: bool, priority: int): """Adds a rule, which allows access to 'path' only to users with 'role'""" @@ -84,6 +88,10 @@ def set_deny_with(self, code, value): def add_http_metadata(self, name, endpoint, method): """Set metadata http external auth feature""" + @abc.abstractmethod + def add_user_info_metadata(self, name, identity_source): + """Set metadata OIDC user info""" + class PreexistingAuthorino(Authorino): """Authorino which is already deployed prior to the testrun""" diff --git a/testsuite/openshift/objects/auth_config.py b/testsuite/openshift/objects/auth_config.py index 188e45b5..33aba550 100644 --- a/testsuite/openshift/objects/auth_config.py +++ b/testsuite/openshift/objects/auth_config.py @@ -19,6 +19,22 @@ class MatchExpression: key: str = "group" +@dataclass +class Rule: + """ + Data class for authorization rules represented by simple pattern-matching expressions. + Args: + :param selector: that is fetched from the Authorization JSON + :param operator: `eq` (equals), `neq` (not equal), `incl` (includes) and `excl` (excludes), for arrays + `matches`, for regular expressions + :param value: a fixed comparable value + """ + + selector: str + operator: Literal["eq", "neq", "incl", "excl", "matches"] + value: str + + class AuthConfig(OpenShiftObject, Authorization): """Represents AuthConfig CR from Authorino""" @@ -119,33 +135,33 @@ def add_anonymous_identity(self, name): identities.append({"name": name, "anonymous": {}}) @modify - def add_role_rule(self, name: str, role: str, path: str, metrics=False, priority=0): - """ - Adds a rule, which allows access to 'path' only to users with 'role' - :param name: name of rule - :param role: name of role - :param path: path to apply this rule to - :param metrics: bool, allows metrics - :param priority: priority of rule - """ + def add_auth_rule(self, name, rule: Rule, when: Rule = None, metrics=False, priority=0): + """Adds JSON pattern-matching authorization rule (authorization.json)""" authorization = self.model.spec.setdefault("authorization", []) authorization.append({ "name": name, "metrics": metrics, "priority": priority, "json": { - "rules": [{ - "operator": "incl", - "selector": "auth.identity.realm_access.roles", - "value": role - }] + "rules": [asdict(rule)] }, - "when": [{ - "operator": "matches", - "selector": "context.request.http.path", - "value": path - }] }) + if when: + authorization[0].update({"when": asdict(when)}) + + def add_role_rule(self, name: str, role: str, path: str, metrics=False, priority=0): + """ + Adds a rule, which allows access to 'path' only to users with 'role' + Args: + :param name: name of rule + :param role: name of role + :param path: path to apply this rule to + :param metrics: bool, allows metrics + :param priority: priority of rule + """ + rule = Rule("auth.identity.realm_access.roles", "incl", role) + when = Rule("context.request.http.path", "matches", path) + self.add_auth_rule(name, rule, when) @modify def remove_all_identities(self): @@ -194,6 +210,7 @@ def set_deny_with(self, code, value): @modify def add_http_metadata(self, name, endpoint, method: Literal["GET", "POST"]): + """"Set metadata http external auth feature""" metadata = self.model.spec.setdefault("metadata", []) metadata.append({ "name": name, @@ -203,3 +220,14 @@ def add_http_metadata(self, name, endpoint, method: Literal["GET", "POST"]): "headers": [{"name": "Accept", "value": "application/json"}] } }) + + @modify + def add_user_info_metadata(self, name, identity_source): + """Set metadata OIDC user info""" + metadata = self.model.spec.setdefault("metadata", []) + metadata.append({ + "name": name, + "userInfo": { + "identitySource": identity_source + } + }) From d36fb8911fab94f7dccd892ed6cb3ed6a68fd09e Mon Sep 17 00:00:00 2001 From: jsmolar Date: Thu, 13 Oct 2022 12:32:33 +0200 Subject: [PATCH 2/2] Add test for metadata UserInfo --- testsuite/objects/__init__.py | 20 +++++++++++- testsuite/openshift/objects/auth_config.py | 24 +++----------- .../authorino/metadata/test_user_info.py | 32 +++++++++++++++++++ 3 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 testsuite/tests/kuadrant/authorino/metadata/test_user_info.py diff --git a/testsuite/objects/__init__.py b/testsuite/objects/__init__.py index 0e7ab09d..6d32c16a 100644 --- a/testsuite/objects/__init__.py +++ b/testsuite/objects/__init__.py @@ -1,5 +1,23 @@ """Module containing base classes for common objects""" import abc +from dataclasses import dataclass +from typing import Literal + + +@dataclass +class Rule: + """ + Data class for authorization rules represented by simple pattern-matching expressions. + Args: + :param selector: that is fetched from the Authorization JSON + :param operator: `eq` (equals), `neq` (not equal), `incl` (includes) and `excl` (excludes), for arrays + `matches`, for regular expressions + :param value: a fixed comparable value + """ + + selector: str + operator: Literal["eq", "neq", "incl", "excl", "matches"] + value: str class LifecycleObject(abc.ABC): @@ -73,7 +91,7 @@ def add_response(self, response): """Add response to AuthConfig""" @abc.abstractmethod - def add_auth_rule(self, name, rule, when, metrics, priority): + def add_auth_rule(self, name: str, rule: Rule, when: Rule, metrics: bool, priority: int): """Adds JSON pattern-matching authorization rule (authorization.json)""" @abc.abstractmethod diff --git a/testsuite/openshift/objects/auth_config.py b/testsuite/openshift/objects/auth_config.py index 33aba550..2f48b5ef 100644 --- a/testsuite/openshift/objects/auth_config.py +++ b/testsuite/openshift/objects/auth_config.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, asdict from typing import Dict, Literal, List -from testsuite.objects import Authorization +from testsuite.objects import Authorization, Rule from testsuite.openshift.client import OpenShiftClient from testsuite.openshift.objects import OpenShiftObject, modify @@ -19,22 +19,6 @@ class MatchExpression: key: str = "group" -@dataclass -class Rule: - """ - Data class for authorization rules represented by simple pattern-matching expressions. - Args: - :param selector: that is fetched from the Authorization JSON - :param operator: `eq` (equals), `neq` (not equal), `incl` (includes) and `excl` (excludes), for arrays - `matches`, for regular expressions - :param value: a fixed comparable value - """ - - selector: str - operator: Literal["eq", "neq", "incl", "excl", "matches"] - value: str - - class AuthConfig(OpenShiftObject, Authorization): """Represents AuthConfig CR from Authorino""" @@ -144,10 +128,10 @@ def add_auth_rule(self, name, rule: Rule, when: Rule = None, metrics=False, prio "priority": priority, "json": { "rules": [asdict(rule)] - }, + } }) if when: - authorization[0].update({"when": asdict(when)}) + authorization[0].update({"when": [asdict(when)]}) def add_role_rule(self, name: str, role: str, path: str, metrics=False, priority=0): """ @@ -161,7 +145,7 @@ def add_role_rule(self, name: str, role: str, path: str, metrics=False, priority """ rule = Rule("auth.identity.realm_access.roles", "incl", role) when = Rule("context.request.http.path", "matches", path) - self.add_auth_rule(name, rule, when) + self.add_auth_rule(name, rule, when, metrics, priority) @modify def remove_all_identities(self): diff --git a/testsuite/tests/kuadrant/authorino/metadata/test_user_info.py b/testsuite/tests/kuadrant/authorino/metadata/test_user_info.py new file mode 100644 index 00000000..9cfdab4f --- /dev/null +++ b/testsuite/tests/kuadrant/authorino/metadata/test_user_info.py @@ -0,0 +1,32 @@ +""" +Tests for external auth metadata. Online fetching of (OIDC) UserInfo data, associated with an OIDC identity source: +https://github.com/Kuadrant/authorino/blob/main/docs/features.md#oidc-userinfo-metadatauserinfo +""" +import pytest + +from testsuite.openshift.objects.auth_config import Rule + + +@pytest.fixture(scope="module") +def authorization(authorization, rhsso): + """ + Adds auth metadata OIDC UserInfo which fetches OIDC UserInfo in request-time. + Adds a simple rule that accepts only when fetched UserInfo contains the email address of the default RHSSO user. + """ + user = rhsso.client.admin.get_user(rhsso.user) + authorization.add_user_info_metadata("user-info", "rhsso") + authorization.add_auth_rule("rule", Rule("auth.metadata.user-info.email", "eq", user["email"])) + return authorization + + +def test_correct_auth(client, auth): + """Tests auth when UserInfo email matches the email address""" + response = client.get("get", auth=auth) + assert response.status_code == 200 + + +def test_incorrect_auth(client, auth, rhsso): + """Updates RHSSO user email address and tests incorrect auth""" + rhsso.client.admin.update_user(rhsso.user, {"email": "updatedMail@anything.invalid"}) + response = client.get("get", auth=auth) + assert response.status_code == 403