diff --git a/jans-cedarling/bindings/cedarling_python/Cargo.toml b/jans-cedarling/bindings/cedarling_python/Cargo.toml index cfe7c3b8cac..91e89b70917 100644 --- a/jans-cedarling/bindings/cedarling_python/Cargo.toml +++ b/jans-cedarling/bindings/cedarling_python/Cargo.toml @@ -10,5 +10,6 @@ crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.22.0", features = ["extension-module"] } cedarling = { workspace = true } +serde = { workspace = true } serde_json = { workspace = true } serde-pyobject = "0.4.0" diff --git a/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md b/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md index 4e9a8f7c94d..71fcd836693 100644 --- a/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md +++ b/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md @@ -155,54 +155,145 @@ Methods :param config: A `BootstrapConfig` object with startup settings. -.. method:: pop_logs(self) +.. method:: pop_logs(self) -> List[dict] Retrieves and removes all logs from storage. :returns: A list of log entries as Python objects. - :rtype: List[PyObject] :raises ValueError: If an error occurs while fetching logs. -.. method:: get_log_by_id(self, id) +.. method:: get_log_by_id(self, id: str) -> dict|None Gets a log entry by its ID. :param id: The log entry ID. - :type id: str - - :returns: The log entry as a Python object or None if not found. - :rtype: Optional[PyObject] :raises ValueError: If an error occurs while fetching the log. -.. method:: get_log_ids(self) +.. method:: get_log_ids(self) -> List[str] Retrieves all stored log IDs. - :returns: A list of log entry IDs. - :rtype: List[str] +.. method:: authorize(self, request: Request) -> AuthorizeResult + + Execute authorize request + :param request: Request struct for authorize. + +___ -.. method:: authorize(self, Request) +ResourceData +============ - Evaluate Authorization Request. +A Python wrapper for the Rust `cedarling::ResourceData` struct. This class represents +a resource entity with a type, ID, and attributes. Attributes are stored as a payload +in a dictionary format. +Attributes +---------- +:param resource_type: Type of the resource entity. +:param id: ID of the resource entity. +:param payload: Optional dictionary of attributes. + +Methods +------- +.. method:: __init__(self, resource_type: str, id: str, **kwargs: dict) + Initialize a new ResourceData. In kwargs the payload is a dictionary of entity attributes. + +.. method:: from_dict(cls, value: dict) -> ResourceData + Initialize a new ResourceData from a dictionary. + To pass `resource_type` you need to use `type` key. ___ Request ======= -Python wrapper for the Rust `cedarling::Request` struct. -Stores authorization data +A Python wrapper for the Rust `cedarling::Request` struct. Represents +authorization data with access token, action, resource, and context. Attributes ---------- -:param access_token: A string containing the access token. +:param access_token: The access token string. +:param action: The action to be authorized. +:param resource: Resource data (wrapped `ResourceData` object). +:param context: Python dictionary with additional context. Example ------- +```python +# Create a request for authorization +request = Request(access_token="token123", action="read", resource=resource, context={}) ``` -req = Request(access_token="your_token") -``` +___ + +AuthorizeResult +=============== + +A Python wrapper for the Rust `cedarling::AuthorizeResult` struct. +Represents the result of an authorization request. + +Methods +------- +.. method:: is_allowed(self) -> bool + Returns whether the request is allowed. + +.. method:: workload(self) -> AuthorizeResultResponse + Returns the detailed response as an `AuthorizeResultResponse` object. + +___ + +AuthorizeResultResponse +======================= + +A Python wrapper for the Rust `cedar_policy::Response` struct. +Represents the result of an authorization request. + +Attributes +---------- +:param decision: The authorization decision (wrapped `Decision` object). +:param diagnostics: Additional information on the decision (wrapped `Diagnostics` object). +___ + +Decision +======== + +Represents the decision result of a Cedar policy authorization. + +Methods +------- +value() -> str + Returns the string value of the decision. +__str__() -> str + Returns the string representation of the decision. +__repr__() -> str + Returns the detailed type representation of the decision. +__eq__(other: Decision) -> bool + Compares two `Decision` objects for equality. +___ + +Diagnostics +=========== + +Provides detailed information about how a policy decision was made, including policies that contributed to the decision and any errors encountered during evaluation. + +Attributes +---------- +reason : set of str + A set of `PolicyId`s for the policies that contributed to the decision. If no policies applied, this set is empty. +errors : list of PolicyEvaluationError + A list of errors that occurred during the authorization process. These are unordered as policies may be evaluated in any order. +___ + +PolicyEvaluationError +===================== + +Represents an error that occurred when evaluating a Cedar policy. + +Attributes +---------- +id : str + The ID of the policy that caused the error. +error : str + The error message describing the evaluation failure. ___ diff --git a/jans-cedarling/bindings/cedarling_python/example.py b/jans-cedarling/bindings/cedarling_python/example.py index a76a6a35a48..e2cff5a0e41 100644 --- a/jans-cedarling/bindings/cedarling_python/example.py +++ b/jans-cedarling/bindings/cedarling_python/example.py @@ -1,7 +1,7 @@ from cedarling_python import MemoryLogConfig, DisabledLoggingConfig, StdOutLogConfig from cedarling_python import PolicyStoreSource, PolicyStoreConfig, BootstrapConfig, JwtConfig from cedarling_python import Cedarling - +from cedarling_python import ResourceData, Request # use log config to store logs in memory with a time-to-live of 120 seconds # by default it is 60 seconds @@ -13,7 +13,7 @@ # log_config = DisabledLoggingConfig() # use log config to print logs to stdout -# log_config = StdOutLogConfig() +log_config = StdOutLogConfig() # Create policy source configuration with open("example_files/policy-store.json", @@ -53,3 +53,46 @@ # show logs print("Logs stored in memory:") print(*instance.pop_logs(), sep="\n\n") + + +# //// Execute authentication request //// + +# field resource_type and id is mandatory +# other fields are attributes of the resource. +resource = ResourceData(resource_type="Jans::Issue", + id="random_id", org_id="some_long_id") +# or we can init resource using dict +resource = ResourceData.from_dict({ + "type": "Jans::Issue", + "id": "random_id", + "org_id": 1 # "some_long_id" +}) + +action_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJib0c4ZGZjNU1LVG4zN283Z3NkQ2V5cUw4THBXUXRnb080MW0xS1p3ZHEwIiwiY29kZSI6ImJmMTkzNGY2LTM5MDUtNDIwYS04Mjk5LTZiMmUzZmZkZGQ2ZSIsImlzcyI6Imh0dHBzOi8vYWRtaW4tdWktdGVzdC5nbHV1Lm9yZyIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJjbGllbnRfaWQiOiI1YjQ0ODdjNC04ZGIxLTQwOWQtYTY1My1mOTA3YjgwOTQwMzkiLCJhdWQiOiI1YjQ0ODdjNC04ZGIxLTQwOWQtYTY1My1mOTA3YjgwOTQwMzkiLCJhY3IiOiJiYXNpYyIsIng1dCNTMjU2IjoiIiwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJvcmdfaWQiOiJzb21lX2xvbmdfaWQiLCJhdXRoX3RpbWUiOjE3MjQ4MzA3NDYsImV4cCI6MTcyNDk0NTk3OCwiaWF0IjoxNzI0ODMyMjU5LCJqdGkiOiJseFRtQ1ZSRlR4T2pKZ3ZFRXBvek1RIiwibmFtZSI6IkRlZmF1bHQgQWRtaW4gVXNlciIsInN0YXR1cyI6eyJzdGF0dXNfbGlzdCI6eyJpZHgiOjIwMSwidXJpIjoiaHR0cHM6Ly9hZG1pbi11aS10ZXN0LmdsdXUub3JnL2phbnMtYXV0aC9yZXN0djEvc3RhdHVzX2xpc3QifX19._eQT-DsfE_kgdhA0YOyFxxPEMNw44iwoelWa5iU1n9s" + +request = Request( + action_token, + action='Jans::Action::"Update"', + context={}, resource=resource) + +authorize_result = instance.authorize(request) +print(*instance.pop_logs(), sep="\n\n") + +# if you change org_id result will be false +assert authorize_result.is_allowed() + +# watch on the decision for workload +workload_result = authorize_result.workload() +print(workload_result.decision) + +# show diagnostic information +workload_diagnostic = workload_result.diagnostics +for i, reason in enumerate(workload_diagnostic.reason): + if i == 0: + print("reason policies:") + print(reason) + +for i, error in enumerate(workload_diagnostic.errors): + if i == 0: + print("errors:") + print("id:", error.id, "error:", error.error) diff --git a/jans-cedarling/bindings/cedarling_python/example_files/policy-store.json b/jans-cedarling/bindings/cedarling_python/example_files/policy-store.json index 4e96036ec19..784f3973083 100644 --- a/jans-cedarling/bindings/cedarling_python/example_files/policy-store.json +++ b/jans-cedarling/bindings/cedarling_python/example_files/policy-store.json @@ -4,32 +4,12 @@ "description": "gluu", "policies": { "840da5d85403f35ea76519ed1a18a33989f855bf1cf8": { - "description": "admin access", + "description": "simple policy example", "creation_date": "2024-09-20T17:22:39.996050", - "policy_content": "QGlkKCJhZG1pbiBhY2Nlc3MiKQpwZXJtaXQKKAogcHJpbmNpcGFsID09IEphbnM6OlJvbGU6OiJBZG1pbiIsCiBhY3Rpb24gaW4gW0phbnM6OkFjdGlvbjo6IkNvbXBhcmUiLEphbnM6OkFjdGlvbjo6IkV4ZWN1dGUiXSwKIHJlc291cmNlID09IEphbnM6OkFwcGxpY2F0aW9uOjoiQWRtaW4iCikKd2hlbgp7CiBKYW5zOjpBY2Nlc3NfdG9rZW46OiJhYmMiLnNjb3BlPT0iYWJjIiAmJiBKYW5zOjppZF90b2tlbjo6ImlkeHh4Ii5hbXI9PSJpZHh4eCIgIAp9Ow==" - }, - "b6313811924c9e67f898257cbf017674e08203779ae9": { - "description": "manager access", - "creation_date": "2024-09-20T18:11:26.442574", - "policy_content": "QGlkKCJtYW5hZ2VyIGFjY2VzcyIpCnBlcm1pdAooCiBwcmluY2lwYWwsCiBhY3Rpb24sCiByZXNvdXJjZQopCndoZW4KewogSmFuczo6QWNjZXNzX3Rva2VuOjoieHh4Ii5zY29wZT09Inh4eCIgfHwgSmFuczo6aWRfdG9rZW46OiJpZHh4eCIuYW1yPT0iaWR4eHgiICYmIGNvbnRleHQubmV0d29yay5pc0luUmFuZ2UoaXAoIjIyMi4yMjIuMjIyLjAvMjQiKSkgIAp9Ow==" - }, - "f2b38413cad977ab21616bd4a63c233548491cf25b72": { - "description": "manager access", - "creation_date": "2024-09-20T18:11:37.774401", - "policy_content": "QGlkKCJtYW5hZ2VyIGFjY2VzcyIpCnBlcm1pdAooCiBwcmluY2lwYWwsCiBhY3Rpb24sCiByZXNvdXJjZQopCndoZW4KewogSmFuczo6QWNjZXNzX3Rva2VuOjoieHh4Ii5zY29wZT09Inh4eCIgfHwgSmFuczo6aWRfdG9rZW46OiJpZHh4eCIuYW1yPT0iaWR4eHgiICYmIGNvbnRleHQubmV0d29yay5pc0luUmFuZ2UoaXAoIjIyMi4yMjIuMjIyLjAvMjQiKSkgIAp9Ow==" - }, - "fa6a3f46ab5f741e806deff0f81d0f848af37604500f": { - "description": "without condition", - "creation_date": "2024-09-22T18:18:35.801566", - "policy_content": "QGlkKCJ3aXRob3V0IGNvbmRpdGlvbiIpCnBlcm1pdAooCiBwcmluY2lwYWwgPT0gSmFuczo6Um9sZTo6IkFkbWluIiwKIGFjdGlvbiwKIHJlc291cmNlCikKOw==" - }, - "96deb02f8ce44c46d497d44dbfec80b3b6a64fe22994": { - "description": "forbid", - "creation_date": "2024-09-23T14:51:21.480763", - "policy_content": "QGlkKCJmb3JiaWQiKQpmb3JiaWQKKAogcHJpbmNpcGFsIGluIEphbnM6OlJvbGU6OiJBZG1pbiIsCiBhY3Rpb24gaW4gW0phbnM6OkFjdGlvbjo6IlNlYXJjaCIsSmFuczo6QWN0aW9uOjoiVGFnIl0sCiByZXNvdXJjZSBpbiBKYW5zOjpBcHBsaWNhdGlvbjo6IkFkbWluUG9ydGFsIgopCndoZW4KewogSmFuczo6QWNjZXNzX3Rva2VuOjoieHh4Ii5leHA+MTIzICYmIEphbnM6OkFjY2Vzc190b2tlbjo6ImFhYSIuZXhwPDMyMSB8fCBKYW5zOjpBY2Nlc3NfdG9rZW46OiJhYWEiLmlhdD49MTExICAKfTs=" + "policy_content": "cGVybWl0KAogICAgcHJpbmNpcGFsIGlzIEphbnM6Oldvcmtsb2FkLAogICAgYWN0aW9uIGluIFtKYW5zOjpBY3Rpb246OiJVcGRhdGUiXSwKICAgIHJlc291cmNlIGlzIEphbnM6Oklzc3VlCil3aGVuewogICAgcHJpbmNpcGFsLm9yZ19pZCA9PSByZXNvdXJjZS5vcmdfaWQKfTs=" } }, "identity_source": {}, - "schema": "eyJKYW5zIjp7ImNvbW1vblR5cGVzIjp7IlVybCI6eyJ0eXBlIjoiUmVjb3JkIiwiYXR0cmlidXRlcyI6eyJob3N0Ijp7InR5cGUiOiJTdHJpbmcifSwicGF0aCI6eyJ0eXBlIjoiU3RyaW5nIn0sInByb3RvY29sIjp7InR5cGUiOiJTdHJpbmcifX19LCJDb250ZXh0Ijp7InR5cGUiOiJSZWNvcmQiLCJhdHRyaWJ1dGVzIjp7ImJyb3dzZXIiOnsidHlwZSI6IlN0cmluZyJ9LCJjdXJyZW50X3RpbWUiOnsidHlwZSI6IkxvbmcifSwiZGV2aWNlX2hlYWx0aCI6eyJ0eXBlIjoiU2V0IiwiZWxlbWVudCI6eyJ0eXBlIjoiU3RyaW5nIn19LCJmcmF1ZF9pbmRpY2F0b3JzIjp7InR5cGUiOiJTZXQiLCJlbGVtZW50Ijp7InR5cGUiOiJTdHJpbmcifX0sImdlb2xvY2F0aW9uIjp7InR5cGUiOiJTZXQiLCJlbGVtZW50Ijp7InR5cGUiOiJTdHJpbmcifX0sIm5ldHdvcmsiOnsidHlwZSI6IkV4dGVuc2lvbiIsIm5hbWUiOiJpcGFkZHIifSwibmV0d29ya190eXBlIjp7InR5cGUiOiJTdHJpbmcifSwib3BlcmF0aW5nX3N5c3RlbSI6eyJ0eXBlIjoiU3RyaW5nIn19fSwiZW1haWxfYWRkcmVzcyI6eyJ0eXBlIjoiUmVjb3JkIiwiYXR0cmlidXRlcyI6eyJkb21haW4iOnsidHlwZSI6IlN0cmluZyJ9LCJpZCI6eyJ0eXBlIjoiU3RyaW5nIn19fX0sImVudGl0eVR5cGVzIjp7IlVzZXJpbmZvX3Rva2VuIjp7InNoYXBlIjp7InR5cGUiOiJSZWNvcmQiLCJhdHRyaWJ1dGVzIjp7ImF1ZCI6eyJ0eXBlIjoiU3RyaW5nIn0sImJpcnRoZGF0ZSI6eyJ0eXBlIjoiU3RyaW5nIn0sImVtYWlsIjp7InR5cGUiOiJlbWFpbF9hZGRyZXNzIn0sImV4cCI6eyJ0eXBlIjoiTG9uZyJ9LCJpYXQiOnsidHlwZSI6IkxvbmcifSwiaXNzIjp7InR5cGUiOiJFbnRpdHkiLCJuYW1lIjoiVHJ1c3RlZElzc3VlciJ9LCJqdGkiOnsidHlwZSI6IlN0cmluZyJ9LCJuYW1lIjp7InR5cGUiOiJTdHJpbmcifSwicGhvbmVfbnVtYmVyIjp7InR5cGUiOiJTdHJpbmcifSwicm9sZSI6eyJ0eXBlIjoiU2V0IiwiZWxlbWVudCI6eyJ0eXBlIjoiU3RyaW5nIn19LCJzdWIiOnsidHlwZSI6IlN0cmluZyJ9fX19LCJBY2Nlc3NfdG9rZW4iOnsic2hhcGUiOnsidHlwZSI6IlJlY29yZCIsImF0dHJpYnV0ZXMiOnsiYXVkIjp7InR5cGUiOiJTdHJpbmcifSwiZXhwIjp7InR5cGUiOiJMb25nIn0sImlhdCI6eyJ0eXBlIjoiTG9uZyJ9LCJpc3MiOnsidHlwZSI6IkVudGl0eSIsIm5hbWUiOiJUcnVzdGVkSXNzdWVyIn0sImp0aSI6eyJ0eXBlIjoiU3RyaW5nIn0sIm5iZiI6eyJ0eXBlIjoiTG9uZyJ9LCJzY29wZSI6eyJ0eXBlIjoiU3RyaW5nIn19fX0sIkFwcGxpY2F0aW9uIjp7InNoYXBlIjp7InR5cGUiOiJSZWNvcmQiLCJhdHRyaWJ1dGVzIjp7ImNsaWVudCI6eyJ0eXBlIjoiRW50aXR5IiwibmFtZSI6IkNsaWVudCJ9LCJuYW1lIjp7InR5cGUiOiJTdHJpbmcifX19fSwiUm9sZSI6e30sIlRydXN0ZWRJc3N1ZXIiOnsic2hhcGUiOnsidHlwZSI6IlJlY29yZCIsImF0dHJpYnV0ZXMiOnsiaXNzdWVyX2VudGl0eV9pZCI6eyJ0eXBlIjoiVXJsIn19fX0sIkNsaWVudCI6eyJzaGFwZSI6eyJ0eXBlIjoiUmVjb3JkIiwiYXR0cmlidXRlcyI6eyJjbGllbnRfaWQiOnsidHlwZSI6IlN0cmluZyJ9LCJpc3MiOnsidHlwZSI6IkVudGl0eSIsIm5hbWUiOiJUcnVzdGVkSXNzdWVyIn19fX0sImlkX3Rva2VuIjp7InNoYXBlIjp7InR5cGUiOiJSZWNvcmQiLCJhdHRyaWJ1dGVzIjp7ImFjciI6eyJ0eXBlIjoiU2V0IiwiZWxlbWVudCI6eyJ0eXBlIjoiU3RyaW5nIn19LCJhbXIiOnsidHlwZSI6IlN0cmluZyJ9LCJhdWQiOnsidHlwZSI6IlN0cmluZyJ9LCJhenAiOnsidHlwZSI6IlN0cmluZyJ9LCJiaXJ0aGRhdGUiOnsidHlwZSI6IlN0cmluZyJ9LCJlbWFpbCI6eyJ0eXBlIjoiZW1haWxfYWRkcmVzcyJ9LCJleHAiOnsidHlwZSI6IkxvbmcifSwiaWF0Ijp7InR5cGUiOiJMb25nIn0sImlzcyI6eyJ0eXBlIjoiRW50aXR5IiwibmFtZSI6IlRydXN0ZWRJc3N1ZXIifSwianRpIjp7InR5cGUiOiJTdHJpbmcifSwibmFtZSI6eyJ0eXBlIjoiU3RyaW5nIn0sInBob25lX251bWJlciI6eyJ0eXBlIjoiU3RyaW5nIn0sInJvbGUiOnsidHlwZSI6IlNldCIsImVsZW1lbnQiOnsidHlwZSI6IlN0cmluZyJ9fSwic3ViIjp7InR5cGUiOiJTdHJpbmcifX19fSwiVXNlciI6eyJtZW1iZXJPZlR5cGVzIjpbIlJvbGUiXSwic2hhcGUiOnsidHlwZSI6IlJlY29yZCIsImF0dHJpYnV0ZXMiOnsiZW1haWwiOnsidHlwZSI6ImVtYWlsX2FkZHJlc3MifSwicGhvbmVfbnVtYmVyIjp7InR5cGUiOiJTdHJpbmcifSwicm9sZSI6eyJ0eXBlIjoiU2V0IiwiZWxlbWVudCI6eyJ0eXBlIjoiU3RyaW5nIn19LCJzdWIiOnsidHlwZSI6IlN0cmluZyJ9LCJ1c2VybmFtZSI6eyJ0eXBlIjoiU3RyaW5nIn19fX0sIkhUVFBfUmVxdWVzdCI6eyJzaGFwZSI6eyJ0eXBlIjoiUmVjb3JkIiwiYXR0cmlidXRlcyI6eyJhY2NlcHQiOnsidHlwZSI6IlNldCIsImVsZW1lbnQiOnsidHlwZSI6IlN0cmluZyJ9fSwiaGVhZGVyIjp7InR5cGUiOiJTZXQiLCJlbGVtZW50Ijp7InR5cGUiOiJTdHJpbmcifX0sInVybCI6eyJ0eXBlIjoiVXJsIn19fX19LCJhY3Rpb25zIjp7IkdFVCI6eyJhcHBsaWVzVG8iOnsicmVzb3VyY2VUeXBlcyI6WyJIVFRQX1JlcXVlc3QiXSwicHJpbmNpcGFsVHlwZXMiOlsiQ2xpZW50Il0sImNvbnRleHQiOnsidHlwZSI6IkNvbnRleHQifX19LCJERUxFVEUiOnsiYXBwbGllc1RvIjp7InJlc291cmNlVHlwZXMiOlsiSFRUUF9SZXF1ZXN0Il0sInByaW5jaXBhbFR5cGVzIjpbIkNsaWVudCJdLCJjb250ZXh0Ijp7InR5cGUiOiJDb250ZXh0In19fSwiUE9TVCI6eyJhcHBsaWVzVG8iOnsicmVzb3VyY2VUeXBlcyI6WyJIVFRQX1JlcXVlc3QiXSwicHJpbmNpcGFsVHlwZXMiOlsiQ2xpZW50Il0sImNvbnRleHQiOnsidHlwZSI6IkNvbnRleHQifX19LCJIRUFEIjp7ImFwcGxpZXNUbyI6eyJyZXNvdXJjZVR5cGVzIjpbIkhUVFBfUmVxdWVzdCJdLCJwcmluY2lwYWxUeXBlcyI6WyJDbGllbnQiXSwiY29udGV4dCI6eyJ0eXBlIjoiQ29udGV4dCJ9fX0sIlBVVCI6eyJhcHBsaWVzVG8iOnsicmVzb3VyY2VUeXBlcyI6WyJIVFRQX1JlcXVlc3QiXSwicHJpbmNpcGFsVHlwZXMiOlsiQ2xpZW50Il0sImNvbnRleHQiOnsidHlwZSI6IkNvbnRleHQifX19LCJQQVRDSCI6eyJhcHBsaWVzVG8iOnsicmVzb3VyY2VUeXBlcyI6WyJIVFRQX1JlcXVlc3QiXSwicHJpbmNpcGFsVHlwZXMiOlsiQ2xpZW50Il0sImNvbnRleHQiOnsidHlwZSI6IkNvbnRleHQifX19LCJBY2Nlc3MiOnsiYXBwbGllc1RvIjp7InJlc291cmNlVHlwZXMiOlsiQXBwbGljYXRpb24iXSwicHJpbmNpcGFsVHlwZXMiOlsiVXNlciIsIlJvbGUiXSwiY29udGV4dCI6eyJ0eXBlIjoiQ29udGV4dCJ9fX19fX0=" + "schema": "ewogICAgIkphbnMiOiB7CiAgICAgICAgImNvbW1vblR5cGVzIjogewogICAgICAgICAgICAiVXJsIjogewogICAgICAgICAgICAgICAgInR5cGUiOiAiUmVjb3JkIiwKICAgICAgICAgICAgICAgICJhdHRyaWJ1dGVzIjogewogICAgICAgICAgICAgICAgICAgICJob3N0IjogewogICAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJFbnRpdHlPckNvbW1vbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIlN0cmluZyIKICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICJwYXRoIjogewogICAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJFbnRpdHlPckNvbW1vbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIlN0cmluZyIKICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICJwcm90b2NvbCI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRW50aXR5T3JDb21tb24iLAogICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJTdHJpbmciCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAiZW50aXR5VHlwZXMiOiB7CiAgICAgICAgICAgICJBY2Nlc3NfdG9rZW4iOiB7CiAgICAgICAgICAgICAgICAic2hhcGUiOiB7CiAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiUmVjb3JkIiwKICAgICAgICAgICAgICAgICAgICAiYXR0cmlidXRlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgImF1ZCI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkVudGl0eU9yQ29tbW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIlN0cmluZyIKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgImV4cCI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkVudGl0eU9yQ29tbW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIkxvbmciCiAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICJpYXQiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJFbnRpdHlPckNvbW1vbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJMb25nIgogICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAiaXNzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRW50aXR5T3JDb21tb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiVHJ1c3RlZElzc3VlciIKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgImp0aSI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkVudGl0eU9yQ29tbW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIlN0cmluZyIKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgIklzc3VlIjogewogICAgICAgICAgICAgICAgInNoYXBlIjogewogICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIlJlY29yZCIsCiAgICAgICAgICAgICAgICAgICAgImF0dHJpYnV0ZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICJvcmdfaWQiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJFbnRpdHlPckNvbW1vbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJTdHJpbmciCiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJUcnVzdGVkSXNzdWVyIjogewogICAgICAgICAgICAgICAgInNoYXBlIjogewogICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIlJlY29yZCIsCiAgICAgICAgICAgICAgICAgICAgImF0dHJpYnV0ZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICJpc3N1ZXJfZW50aXR5X2lkIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRW50aXR5T3JDb21tb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiVXJsIgogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICAiV29ya2xvYWQiOiB7CiAgICAgICAgICAgICAgICAic2hhcGUiOiB7CiAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiUmVjb3JkIiwKICAgICAgICAgICAgICAgICAgICAiYXR0cmlidXRlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgImNsaWVudF9pZCI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkVudGl0eU9yQ29tbW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIlN0cmluZyIKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgImlzcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkVudGl0eU9yQ29tbW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIlRydXN0ZWRJc3N1ZXIiCiAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRW50aXR5T3JDb21tb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiU3RyaW5nIgogICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAib3JnX2lkIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRW50aXR5T3JDb21tb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiU3RyaW5nIgogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAiYWN0aW9ucyI6IHsKICAgICAgICAgICAgIlVwZGF0ZSI6IHsKICAgICAgICAgICAgICAgICJhcHBsaWVzVG8iOiB7CiAgICAgICAgICAgICAgICAgICAgInJlc291cmNlVHlwZXMiOiBbCiAgICAgICAgICAgICAgICAgICAgICAgICJJc3N1ZSIKICAgICAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgICAgICJwcmluY2lwYWxUeXBlcyI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgIldvcmtsb2FkIgogICAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0KfQ==" } } \ No newline at end of file diff --git a/jans-cedarling/bindings/cedarling_python/print_documentation.py b/jans-cedarling/bindings/cedarling_python/print_documentation.py index c4091576420..203640c7853 100644 --- a/jans-cedarling/bindings/cedarling_python/print_documentation.py +++ b/jans-cedarling/bindings/cedarling_python/print_documentation.py @@ -1,6 +1,7 @@ from cedarling_python import MemoryLogConfig, DisabledLoggingConfig, StdOutLogConfig from cedarling_python import PolicyStoreSource, PolicyStoreConfig, BootstrapConfig from cedarling_python import Cedarling +from cedarling_python import ResourceData, Request, AuthorizeResult, AuthorizeResultResponse, Decision, Diagnostics, PolicyEvaluationError import inspect @@ -61,7 +62,9 @@ def print_doc(type_value): types = [MemoryLogConfig, DisabledLoggingConfig, StdOutLogConfig, PolicyStoreSource, PolicyStoreConfig, BootstrapConfig, - Cedarling] + Cedarling, + ResourceData, Request, AuthorizeResult, AuthorizeResultResponse, Decision, Diagnostics, PolicyEvaluationError, + ] if __name__ == "__main__": print_header() diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result.rs new file mode 100644 index 00000000000..c4a9a8aa88f --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result.rs @@ -0,0 +1,46 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ +use super::authorize_result_response::AuthorizeResultResponse; +use pyo3::prelude::*; + +/// AuthorizeResult +/// =============== +/// +/// A Python wrapper for the Rust `cedarling::AuthorizeResult` struct. +/// Represents the result of an authorization request. +/// +/// Methods +/// ------- +/// .. method:: is_allowed(self) -> bool +/// Returns whether the request is allowed. +/// +/// .. method:: workload(self) -> AuthorizeResultResponse +/// Returns the detailed response as an `AuthorizeResultResponse` object. +/// +#[pyclass] +pub struct AuthorizeResult { + inner: cedarling::AuthorizeResult, +} + +#[pymethods] +impl AuthorizeResult { + /// Returns true if request is allowed + fn is_allowed(&self) -> bool { + self.inner.is_allowed() + } + + /// Get the decision value for workload + fn workload(&self) -> AuthorizeResultResponse { + self.inner.workload.clone().into() + } +} + +impl From for AuthorizeResult { + fn from(value: cedarling::AuthorizeResult) -> Self { + Self { inner: value } + } +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result_response.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result_response.rs new file mode 100644 index 00000000000..c05c897bdab --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result_response.rs @@ -0,0 +1,46 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ +use super::decision::Decision; +use super::diagnostics::Diagnostics; +use cedarling::bindings::cedar_policy; +use pyo3::prelude::*; + +/// AuthorizeResultResponse +/// ======================= +/// +/// A Python wrapper for the Rust `cedar_policy::Response` struct. +/// Represents the result of an authorization request. +/// +/// Attributes +/// ---------- +/// :param decision: The authorization decision (wrapped `Decision` object). +/// :param diagnostics: Additional information on the decision (wrapped `Diagnostics` object). +#[pyclass] +pub struct AuthorizeResultResponse { + inner: cedar_policy::Response, +} + +#[pymethods] +impl AuthorizeResultResponse { + /// Authorization decision + #[getter] + fn decision(&self) -> Decision { + self.inner.decision().into() + } + + /// Diagnostics providing more information on how this decision was reached + #[getter] + fn diagnostics(&self) -> Diagnostics { + self.inner.diagnostics().into() + } +} + +impl From for AuthorizeResultResponse { + fn from(value: cedar_policy::Response) -> Self { + Self { inner: value } + } +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/decision.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/decision.rs new file mode 100644 index 00000000000..2f5c32ff899 --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/decision.rs @@ -0,0 +1,61 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ +use cedarling::bindings::cedar_policy; +use pyo3::prelude::*; + +/// Decision +/// ======== +/// +/// Represents the decision result of a Cedar policy authorization. +/// +/// Methods +/// ------- +/// value() -> str +/// Returns the string value of the decision. +/// __str__() -> str +/// Returns the string representation of the decision. +/// __repr__() -> str +/// Returns the detailed type representation of the decision. +/// __eq__(other: Decision) -> bool +/// Compares two `Decision` objects for equality. +#[derive(Debug, Clone)] +#[pyclass] +pub struct Decision { + inner: cedarling::bindings::Decision, +} + +#[pymethods] +impl Decision { + /// get the value of the decision + #[getter] + fn value(&self) -> String { + self.inner.to_string() + } + + /// string representation of the decision + fn __str__(&self) -> String { + self.value() + } + + /// type string representation of the decision + fn __repr__(&self) -> String { + format!("Decision({})", self.value()) + } + + /// equality operator + fn __eq__(&self, other: &Decision) -> bool { + self.inner == other.inner + } +} + +impl From for Decision { + fn from(value: cedar_policy::Decision) -> Self { + Self { + inner: value.into(), + } + } +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/diagnostics.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/diagnostics.rs new file mode 100644 index 00000000000..3e38aa8040d --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/diagnostics.rs @@ -0,0 +1,50 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ + +use super::policy_evaluation_error::PolicyEvaluationError; +use cedarling::bindings::cedar_policy; +use std::collections::HashSet; + +use pyo3::prelude::*; + +/// Diagnostics +/// =========== +/// +/// Provides detailed information about how a policy decision was made, including policies that contributed to the decision and any errors encountered during evaluation. +/// +/// Attributes +/// ---------- +/// reason : set of str +/// A set of `PolicyId`s for the policies that contributed to the decision. If no policies applied, this set is empty. +/// errors : list of PolicyEvaluationError +/// A list of errors that occurred during the authorization process. These are unordered as policies may be evaluated in any order. +#[derive(Debug, Clone)] +#[pyclass(get_all)] +pub struct Diagnostics { + /// `PolicyId`s of the policies that contributed to the decision. + /// If no policies applied to the request, this set will be empty. + reason: HashSet, + /// Errors that occurred during authorization. The errors should be + /// treated as unordered, since policies may be evaluated in any order. + errors: Vec, +} + +impl From<&cedar_policy::Diagnostics> for Diagnostics { + fn from(value: &cedar_policy::Diagnostics) -> Self { + // use type for logging + let diagnostics_info: cedarling::bindings::Diagnostics = value.into(); + + Self { + reason: diagnostics_info.reason, + errors: diagnostics_info + .errors + .into_iter() + .map(|e| e.into()) + .collect(), + } + } +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/errors.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/errors.rs new file mode 100644 index 00000000000..634f7e85953 --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/errors.rs @@ -0,0 +1,118 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ + +use cedarling::AuthorizeError as CedarlingAuthorizeError; +use pyo3::{create_exception, prelude::*}; + +// Define a base class for Authorization errors in Python +create_exception!( + authorize_errors, + AuthorizeError, + pyo3::exceptions::PyException, + "Exception raised by authorize_errors" +); + +create_exception!( + authorize_errors, + JWTError, + AuthorizeError, + "Error encountered while decoding JWT token data" +); + +create_exception!( + authorize_errors, + AccessTokenEntitiesError, + AuthorizeError, + "Error encountered while creating access token entities" +); + +create_exception!( + authorize_errors, + ResourceEntityError, + AuthorizeError, + "Error encountered while creating resource entity" +); + +create_exception!( + authorize_errors, + ActionError, + AuthorizeError, + "Error encountered while parsing Action to EntityUid" +); + +create_exception!( + authorize_errors, + CreateContextError, + AuthorizeError, + "Error encountered while validating context according to the schema" +); + +create_exception!( + authorize_errors, + CreateRequestError, + AuthorizeError, + "Error encountered while creating cedar_policy::Request" +); + +create_exception!( + authorize_errors, + EntitiesError, + AuthorizeError, + "Error encountered while collecting all entities" +); + +#[pyclass] +#[derive()] +pub struct ErrorPayload(CedarlingAuthorizeError); + +#[pymethods] +impl ErrorPayload { + fn __str__(&self) -> String { + self.0.to_string() + } +} + +// macros to write logic with errors only once +macro_rules! errors_functions { + ($($case_name:ident => $error_class:ident),*) => { + // is used to map CedarlingAuthorizeError to python error + pub fn authorize_error_to_py(err: CedarlingAuthorizeError) -> PyErr { + match err { + $(CedarlingAuthorizeError::$case_name(_) => { + let err_args = ErrorPayload(err); + PyErr::new::<$error_class, _>(err_args) + },)* + } + } + + // is used to register errors in py module + pub fn register_errors(m: &Bound<'_, PyModule>) -> PyResult<()> { + $( + m.add(stringify!($error_class), m.py().get_type_bound::<$error_class>())?; + )* + Ok(()) + } + }; +} + +errors_functions! { + JWT => JWTError, + AccessTokenEntities => AccessTokenEntitiesError, + ResourceEntity => ResourceEntityError, + Action => ActionError, + CreateContext => CreateContextError, + CreateRequest => CreateRequestError, + Entities => EntitiesError +} + +pub fn authorize_errors_module(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add("AuthorizeError", m.py().get_type_bound::())?; + register_errors(m)?; + // m.add_function(wrap_pyfunction!(raise_myerror, m)?)?; + // Ok(()) + Ok(()) +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/mod.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/mod.rs new file mode 100644 index 00000000000..eaf2b7b0aee --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/mod.rs @@ -0,0 +1,33 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ +use pyo3::prelude::*; +use pyo3::Bound; + +pub(crate) mod authorize_result; +mod authorize_result_response; +mod decision; +mod diagnostics; +pub(crate) mod errors; +mod policy_evaluation_error; +pub(crate) mod request; +mod resource_data; + +pub fn register_entities(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + let submodule = PyModule::new_bound(m.py(), "authorize_errors")?; + errors::authorize_errors_module(&submodule)?; + m.add_submodule(&submodule)?; + + Ok(()) +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/policy_evaluation_error.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/policy_evaluation_error.rs new file mode 100644 index 00000000000..21599112a26 --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/policy_evaluation_error.rs @@ -0,0 +1,37 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ + +use pyo3::prelude::*; + +/// PolicyEvaluationError +/// ===================== +/// +/// Represents an error that occurred when evaluating a Cedar policy. +/// +/// Attributes +/// ---------- +/// id : str +/// The ID of the policy that caused the error. +/// error : str +/// The error message describing the evaluation failure. +#[derive(Debug, Clone)] +#[pyclass(get_all)] +pub struct PolicyEvaluationError { + /// Id of the policy with an error + id: String, + /// Underlying evaluation error string representation + error: String, +} + +impl From for PolicyEvaluationError { + fn from(value: cedarling::bindings::PolicyEvaluationError) -> Self { + Self { + id: value.id, + error: value.error, + } + } +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/request.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/request.rs new file mode 100644 index 00000000000..4d8ebaf7472 --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/request.rs @@ -0,0 +1,77 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ + +// use cedarling::ResourceData; +use super::resource_data::ResourceData; +use pyo3::prelude::*; +use pyo3::types::PyDict; +use serde_pyobject::from_pyobject; + +/// Request +/// ======= +/// +/// A Python wrapper for the Rust `cedarling::Request` struct. Represents +/// authorization data with access token, action, resource, and context. +/// +/// Attributes +/// ---------- +/// :param access_token: The access token string. +/// :param action: The action to be authorized. +/// :param resource: Resource data (wrapped `ResourceData` object). +/// :param context: Python dictionary with additional context. +/// +/// Example +/// ------- +/// ```python +/// # Create a request for authorization +/// request = Request(access_token="token123", action="read", resource=resource, context={}) +/// ``` +#[pyclass(get_all, set_all)] +pub struct Request { + /// Access token raw value + pub access_token: String, + /// cedar_policy action + pub action: String, + /// cedar_policy resource data + pub resource: ResourceData, + /// context to be used in cedar_policy + pub context: Py, +} + +#[pymethods] +impl Request { + #[new] + fn new( + access_token: String, + action: String, + resource: ResourceData, + context: Py, + ) -> Self { + Self { + access_token, + action, + resource, + context, + } + } +} + +impl Request { + pub fn to_cedarling(&self) -> Result { + let context = Python::with_gil(|py| -> Result { + let context = self.context.clone_ref(py).into_bound(py); + from_pyobject(context).map_err(|err| err.0) + })?; + + Ok(cedarling::Request { + access_token: self.access_token.clone(), + action: self.action.clone(), + resource: self.resource.clone().into(), + context, + }) + } +} diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/resource_data.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/resource_data.rs new file mode 100644 index 00000000000..ff9aa433ed2 --- /dev/null +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/resource_data.rs @@ -0,0 +1,96 @@ +/* + * This software is available under the Apache-2.0 license. + * See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. + * + * Copyright (c) 2024, Gluu, Inc. + */ + +use std::collections::HashMap; + +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyType}; +use serde_pyobject::from_pyobject; + +/// ResourceData +/// ============ +/// +/// A Python wrapper for the Rust `cedarling::ResourceData` struct. This class represents +/// a resource entity with a type, ID, and attributes. Attributes are stored as a payload +/// in a dictionary format. +/// +/// Attributes +/// ---------- +/// :param resource_type: Type of the resource entity. +/// :param id: ID of the resource entity. +/// :param payload: Optional dictionary of attributes. +/// +/// Methods +/// ------- +/// .. method:: __init__(self, resource_type: str, id: str, **kwargs: dict) +/// Initialize a new ResourceData. In kwargs the payload is a dictionary of entity attributes. +/// +/// .. method:: from_dict(cls, value: dict) -> ResourceData +/// Initialize a new ResourceData from a dictionary. +/// To pass `resource_type` you need to use `type` key. +#[derive(Clone, serde::Deserialize)] +#[pyclass] +pub struct ResourceData { + /// entity type name + #[pyo3(set)] + #[serde(rename = "type")] + pub resource_type: String, + /// entity id + #[pyo3(set)] + pub id: String, + /// entity attributes + #[serde(flatten)] + pub payload: PayloadType, +} + +// type alias for the payload hash map +type PayloadType = HashMap; + +fn get_payload(object: Bound<'_, PyDict>) -> PyResult { + from_pyobject(object).map_err(|err| err.0) +} + +#[pymethods] +impl ResourceData { + #[new] + #[pyo3(signature = (resource_type, id, **kwargs))] + fn new(resource_type: String, id: String, kwargs: Option>) -> PyResult { + let payload = kwargs + .map(|dict| get_payload(dict)) + .unwrap_or(Ok(HashMap::new()))?; + + Ok(Self { + resource_type, + id, + payload, + }) + } + + /// setter for payload attribute + #[setter] + fn payload(&mut self, value: Bound<'_, PyDict>) -> PyResult<()> { + self.payload = get_payload(value)?; + Ok(()) + } + + /// method to build a resource data object from a python dictionary + #[classmethod] + fn from_dict(_cls: Bound<'_, PyType>, value: Bound<'_, PyDict>) -> PyResult { + from_pyobject(value).map_err(|err| PyValueError::new_err(err.0)) + } +} + +impl From for cedarling::ResourceData { + fn from(value: ResourceData) -> Self { + Self { + resource_type: value.resource_type, + id: value.id, + payload: value.payload, + } + } +} diff --git a/jans-cedarling/bindings/cedarling_python/src/cedarling.rs b/jans-cedarling/bindings/cedarling_python/src/cedarling.rs index cb12fc6b9b8..3ac8cc7caf5 100644 --- a/jans-cedarling/bindings/cedarling_python/src/cedarling.rs +++ b/jans-cedarling/bindings/cedarling_python/src/cedarling.rs @@ -9,6 +9,9 @@ use cedarling::LogStorage; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; +use crate::authorize::authorize_result::AuthorizeResult; +use crate::authorize::errors::authorize_error_to_py; +use crate::authorize::request::Request; use crate::config::bootstrap_config::BootstrapConfig; use serde_pyobject::to_pyobject; @@ -31,33 +34,31 @@ use serde_pyobject::to_pyobject; /// /// :param config: A `BootstrapConfig` object with startup settings. /// -/// .. method:: pop_logs(self) +/// .. method:: pop_logs(self) -> List[dict] /// /// Retrieves and removes all logs from storage. /// /// :returns: A list of log entries as Python objects. -/// :rtype: List[PyObject] /// /// :raises ValueError: If an error occurs while fetching logs. /// -/// .. method:: get_log_by_id(self, id) +/// .. method:: get_log_by_id(self, id: str) -> dict|None /// /// Gets a log entry by its ID. /// /// :param id: The log entry ID. -/// :type id: str -/// -/// :returns: The log entry as a Python object or None if not found. -/// :rtype: Optional[PyObject] /// /// :raises ValueError: If an error occurs while fetching the log. /// -/// .. method:: get_log_ids(self) +/// .. method:: get_log_ids(self) -> List[str] /// /// Retrieves all stored log IDs. /// -/// :returns: A list of log entry IDs. -/// :rtype: List[str] +/// .. method:: authorize(self, request: Request) -> AuthorizeResult +/// +/// Execute authorize request +/// :param request: Request struct for authorize. +/// #[derive(Clone)] #[pyclass] pub struct Cedarling { @@ -100,6 +101,15 @@ impl Cedarling { fn get_log_ids(&self) -> Vec { self.inner.get_log_ids() } + + /// Authorize request + fn authorize(&self, request: Bound<'_, Request>) -> Result { + let cedarling_instance = self + .inner + .authorize(request.borrow().to_cedarling()?) + .map_err(authorize_error_to_py)?; + Ok(cedarling_instance.into()) + } } fn log_entry_to_py(gil: Python, entry: &cedarling::LogEntry) -> PyResult { diff --git a/jans-cedarling/bindings/cedarling_python/src/lib.rs b/jans-cedarling/bindings/cedarling_python/src/lib.rs index 890d5c0e356..685260123e0 100644 --- a/jans-cedarling/bindings/cedarling_python/src/lib.rs +++ b/jans-cedarling/bindings/cedarling_python/src/lib.rs @@ -8,12 +8,14 @@ use pyo3::prelude::*; use pyo3::Bound; +mod authorize; mod cedarling; mod config; #[pymodule] fn cedarling_python(m: &Bound<'_, PyModule>) -> PyResult<()> { config::register_entities(m)?; + authorize::register_entities(m)?; m.add_class::()?; diff --git a/jans-cedarling/cedarling/examples/authorize.rs b/jans-cedarling/cedarling/examples/authorize.rs index dc6b3e8f295..2cf670f0eed 100644 --- a/jans-cedarling/cedarling/examples/authorize.rs +++ b/jans-cedarling/cedarling/examples/authorize.rs @@ -26,7 +26,7 @@ fn main() -> Result<(), Box> { jwt_config: JwtConfig::Disabled, })?; - let access_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJib0c4ZGZjNU1LVG4zN283Z3NkQ2V5cUw4THBXUXRnb080MW0xS1p3ZHEwIiwiY29kZSI6ImJmMTkzNGY2LTM5MDUtNDIwYS04Mjk5LTZiMmUzZmZkZGQ2ZSIsImlzcyI6Imh0dHBzOi8vYWRtaW4tdWktdGVzdC5nbHV1Lm9yZyIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJjbGllbnRfaWQiOiI1YjQ0ODdjNC04ZGIxLTQwOWQtYTY1My1mOTA3YjgwOTQwMzkiLCJhdWQiOiI1YjQ0ODdjNC04ZGIxLTQwOWQtYTY1My1mOTA3YjgwOTQwMzkiLCJhY3IiOiJiYXNpYyIsIng1dCNTMjU2IjoiIiwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJvcmdfaWQiOiJzb21lX2xvbmdfaWQiLCJhdXRoX3RpbWUiOjE3MjQ4MzA3NDYsImV4cCI6MTcyNDk0NTk3OCwiaWF0IjoxNzI0ODMyMjU5LCJqdGkiOiJseFRtQ1ZSRlR4T2pKZ3ZFRXBvek1RIiwibmFtZSI6IkRlZmF1bHQgQWRtaW4gVXNlciIsInN0YXR1cyI6eyJzdGF0dXNfbGlzdCI6eyJpZHgiOjIwMSwidXJpIjoiaHR0cHM6Ly9hZG1pbi11aS10ZXN0LmdsdXUub3JnL2phbnMtYXV0aC9yZXN0djEvc3RhdHVzX2xpc3QifX19._eQT-DsfE_kgdhA0YOyFxxPEMNw44iwoelWa5iU1n9s"; + let access_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJib0c4ZGZjNU1LVG4zN283Z3NkQ2V5cUw4THBXUXRnb080MW0xS1p3ZHEwIiwiY29kZSI6ImJmMTkzNGY2LTM5MDUtNDIwYS04Mjk5LTZiMmUzZmZkZGQ2ZSIsImlzcyI6Imh0dHBzOi8vYWRtaW4tdWktdGVzdC5nbHV1Lm9yZyIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJjbGllbnRfaWQiOiI1YjQ0ODdjNC04ZGIxLTQwOWQtYTY1My1mOTA3YjgwOTQwMzkiLCJhdWQiOiI1YjQ0ODdjNC04ZGIxLTQwOWQtYTY1My1mOTA3YjgwOTQwMzkiLCJhY3IiOiJiYXNpYyIsIng1dCNTMjU2IjoiIiwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJvcmdfaWQiOiJzb21lX2xvbmdfaWQiLCJhdXRoX3RpbWUiOjE3MjQ4MzA3NDYsImV4cCI6MTcyNDk0NTk3OCwiaWF0IjoxNzI0ODMyMjU5LCJqdGkiOiJseFRtQ1ZSRlR4T2pKZ3ZFRXBvek1RIiwibmFtZSI6IkRlZmF1bHQgQWRtaW4gVXNlciIsInN0YXR1cyI6eyJzdGF0dXNfbGlzdCI6eyJpZHgiOjIwMSwidXJpIjoiaHR0cHM6Ly9hZG1pbi11aS10ZXN0LmdsdXUub3JnL2phbnMtYXV0aC9yZXN0djEvc3RhdHVzX2xpc3QifX19._eQT-DsfE_kgdhA0YOyFxxPEMNw44iwoelWa5iU1n9s".to_string(); let result = cedarling.authorize(Request { access_token, diff --git a/jans-cedarling/cedarling/src/authz/mod.rs b/jans-cedarling/cedarling/src/authz/mod.rs index cfc754f6ad8..e1fa7861860 100644 --- a/jans-cedarling/cedarling/src/authz/mod.rs +++ b/jans-cedarling/cedarling/src/authz/mod.rs @@ -69,7 +69,9 @@ impl Authz { pub fn authorize(&self, request: Request) -> Result { let access_token_entities = create_access_token_entities( &self.policy_store.schema.json, - &self.jwt_service.decode_token_data(request.access_token)?, + &self + .jwt_service + .decode_token_data(request.access_token.as_str())?, )?; let resource_entity = @@ -120,7 +122,7 @@ impl Authz { context: request.context, decision: decision.decision().into(), principal: principal_workload_uid.to_string(), - diagnostics: decision.diagnostics().clone().into(), + diagnostics: decision.diagnostics().into(), resource: resource_uid.to_string(), }) .set_message("Result of authorize with resource as workload entity".to_string()), diff --git a/jans-cedarling/cedarling/src/lib.rs b/jans-cedarling/cedarling/src/lib.rs index 71c947545c2..43cf802128a 100644 --- a/jans-cedarling/cedarling/src/lib.rs +++ b/jans-cedarling/cedarling/src/lib.rs @@ -41,6 +41,14 @@ pub use models::log_entry::LogEntry; use models::log_entry::LogType; pub use models::request::{Request, ResourceData}; +#[doc(hidden)] +pub mod bindings { + pub use super::models::log_entry::{ + AuthorizationLogInfo, Decision, Diagnostics, PolicyEvaluationError, + }; + pub use cedar_policy; +} + /// Errors that can occur during initialization Cedarling. #[derive(Debug, thiserror::Error)] pub enum InitCedarlingError { diff --git a/jans-cedarling/cedarling/src/models/authorize_result.rs b/jans-cedarling/cedarling/src/models/authorize_result.rs index b27efe8b22b..30d6370d197 100644 --- a/jans-cedarling/cedarling/src/models/authorize_result.rs +++ b/jans-cedarling/cedarling/src/models/authorize_result.rs @@ -11,7 +11,7 @@ pub struct AuthorizeResult { } impl AuthorizeResult { - /// Returns true if workload is allowed + /// Returns true if request is allowed pub fn is_allowed(&self) -> bool { // in future we should also check if the person is allowed self.workload.decision() == Decision::Allow diff --git a/jans-cedarling/cedarling/src/models/log_entry.rs b/jans-cedarling/cedarling/src/models/log_entry.rs index c033c592bc6..5e16abfd86f 100644 --- a/jans-cedarling/cedarling/src/models/log_entry.rs +++ b/jans-cedarling/cedarling/src/models/log_entry.rs @@ -106,13 +106,24 @@ pub struct AuthorizationLogInfo { } /// Cedar-policy decision of the authorization -#[derive(Debug, Clone, PartialEq, Copy, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum Decision { + /// Determined that the request should be allowed Allow, + /// Determined that the request should be denied. Deny, } +impl ToString for Decision { + fn to_string(&self) -> String { + match self { + Decision::Allow => "ALLOW".to_string(), + Decision::Deny => "DENY".to_string(), + } + } +} + #[doc(hidden)] impl From for Decision { fn from(value: cedar_policy::Decision) -> Self { @@ -127,9 +138,9 @@ impl From for Decision { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct PolicyEvaluationError { /// Id of the policy with an error - id: String, + pub id: String, /// Underlying evaluation error string representation - error: String, + pub error: String, } #[doc(hidden)] @@ -151,15 +162,15 @@ impl From<&cedar_policy::AuthorizationError> for PolicyEvaluationError { pub struct Diagnostics { /// `PolicyId`s of the policies that contributed to the decision. /// If no policies applied to the request, this set will be empty. - reason: HashSet, + pub reason: HashSet, /// Errors that occurred during authorization. The errors should be /// treated as unordered, since policies may be evaluated in any order. - errors: Vec, + pub errors: Vec, } #[doc(hidden)] -impl From for Diagnostics { - fn from(value: cedar_policy::Diagnostics) -> Self { +impl From<&cedar_policy::Diagnostics> for Diagnostics { + fn from(value: &cedar_policy::Diagnostics) -> Self { Self { reason: HashSet::from_iter( value diff --git a/jans-cedarling/cedarling/src/models/request.rs b/jans-cedarling/cedarling/src/models/request.rs index 9e914cc4791..7e34e5563c7 100644 --- a/jans-cedarling/cedarling/src/models/request.rs +++ b/jans-cedarling/cedarling/src/models/request.rs @@ -11,11 +11,11 @@ use cedar_policy::{EntityId, EntityTypeName, EntityUid, ParseErrors}; /// Box to store authorization data #[derive(Debug, serde::Deserialize)] -pub struct Request<'a> { +pub struct Request { /// Access token raw value - pub access_token: &'a str, - // pub id_token: &'a str, - // pub userinfo_token: &'a str, + pub access_token: String, + // pub id_token: String, + // pub userinfo_token: String, /// cedar_policy action pub action: String, /// cedar_policy resource data @@ -25,7 +25,7 @@ pub struct Request<'a> { } /// Cedar policy resource data -/// field represent EntityUid +/// fields represent EntityUid #[derive(serde::Deserialize, Debug, Clone)] pub struct ResourceData { /// entity type name