diff --git a/testsuite/objects/__init__.py b/testsuite/objects/__init__.py index daf37c77..6305f93d 100644 --- a/testsuite/objects/__init__.py +++ b/testsuite/objects/__init__.py @@ -22,7 +22,7 @@ class MatchExpression: @dataclass class Rule: """ - Data class for authorization rules represented by simple pattern-matching expressions. + Data class for 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 @@ -35,6 +35,41 @@ class Rule: value: str +class Value: + """Dataclass for specifying a Value in Authorization, can be either constant on value from AuthJson (jsonPath)""" + + # pylint: disable=invalid-name + def __init__(self, value=None, jsonPath=None) -> None: + super().__init__() + if not (value is None) ^ (jsonPath is None): + raise AttributeError("Exactly one of the `value` and `jsonPath` argument must be specified") + self.value = value + self.jsonPath = jsonPath + + def to_dict(self): + """Returns dict representation of itself (shallow copy only)""" + return {"value": self.value} if self.value else {"valueFrom": {"authJson": self.jsonPath}} + + +@dataclass +class Cache: + """Dataclass for specifying Cache in Authorization""" + ttl: int + # pylint: disable=invalid-name + value: Value + + def to_dict(self): + """Returns dict representation of itself (shallow copy only)""" + return {"ttl": self.ttl, "value": self.value.to_dict()} + + +@dataclass +class PatternRef: + """Dataclass for specifying Pattern reference in Authorization""" + # pylint: disable=invalid-name + patternRef: str + + class LifecycleObject(abc.ABC): """Any objects which has its lifecycle controlled by create() and delete() methods""" diff --git a/testsuite/objects/sections.py b/testsuite/objects/sections.py index 22328afd..de88c7f0 100644 --- a/testsuite/objects/sections.py +++ b/testsuite/objects/sections.py @@ -11,19 +11,19 @@ class Authorizations(abc.ABC): """Authorization configuration""" @abc.abstractmethod - def opa_policy(self, name, rego_policy): + def opa_policy(self, name, rego_policy, **common_features): """Adds OPA inline Rego policy""" @abc.abstractmethod - def external_opa_policy(self, name, endpoint, ttl): + def external_opa_policy(self, name, endpoint, ttl, **common_features): """Adds OPA policy from external registry""" @abc.abstractmethod - def role_rule(self, name: str, role: str, path: str, metrics: bool, priority: int): + def role_rule(self, name: str, role: str, path: str, metrics: bool, priority: int, **common_features): """Adds a rule, which allows access to 'path' only to users with 'role'""" @abc.abstractmethod - def auth_rule(self, name: str, rule: "Rule", when: "Rule", metrics: bool, priority: int): + def auth_rule(self, name: str, rule: "Rule", when: "Rule", metrics: bool, priority: int, **common_features): """Adds JSON pattern-matching authorization rule (authorization.json)""" @@ -31,19 +31,19 @@ class Identities(abc.ABC): """Identities configuration""" @abc.abstractmethod - def oidc(self, name, endpoint, credentials, selector): + def oidc(self, name, endpoint, credentials, selector, **common_features): """Adds OIDC identity provider""" @abc.abstractmethod - def api_key(self, name, all_namespaces, match_label, match_expression, credentials, selector): + def api_key(self, name, all_namespaces, match_label, match_expression, credentials, selector, **common_features): """Adds API Key identity""" @abc.abstractmethod - def mtls(self, name: str, selector_key: str, selector_value: str): + def mtls(self, name: str, selector_key: str, selector_value: str, **common_features): """Adds mTLS identity""" @abc.abstractmethod - def anonymous(self, name): + def anonymous(self, name, **common_features): """Adds anonymous identity""" @abc.abstractmethod @@ -55,11 +55,11 @@ class Metadata(abc.ABC): """Metadata configuration""" @abc.abstractmethod - def http_metadata(self, name, endpoint, method): + def http_metadata(self, name, endpoint, method, **common_features): """Set metadata http external auth feature""" @abc.abstractmethod - def user_info_metadata(self, name, identity_source): + def user_info_metadata(self, name, identity_source, **common_features): """Set metadata OIDC user info""" @@ -67,5 +67,5 @@ class Responses(abc.ABC): """Responses configuration""" @abc.abstractmethod - def add(self, response): + def add(self, response, **common_features): """Add response to AuthConfig""" diff --git a/testsuite/openshift/objects/auth_config/sections.py b/testsuite/openshift/objects/auth_config/sections.py index f9faa9b1..5373c8d2 100644 --- a/testsuite/openshift/objects/auth_config/sections.py +++ b/testsuite/openshift/objects/auth_config/sections.py @@ -1,8 +1,8 @@ """AuthConfig CR object""" from dataclasses import asdict -from typing import Dict, Literal +from typing import Dict, Literal, Iterable -from testsuite.objects import Identities, Metadata, Responses, MatchExpression, Authorizations, Rule +from testsuite.objects import Identities, Metadata, Responses, MatchExpression, Authorizations, Rule, Cache from testsuite.openshift.objects import OpenShiftObject, modify @@ -29,16 +29,26 @@ def section(self): """The actual dict section which will be edited""" return self.obj.model.spec.setdefault(self.section_name, []) - def add_item(self, name, value): + def add_item(self, name, value, priority: int = None, when: Iterable[Rule] = None, + metrics: bool = None, cache: Cache = None): """Adds item to the section""" - self.section.append({"name": name, **value}) + item = {"name": name, **value} + if when: + item["when"] = [asdict(x) for x in when] + if metrics: + item["metrics"] = metrics + if cache: + item["cache"] = cache.to_dict() + if priority: + item["priority"] = priority + self.section.append(item) class IdentitySection(Section, Identities): """Section which contains identity configuration""" @modify - def mtls(self, name: str, selector_key: str, selector_value: str): + def mtls(self, name: str, selector_key: str, selector_value: str, **common_features): """Adds mTLS identity Args: :param name: name of the identity @@ -53,10 +63,10 @@ def mtls(self, name: str, selector_key: str, selector_value: str): } } } - }) + }, **common_features) @modify - def oidc(self, name, endpoint, credentials="authorization_header", selector="Bearer"): + def oidc(self, name, endpoint, credentials="authorization_header", selector="Bearer", **common_features): """Adds OIDC identity""" self.add_item(name, { "oidc": { @@ -66,12 +76,12 @@ def oidc(self, name, endpoint, credentials="authorization_header", selector="Bea "in": credentials, "keySelector": selector } - }) + }, **common_features) @modify def api_key(self, name, all_namespaces: bool = False, match_label=None, match_expression: MatchExpression = None, - credentials="authorization_header", selector="APIKEY"): + credentials="authorization_header", selector="APIKEY", **common_features): """ Adds API Key identity Args: @@ -107,12 +117,12 @@ def api_key(self, name, all_namespaces: bool = False, "in": credentials, "keySelector": selector } - }) + }, **common_features) @modify - def anonymous(self, name): + def anonymous(self, name, **common_features): """Adds anonymous identity""" - self.add_item(name, {"anonymous": {}}) + self.add_item(name, {"anonymous": {}}, **common_features) @modify def remove_all(self): @@ -123,7 +133,7 @@ def remove_all(self): class MetadataSection(Section, Metadata): """Section which contains metadata configuration""" @modify - def http_metadata(self, name, endpoint, method: Literal["GET", "POST"]): + def http_metadata(self, name, endpoint, method: Literal["GET", "POST"], **common_features): """Set metadata http external auth feature""" self.add_item(name, { "http": { @@ -131,32 +141,32 @@ def http_metadata(self, name, endpoint, method: Literal["GET", "POST"]): "method": method, "headers": [{"name": "Accept", "value": "application/json"}] } - }) + }, **common_features) @modify - def user_info_metadata(self, name, identity_source): + def user_info_metadata(self, name, identity_source, **common_features): """Set metadata OIDC user info""" self.add_item(name, { "userInfo": { "identitySource": identity_source } - }) + }, **common_features) class ResponsesSection(Section, Responses): """Section which contains response configuration""" @modify - def add(self, response): + def add(self, response, **common_features): """Adds response section to AuthConfig.""" - self.add_item(response.pop("name"), response) + self.add_item(response.pop("name"), response, **common_features) class AuthorizationsSection(Section, Authorizations): """Section which contains authorization configuration""" @modify - def auth_rule(self, name, rule: Rule, when: Rule = None, metrics=False, priority=0): + def auth_rule(self, name, rule: Rule, when: Rule = None, metrics=False, priority=0, **common_features): """Adds JSON pattern-matching authorization rule (authorization.json)""" section = { "metrics": metrics, @@ -167,9 +177,9 @@ def auth_rule(self, name, rule: Rule, when: Rule = None, metrics=False, priority } if when: section["when"] = [asdict(when)] - self.add_item(name, section) + self.add_item(name, section, **common_features) - def role_rule(self, name: str, role: str, path: str, metrics=False, priority=0): + def role_rule(self, name: str, role: str, path: str, metrics=False, priority=0, **common_features): """ Adds a rule, which allows access to 'path' only to users with 'role' Args: @@ -181,19 +191,19 @@ def role_rule(self, name: str, role: str, path: str, metrics=False, priority=0): """ rule = Rule("auth.identity.realm_access.roles", "incl", role) when = Rule("context.request.http.path", "matches", path) - self.auth_rule(name, rule, when, metrics, priority) + self.auth_rule(name, rule, when, metrics, priority, **common_features) @modify - def opa_policy(self, name, rego_policy): + def opa_policy(self, name, rego_policy, **common_features): """Adds Opa (https://www.openpolicyagent.org/docs/latest/) policy to the AuthConfig""" self.add_item(name, { "opa": { "inlineRego": rego_policy } - }) + }, **common_features) @modify - def external_opa_policy(self, name, endpoint, ttl=0): + def external_opa_policy(self, name, endpoint, ttl=0, **common_features): """ Adds OPA policy that is declared as an HTTP endpoint """ @@ -204,4 +214,4 @@ def external_opa_policy(self, name, endpoint, ttl=0): "ttl": ttl } } - }) + }, **common_features)