From 5566a33dd61fd2822f07da16ef3742e2deb85a53 Mon Sep 17 00:00:00 2001 From: Richard Marin <34529290+rmarinn@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:42:22 +0800 Subject: [PATCH] refactor(jans-cedarling): make all tokens optional when calling Cedarling::authorize (#10436) * refactor(jans-cedarling): make process_tokens params optional - make the `access_token` param for the JwtService::process_tokens optional - make the `id_token` param for the JwtService::process_tokens optional - return an `Unimplemented` error when any `None` is passed in for the tokens since the `authz` module doesn't support optional params yet. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * feat(jans-cedarling): implement processing single tokens - implement JwtService::process_token which decodes and optionally validates a single JWT. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): replace jwt validation implementation - replace the old jwt validation implementation that needs multiple tokens at once with the new implementation which validates the tokens one at a time. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * feat(jans-cedarling): make tokens optional when calling authorize - implement allowing passing `None` for some tokens when calling authorize. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): make creation of workload entity optional Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * fix(jans-cedarling): user and role entity creation Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): make creation of user entity optional Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): use if-else instead of match for bool Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): improve functionality for decoded JWTs implement convenience function for getting the following data from a decoded JWT: - metadata - user_mapping - claim_mapping - role_mapping - logging_info Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): removed unused functions for TrustedIssuer Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): improve create_workload_entity func implemenation - change order for creating workload entities to: id_token then access_token - improve `create_workload` error handling - renamed `CedarPolicyCreateTypeError` to `CreateCedarEntityError` Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): improve create_user_entity func implemenation - change order for creating workload entities to: id_token, access_token, then userinfo_token - improve `create_user_entitiy` error handling Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): resolve clippy issues Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): error check when loading bootstrap config - Error when both `CEDARLING_USER_AUTHZ` and `CEDARLING_WORKLOAD_AUTHZ` are disabled. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): add new fields to TokenEntityMetadata - add `trusted` and `principal_identifier` to the TokenEntityMetadata struct so all tokens have access to them. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): remove some cloning when logging authz Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * test(jans-cedarling): add unit tests for entity creation - add tests for creating user entity - add tests for creating workload entity Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): improve error handling for entity creation - errors that occur because no tokens were available should now be tracable when creating user and workload entities Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * test(jans-cedarling): update python tests expected error Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * feat(jans-cedarling): make tokens optional in the python binding Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): change error message to start with a lowercase letter Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): flattened `use` statements for readability Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): remove unused JwtProcessingError variant Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): simplify iterator creation Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): make workload entity creation optional Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): make user entity creation optional Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): remove outdated comment Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): convert Token into a struct from an enum Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): unify token entity creation logic - combine `entities::create__token` functions into a single, unified function. - prevent incorrect token types from creating entities of the wrong kind (e.g., using access_tokens to create id_token entities). - simplify logic and enforce stricter type safety for token-to-entity mapping. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): eliminate access_token from user entity creation - remove the use of access_token in the create_user_entity function - simplify the user entity creation process. Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * fix(jans-cedarling): field name conflict for serializing/deserializing - fix field name conflict by using serde_rename for UserAuthorizeInfo and WorkloadAuthorizeInfo Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): remove unused commented code Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): token priority when creating userinfo entity - change the token priority when creating the userinfo entity to be 1. userinfo_token then 2. id_token Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): resolve clippy issues Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * test(jans-cedarling): update expected error in python bindings test Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): implement rename for logging info fields - rename UserAuthorizeInfo.principal to user_principal - rename UserAuthorizeInfo.diagnostics to user_diagnostics - rename UserAuthorizeInfo.decision to user_decision - rename WorkloadAuthorizeInfo.principal to workload_principal - rename WorkloadAuthorizeInfo.diagnostics to workload_diagnostics - rename WorkloadAuthorizeInfo.decision to workload_decision Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): run cargo fmt and add missing license headers Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): rename the Vec of role from `role` to `roles` Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): remove unused function Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * refactor(jans-cedarling): rename UserAuthorizeInfo fields - change `user` to `person` Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): run cargo fmt Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> * chore(jans-cedarling): refactor to avoid unnecessary cloning Signed-off-by: Oleh Bohzok --------- Signed-off-by: rmarinn <34529290+rmarinn@users.noreply.github.com> Signed-off-by: Oleh Bohzok Co-authored-by: Oleh Bohzok --- docs/cedarling/cedarling-policy-store.md | 8 +- .../bindings/cedarling_python/PYTHON_TYPES.md | 28 +- .../cedarling_python/cedarling_python.pyi | 6 +- .../cedarling_python/src/authorize/errors.rs | 8 +- .../cedarling_python/src/authorize/request.rs | 19 +- .../cedarling_python/tests/test_authorize.py | 20 +- .../examples/authorize_with_jwt_validation.rs | 19 +- .../authorize_without_jwt_validation.rs | 19 +- jans-cedarling/cedarling/examples/log_init.rs | 13 +- .../cedarling/src/authz/authorize_result.rs | 13 +- .../cedarling/src/authz/entities/create.rs | 217 ++++---- .../cedarling/src/authz/entities/mod.rs | 301 +++++------ .../src/authz/entities/test_create.rs | 213 +++++--- .../src/authz/entities/trait_as_expression.rs | 10 +- .../cedarling/src/authz/entities/user.rs | 252 +++++++++ .../cedarling/src/authz/entities/workload.rs | 245 +++++++++ .../cedarling/src/authz/merge_json.rs | 8 +- jans-cedarling/cedarling/src/authz/mod.rs | 496 ++++++++++-------- jans-cedarling/cedarling/src/authz/request.rs | 19 +- .../cedarling/src/authz/token_data.rs | 184 ------- .../bootstrap_config/authorization_config.rs | 10 +- .../cedarling/src/bootstrap_config/decode.rs | 32 +- .../src/bootstrap_config/jwt_config.rs | 14 +- .../src/bootstrap_config/log_config.rs | 10 +- .../cedarling/src/bootstrap_config/mod.rs | 46 +- .../bootstrap_config/policy_store_config.rs | 10 +- .../cedarling/src/common/app_types.rs | 10 +- .../src/common/cedar_schema/cedar_json.rs | 51 +- .../common/cedar_schema/cedar_json/action.rs | 66 ++- .../cedar_schema/cedar_json/entity_types.rs | 14 +- .../cedarling/src/common/cedar_schema/mod.rs | 22 +- jans-cedarling/cedarling/src/common/mod.rs | 11 +- .../cedarling/src/common/policy_store.rs | 165 ++---- .../src/common/policy_store/claim_mapping.rs | 22 +- .../cedarling/src/common/policy_store/test.rs | 31 +- .../policy_store/token_entity_metadata.rs | 72 ++- jans-cedarling/cedarling/src/http/blocking.rs | 17 +- jans-cedarling/cedarling/src/http/mod.rs | 18 +- jans-cedarling/cedarling/src/http/wasm.rs | 13 +- jans-cedarling/cedarling/src/init/mod.rs | 11 +- .../cedarling/src/init/policy_store.rs | 17 +- .../cedarling/src/init/service_config.rs | 13 +- .../cedarling/src/init/service_factory.rs | 17 +- .../cedarling/src/jwt/issuers_store.rs | 15 +- jans-cedarling/cedarling/src/jwt/jwk_store.rs | 37 +- .../cedarling/src/jwt/key_service.rs | 30 +- jans-cedarling/cedarling/src/jwt/mod.rs | 216 ++++---- .../cedarling/src/jwt/test_utils.rs | 13 +- jans-cedarling/cedarling/src/jwt/token.rs | 220 ++++++++ jans-cedarling/cedarling/src/jwt/validator.rs | 65 +-- .../cedarling/src/jwt/validator/config.rs | 20 +- .../cedarling/src/jwt/validator/test.rs | 22 +- jans-cedarling/cedarling/src/lib.rs | 29 +- jans-cedarling/cedarling/src/lock/mod.rs | 11 +- jans-cedarling/cedarling/src/log/interface.rs | 15 +- jans-cedarling/cedarling/src/log/log_entry.rs | 60 +-- jans-cedarling/cedarling/src/log/log_level.rs | 13 +- .../cedarling/src/log/log_strategy.rs | 13 +- .../cedarling/src/log/memory_logger.rs | 23 +- jans-cedarling/cedarling/src/log/mod.rs | 13 +- .../cedarling/src/log/nop_logger.rs | 10 +- .../cedarling/src/log/stdout_logger.rs | 22 +- jans-cedarling/cedarling/src/log/test.rs | 10 +- .../cases_authorize_different_principals.rs | 26 +- .../tests/cases_authorize_namespace_jans2.rs | 15 +- .../cases_authorize_without_check_jwt.rs | 15 +- .../cedarling/src/tests/mapping_entities.rs | 115 ++-- jans-cedarling/cedarling/src/tests/mod.rs | 10 +- .../src/tests/schema_type_mapping.rs | 17 +- .../cedarling/src/tests/success_test_json.rs | 10 +- .../src/tests/utils/cedarling_util.rs | 10 +- .../cedarling/src/tests/utils/mod.rs | 13 +- .../cedarling/src/tests/utils/token_claims.rs | 14 +- .../test_files/policy-store_ok_2.yaml | 20 +- 74 files changed, 2227 insertions(+), 1685 deletions(-) create mode 100644 jans-cedarling/cedarling/src/authz/entities/user.rs create mode 100644 jans-cedarling/cedarling/src/authz/entities/workload.rs delete mode 100644 jans-cedarling/cedarling/src/authz/token_data.rs create mode 100644 jans-cedarling/cedarling/src/jwt/token.rs diff --git a/docs/cedarling/cedarling-policy-store.md b/docs/cedarling/cedarling-policy-store.md index 504f9cf69ca..55bdf64de8c 100644 --- a/docs/cedarling/cedarling-policy-store.md +++ b/docs/cedarling/cedarling-policy-store.md @@ -178,11 +178,7 @@ This record contains the information needed to validate tokens from this issuer: - **description** : (*String*) A brief description of the trusted issuer, providing context for administrators. - **openid_configuration_endpoint** : (*String*) The HTTPS URL for the OpenID Connect configuration endpoint (usually found at `/.well-known/openid-configuration`). - **identity_source** : (*Object*, *optional*) Metadata related to the tokens issued by this issuer. - -**Notes**: - -- The `access_tokens`, `id_tokens`, `userinfo_tokens`, and `tx_tokens` fields will follow the [Token Metadata Schema](#token-metadata-schema). -- The `access_tokens` will contain a `trusted` and `principal_identifier` field in addition to the fields from the `Token Metadata Schema`. +- **`access_tokens`, `id_tokens`, `userinfo_tokens`, and `tx_tokens`**: See: [Token Metadata Schema](#token-metadata-schema). ### Token Metadata Schema @@ -190,6 +186,8 @@ The Token Entity Metadata Schema defines how tokens are mapped, parsed, and tran ```json { + "trusted": bool, + "principal_identifier": "str", "user_id": "", "role_mapping": "", "claim_mapping": { diff --git a/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md b/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md index 3bb7f041f4d..17093046fcf 100644 --- a/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md +++ b/jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md @@ -113,12 +113,12 @@ authorization data with access token, action, resource, and context. Attributes ---------- -:param access_token: The access token string. -:param id_token: The id token string. -:param userinfo_token: The userinfo token string. :param action: The action to be authorized. :param resource: Resource data (wrapped `ResourceData` object). -:param context: Python dictionary with additional context. +:param context: Python dictionary with additional context. +:param access_token: (Optional) The access token string. +:param id_token: (Optional) The id token string. +:param userinfo_token: (Optional) The userinfo token string. Example ------- @@ -203,6 +203,10 @@ ___ Error encountered while parsing Action to EntityUid ___ +# authorize_errors.AddEntitiesIntoContextError +Error encountered while adding entities into context +___ + # authorize_errors.AuthorizeError Exception raised by authorize_errors ___ @@ -219,14 +223,6 @@ ___ Error encountered while creating id token entities ___ -# authorize_errors.CreateRequestUserEntityError -Error encountered while creating cedar_policy::Request for user entity principal -___ - -# authorize_errors.CreateRequestWorkloadEntityError -Error encountered while creating cedar_policy::Request for workload entity principal -___ - # authorize_errors.CreateUserEntityError Error encountered while creating User entity ___ @@ -259,3 +255,11 @@ ___ Error encountered while creating role entity ___ +# authorize_errors.UserRequestValidationError +Error encountered while creating cedar_policy::Request for user entity principal +___ + +# authorize_errors.WorkloadRequestValidationError +Error encountered while creating cedar_policy::Request for workload entity principal +___ + diff --git a/jans-cedarling/bindings/cedarling_python/cedarling_python.pyi b/jans-cedarling/bindings/cedarling_python/cedarling_python.pyi index 15f75ffac62..11435ed908e 100644 --- a/jans-cedarling/bindings/cedarling_python/cedarling_python.pyi +++ b/jans-cedarling/bindings/cedarling_python/cedarling_python.pyi @@ -128,12 +128,12 @@ class Cedarling: @final class Request: - access_token: str - id_token: str - userinfo_token: str action: str resource: ResourceData context: Dict[str, Any] + access_token: str | None + id_token: str | None + userinfo_token: str | None def __init__(self, access_token: str, diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/errors.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/errors.rs index 11a6fd9c95e..89ad49ba9a9 100644 --- a/jans-cedarling/bindings/cedarling_python/src/authorize/errors.rs +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/errors.rs @@ -94,14 +94,14 @@ create_exception!( create_exception!( authorize_errors, - CreateRequestWorkloadEntityError, + WorkloadRequestValidationError, AuthorizeError, "Error encountered while creating cedar_policy::Request for workload entity principal" ); create_exception!( authorize_errors, - CreateRequestUserEntityError, + UserRequestValidationError, AuthorizeError, "Error encountered while creating cedar_policy::Request for user entity principal" ); @@ -175,8 +175,8 @@ errors_functions! { RoleEntity => RoleEntityError, Action => ActionError, CreateContext => CreateContextError, - CreateRequestWorkloadEntity => CreateRequestWorkloadEntityError, - CreateRequestUserEntity => CreateRequestUserEntityError, + WorkloadRequestValidation => WorkloadRequestValidationError, + UserRequestValidation => UserRequestValidationError, BuildContext => AddEntitiesIntoContextError, Entities => EntitiesError, EntitiesToJson => EntitiesToJsonError diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/request.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/request.rs index bbeb6fe953e..d4b99f6b160 100644 --- a/jans-cedarling/bindings/cedarling_python/src/authorize/request.rs +++ b/jans-cedarling/bindings/cedarling_python/src/authorize/request.rs @@ -20,12 +20,12 @@ use serde_pyobject::from_pyobject; /// /// Attributes /// ---------- -/// :param access_token: The access token string. -/// :param id_token: The id token string. -/// :param userinfo_token: The userinfo token string. /// :param action: The action to be authorized. /// :param resource: Resource data (wrapped `ResourceData` object). /// :param context: Python dictionary with additional context. +/// :param access_token: (Optional) The access token string. +/// :param id_token: (Optional) The id token string. +/// :param userinfo_token: (Optional) The userinfo token string. /// /// Example /// ------- @@ -36,11 +36,11 @@ use serde_pyobject::from_pyobject; #[pyclass(get_all, set_all)] pub struct Request { /// Access token raw value - pub access_token: String, + pub access_token: Option, /// Id token raw value - pub id_token: String, + pub id_token: Option, /// Userinfo token raw value - pub userinfo_token: String, + pub userinfo_token: Option, /// cedar_policy action pub action: String, /// cedar_policy resource data @@ -52,13 +52,14 @@ pub struct Request { #[pymethods] impl Request { #[new] + #[pyo3(signature = (action, resource, context, access_token=None, id_token=None, userinfo_token=None))] fn new( - access_token: String, - id_token: String, - userinfo_token: String, action: String, resource: ResourceData, context: Py, + access_token: Option, + id_token: Option, + userinfo_token: Option, ) -> Self { Self { access_token, diff --git a/jans-cedarling/bindings/cedarling_python/tests/test_authorize.py b/jans-cedarling/bindings/cedarling_python/tests/test_authorize.py index 169aa3e3a8b..6b1c8a70dbe 100644 --- a/jans-cedarling/bindings/cedarling_python/tests/test_authorize.py +++ b/jans-cedarling/bindings/cedarling_python/tests/test_authorize.py @@ -115,11 +115,13 @@ def test_authorize_ok(): }) request = Request( - ACCESS_TOKEN, - ID_TOKEN, - USERINFO_TOKEN, + access_token=ACCESS_TOKEN, + id_token=ID_TOKEN, + userinfo_token=USERINFO_TOKEN, action='Jans::Action::"Update"', - context={}, resource=resource) + context={}, + resource=resource, + ) authorize_result = instance.authorize(request) assert authorize_result.is_allowed(), "request should be allowed" @@ -170,9 +172,9 @@ def raise_authorize_error(bootstrap_config): }) request = Request( - ACCESS_TOKEN, - ID_TOKEN, - USERINFO_TOKEN, + access_token=ACCESS_TOKEN, + id_token=ID_TOKEN, + userinfo_token=USERINFO_TOKEN, action='Jans::Action::"Update"', context={}, resource=resource) @@ -189,7 +191,7 @@ def test_resource_entity_error(): try: raise_authorize_error(load_bootstrap_config()) except authorize_errors.ResourceEntityError as e: - assert str(e) == "could not create resource entity: could not get attribute value from payload: could not convert json field with key: org_id to: String, got: number" + assert str(e) == "could not create resource entity: could not get attribute value from payload: type mismatch for key 'org_id'. expected: 'String', but found: 'number'" def test_authorize_error(): @@ -201,4 +203,4 @@ def test_authorize_error(): try: raise_authorize_error(load_bootstrap_config()) except authorize_errors.AuthorizeError as e: - assert str(e) == "could not create resource entity: could not get attribute value from payload: could not convert json field with key: org_id to: String, got: number" + assert str(e) == "could not create resource entity: could not get attribute value from payload: type mismatch for key 'org_id'. expected: 'String', but found: 'number'" diff --git a/jans-cedarling/cedarling/examples/authorize_with_jwt_validation.rs b/jans-cedarling/cedarling/examples/authorize_with_jwt_validation.rs index af9c34801d5..802712a49f7 100644 --- a/jans-cedarling/cedarling/examples/authorize_with_jwt_validation.rs +++ b/jans-cedarling/cedarling/examples/authorize_with_jwt_validation.rs @@ -1,9 +1,9 @@ -/* - * 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. - */ +// 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, HashSet}; use cedarling::{ AuthorizationConfig, BootstrapConfig, Cedarling, IdTokenTrustMode, JwtConfig, LogConfig, @@ -11,7 +11,6 @@ use cedarling::{ TokenValidationConfig, WorkloadBoolOp, }; use jsonwebtoken::Algorithm; -use std::collections::{HashMap, HashSet}; static POLICY_STORE_RAW_YAML: &str = include_str!("../../test_files/policy-store_with_trusted_issuers_ok.yaml"); @@ -62,9 +61,9 @@ fn main() -> Result<(), Box> { // on a specific resource. Each token (access, ID, and userinfo) is required for the // authorization process, alongside resource and action details. let result = cedarling.authorize(Request { - access_token, - id_token, - userinfo_token, + access_token: Some(access_token), + id_token: Some(id_token), + userinfo_token: Some(userinfo_token), action: "Jans::Action::\"Update\"".to_string(), context: serde_json::json!({}), resource: ResourceData { diff --git a/jans-cedarling/cedarling/examples/authorize_without_jwt_validation.rs b/jans-cedarling/cedarling/examples/authorize_without_jwt_validation.rs index c7aad3b4a09..af31d0f2f78 100644 --- a/jans-cedarling/cedarling/examples/authorize_without_jwt_validation.rs +++ b/jans-cedarling/cedarling/examples/authorize_without_jwt_validation.rs @@ -1,15 +1,14 @@ -/* - * 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. - */ +// 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 cedarling::{ AuthorizationConfig, BootstrapConfig, Cedarling, JwtConfig, LogConfig, LogLevel, LogTypeConfig, PolicyStoreConfig, PolicyStoreSource, Request, ResourceData, WorkloadBoolOp, }; -use std::collections::HashMap; static POLICY_STORE_RAW: &str = include_str!("../../test_files/policy-store_ok.yaml"); @@ -113,9 +112,9 @@ fn main() -> Result<(), Box> { let userinfo_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FkbWluLXVpLXRlc3QuZ2x1dS5vcmciLCJzdWIiOiJib0c4ZGZjNU1LVG4zN283Z3NkQ2V5cUw4THBXUXRnb080MW0xS1p3ZHEwIiwiY2xpZW50X2lkIjoiNWI0NDg3YzQtOGRiMS00MDlkLWE2NTMtZjkwN2I4MDk0MDM5IiwiYXVkIjoiNWI0NDg3YzQtOGRiMS00MDlkLWE2NTMtZjkwN2I4MDk0MDM5IiwidXNlcm5hbWUiOiJhZG1pbkBnbHV1Lm9yZyIsIm5hbWUiOiJEZWZhdWx0IEFkbWluIFVzZXIiLCJlbWFpbCI6ImFkbWluQGdsdXUub3JnIiwiY291bnRyeSI6IlVTIiwianRpIjoidXNyaW5mb190a25fanRpIn0.NoR53vPZFpfb4vFk85JH9RPx7CHsaJMZwrH3fnB-N60".to_string(); let result = cedarling.authorize(Request { - access_token, - id_token, - userinfo_token, + access_token: Some(access_token), + id_token: Some(id_token), + userinfo_token: Some(userinfo_token), action: "Jans::Action::\"Update\"".to_string(), context: serde_json::json!({}), resource: ResourceData { diff --git a/jans-cedarling/cedarling/examples/log_init.rs b/jans-cedarling/cedarling/examples/log_init.rs index 8954ab522f7..585beb5f406 100644 --- a/jans-cedarling/cedarling/examples/log_init.rs +++ b/jans-cedarling/cedarling/examples/log_init.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. // The following macro uses conditional compilation to include this file code // only when the target platform is NOT WebAssembly. This is not required to @@ -11,11 +9,12 @@ // and `use std::env` prevents that compilation. #![cfg(not(target_family = "wasm"))] +use std::env; + use cedarling::{ AuthorizationConfig, BootstrapConfig, Cedarling, JwtConfig, LogConfig, LogLevel, LogStorage, LogTypeConfig, MemoryLogConfig, PolicyStoreConfig, PolicyStoreSource, WorkloadBoolOp, }; -use std::env; // The human-readable policy and schema file is located in next folder: // `test_files\policy-store_ok` diff --git a/jans-cedarling/cedarling/src/authz/authorize_result.rs b/jans-cedarling/cedarling/src/authz/authorize_result.rs index 7c8bd388345..8d5da5cfa59 100644 --- a/jans-cedarling/cedarling/src/authz/authorize_result.rs +++ b/jans-cedarling/cedarling/src/authz/authorize_result.rs @@ -1,14 +1,13 @@ -/* - * 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. - */ +// 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::HashSet; use cedar_policy::Decision; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; -use std::collections::HashSet; use crate::bootstrap_config::WorkloadBoolOp; diff --git a/jans-cedarling/cedarling/src/authz/entities/create.rs b/jans-cedarling/cedarling/src/authz/entities/create.rs index 175a232e9bb..4b2721d067f 100644 --- a/jans-cedarling/cedarling/src/authz/entities/create.rs +++ b/jans-cedarling/cedarling/src/authz/entities/create.rs @@ -1,35 +1,26 @@ -/* - * 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, HashSet}, - str::FromStr, -}; +// 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 crate::common::{ - cedar_schema::{ - cedar_json::{CedarSchemaEntityShape, CedarSchemaRecord, CedarType, GetCedarTypeError}, - CedarSchemaJson, - }, - policy_store::ClaimMappings, -}; -use crate::{ - authz::token_data::{GetTokenClaimValue, Payload, TokenPayload}, - common::cedar_schema::cedar_json::SchemaDefinedType, -}; +use std::collections::{HashMap, HashSet}; +use std::str::FromStr; use cedar_policy::{EntityId, EntityTypeName, EntityUid, RestrictedExpression}; use super::trait_as_expression::AsExpression; +use crate::common::cedar_schema::cedar_json::{ + CedarSchemaEntityShape, CedarSchemaRecord, CedarType, GetCedarTypeError, SchemaDefinedType, +}; +use crate::common::cedar_schema::CedarSchemaJson; +use crate::common::policy_store::ClaimMappings; +use crate::jwt::{Token, TokenClaim, TokenClaimTypeError, TokenClaims}; pub const CEDAR_POLICY_SEPARATOR: &str = "::"; /// Meta information about an entity type. /// Is used to store in `static` variable. +#[derive(Debug)] pub(crate) struct EntityMetadata<'a> { pub entity_type: EntityParsedTypeName<'a>, pub entity_id_data_key: &'a str, @@ -45,25 +36,29 @@ impl<'a> EntityMetadata<'a> { } /// Create entity from token data. - // // we also can create entity using the ['create_entity'] function. pub fn create_entity( &'a self, schema: &'a CedarSchemaJson, - data: &'a TokenPayload, + token: &Token, parents: HashSet, claim_mapping: &ClaimMappings, - ) -> Result { + ) -> Result { let entity_uid = build_entity_uid( self.entity_type.full_type_name().as_str(), - data.get_payload(self.entity_id_data_key)?.as_str()?, + token + .get_claim(self.entity_id_data_key) + .ok_or(CreateCedarEntityError::MissingClaim( + self.entity_id_data_key.to_string(), + ))? + .as_str()?, )?; create_entity( entity_uid, &self.entity_type, schema, - data, + token.claims(), parents, claim_mapping, ) @@ -74,11 +69,10 @@ impl<'a> EntityMetadata<'a> { pub(crate) fn build_entity_uid( entity_type: &str, entity_id: &str, -) -> Result { +) -> Result { let entity_uid = EntityUid::from_type_name_and_id( - EntityTypeName::from_str(entity_type).map_err(|err| { - CedarPolicyCreateTypeError::EntityTypeName(entity_type.to_string(), err) - })?, + EntityTypeName::from_str(entity_type) + .map_err(|err| CreateCedarEntityError::EntityTypeName(entity_type.to_string(), err))?, EntityId::new(entity_id), ); @@ -87,23 +81,24 @@ pub(crate) fn build_entity_uid( /// Parsed result of entity type name and namespace. /// Analog to the internal cedar_policy type `InternalName` +#[derive(Debug)] pub(crate) struct EntityParsedTypeName<'a> { - pub typename: &'a str, + pub type_name: &'a str, pub namespace: &'a str, } impl<'a> EntityParsedTypeName<'a> { pub fn new(typename: &'a str, namespace: &'a str) -> Self { EntityParsedTypeName { - typename, + type_name: typename, namespace, } } pub fn full_type_name(&self) -> String { if self.namespace.is_empty() { - self.typename.to_string() + self.type_name.to_string() } else { - [self.namespace, self.typename].join(CEDAR_POLICY_SEPARATOR) + [self.namespace, self.type_name].join(CEDAR_POLICY_SEPARATOR) } } } @@ -121,19 +116,17 @@ pub fn parse_namespace_and_typename(raw_entity_type: &str) -> (&str, String) { fn fetch_schema_record<'a>( entity_info: &EntityParsedTypeName, schema: &'a CedarSchemaJson, -) -> Result<&'a CedarSchemaEntityShape, CedarPolicyCreateTypeError> { +) -> Result<&'a CedarSchemaEntityShape, CreateCedarEntityError> { let entity_shape = schema - .entity_schema(entity_info.namespace, entity_info.typename) - .ok_or(CedarPolicyCreateTypeError::CouldNotFindEntity( - entity_info.typename.to_string(), + .entity_schema(entity_info.namespace, entity_info.type_name) + .ok_or(CreateCedarEntityError::CouldNotFindEntity( + entity_info.type_name.to_string(), ))?; - // just to check if the entity is a record to be sure - // if shape not empty if let Some(entity_record) = &entity_shape.shape { if !entity_record.is_record() { - return Err(CedarPolicyCreateTypeError::NotRecord( - entity_info.typename.to_string(), + return Err(CreateCedarEntityError::NotRecord( + entity_info.type_name.to_string(), )); }; } @@ -164,9 +157,9 @@ fn entity_meta_attributes( fn build_entity_attributes( schema: &CedarSchemaJson, parsed_typename: &EntityParsedTypeName, - data: &TokenPayload, + tkn_data: &TokenClaims, claim_mapping: &ClaimMappings, -) -> Result, CedarPolicyCreateTypeError> { +) -> Result, CreateCedarEntityError> { // fetch the schema entity shape from the json-schema. let schema_shape = fetch_schema_record(parsed_typename, schema)?; @@ -177,7 +170,7 @@ fn build_entity_attributes( let attr_name = attr.attribute_name; let cedar_exp_result = token_attribute_to_cedar_exp( &attr, - data, + tkn_data, parsed_typename, schema, claim_mapping, @@ -185,16 +178,14 @@ fn build_entity_attributes( match (cedar_exp_result, attr.is_required) { (Ok(cedar_exp), _) => Some(Ok((attr_name.to_string(), cedar_exp))), ( - Err(CedarPolicyCreateTypeError::GetTokenClaimValue( - GetTokenClaimValue::KeyNotFound(_), - )), + Err(CreateCedarEntityError::MissingClaim(_)), false, // when the attribute is not required and not found in token data we skip it ) => None, (Err(err), _) => Some(Err(err)), } }) - .collect::, CedarPolicyCreateTypeError>>()?; + .collect::, CreateCedarEntityError>>()?; Ok(HashMap::from_iter(attr_vec)) } else { Ok(HashMap::new()) @@ -206,15 +197,15 @@ pub fn create_entity( entity_uid: EntityUid, parsed_typename: &EntityParsedTypeName, schema: &CedarSchemaJson, - data: &TokenPayload, + tkn_data: &TokenClaims, parents: HashSet, claim_mapping: &ClaimMappings, -) -> Result { - let attrs = build_entity_attributes(schema, parsed_typename, data, claim_mapping)?; +) -> Result { + let attrs = build_entity_attributes(schema, parsed_typename, tkn_data, claim_mapping)?; let entity_uid_string = entity_uid.to_string(); cedar_policy::Entity::new(entity_uid, attrs, parents) - .map_err(|err| CedarPolicyCreateTypeError::CreateEntity(entity_uid_string, err)) + .map_err(|err| CreateCedarEntityError::CreateEntity(entity_uid_string, err)) } /// Meta information about an attribute for cedar policy. @@ -231,14 +222,19 @@ pub struct EntityAttributeMetadata<'a> { /// Get the cedar policy expression value for a given type. fn token_attribute_to_cedar_exp( attribute_metadata: &EntityAttributeMetadata, - claim: &TokenPayload, + tkn_data: &TokenClaims, entity_typename: &EntityParsedTypeName, schema: &CedarSchemaJson, claim_mapping: &ClaimMappings, -) -> Result { +) -> Result { let token_claim_key = attribute_metadata.attribute_name; - let token_claim_value = claim.get_payload(token_claim_key)?; + let token_claim_value = + tkn_data + .get_claim(token_claim_key) + .ok_or(CreateCedarEntityError::MissingClaim( + token_claim_key.to_string(), + ))?; get_expression( &attribute_metadata.cedar_policy_type, @@ -252,46 +248,40 @@ fn token_attribute_to_cedar_exp( /// Build [`RestrictedExpression`] based on input parameters. fn get_expression( cedar_type: &CedarType, - token_claim_value: &Payload, + claim: &TokenClaim, base_entity_typename: &EntityParsedTypeName, schema: &CedarSchemaJson, claim_mapping: &ClaimMappings, -) -> Result { +) -> Result { match cedar_type { - CedarType::String => Ok(token_claim_value.as_str()?.to_string().to_expression()), - CedarType::Long => Ok(token_claim_value.as_i64()?.to_expression()), - CedarType::Boolean => Ok(token_claim_value.as_bool()?.to_expression()), + CedarType::String => Ok(claim.as_str()?.to_string().to_expression()), + CedarType::Long => Ok(claim.as_i64()?.to_expression()), + CedarType::Boolean => Ok(claim.as_bool()?.to_expression()), CedarType::TypeName(cedar_typename) => { match schema.find_type(cedar_typename, base_entity_typename.namespace) { Some(SchemaDefinedType::Entity(_)) => { - get_entity_expression(cedar_typename, base_entity_typename, token_claim_value) + get_entity_expression(cedar_typename, base_entity_typename, claim) }, Some(SchemaDefinedType::CommonType(record)) => { let record_typename = EntityParsedTypeName::new(cedar_typename, base_entity_typename.namespace); - get_record_expression( - record, - &record_typename, - token_claim_value, - schema, - claim_mapping, - ) - .map_err(|err| { - CedarPolicyCreateTypeError::CreateRecord( - record_typename.full_type_name(), - Box::new(err), - ) - }) + get_record_expression(record, &record_typename, claim, schema, claim_mapping) + .map_err(|err| { + CreateCedarEntityError::CreateRecord( + record_typename.full_type_name(), + Box::new(err), + ) + }) }, - None => Err(CedarPolicyCreateTypeError::FindType( + None => Err(CreateCedarEntityError::FindType( EntityParsedTypeName::new(cedar_typename, base_entity_typename.namespace) .full_type_name(), )), } }, CedarType::Set(cedar_type) => { - let vec_of_expression = token_claim_value + let vec_of_expression = claim .as_array()? .into_iter() .map(|payload| { @@ -314,8 +304,8 @@ fn get_expression( fn get_entity_expression( cedar_typename: &str, base_entity_typename: &EntityParsedTypeName<'_>, - token_claim_value: &Payload<'_>, -) -> Result { + token_claim: &TokenClaim, +) -> Result { let restricted_expression = { let entity_full_type_name = EntityParsedTypeName::new(cedar_typename, base_entity_typename.namespace) @@ -323,9 +313,9 @@ fn get_entity_expression( let uid = EntityUid::from_type_name_and_id( EntityTypeName::from_str(entity_full_type_name.as_str()).map_err(|err| { - CedarPolicyCreateTypeError::EntityTypeName(entity_full_type_name.to_string(), err) + CreateCedarEntityError::EntityTypeName(entity_full_type_name.to_string(), err) })?, - EntityId::new(token_claim_value.as_str()?), + EntityId::new(token_claim.as_str()?), ); RestrictedExpression::new_entity_uid(uid) }; @@ -333,37 +323,40 @@ fn get_entity_expression( } /// Build [`RestrictedExpression`] based on token_claim_value. -/// It tries to find mapping and apply it to `token_claim_value` json value. +/// It tries to find mapping and apply it to `token_claim` json value. fn get_record_expression( record: &CedarSchemaRecord, cedar_record_type: &EntityParsedTypeName<'_>, - token_claim_value: &Payload<'_>, + token_claim: &TokenClaim, schema: &CedarSchemaJson, claim_mapping: &ClaimMappings, -) -> Result { - // map json value of `token_claim_value` to TokenPayload object (HashMap) - let mapped_claim: TokenPayload = match claim_mapping.get_mapping( - token_claim_value.get_key(), - &cedar_record_type.full_type_name(), - ) { - Some(m) => m.apply_mapping(token_claim_value.get_value()).into(), - // if we do not have mapping, and value is json object, return TokenPayload based on it. - // if value is not json object, return empty value - None => { - if let Some(map) = token_claim_value.get_value().as_object() { - TokenPayload::from_json_map(map.to_owned()) - } else { - TokenPayload::default() - } - }, - }; +) -> Result { + // map json value of `token_claim` to TokenPayload object (HashMap) + let mapped_claim: TokenClaims = + match claim_mapping.get_mapping(token_claim.key(), &cedar_record_type.full_type_name()) { + Some(m) => m.apply_mapping(token_claim.value()).into(), + // if we do not have mapping, and value is json object, return TokenPayload based on it. + // if value is not json object, return empty value + None => { + if let Some(map) = token_claim.value().as_object() { + TokenClaims::from_json_map(map.to_owned()) + } else { + TokenClaims::default() + } + }, + }; let mut record_restricted_exps = Vec::new(); for (attribute_key, entity_attribute) in record.attributes.iter() { let attribute_type = entity_attribute.get_type()?; - let mapped_claim_value = mapped_claim.get_payload(attribute_key)?; + let mapped_claim_value = + mapped_claim + .get_claim(attribute_key) + .ok_or(CreateCedarEntityError::MissingClaim( + attribute_key.to_string(), + ))?; let exp = get_expression( &attribute_type, @@ -373,7 +366,7 @@ fn get_record_expression( claim_mapping, ) .map_err(|err| { - CedarPolicyCreateTypeError::BuildAttribute( + CreateCedarEntityError::BuildAttribute( cedar_record_type.full_type_name(), attribute_key.to_string(), Box::new(err), @@ -385,13 +378,13 @@ fn get_record_expression( let restricted_expression = RestrictedExpression::new_record(record_restricted_exps.into_iter()) - .map_err(CedarPolicyCreateTypeError::CreateRecordFromIter)?; + .map_err(CreateCedarEntityError::CreateRecordFromIter)?; Ok(restricted_expression) } /// Describe errors on creating entity #[derive(thiserror::Error, Debug)] -pub enum CedarPolicyCreateTypeError { +pub enum CreateCedarEntityError { /// Could not parse entity type #[error("could not parse entity type name: {0}, error: {1}")] EntityTypeName(String, cedar_policy::ParseErrors), @@ -410,7 +403,7 @@ pub enum CedarPolicyCreateTypeError { /// Could not get attribute value from payload #[error("could not get attribute value from payload: {0}")] - GetTokenClaimValue(#[from] GetTokenClaimValue), + GetTokenClaim(#[from] TokenClaimTypeError), /// Could not retrieve attribute from cedar-policy schema #[error("could not retrieve attribute from cedar-policy schema: {0}")] @@ -418,11 +411,11 @@ pub enum CedarPolicyCreateTypeError { /// Error on cedar-policy type attribute #[error("err build cedar-policy type: {0}, mapped JWT attribute `{1}`: {2}")] - BuildAttribute(String, String, Box), + BuildAttribute(String, String, Box), /// Error on creating `cedar-policy` record, in schema it is named as type #[error("could not create `cedar-policy` record/type {0} : {1}")] - CreateRecord(String, Box), + CreateRecord(String, Box), /// Wrapped error on [`RestrictedExpression::new_record`] // this error probably newer happen @@ -436,4 +429,12 @@ pub enum CedarPolicyCreateTypeError { /// Error when using the transaction token. Its usage is currently not implemented. #[error("transaction token not implemented")] TransactionToken, + + /// Indicates that the creation of an Entity failed due to the absence of available tokens. + #[error("no available token to build the entity from")] + UnavailableToken, + + /// Missing claim + #[error("{0} Entity creation failed: no available token to build the entity from")] + MissingClaim(String), } diff --git a/jans-cedarling/cedarling/src/authz/entities/mod.rs b/jans-cedarling/cedarling/src/authz/entities/mod.rs index 810ee224af6..cad03f4e371 100644 --- a/jans-cedarling/cedarling/src/authz/entities/mod.rs +++ b/jans-cedarling/cedarling/src/authz/entities/mod.rs @@ -1,165 +1,148 @@ -/* - * 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. - */ +// 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. //! Module for creating cedar-policy entities mod create; mod trait_as_expression; +mod user; +mod workload; #[cfg(test)] mod test_create; use std::collections::HashSet; -use crate::common::cedar_schema::CedarSchemaJson; - -use crate::authz::token_data::{AccessTokenData, IdTokenData, UserInfoTokenData}; -use crate::common::policy_store::{ - AccessTokenEntityMetadata, ClaimMappings, PolicyStore, RoleMapping, TokenKind, TrustedIssuer, +use cedar_policy::{Entity, EntityUid}; +use create::{ + build_entity_uid, create_entity, parse_namespace_and_typename, EntityMetadata, + EntityParsedTypeName, }; -use crate::jwt; -use cedar_policy::EntityUid; -pub use create::CedarPolicyCreateTypeError; -use create::EntityParsedTypeName; -pub use create::CEDAR_POLICY_SEPARATOR; -use create::{build_entity_uid, create_entity, parse_namespace_and_typename, EntityMetadata}; +pub use create::{CreateCedarEntityError, CEDAR_POLICY_SEPARATOR}; +pub use user::*; +pub use workload::*; use super::request::ResourceData; -use super::token_data::TokenPayload; - -pub(crate) type ProcessTokensResult<'a> = - jwt::ProcessTokensResult<'a, AccessTokenData, IdTokenData, UserInfoTokenData>; - -/// Create workload entity -pub fn create_workload( - entity_mapping: Option<&str>, - policy_store: &PolicyStore, - data: &AccessTokenData, - meta: &AccessTokenEntityMetadata, -) -> Result { - let schema = &policy_store.schema.json; - let namespace = policy_store.namespace(); - let claim_mapping = &meta.entity_metadata.claim_mapping; - - let workload_entity_meta = EntityMetadata::new( - EntityParsedTypeName { - typename: entity_mapping.unwrap_or("Workload"), - namespace, - }, - "client_id", - ); - - workload_entity_meta.create_entity(schema, data, HashSet::new(), claim_mapping) +use super::AuthorizeError; +use crate::common::cedar_schema::CedarSchemaJson; +use crate::common::policy_store::{ClaimMappings, PolicyStore, TokenKind}; +use crate::jwt::Token; +use crate::AuthorizationConfig; + +const DEFAULT_ACCESS_TKN_ENTITY_TYPE_NAME: &str = "Access_token"; +const DEFAULT_ID_TKN_ENTITY_TYPE_NAME: &str = "id_token"; +const DEFAULT_USERINFO_TKN_ENTITY_TYPE_NAME: &str = "Userinfo_token"; +const DEFAULT_TKN_PRINCIPAL_IDENTIFIER: &str = "jti"; + +pub struct DecodedTokens<'a> { + pub access_token: Option>, + pub id_token: Option>, + pub userinfo_token: Option>, } -/// Create access_token entity -pub fn create_access_token( - entity_mapping: Option<&str>, - policy_store: &PolicyStore, - data: &AccessTokenData, - meta: &AccessTokenEntityMetadata, -) -> Result { - let schema = &policy_store.schema.json; - let namespace = policy_store.namespace(); - let claim_mapping = &meta.entity_metadata.claim_mapping; - - let access_entity_meta = EntityMetadata::new( - EntityParsedTypeName { - typename: entity_mapping.unwrap_or("Access_token"), - namespace, - }, - meta.principal_identifier.as_deref().unwrap_or("jti"), - ); +impl DecodedTokens<'_> { + pub fn iter(&self) -> impl Iterator { + [ + self.access_token.as_ref(), + self.id_token.as_ref(), + self.userinfo_token.as_ref(), + ] + .into_iter() + .flatten() + } +} - access_entity_meta.create_entity(schema, data, HashSet::new(), claim_mapping) +pub struct TokenEntities { + pub access: Option, + pub id: Option, + pub userinfo: Option, } -/// Create id_token entity -pub fn create_id_token_entity( - entity_mapping: Option<&str>, +pub fn create_token_entities( + conf: &AuthorizationConfig, policy_store: &PolicyStore, - data: &IdTokenData, - claim_mapping: &ClaimMappings, -) -> Result { + tokens: &DecodedTokens, +) -> Result { let schema = &policy_store.schema.json; let namespace = policy_store.namespace(); - EntityMetadata::new( - EntityParsedTypeName { - typename: entity_mapping.unwrap_or("id_token"), - namespace, - }, - "jti", - ) - .create_entity(schema, data, HashSet::new(), claim_mapping) -} - -/// Create user entity -pub fn create_user_entity( - entity_mapping: Option<&str>, - policy_store: &PolicyStore, - tokens: &ProcessTokensResult, - parents: HashSet, - trusted_issuer: &TrustedIssuer, -) -> Result { - let user_id_mapping = trusted_issuer.get_user_id_mapping().unwrap_or_default(); + // create access token entity + let access = if let Some(token) = tokens.access_token.as_ref() { + let type_name = conf + .mapping_access_token + .as_deref() + .unwrap_or(DEFAULT_ACCESS_TKN_ENTITY_TYPE_NAME); + Some( + create_token_entity(token, schema, namespace, type_name) + .map_err(AuthorizeError::CreateAccessTokenEntity)?, + ) + } else { + None + }; - let schema: &CedarSchemaJson = &policy_store.schema.json; - let namespace = policy_store.namespace(); + // create id token entity + let id = if let Some(token) = tokens.id_token.as_ref() { + let type_name = conf + .mapping_id_token + .as_deref() + .unwrap_or(DEFAULT_ID_TKN_ENTITY_TYPE_NAME); + Some( + create_token_entity(token, schema, namespace, type_name) + .map_err(AuthorizeError::CreateIdTokenEntity)?, + ) + } else { + None + }; - // payload and claim mapping for getting user ID - let (payload, claim_mapping): (&TokenPayload, &ClaimMappings) = match user_id_mapping.kind { - TokenKind::Access => ( - &tokens.access_token, - &trusted_issuer.access_tokens.entity_metadata.claim_mapping, - ), - TokenKind::Id => (&tokens.id_token, &trusted_issuer.id_tokens.claim_mapping), - TokenKind::Userinfo => ( - &tokens.userinfo_token, - &trusted_issuer.userinfo_tokens.claim_mapping, - ), - TokenKind::Transaction => return Err(CedarPolicyCreateTypeError::TransactionToken), + // create userinfo token entity + let userinfo = if let Some(token) = tokens.userinfo_token.as_ref() { + let type_name = conf + .mapping_userinfo_token + .as_deref() + .unwrap_or(DEFAULT_USERINFO_TKN_ENTITY_TYPE_NAME); + Some( + create_token_entity(token, schema, namespace, type_name) + .map_err(AuthorizeError::CreateUserinfoTokenEntity)?, + ) + } else { + None }; - EntityMetadata::new( - EntityParsedTypeName { - typename: entity_mapping.unwrap_or("User"), - namespace, - }, - user_id_mapping.mapping_field, - ) - .create_entity(schema, payload, parents, claim_mapping) + Ok(TokenEntities { + access, + id, + userinfo, + }) } -/// Create `Userinfo_token` entity -pub fn create_userinfo_token_entity( - entity_mapping: Option<&str>, - policy_store: &PolicyStore, - data: &UserInfoTokenData, - claim_mapping: &ClaimMappings, -) -> Result { - let schema = &policy_store.schema.json; - let namespace = policy_store.namespace(); - - EntityMetadata::new( +fn create_token_entity( + token: &Token, + schema: &CedarSchemaJson, + namespace: &str, + type_name: &str, +) -> Result { + let claim_mapping = token.claim_mapping(); + let tkn_metadata = EntityMetadata::new( EntityParsedTypeName { - typename: entity_mapping.unwrap_or("Userinfo_token"), + type_name, namespace, }, - "jti", - ) - .create_entity(schema, data, HashSet::new(), claim_mapping) + token + .metadata() + .principal_identifier + .as_deref() + .unwrap_or(DEFAULT_TKN_PRINCIPAL_IDENTIFIER), + ); + tkn_metadata.create_entity(schema, token, HashSet::new(), claim_mapping) } /// Describe errors on creating resource entity #[derive(thiserror::Error, Debug)] pub enum ResourceEntityError { #[error("could not create resource entity: {0}")] - Create(#[from] CedarPolicyCreateTypeError), + Create(#[from] CreateCedarEntityError), } /// Create entity from [`ResourceData`] @@ -168,7 +151,7 @@ pub fn create_resource_entity( schema: &CedarSchemaJson, ) -> Result { let entity_uid = resource.entity_uid().map_err(|err| { - CedarPolicyCreateTypeError::EntityTypeName(resource.resource_type.clone(), err) + CreateCedarEntityError::EntityTypeName(resource.resource_type.clone(), err) })?; let (typename, namespace) = parse_namespace_and_typename(&resource.resource_type); @@ -189,28 +172,24 @@ pub fn create_resource_entity( pub enum RoleEntityError { #[error("could not create Jans::Role entity from {token_kind} token: {error}")] Create { - error: CedarPolicyCreateTypeError, + error: CreateCedarEntityError, token_kind: TokenKind, }, + + /// Indicates that the creation of the Role Entity failed due to the absence of available tokens. + #[error("Role Entity creation failed: no available token to build the entity from")] + UnavailableToken, } /// Create `Role` entites from based on `TrustedIssuer` role mapping for each token or default value of `RoleMapping` pub fn create_role_entities( policy_store: &PolicyStore, - tokens: &ProcessTokensResult, - trusted_issuer: &TrustedIssuer, + tokens: &DecodedTokens, ) -> Result, RoleEntityError> { let mut role_entities = Vec::new(); - // Iterate via role mappings for each token or default `RoleMapping` value - let role_mappings = trusted_issuer - .get_role_mapping() - .unwrap_or(vec![RoleMapping::default()]); - - for role_mapping in role_mappings { - let mut entities = - extract_roles_from_tokens(policy_store, role_mapping, tokens, trusted_issuer)?; - // Moves all the elements to `role_entities` + for token in tokens.iter() { + let mut entities = extract_roles_from_token(policy_store, token)?; role_entities.append(&mut entities); } @@ -218,43 +197,15 @@ pub fn create_role_entities( } /// Extract `Role` entites based on single `RoleMapping` -fn extract_roles_from_tokens( +fn extract_roles_from_token( policy_store: &PolicyStore, - role_mapping: RoleMapping, - tokens: &ProcessTokensResult, - trusted_issuer: &TrustedIssuer, + token: &Token, ) -> Result, RoleEntityError> { let parsed_typename = EntityParsedTypeName::new("Role", policy_store.namespace()); let role_entity_type = parsed_typename.full_type_name(); - // map payload from token - let token_data: &'_ TokenPayload = match role_mapping.kind { - TokenKind::Access => &tokens.access_token, - TokenKind::Id => &tokens.id_token, - TokenKind::Userinfo => &tokens.userinfo_token, - TokenKind::Transaction => { - return Err(RoleEntityError::Create { - error: CedarPolicyCreateTypeError::TransactionToken, - token_kind: TokenKind::Transaction, - }); - }, - }; - - // we don't really need mapping for `role` but if user specify custom role with - let token_mapping = match role_mapping.kind { - TokenKind::Access => &trusted_issuer.access_tokens.entity_metadata.claim_mapping, - TokenKind::Id => &trusted_issuer.id_tokens.claim_mapping, - TokenKind::Userinfo => &trusted_issuer.userinfo_tokens.claim_mapping, - TokenKind::Transaction => { - return Err(RoleEntityError::Create { - error: CedarPolicyCreateTypeError::TransactionToken, - token_kind: TokenKind::Transaction, - }); - }, - }; - // get payload of role id in JWT token data - let Ok(payload) = token_data.get_payload(role_mapping.mapping_field) else { + let Some(payload) = token.get_claim(token.role_mapping()) else { // if key not found we return empty vector return Ok(Vec::new()); }; @@ -266,7 +217,7 @@ fn extract_roles_from_tokens( build_entity_uid(role_entity_type.as_str(), payload_str).map_err(|err| { RoleEntityError::Create { error: err, - token_kind: role_mapping.kind, + token_kind: token.kind, } })?; vec![entity_uid] @@ -283,13 +234,13 @@ fn extract_roles_from_tokens( // get each element of array as `str` payload_el.as_str().map_err(|err| RoleEntityError::Create { error: err.into(), - token_kind: role_mapping.kind, + token_kind: token.kind, }) // build entity uid .and_then(|name| build_entity_uid(role_entity_type.as_str(), name) .map_err(|err| RoleEntityError::Create { error: err, - token_kind: role_mapping.kind, + token_kind: token.kind, })) }) .collect::, _>>()? @@ -298,7 +249,7 @@ fn extract_roles_from_tokens( // Handle the case where the payload is neither a string nor an array return Err(RoleEntityError::Create { error: err.into(), - token_kind: role_mapping.kind, + token_kind: token.kind, }); }, } @@ -314,13 +265,13 @@ fn extract_roles_from_tokens( entity_uid, &parsed_typename, schema, - token_data, + token.claims(), HashSet::new(), - token_mapping, + token.claim_mapping(), ) .map_err(|err| RoleEntityError::Create { error: err, - token_kind: role_mapping.kind, + token_kind: token.kind, }) }) .collect::, _>>() diff --git a/jans-cedarling/cedarling/src/authz/entities/test_create.rs b/jans-cedarling/cedarling/src/authz/entities/test_create.rs index afc8118530d..b00c2c335aa 100644 --- a/jans-cedarling/cedarling/src/authz/entities/test_create.rs +++ b/jans-cedarling/cedarling/src/authz/entities/test_create.rs @@ -1,21 +1,17 @@ -/* - * 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. - */ +// 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. //! Testing the creating entities use std::collections::HashSet; -use test_utils::assert_eq; - -use crate::authz::token_data::{GetTokenClaimValue, TokenPayload}; -use crate::common::cedar_schema::CedarSchemaJson; +use test_utils::{assert_eq, SortedJson}; use super::create::*; -use test_utils::SortedJson; +use crate::common::cedar_schema::CedarSchemaJson; +use crate::jwt::{Token, TokenClaimTypeError, TokenClaims}; // test all successful cases // with empty namespace @@ -27,7 +23,7 @@ fn successful_scenario_empty_namespace() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -42,10 +38,15 @@ fn successful_scenario_empty_namespace() { "set_set_key": [["some_string"]] }); - let payload: TokenPayload = serde_json::from_value(json).unwrap(); + let payload: TokenClaims = serde_json::from_value(json).unwrap(); let entity = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect("entity should be created"); let entity_json = entity.to_json_value().expect("should serialize to json"); @@ -84,7 +85,7 @@ fn successful_scenario_not_empty_namespace() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "Jans", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -97,10 +98,15 @@ fn successful_scenario_not_empty_namespace() { "bool_key": true, }); - let payload: TokenPayload = serde_json::from_value(json).unwrap(); + let payload: TokenClaims = serde_json::from_value(json).unwrap(); let entity = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect("entity should be created"); let entity_json = entity.to_json_value().expect("should serialize to json"); @@ -136,7 +142,7 @@ fn get_token_claim_type_string_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -153,28 +159,31 @@ fn get_token_claim_type_string_error() { "set_set_key": [["some_string"]] }); - let payload: TokenPayload = serde_json::from_value(json.clone()).unwrap(); + let payload: TokenClaims = serde_json::from_value(json.clone()).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType { - key, - got_type, - .. + if let CreateCedarEntityError::GetTokenClaim(TokenClaimTypeError { + key, actual_type, .. }) = entity_creation_error { let json_attr_value = json.as_object().unwrap().get(test_key).unwrap(); - let origin_type = GetTokenClaimValue::json_value_type_name(json_attr_value); + let origin_type = TokenClaimTypeError::json_value_type_name(json_attr_value); assert!(key == test_key, "expected key: {test_key}, but got: {key}"); assert!( - got_type == origin_type, - "expected type: {origin_type}, but got: {got_type}" + actual_type == origin_type, + "expected type: {origin_type}, but got: {actual_type}" ); } else { - panic!("expected error type: CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType), but got: {entity_creation_error}"); + panic!("expected error type: CedarPolicyCreateTypeError::TokenClaimTypeError(GetTokenClaimError::KeyNotCorrectType), but got: {entity_creation_error}"); } } @@ -187,7 +196,7 @@ fn get_token_claim_type_long_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -204,28 +213,31 @@ fn get_token_claim_type_long_error() { "set_set_key": [["some_string"]] }); - let payload: TokenPayload = serde_json::from_value(json.clone()).unwrap(); + let payload: TokenClaims = serde_json::from_value(json.clone()).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType { - key, - got_type, - .. + if let CreateCedarEntityError::GetTokenClaim(TokenClaimTypeError { + key, actual_type, .. }) = entity_creation_error { let json_attr_value = json.as_object().unwrap().get(test_key).unwrap(); - let origin_type = GetTokenClaimValue::json_value_type_name(json_attr_value); + let origin_type = TokenClaimTypeError::json_value_type_name(json_attr_value); assert!(key == test_key, "expected key: {test_key}, but got: {key}"); assert!( - got_type == origin_type, - "expected type: {origin_type}, but got: {got_type}" + actual_type == origin_type, + "expected type: {origin_type}, but got: {actual_type}" ); } else { - panic!("expected error type: CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType), but got: {entity_creation_error}"); + panic!("expected error type: CedarPolicyCreateTypeError::TokenClaimTypeError(GetTokenClaimError::KeyNotCorrectType), but got: {entity_creation_error}"); } } @@ -238,7 +250,7 @@ fn get_token_claim_type_entity_uid_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -255,28 +267,31 @@ fn get_token_claim_type_entity_uid_error() { "set_set_key": [["some_string"]] }); - let payload: TokenPayload = serde_json::from_value(json.clone()).unwrap(); + let payload: TokenClaims = serde_json::from_value(json.clone()).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType { - key, - got_type, - .. + if let CreateCedarEntityError::GetTokenClaim(TokenClaimTypeError { + key, actual_type, .. }) = entity_creation_error { let json_attr_value = json.as_object().unwrap().get(test_key).unwrap(); - let origin_type = GetTokenClaimValue::json_value_type_name(json_attr_value); + let origin_type = TokenClaimTypeError::json_value_type_name(json_attr_value); assert!(key == test_key, "expected key: {test_key}, but got: {key}"); assert!( - got_type == origin_type, - "expected type: {origin_type}, but got: {got_type}" + actual_type == origin_type, + "expected type: {origin_type}, but got: {actual_type}" ); } else { - panic!("expected error type: CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType), but got: {entity_creation_error}"); + panic!("expected error type: CedarPolicyCreateTypeError::TokenClaimTypeError(GetTokenClaimError::KeyNotCorrectType), but got: {entity_creation_error}"); } } @@ -289,7 +304,7 @@ fn get_token_claim_type_boolean_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -306,31 +321,37 @@ fn get_token_claim_type_boolean_error() { "set_set_key": [["some_string"]] }); - let payload: TokenPayload = serde_json::from_value(json.clone()).unwrap(); + let payload: TokenClaims = serde_json::from_value(json.clone()).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType { + if let CreateCedarEntityError::GetTokenClaim(TokenClaimTypeError { key, - got_type, + actual_type, expected_type, }) = entity_creation_error { let json_attr_value = json.as_object().unwrap().get(test_key).unwrap(); - let origin_type = GetTokenClaimValue::json_value_type_name(json_attr_value); + let origin_type = TokenClaimTypeError::json_value_type_name(json_attr_value); assert!( key == test_key, "expected key: {test_key}, but got: {key} with schema expected_type: {expected_type}" ); assert!( - got_type == origin_type, - "expected type: {origin_type}, but got: {got_type} with schema expected_type: {expected_type}" + actual_type == origin_type, + "expected type: {origin_type}, but got: {actual_type} with schema expected_type: \ + {expected_type}" ); } else { - panic!("expected error type: CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType), but got: {entity_creation_error}"); + panic!("expected error type: CedarPolicyCreateTypeError::TokenClaimTypeError(GetTokenClaimError::KeyNotCorrectType), but got: {entity_creation_error}"); } } @@ -343,7 +364,7 @@ fn get_token_claim_type_set_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -360,31 +381,37 @@ fn get_token_claim_type_set_error() { "set_set_key": [["some_string"]] }); - let payload: TokenPayload = serde_json::from_value(json.clone()).unwrap(); + let payload: TokenClaims = serde_json::from_value(json.clone()).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType { + if let CreateCedarEntityError::GetTokenClaim(TokenClaimTypeError { key, - got_type, + actual_type, expected_type, }) = entity_creation_error { let json_attr_value = json.as_object().unwrap().get(test_key).unwrap(); - let origin_type = GetTokenClaimValue::json_value_type_name(json_attr_value); + let origin_type = TokenClaimTypeError::json_value_type_name(json_attr_value); assert!( key == test_key, "expected key: {test_key}, but got: {key} with schema expected_type: {expected_type}" ); assert!( - got_type == origin_type, - "expected type: {origin_type}, but got: {got_type} with schema expected_type: {expected_type}" + actual_type == origin_type, + "expected type: {origin_type}, but got: {actual_type} with schema expected_type: \ + {expected_type}" ); } else { - panic!("expected error type: CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType), but got: {entity_creation_error}"); + panic!("expected error type: CedarPolicyCreateTypeError::TokenClaimTypeError(GetTokenClaimError::KeyNotCorrectType), but got: {entity_creation_error}"); } } @@ -397,7 +424,7 @@ fn get_token_claim_type_set_of_set_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -413,21 +440,26 @@ fn get_token_claim_type_set_of_set_error() { "set_set_key": ["some_string"] }); - let payload: TokenPayload = serde_json::from_value(json.clone()).unwrap(); + let payload: TokenClaims = serde_json::from_value(json.clone()).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType { + if let CreateCedarEntityError::GetTokenClaim(TokenClaimTypeError { key, - got_type, + actual_type, expected_type, }) = entity_creation_error { let json_attr_value = json.as_object().unwrap().get("set_set_key").unwrap(); let origin_type = - GetTokenClaimValue::json_value_type_name(&json_attr_value.as_array().unwrap()[0]); + TokenClaimTypeError::json_value_type_name(&json_attr_value.as_array().unwrap()[0]); // key set_set_key and zero element in array let test_key = "set_set_key[0]"; @@ -437,11 +469,12 @@ fn get_token_claim_type_set_of_set_error() { "expected key: {test_key}, but got: {key} with schema expected_type: {expected_type}" ); assert!( - got_type == origin_type, - "expected type: {origin_type}, but got: {got_type} with schema expected_type: {expected_type}" + actual_type == origin_type, + "expected type: {origin_type}, but got: {actual_type} with schema expected_type: \ + {expected_type}" ); } else { - panic!("expected error type: CedarPolicyCreateTypeError::GetTokenClaimValue(GetTokenClaimValue::KeyNotCorrectType), but got: {entity_creation_error}"); + panic!("expected error type: CedarPolicyCreateTypeError::TokenClaimTypeError(GetTokenClaimError::KeyNotCorrectType), but got: {entity_creation_error}"); } } @@ -456,7 +489,7 @@ fn get_token_claim_cedar_typename_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: &namespace, - typename, + type_name: typename, }, "test_id_key", ); @@ -469,13 +502,18 @@ fn get_token_claim_cedar_typename_error() { "bool_key": true, }); - let payload: TokenPayload = serde_json::from_value(json).unwrap(); + let payload: TokenClaims = serde_json::from_value(json).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::EntityTypeName(typename, _) = &entity_creation_error { + if let CreateCedarEntityError::EntityTypeName(typename, _) = &entity_creation_error { assert_eq!("Jans:::Test", typename); } else { panic!( @@ -502,7 +540,7 @@ fn get_token_claim_cedar_typename_in_attr_error() { let metadata = EntityMetadata::new( EntityParsedTypeName { namespace: "Jans", - typename: "Test", + type_name: "Test", }, "test_id_key", ); @@ -515,13 +553,18 @@ fn get_token_claim_cedar_typename_in_attr_error() { "bool_key": true, }); - let payload: TokenPayload = serde_json::from_value(json).unwrap(); + let payload: TokenClaims = serde_json::from_value(json).unwrap(); let entity_creation_error = metadata - .create_entity(&schema, &payload, HashSet::new(), &Default::default()) + .create_entity( + &schema, + &Token::new_id(payload, None), + HashSet::new(), + &Default::default(), + ) .expect_err("entity creating should throw error"); - if let CedarPolicyCreateTypeError::FindType(typename) = &entity_creation_error { + if let CreateCedarEntityError::FindType(typename) = &entity_creation_error { assert_eq!("Jans:::Test2", typename); } else { panic!( diff --git a/jans-cedarling/cedarling/src/authz/entities/trait_as_expression.rs b/jans-cedarling/cedarling/src/authz/entities/trait_as_expression.rs index 8d04bdd231e..cd66476a33a 100644 --- a/jans-cedarling/cedarling/src/authz/entities/trait_as_expression.rs +++ b/jans-cedarling/cedarling/src/authz/entities/trait_as_expression.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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 cedar_policy::RestrictedExpression; diff --git a/jans-cedarling/cedarling/src/authz/entities/user.rs b/jans-cedarling/cedarling/src/authz/entities/user.rs new file mode 100644 index 00000000000..da43a4f6480 --- /dev/null +++ b/jans-cedarling/cedarling/src/authz/entities/user.rs @@ -0,0 +1,252 @@ +// 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::HashSet; +use std::fmt; + +use cedar_policy::EntityUid; + +use super::{CreateCedarEntityError, DecodedTokens, EntityMetadata, EntityParsedTypeName}; +use crate::common::cedar_schema::CedarSchemaJson; +use crate::common::policy_store::{PolicyStore, TokenKind}; +use crate::jwt::Token; + +/// Create user entity +pub fn create_user_entity( + entity_mapping: Option<&str>, + policy_store: &PolicyStore, + tokens: &DecodedTokens, + parents: HashSet, +) -> Result { + let schema: &CedarSchemaJson = &policy_store.schema.json; + let namespace = policy_store.namespace(); + let mut errors = Vec::new(); + + // helper closure to attempt entity creation from a token + let try_create_entity = |token_kind: TokenKind, token: Option<&Token>| { + if let Some(token) = token { + let claim_mapping = token.claim_mapping(); + let user_mapping = token.user_mapping(); + let entity_metadata = EntityMetadata::new( + EntityParsedTypeName { + type_name: entity_mapping.unwrap_or("User"), + namespace, + }, + user_mapping, + ); + entity_metadata + .create_entity(schema, token, parents.clone(), claim_mapping) + .map_err(|e| (token_kind, e)) + } else { + Err((token_kind, CreateCedarEntityError::UnavailableToken)) + } + }; + + // attempt entity creation for each token type that contains user info + for (token_kind, token) in [ + (TokenKind::Userinfo, tokens.userinfo_token.as_ref()), + (TokenKind::Id, tokens.id_token.as_ref()), + ] { + match try_create_entity(token_kind, token) { + Ok(entity) => return Ok(entity), + Err(e) => errors.push(e), + } + } + + Err(CreateUserEntityError { errors }) +} + +#[derive(Debug, thiserror::Error)] +pub struct CreateUserEntityError { + pub errors: Vec<(TokenKind, CreateCedarEntityError)>, +} + +impl fmt::Display for CreateUserEntityError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.errors.is_empty() { + writeln!( + f, + "Failed to create User Entity since no tokens were provided" + )?; + } else { + writeln!( + f, + "Failed to create User Entity due to the following errors:" + )?; + for (token_kind, error) in &self.errors { + writeln!(f, "- TokenKind {:?}: {}", token_kind, error)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::collections::{HashMap, HashSet}; + use std::path::Path; + + use cedar_policy::{Entity, RestrictedExpression}; + use serde_json::json; + use test_utils::assert_eq; + + use super::create_user_entity; + use crate::authz::entities::DecodedTokens; + use crate::common::policy_store::TokenKind; + use crate::init::policy_store::load_policy_store; + use crate::jwt::Token; + use crate::{CreateCedarEntityError, PolicyStoreConfig, PolicyStoreSource}; + + #[test] + fn can_create_from_id_token() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + let tokens = DecodedTokens { + access_token: None, + id_token: Some(Token::new_id( + HashMap::from([ + ("sub".to_string(), json!("user-1")), + ("country".to_string(), json!("US")), + ]) + .into(), + None, + )), + userinfo_token: None, + }; + let result = create_user_entity(entity_mapping, &policy_store, &tokens, HashSet::new()) + .expect("expected to create user entity"); + assert_eq!( + result, + Entity::new( + "Jans::User::\"user-1\"" + .parse() + .expect("expected to create user UID"), + HashMap::from([( + "country".to_string(), + RestrictedExpression::new_string("US".to_string()) + )]), + HashSet::new(), + ) + .expect("should create expected user entity") + ) + } + + #[test] + fn can_create_from_userinfo_token() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + let tokens = DecodedTokens { + id_token: None, + access_token: None, + userinfo_token: Some(Token::new_userinfo( + HashMap::from([ + ("sub".to_string(), json!("user-1")), + ("country".to_string(), json!("US")), + ]) + .into(), + None, + )), + }; + let result = create_user_entity(entity_mapping, &policy_store, &tokens, HashSet::new()) + .expect("expected to create user entity"); + assert_eq!( + result, + Entity::new( + "Jans::User::\"user-1\"" + .parse() + .expect("expected to create user UID"), + HashMap::from([( + "country".to_string(), + RestrictedExpression::new_string("US".to_string()) + )]), + HashSet::new(), + ) + .expect("should create expected user entity") + ) + } + + #[test] + fn errors_when_tokens_have_missing_claims() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + let tokens = DecodedTokens { + access_token: Some(Token::new_access(HashMap::from([]).into(), None)), + id_token: Some(Token::new_id(HashMap::from([]).into(), None)), + userinfo_token: Some(Token::new_userinfo(HashMap::from([]).into(), None)), + }; + + let result = create_user_entity(entity_mapping, &policy_store, &tokens, HashSet::new()) + .expect_err("expected to error while creating user entity"); + + for (tkn_kind, err) in result.errors.iter() { + match tkn_kind { + TokenKind::Access => assert!( + matches!(err, CreateCedarEntityError::MissingClaim(ref claim) if claim == "sub"), + "expected error MissingClaim(\"sub\")" + ), + TokenKind::Id => assert!( + matches!(err, CreateCedarEntityError::MissingClaim(ref claim) if claim == "sub"), + "expected error MissingClaim(\"sub\")" + ), + TokenKind::Userinfo => assert!( + matches!(err, CreateCedarEntityError::MissingClaim(ref claim) if claim == "sub"), + "expected error MissingClaim(\"sub\")" + ), + TokenKind::Transaction => (), // we don't support these yet + } + } + } + + #[test] + fn errors_when_tokens_unavailable() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + let tokens = DecodedTokens { + access_token: None, + id_token: None, + userinfo_token: None, + }; + + let result = create_user_entity(entity_mapping, &policy_store, &tokens, HashSet::new()) + .expect_err("expected to error while creating user entity"); + + assert_eq!(result.errors.len(), 2); + for (_tkn_kind, err) in result.errors.iter() { + assert!( + matches!(err, CreateCedarEntityError::UnavailableToken), + "expected error UnavailableToken, got: {:?}", + err + ); + } + } +} diff --git a/jans-cedarling/cedarling/src/authz/entities/workload.rs b/jans-cedarling/cedarling/src/authz/entities/workload.rs new file mode 100644 index 00000000000..bb325b7cf59 --- /dev/null +++ b/jans-cedarling/cedarling/src/authz/entities/workload.rs @@ -0,0 +1,245 @@ +// 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::HashSet; +use std::fmt; + +use super::{CreateCedarEntityError, DecodedTokens, EntityMetadata, EntityParsedTypeName}; +use crate::common::policy_store::{PolicyStore, TokenKind}; +use crate::jwt::Token; + +/// Create workload entity +pub fn create_workload_entity( + entity_mapping: Option<&str>, + policy_store: &PolicyStore, + tokens: &DecodedTokens, +) -> Result { + let namespace = policy_store.namespace(); + let schema = &policy_store.schema.json; + let mut errors = Vec::new(); + + // helper closure to attempt entity creation from a token + let try_create_entity = |token_kind: TokenKind, token: Option<&Token>, key: &str| { + if let Some(token) = token { + let claim_mapping = token.claim_mapping(); + let entity_metadta = EntityMetadata::new( + EntityParsedTypeName { + type_name: entity_mapping.unwrap_or("Workload"), + namespace, + }, + key, + ); + entity_metadta + .create_entity(schema, token, HashSet::new(), claim_mapping) + .map_err(|e| (token_kind, e)) + } else { + Err((token_kind, CreateCedarEntityError::UnavailableToken)) + } + }; + + // attempt entity creation for each token type + for (token_kind, token, key) in [ + (TokenKind::Access, tokens.access_token.as_ref(), "client_id"), + (TokenKind::Id, tokens.id_token.as_ref(), "aud"), + (TokenKind::Userinfo, tokens.userinfo_token.as_ref(), "aud"), + ] { + match try_create_entity(token_kind, token, key) { + Ok(entity) => return Ok(entity), + Err(e) => errors.push(e), + } + } + + Err(CreateWorkloadEntityError { errors }) +} + +#[derive(Debug, thiserror::Error)] +pub struct CreateWorkloadEntityError { + pub errors: Vec<(TokenKind, CreateCedarEntityError)>, +} + +impl fmt::Display for CreateWorkloadEntityError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.errors.is_empty() { + writeln!( + f, + "Failed to create Workload Entity since no tokens were provided" + )?; + } else { + writeln!( + f, + "Failed to create Workload Entity due to the following errors:" + )?; + for (token_kind, error) in &self.errors { + writeln!(f, "- TokenKind {:?}: {}", token_kind, error)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::collections::{HashMap, HashSet}; + use std::path::Path; + + use cedar_policy::{Entity, RestrictedExpression}; + use serde_json::json; + use test_utils::assert_eq; + + use super::create_workload_entity; + use crate::authz::entities::DecodedTokens; + use crate::common::policy_store::TokenKind; + use crate::init::policy_store::load_policy_store; + use crate::jwt::Token; + use crate::{CreateCedarEntityError, PolicyStoreConfig, PolicyStoreSource}; + + #[test] + fn can_create_from_id_token() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + let tokens = DecodedTokens { + access_token: None, + id_token: Some(Token::new_id( + HashMap::from([ + ("aud".to_string(), json!("workload-1")), + ("org_id".to_string(), json!("some-org-123")), + ]) + .into(), + None, + )), + userinfo_token: None, + }; + let result = create_workload_entity(entity_mapping, &policy_store, &tokens) + .expect("expected to create workload entity"); + assert_eq!( + result, + Entity::new( + "Jans::Workload::\"workload-1\"" + .parse() + .expect("expected to create workload UID"), + HashMap::from([( + "org_id".to_string(), + RestrictedExpression::new_string("some-org-123".to_string()) + )]), + HashSet::new(), + ) + .expect("should create expected workload entity") + ) + } + + #[test] + fn can_create_from_access_token() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + let tokens = DecodedTokens { + access_token: Some(Token::new_access( + HashMap::from([ + ("client_id".to_string(), json!("workload-1")), + ("org_id".to_string(), json!("some-org-123")), + ]) + .into(), + None, + )), + id_token: None, + userinfo_token: None, + }; + let result = create_workload_entity(entity_mapping, &policy_store, &tokens) + .expect("expected to create workload entity"); + assert_eq!( + result, + Entity::new( + "Jans::Workload::\"workload-1\"" + .parse() + .expect("expected to create workload UID"), + HashMap::from([( + "org_id".to_string(), + RestrictedExpression::new_string("some-org-123".to_string()) + )]), + HashSet::new(), + ) + .expect("should create expected workload entity") + ) + } + + #[test] + fn errors_when_tokens_have_missing_claims() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + let tokens = DecodedTokens { + access_token: Some(Token::new_access(HashMap::from([]).into(), None)), + id_token: Some(Token::new_id(HashMap::from([]).into(), None)), + userinfo_token: Some(Token::new_userinfo(HashMap::from([]).into(), None)), + }; + + let result = create_workload_entity(entity_mapping, &policy_store, &tokens) + .expect_err("expected to error while creating workload entity"); + + for (tkn_kind, err) in result.errors.iter() { + match tkn_kind { + TokenKind::Access => assert!( + matches!(err, CreateCedarEntityError::MissingClaim(ref claim) if claim == "client_id"), + "expected error MissingClaim(\"client_id\")" + ), + TokenKind::Id => assert!( + matches!(err, CreateCedarEntityError::MissingClaim(ref claim) if claim == "aud"), + "expected error MissingClaim(\"aud\")" + ), + _ => (), // we don't create workload tokens using other tokens + } + } + } + + #[test] + fn errors_when_tokens_unavailable() { + let entity_mapping = None; + let policy_store = load_policy_store(&PolicyStoreConfig { + source: PolicyStoreSource::FileYaml( + Path::new("../test_files/policy-store_ok_2.yaml").into(), + ), + }) + .expect("Should load policy store") + .store; + + // we can only create the workload from the access_token and id_token + let tokens = DecodedTokens { + access_token: None, + id_token: None, + userinfo_token: None, + }; + + let result = create_workload_entity(entity_mapping, &policy_store, &tokens) + .expect_err("expected to error while creating workload entity"); + + assert_eq!(result.errors.len(), 3); + for (_tkn_kind, err) in result.errors.iter() { + assert!( + matches!(err, CreateCedarEntityError::UnavailableToken), + "expected error UnavailableToken, got: {:?}", + err + ); + } + } +} diff --git a/jans-cedarling/cedarling/src/authz/merge_json.rs b/jans-cedarling/cedarling/src/authz/merge_json.rs index 2d764066819..c60a2d032a3 100644 --- a/jans-cedarling/cedarling/src/authz/merge_json.rs +++ b/jans-cedarling/cedarling/src/authz/merge_json.rs @@ -1,3 +1,8 @@ +// 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 serde_json::Value; #[derive(Debug, thiserror::Error)] @@ -22,9 +27,8 @@ pub fn merge_json_values(mut base: Value, other: Value) -> Result(&'a self, request: &'a Request) -> Result, AuthorizeError> { - // decode JWT tokens to structs AccessTokenData, IdTokenData, UserInfoTokenData using jwt service - Ok(self - .config - .jwt_service - .process_tokens::( - &request.access_token, - &request.id_token, - Some(&request.userinfo_token), - )?) + pub(crate) fn decode_tokens<'a>( + &'a self, + request: &'a Request, + ) -> Result, AuthorizeError> { + let access_token = request + .access_token + .as_ref() + .map(|tkn| self.config.jwt_service.process_token(TokenStr::Access(tkn))) + .transpose()?; + let id_token = request + .id_token + .as_ref() + .map(|tkn| self.config.jwt_service.process_token(TokenStr::Id(tkn))) + .transpose()?; + let userinfo_token = request + .userinfo_token + .as_ref() + .map(|tkn| { + self.config + .jwt_service + .process_token(TokenStr::Userinfo(tkn)) + }) + .transpose()?; + + Ok(DecodedTokens { + access_token, + id_token, + userinfo_token, + }) } /// Evaluate Authorization Request @@ -104,23 +118,18 @@ impl Authz { /// - evaluate if authorization is granted for *workload* pub fn authorize(&self, request: Request) -> Result { let start_time = Instant::now(); - let schema = &self.config.policy_store.schema; - let tokens = self.decode_tokens(&request)?; - // Parse action UID. let action = cedar_policy::EntityUid::from_str(request.action.as_str()) .map_err(AuthorizeError::Action)?; // Parse [`cedar_policy::Entity`]-s to [`AuthorizeEntitiesData`] that hold all entities (for usability). - let entities_data: AuthorizeEntitiesData = self.authorize_entities_data(&request,&tokens)?; + let entities_data: AuthorizeEntitiesData = self.build_entities(&request, &tokens)?; // Get entity UIDs what we will be used on authorize check - let principal_workload_uid = entities_data.workload_entity.uid(); - let resource_uid = entities_data.resource_entity.uid(); - let principal_user_entity_uid = entities_data.user_entity.uid(); + let resource_uid = entities_data.resource.uid(); let context = build_context( &self.config, @@ -130,47 +139,100 @@ impl Authz { &action, )?; + let workload_principal = entities_data.workload.as_ref().map(|e| e.uid()).to_owned(); + let user_principal = entities_data.user.as_ref().map(|e| e.uid()).to_owned(); + // Convert [`AuthorizeEntitiesData`] to [`cedar_policy::Entities`] structure, // hold all entities that will be used on authorize check. let entities = entities_data.entities(Some(&schema.schema))?; - // Check authorize where principal is `"Jans::Workload"` from cedar-policy schema. - let workload_result: Option = if self.config.authorization.use_workload_principal - { - match self.execute_authorize(ExecuteAuthorizeParameters { - entities: &entities, - principal: principal_workload_uid.clone(), - action: action.clone(), - resource: resource_uid.clone(), - context: context.clone(), - }) { - Ok(resp) => Some(resp), - Err(err) => return Err(AuthorizeError::CreateRequestWorkloadEntity(err)), - } - } else { - None - }; + let (workload_authz_result, workload_authz_info, workload_entity_claims) = + if let Some(workload) = workload_principal { + let principal = workload; + + let authz_result = self + .execute_authorize(ExecuteAuthorizeParameters { + entities: &entities, + principal: principal.clone(), + action: action.clone(), + resource: resource_uid.clone(), + context: context.clone(), + }) + .map_err(AuthorizeError::WorkloadRequestValidation)?; + + let authz_info = WorkloadAuthorizeInfo { + principal: principal.to_string(), + diagnostics: Diagnostics::new( + authz_result.diagnostics(), + &self.config.policy_store.policies, + ), + decision: authz_result.decision().into(), + }; + + let workload_entity_claims = get_entity_claims( + self.config + .authorization + .decision_log_workload_claims + .as_slice(), + &entities, + principal, + ); + + ( + Some(authz_result), + Some(authz_info), + Some(workload_entity_claims), + ) + } else { + (None, None, None) + }; // Check authorize where principal is `"Jans::User"` from cedar-policy schema. - let person_result: Option = if self.config.authorization.use_user_principal { - match self.execute_authorize(ExecuteAuthorizeParameters { - entities: &entities, - principal: principal_user_entity_uid.clone(), - action: action.clone(), - resource: resource_uid.clone(), - context: context.clone(), - }) { - Ok(resp) => Some(resp), - Err(err) => return Err(AuthorizeError::CreateRequestUserEntity(err)), - } - } else { - None - }; + let (user_authz_result, user_authz_info, user_entity_claims) = + if let Some(user) = user_principal { + let principal = user; + + let authz_result = self + .execute_authorize(ExecuteAuthorizeParameters { + entities: &entities, + principal: principal.clone(), + action: action.clone(), + resource: resource_uid.clone(), + context: context.clone(), + }) + .map_err(AuthorizeError::UserRequestValidation)?; + + let authz_info = UserAuthorizeInfo { + principal: principal.to_string(), + diagnostics: Diagnostics::new( + authz_result.diagnostics(), + &self.config.policy_store.policies, + ), + decision: authz_result.decision().into(), + }; + + let user_entity_claims = get_entity_claims( + self.config + .authorization + .decision_log_user_claims + .as_slice(), + &entities, + principal, + ); + + ( + Some(authz_result), + Some(authz_info), + Some(user_entity_claims), + ) + } else { + (None, None, None) + }; let result = AuthorizeResult::new( self.config.authorization.user_workload_operator, - workload_result, - person_result, + workload_authz_result, + user_authz_result, ); // measure time how long request executes @@ -194,55 +256,60 @@ impl Authz { self.config.pdp_id, Some(self.config.application_name.clone()), LogType::System, - ).set_level(LogLevel::DEBUG) + ) + .set_level(LogLevel::DEBUG) .set_auth_info(AuthorizationLogInfo { action: request.action.clone(), context: request.context.clone(), resource: resource_uid.to_string(), entities: entities_json, - - person_authorize_info: result.person.as_ref().map(|response| PersonAuthorizeInfo { - person_principal: principal_user_entity_uid.to_string(), - person_diagnostics: Diagnostics::new( - response.diagnostics(), - &self.config.policy_store.policies, - ), - person_decision: response.decision().into(), - }), - - workload_authorize_info: result.workload.as_ref().map(|response| { - WorkloadAuthorizeInfo { - workload_principal: principal_workload_uid.to_string(), - workload_diagnostics: Diagnostics::new( - response.diagnostics(), - &self.config.policy_store.policies, - ), - workload_decision: response.decision().into(), - } - }), - + person_authorize_info: user_authz_info, + workload_authorize_info: workload_authz_info, authorized: result.is_allowed(), }) .set_message("Result of authorize.".to_string()), ); + let tokens_logging_info = LogTokensInfo { + access: tokens.access_token.as_ref().map(|tkn| { + tkn.logging_info( + self.config + .authorization + .decision_log_default_jwt_id + .as_str(), + ) + }), + id_token: tokens.access_token.as_ref().map(|tkn| { + tkn.logging_info( + self.config + .authorization + .decision_log_default_jwt_id + .as_str(), + ) + }), + userinfo: tokens.userinfo_token.as_ref().map(|tkn| { + tkn.logging_info( + self.config + .authorization + .decision_log_default_jwt_id + .as_str(), + ) + }), + }; + // Decision log self.config.log_service.as_ref().log_any(&DecisionLogEntry { base: BaseLogEntry::new(self.config.pdp_id, LogType::Decision), policystore_id: self.config.policy_store.id.as_str(), policystore_version: self.config.policy_store.get_store_version(), principal: PrincipalLogEntry::new(&self.config.authorization), - user: get_entity_claims( self.config.authorization.decision_log_user_claims.as_slice(),&entities,principal_user_entity_uid), - workload: get_entity_claims( self.config.authorization.decision_log_workload_claims.as_slice(),&entities,principal_workload_uid), + user: user_entity_claims, + workload: workload_entity_claims, lock_client_id: None, action: request.action.clone(), resource: resource_uid.to_string(), decision: result.decision().into(), - tokens: LogTokensInfo{ - access: tokens.access_token.get_log_tokens_info(self.config.authorization.decision_log_default_jwt_id.as_str()), - id_token: tokens.id_token.get_log_tokens_info(self.config.authorization.decision_log_default_jwt_id.as_str()), - userinfo: tokens.userinfo_token.get_log_tokens_info(self.config.authorization.decision_log_default_jwt_id.as_str()), - }, + tokens: tokens_logging_info, decision_time_ms: elapsed_ms, }); @@ -272,75 +339,60 @@ impl Authz { Ok(response) } - /// Create all [`Entity`]-s from [`Request`] - pub fn authorize_entities_data( + /// Build all the Cedar [`Entities`] from a [`Request`] + /// + /// [`Entities`]: Entity + pub fn build_entities( &self, request: &Request, - tokens: &ProcessTokensResult, + tokens: &DecodedTokens, ) -> Result { let policy_store = &self.config.policy_store; let auth_conf = &self.config.authorization; - // decode JWT tokens to structs AccessTokenData, IdTokenData, UserInfoTokenData using jwt service - let decode_result: ProcessTokensResult = self - .config - .jwt_service - .process_tokens::( - &request.access_token, - &request.id_token, - Some(&request.userinfo_token), - )?; + // build workload entity + let workload = if self.config.authorization.use_workload_principal { + Some(create_workload_entity( + auth_conf.mapping_workload.as_deref(), + policy_store, + tokens, + )?) + } else { + None + }; - let trusted_issuer = tokens.trusted_issuer.unwrap_or_default(); - let tokens_metadata = trusted_issuer.tokens_metadata(); - - let role_entities = create_role_entities(policy_store, tokens, trusted_issuer)?; - - let data = AuthorizeEntitiesData::builder() - // Add workload entity - .workload_entity(create_workload(auth_conf.mapping_workload.as_deref(), policy_store, - &decode_result.access_token, - tokens_metadata.access_tokens).map_err(AuthorizeError::CreateWorkloadEntity)?) - // Add access token entity - .access_token(create_access_token(auth_conf.mapping_access_token.as_deref(), policy_store, - &tokens.access_token, - tokens_metadata.access_tokens).map_err(AuthorizeError::CreateAccessTokenEntity)?) - // add id_token entity - .id_token_entity( - create_id_token_entity(auth_conf.mapping_id_token.as_deref(), - policy_store, - &tokens.id_token, - &tokens_metadata.id_tokens.claim_mapping) - .map_err(AuthorizeError::CreateIdTokenEntity)?, - ) - // Add userinfo_token entity - .userinfo_token( - create_userinfo_token_entity(auth_conf.mapping_userinfo_token.as_deref(), - policy_store, - &tokens.userinfo_token, - &tokens_metadata.userinfo_tokens.claim_mapping) - .map_err(AuthorizeError::CreateUserinfoTokenEntity)? - ) - // Add User entity - .user_entity( - create_user_entity(auth_conf.mapping_user.as_deref(), - policy_store, - tokens, - // parents for Jans::User entity - HashSet::from_iter(role_entities.iter().map(|e|e.uid())), - trusted_issuer - ) - .map_err(AuthorizeError::CreateUserEntity)?, - ) - // Add an entity created from the resource in the request - .resource_entity(create_resource_entity( - request.resource.clone(), - &self.config.policy_store.schema.json, + // build role entity + let roles = create_role_entities(policy_store, tokens)?; + + // build user entity + let user = if self.config.authorization.use_user_principal { + Some(create_user_entity( + auth_conf.mapping_user.as_deref(), + policy_store, + tokens, + HashSet::from_iter(roles.iter().map(|e| e.uid())), )?) - // Add Role entities - .role_entities(role_entities); + } else { + None + }; - Ok(data.build()) + let token_entities = create_token_entities(auth_conf, policy_store, tokens)?; + + // build resource entity + let resource = create_resource_entity( + request.resource.clone(), + &self.config.policy_store.schema.json, + )?; + + Ok(AuthorizeEntitiesData { + workload, + access_token: token_entities.access, + id_token: token_entities.id, + userinfo_token: token_entities.userinfo, + user, + resource, + roles, + }) } } @@ -354,22 +406,28 @@ fn build_context( ) -> Result { let namespace = config.policy_store.namespace(); let action_name = action.id().escaped().to_string(); - let action_schema = config.policy_store.schema.json. - find_action(&action_name, namespace) + let action_schema = config + .policy_store + .schema + .json + .find_action(&action_name, namespace) .map_err(|e| BuildContextError::FindActionSchema(action_name.clone(), e))? .ok_or(BuildContextError::MissingActionSchema(action_name))?; - + let mut id_mapping = HashMap::new(); for entity in entities_data.iter() { // we strip the namespace from the type_name then make it lowercase // example: 'Jans::Id_token' -> 'id_token' let type_name = entity.uid().type_name().to_string(); - let type_name = type_name.strip_prefix(&format!("{}{}", namespace, CEDAR_POLICY_SEPARATOR)).unwrap_or(&type_name).to_lowercase(); + let type_name = type_name + .strip_prefix(&format!("{}{}", namespace, CEDAR_POLICY_SEPARATOR)) + .unwrap_or(&type_name) + .to_lowercase(); let type_id = entity.uid().id().escaped(); id_mapping.insert(type_name, type_id.to_string()); } - let entities_context = action_schema.build_ctx_entity_refs_json(id_mapping).unwrap(); + let entities_context = action_schema.build_ctx_entity_refs_json(id_mapping)?; let context = merge_json_values(entities_context, request_context)?; @@ -389,46 +447,49 @@ struct ExecuteAuthorizeParameters<'a> { } /// Structure to hold entites created from tokens -// -// we can't use simple vector because we need use uid-s -// from some entities to check authorizations -#[derive(typed_builder::TypedBuilder)] +#[derive(Clone)] pub struct AuthorizeEntitiesData { - pub workload_entity: Entity, - pub access_token: Entity, - pub id_token_entity: Entity, - pub userinfo_token: Entity, - pub user_entity: Entity, - pub resource_entity: Entity, - pub role_entities: Vec, + pub workload: Option, + pub user: Option, + pub access_token: Option, + pub id_token: Option, + pub userinfo_token: Option, + pub resource: Entity, + pub roles: Vec, } impl AuthorizeEntitiesData { /// Create iterator to get all entities fn into_iter(self) -> impl Iterator { - vec![ - self.workload_entity, - self.access_token, - self.id_token_entity, - self.userinfo_token, - self.user_entity, - self.resource_entity, - ] - .into_iter() - .chain(self.role_entities) + vec![self.resource].into_iter().chain(self.roles).chain( + vec![ + self.user, + self.workload, + self.access_token, + self.userinfo_token, + self.id_token, + ] + .into_iter() + .flatten(), + ) } /// Create iterator to get all entities fn iter(&self) -> impl Iterator { - vec![ - &self.workload_entity, - &self.access_token, - &self.id_token_entity, - &self.userinfo_token, - &self.user_entity, - &self.resource_entity, - ] - .into_iter() + vec![&self.resource] + .into_iter() + .chain(self.roles.iter()) + .chain( + vec![ + self.user.as_ref(), + self.workload.as_ref(), + self.access_token.as_ref(), + self.userinfo_token.as_ref(), + self.id_token.as_ref(), + ] + .into_iter() + .flatten(), + ) } /// Collect all entities to [`cedar_policy::Entities`] @@ -448,19 +509,19 @@ pub enum AuthorizeError { ProcessTokens(#[from] jwt::JwtProcessingError), /// Error encountered while creating id token entity #[error("could not create id_token entity: {0}")] - CreateIdTokenEntity(CedarPolicyCreateTypeError), + CreateIdTokenEntity(CreateCedarEntityError), /// Error encountered while creating userinfo entity #[error("could not create userinfo entity: {0}")] - CreateUserinfoTokenEntity(CedarPolicyCreateTypeError), + CreateUserinfoTokenEntity(CreateCedarEntityError), /// Error encountered while creating access_token entity #[error("could not create access_token entity: {0}")] - CreateAccessTokenEntity(CedarPolicyCreateTypeError), + CreateAccessTokenEntity(CreateCedarEntityError), /// Error encountered while creating user entity #[error("could not create User entity: {0}")] - CreateUserEntity(CedarPolicyCreateTypeError), + CreateUserEntity(#[from] CreateUserEntityError), /// Error encountered while creating workload - #[error("could not create Workload entity: {0}")] - CreateWorkloadEntity(CedarPolicyCreateTypeError), + #[error(transparent)] + CreateWorkloadEntity(#[from] CreateWorkloadEntityError), /// Error encountered while creating resource entity #[error("{0}")] ResourceEntity(#[from] ResourceEntityError), @@ -474,11 +535,11 @@ pub enum AuthorizeError { #[error("could not create context: {0}")] CreateContext(#[from] cedar_policy::ContextJsonError), /// Error encountered while creating [`cedar_policy::Request`] for workload entity principal - #[error("could not create request workload entity principal: {0}")] - CreateRequestWorkloadEntity(cedar_policy::RequestValidationError), + #[error("The request for `Workload` does not conform to the schema: {0}")] + WorkloadRequestValidation(cedar_policy::RequestValidationError), /// Error encountered while creating [`cedar_policy::Request`] for user entity principal - #[error("could not create request user entity principal: {0}")] - CreateRequestUserEntity(cedar_policy::RequestValidationError), + #[error("The request for `User` does not conform to the schema: {0}")] + UserRequestValidation(cedar_policy::RequestValidationError), /// Error encountered while collecting all entities #[error("could not collect all entities: {0}")] Entities(#[from] cedar_policy::entities_errors::EntitiesError), @@ -509,7 +570,6 @@ pub enum BuildContextError { BuildJson(#[from] BuildJsonCtxError), } - #[derive(Debug, derive_more::Error, derive_more::Display)] #[display("could not create request user entity principal for {uid}: {err}")] pub struct CreateRequestRoleError { @@ -519,16 +579,15 @@ pub struct CreateRequestRoleError { uid: EntityUid, } - - /// Get entity claims from list in config -// // To get claims we convert entity to json, because no other way to get introspection -fn get_entity_claims(decision_log_claims: &[String], entities: &Entities, entity_uid: EntityUid) -> HashMap { - HashMap::from_iter( decision_log_claims - .iter() - .filter_map(|claim_key| { - entities +fn get_entity_claims( + decision_log_claims: &[String], + entities: &Entities, + entity_uid: EntityUid, +) -> HashMap { + HashMap::from_iter(decision_log_claims.iter().filter_map(|claim_key| { + entities .get(&entity_uid) // convert entity to json and result to option .and_then(|entity| entity.to_json_value().ok()) @@ -548,10 +607,10 @@ fn get_entity_claims(decision_log_claims: &[String], entities: &Entities, entity // } // ] // }, - .and_then(|json_value| + .and_then(|json_value| // get `attrs` attribute json_value.get("attrs") - .map(|attrs_value| + .map(|attrs_value| // get claim key value attrs_value.get(claim_key) .map(|claim_value| claim_value.to_owned()) @@ -560,6 +619,5 @@ fn get_entity_claims(decision_log_claims: &[String], entities: &Entities, entity .flatten() // convert to (String, Value) tuple .map(|attr_json| (claim_key.clone(),attr_json.clone())) - })) + })) } - diff --git a/jans-cedarling/cedarling/src/authz/request.rs b/jans-cedarling/cedarling/src/authz/request.rs index 0137c61bda6..4f5047cc069 100644 --- a/jans-cedarling/cedarling/src/authz/request.rs +++ b/jans-cedarling/cedarling/src/authz/request.rs @@ -1,11 +1,10 @@ -/* - * 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. - */ +// 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, str::FromStr}; +use std::collections::HashMap; +use std::str::FromStr; use cedar_policy::{EntityId, EntityTypeName, EntityUid, ParseErrors}; @@ -13,11 +12,11 @@ use cedar_policy::{EntityId, EntityTypeName, EntityUid, ParseErrors}; #[derive(Debug, Clone, serde::Deserialize)] pub struct Request { /// Access token raw value - pub access_token: String, + pub access_token: Option, /// Id Token raw value - pub id_token: String, + pub id_token: Option, /// Userinfo Token raw value - pub userinfo_token: String, + pub userinfo_token: Option, /// cedar_policy action pub action: String, /// cedar_policy resource data diff --git a/jans-cedarling/cedarling/src/authz/token_data.rs b/jans-cedarling/cedarling/src/authz/token_data.rs deleted file mode 100644 index b3e5ed9c391..00000000000 --- a/jans-cedarling/cedarling/src/authz/token_data.rs +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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 derive_more::Deref; -use serde_json::Value; - -/// Wrapper around access token decode result -#[derive(Clone, Deref, serde::Deserialize)] -pub(crate) struct AccessTokenData(TokenPayload); - -/// Wrapper around id token decode result -#[derive(Clone, Deref, serde::Deserialize)] -pub(crate) struct IdTokenData(TokenPayload); - -/// Wrapper around userinfo token decode result -#[derive(Clone, Deref, serde::Deserialize)] -pub(crate) struct UserInfoTokenData(TokenPayload); - -/// A container for storing token data or data attributes for the . -/// Provides methods for retrieving payload from the token or attributes for the . -#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] -pub(crate) struct TokenPayload { - #[serde(flatten)] - pub payload: HashMap, -} - -impl TokenPayload { - pub fn new(payload: HashMap) -> Self { - Self { payload } - } - - pub fn from_json_map(map: serde_json::Map) -> Self { - Self::new(HashMap::from_iter(map)) - } - - /// Get [`Payload`] structure that contain key and [serde_json::Value] value. - pub fn get_payload(&self, key: &str) -> Result { - self.payload - .get(key) - .map(|value| Payload { - key: key.to_string(), - value, - }) - .ok_or(GetTokenClaimValue::KeyNotFound(key.to_string())) - } - - /// get tokens info claim for [`LogTokensInfo`] structure - /// if iterator is empty is used claim 'jti' - pub(crate) fn get_log_tokens_info<'a>( - &'a self, - decision_log_default_jwt_id: &'a str, - ) -> HashMap<&'a str, &'a serde_json::Value> { - let claim = if !decision_log_default_jwt_id.is_empty() { - decision_log_default_jwt_id - } else { - "jti" - }; - - let iter = [self.payload.get(claim).map(|value| (claim, value))] - .into_iter() - .flatten(); - - HashMap::from_iter(iter) - } -} - -impl From> for TokenPayload { - fn from(value: HashMap) -> Self { - TokenPayload::new(value) - } -} - -/// Errors that can occur when trying to get claim attribute value from token data -#[derive(Debug, thiserror::Error)] -pub enum GetTokenClaimValue { - #[error("could not find field with key: {0}")] - KeyNotFound(String), - #[error("could not convert json field with key: {key} to: {expected_type}, got: {got_type}")] - KeyNotCorrectType { - key: String, - expected_type: String, - got_type: String, - }, -} - -impl GetTokenClaimValue { - pub(crate) fn json_value_type_name(value: &Value) -> String { - match value { - Value::Null => "null".to_string(), - Value::Bool(_) => "bool".to_string(), - Value::Number(_) => "number".to_string(), - Value::String(_) => "string".to_string(), - Value::Array(_) => "array".to_string(), - Value::Object(_) => "object".to_string(), - } - } - - /// Returns `KeyNotCorrectType` error case - /// is used for useful error message - fn not_correct_type( - key: &str, - expected_type_name: &str, - got_value: &Value, - ) -> GetTokenClaimValue { - let got_value_type_name = Self::json_value_type_name(got_value); - - GetTokenClaimValue::KeyNotCorrectType { - key: key.to_string(), - expected_type: expected_type_name.to_string(), - got_type: got_value_type_name, - } - } -} - -/// Structure that contains information about token claim, only about one attribute -/// key and json value -/// -/// Wrapper to get more readable error messages when we get not correct type of value from json. -/// Is used in the [`TokenPayload::get_payload`] method -pub(crate) struct Payload<'a> { - key: String, - value: &'a serde_json::Value, -} - -impl Payload<'_> { - /// Get key value of payload - pub fn get_key(&self) -> &str { - &self.key - } - - /// Get value of payload - pub fn get_value(&self) -> &serde_json::Value { - self.value - } - - pub fn as_i64(&self) -> Result { - self.value - .as_i64() - .ok_or(GetTokenClaimValue::not_correct_type( - &self.key, "i64", self.value, - )) - } - - pub fn as_str(&self) -> Result<&str, GetTokenClaimValue> { - self.value - .as_str() - .ok_or(GetTokenClaimValue::not_correct_type( - &self.key, "String", self.value, - )) - } - - pub fn as_bool(&self) -> Result { - self.value - .as_bool() - .ok_or(GetTokenClaimValue::not_correct_type( - &self.key, "bool", self.value, - )) - } - - pub fn as_array(&self) -> Result, GetTokenClaimValue> { - self.value - .as_array() - .map(|array| { - array - .iter() - .enumerate() - .map(|(i, v)| Payload { - // show current key and index in array - key: format!("{}[{}]", self.key, i), - value: v, - }) - .collect() - }) - .ok_or(GetTokenClaimValue::not_correct_type( - &self.key, "Array", self.value, - )) - } -} diff --git a/jans-cedarling/cedarling/src/bootstrap_config/authorization_config.rs b/jans-cedarling/cedarling/src/bootstrap_config/authorization_config.rs index 0aa58c8bd89..eddd923c8cc 100644 --- a/jans-cedarling/cedarling/src/bootstrap_config/authorization_config.rs +++ b/jans-cedarling/cedarling/src/bootstrap_config/authorization_config.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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::WorkloadBoolOp; diff --git a/jans-cedarling/cedarling/src/bootstrap_config/decode.rs b/jans-cedarling/cedarling/src/bootstrap_config/decode.rs index 206d9943d30..be2b1726e35 100644 --- a/jans-cedarling/cedarling/src/bootstrap_config/decode.rs +++ b/jans-cedarling/cedarling/src/bootstrap_config/decode.rs @@ -1,19 +1,23 @@ -/* - * 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. - */ +// 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::HashSet; +use std::fmt::Display; +use std::fs; +use std::path::Path; +use std::str::FromStr; + +use jsonwebtoken::Algorithm; +use serde::{Deserialize, Deserializer, Serialize}; + +use super::authorization_config::AuthorizationConfig; use super::{ - authorization_config::AuthorizationConfig, BootstrapConfig, BootstrapConfigLoadingError, - IdTokenTrustMode, JwtConfig, LogConfig, LogTypeConfig, MemoryLogConfig, PolicyStoreConfig, - PolicyStoreSource, TokenValidationConfig, + BootstrapConfig, BootstrapConfigLoadingError, IdTokenTrustMode, JwtConfig, LogConfig, + LogTypeConfig, MemoryLogConfig, PolicyStoreConfig, PolicyStoreSource, TokenValidationConfig, }; use crate::log::LogLevel; -use jsonwebtoken::Algorithm; -use serde::{Deserialize, Deserializer, Serialize}; -use std::{collections::HashSet, fmt::Display, fs, path::Path, str::FromStr}; #[derive(Deserialize, PartialEq, Debug, Default)] /// Struct that represent mapping mapping `Bootstrap properties` to be JSON and YAML compatible @@ -427,6 +431,10 @@ pub struct ParseFeatureToggleError { impl BootstrapConfig { /// Construct an instance from BootstrapConfigRaw pub fn from_raw_config(raw: &BootstrapConfigRaw) -> Result { + if !raw.workload_authz.is_enabled() && !raw.user_authz.is_enabled() { + return Err(BootstrapConfigLoadingError::BothPrincipalsDisabled); + } + // Decode LogCofig let log_type = match raw.log_type { LoggerType::Off => LogTypeConfig::Off, diff --git a/jans-cedarling/cedarling/src/bootstrap_config/jwt_config.rs b/jans-cedarling/cedarling/src/bootstrap_config/jwt_config.rs index a2d4ef94301..c9e1a72d27e 100644 --- a/jans-cedarling/cedarling/src/bootstrap_config/jwt_config.rs +++ b/jans-cedarling/cedarling/src/bootstrap_config/jwt_config.rs @@ -1,13 +1,13 @@ -/* - * 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. - */ +// 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::HashSet; +use std::str::FromStr; use jsonwebtoken::Algorithm; use serde::Deserialize; -use std::{collections::HashSet, str::FromStr}; /// The set of Bootstrap properties related to JWT validation. #[derive(Debug, PartialEq)] diff --git a/jans-cedarling/cedarling/src/bootstrap_config/log_config.rs b/jans-cedarling/cedarling/src/bootstrap_config/log_config.rs index eaf6081a85c..b9bf5565489 100644 --- a/jans-cedarling/cedarling/src/bootstrap_config/log_config.rs +++ b/jans-cedarling/cedarling/src/bootstrap_config/log_config.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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 crate::log::LogLevel; diff --git a/jans-cedarling/cedarling/src/bootstrap_config/mod.rs b/jans-cedarling/cedarling/src/bootstrap_config/mod.rs index 9a8332f1d43..59720f4a1a6 100644 --- a/jans-cedarling/cedarling/src/bootstrap_config/mod.rs +++ b/jans-cedarling/cedarling/src/bootstrap_config/mod.rs @@ -1,9 +1,8 @@ -/* - * 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. - */ +// 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. + //! Module for bootstrap configuration types //! to configure [`Cedarling`](crate::Cedarling) @@ -12,7 +11,8 @@ pub(crate) mod jwt_config; pub(crate) mod log_config; pub(crate) mod policy_store_config; -use std::{fs, io, path::Path}; +use std::path::Path; +use std::{fs, io}; pub use authorization_config::AuthorizationConfig; // reimport to useful import values in root module @@ -51,9 +51,7 @@ impl BootstrapConfig { /// ```rust /// use cedarling::BootstrapConfig; /// - /// let config = - /// BootstrapConfig::load_from_file("../test_files/bootstrap_props.json") - /// .unwrap(); + /// let config = BootstrapConfig::load_from_file("../test_files/bootstrap_props.json").unwrap(); /// ``` pub fn load_from_file(path: &str) -> Result { let file_ext = Path::new(path) @@ -114,11 +112,17 @@ pub enum BootstrapConfigLoadingError { DecodingYAML(#[from] serde_yml::Error), /// Error returned when the boostrap property `CEDARLING_LOG_TTL` is missing. - #[error("Missing bootstrap property: `CEDARLING_LOG_TTL`. This property is required if `CEDARLING_LOG_TYPE` is set to Memory.")] + #[error( + "Missing bootstrap property: `CEDARLING_LOG_TTL`. This property is required if \ + `CEDARLING_LOG_TYPE` is set to Memory." + )] MissingLogTTL, /// Error returned when multiple policy store sources were provided. - #[error("Multiple store options were provided. Make sure you only one of these properties is set: `CEDARLING_POLICY_STORE_URI` or `CEDARLING_LOCAL_POLICY_STORE`")] + #[error( + "Multiple store options were provided. Make sure you only one of these properties is set: \ + `CEDARLING_POLICY_STORE_URI` or `CEDARLING_LOCAL_POLICY_STORE`" + )] ConflictingPolicyStores, /// Error returned when no policy store source was provided. @@ -134,18 +138,28 @@ pub enum BootstrapConfigLoadingError { /// Error returned when failing to load a local JWKS #[error("Failed to load local JWKS from {0}: {1}")] LoadLocalJwks(String, std::io::Error), + + /// Error returned when both `CEDARLING_USER_AUTHZ` and `CEDARLING_WORKLOAD_AUTHZ` are disabled. + /// These two authentication configurations cannot be disabled at the same time. + #[error( + "Both `CEDARLING_USER_AUTHZ` and `CEDARLING_WORKLOAD_AUTHZ` cannot be disabled \ + simultaneously." + )] + BothPrincipalsDisabled, } #[cfg(test)] mod test { - use super::*; - use crate::bootstrap_config::decode::BootstrapConfigRaw; - use std::{collections::HashSet, path::Path}; + use std::collections::HashSet; + use std::path::Path; - use crate::{BootstrapConfig, LogConfig, LogTypeConfig, MemoryLogConfig, PolicyStoreConfig}; use jsonwebtoken::Algorithm; use test_utils::assert_eq; + use super::*; + use crate::bootstrap_config::decode::BootstrapConfigRaw; + use crate::{BootstrapConfig, LogConfig, LogTypeConfig, MemoryLogConfig, PolicyStoreConfig}; + #[test] fn can_deserialize_from_json() { let config_json = include_str!("../../../test_files/bootstrap_props.json"); diff --git a/jans-cedarling/cedarling/src/bootstrap_config/policy_store_config.rs b/jans-cedarling/cedarling/src/bootstrap_config/policy_store_config.rs index 0fbb937e03a..922188c14fd 100644 --- a/jans-cedarling/cedarling/src/bootstrap_config/policy_store_config.rs +++ b/jans-cedarling/cedarling/src/bootstrap_config/policy_store_config.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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::path::Path; diff --git a/jans-cedarling/cedarling/src/common/app_types.rs b/jans-cedarling/cedarling/src/common/app_types.rs index 24616b1da59..2280454f9fc 100644 --- a/jans-cedarling/cedarling/src/common/app_types.rs +++ b/jans-cedarling/cedarling/src/common/app_types.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. //! Module that contains structures used as configuration internally in the application //! It is usefull to use it with DI container diff --git a/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json.rs b/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json.rs index a32d47ef3a7..281facefb9d 100644 --- a/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json.rs +++ b/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. //! Module contains the JSON representation of a [cedar_policy::Schema] //! Support translated schema from human representation to JSON via CLI version `cedar-policy-cli 4.1`. @@ -14,11 +12,11 @@ mod action; mod entity_types; -use action::ActionSchema; -use derive_more::derive::Display; use std::collections::HashMap; +use action::ActionSchema; pub use action::{BuildJsonCtxError, FindActionError}; +use derive_more::derive::Display; pub use entity_types::{CedarSchemaEntityShape, CedarSchemaRecord}; /// Represent `cedar-policy` schema type for external usage. @@ -92,7 +90,6 @@ impl CedarSchemaJson { } /// CedarSchemaEntities hold all entities and their shapes in the namespace. -// // It may contain more fields, but we don't need all of them. #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, PartialEq)] pub struct CedarSchemaEntities { @@ -105,13 +102,14 @@ pub struct CedarSchemaEntities { #[cfg(test)] mod tests { - use super::entity_types::*; - use super::*; + use std::collections::HashSet; + use action::CtxAttribute; use serde_json::json; - use std::collections::HashSet; - use test_utils::assert_eq; - use test_utils::SortedJson; + use test_utils::{assert_eq, SortedJson}; + + use super::entity_types::*; + use super::*; /// Test to parse the cedar json schema /// to debug deserialize the schema @@ -272,7 +270,11 @@ mod tests { let parse_error = serde_json::from_str::(json_value).expect_err("should fail to parse"); - assert_eq!(parse_error.to_string(),"could not deserialize CedarSchemaEntityType: failed to deserialize EntityOrCommon: missing field `name` at line 17 column 1") + assert_eq!( + parse_error.to_string(), + "could not deserialize CedarSchemaEntityType: failed to deserialize EntityOrCommon: \ + missing field `name` at line 17 column 1" + ) } /// test to check if we get error on parsing invalid `PrimitiveType` type @@ -283,7 +285,11 @@ mod tests { let parse_error = serde_json::from_str::(json_value).expect_err("should fail to parse"); - assert_eq!(parse_error.to_string(),"could not deserialize CedarSchemaEntityType: invalid type: integer `123`, expected a string at line 17 column 1") + assert_eq!( + parse_error.to_string(), + "could not deserialize CedarSchemaEntityType: invalid type: integer `123`, expected a \ + string at line 17 column 1" + ) } /// test to check if we get error on parsing invalid nested Sets :`Set>` type @@ -294,7 +300,12 @@ mod tests { let parse_error = serde_json::from_str::(json_value).expect_err("should fail to parse"); - assert_eq!(parse_error.to_string(),"could not deserialize CedarSchemaEntityType: failed to deserialize Set: failed to deserialize Set: failed to deserialize EntityOrCommon: missing field `name` at line 24 column 1") + assert_eq!( + parse_error.to_string(), + "could not deserialize CedarSchemaEntityType: failed to deserialize Set: failed to \ + deserialize Set: failed to deserialize EntityOrCommon: missing field `name` at line \ + 24 column 1" + ) } /// test to check if we get error on parsing invalid type in field `is_required` @@ -305,7 +316,11 @@ mod tests { let parse_error = serde_json::from_str::(json_value).expect_err("should fail to parse"); - assert_eq!(parse_error.to_string(),"could not deserialize CedarSchemaEntityAttribute, field 'is_required': invalid type: integer `1234`, expected a boolean at line 22 column 1") + assert_eq!( + parse_error.to_string(), + "could not deserialize CedarSchemaEntityAttribute, field 'is_required': invalid type: \ + integer `1234`, expected a boolean at line 22 column 1" + ) } #[test] diff --git a/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/action.rs b/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/action.rs index 43a4365124a..5b1b6fc67f4 100644 --- a/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/action.rs +++ b/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/action.rs @@ -1,23 +1,22 @@ -/* - * 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 crate::{ - authz::entities::CEDAR_POLICY_SEPARATOR, common::cedar_schema::cedar_json::SchemaDefinedType, -}; +// 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, HashSet}; + +use serde::ser::SerializeMap; +use serde::{de, Deserialize, Serialize}; +use serde_json::{json, Value}; +use super::entity_types::{ + CedarSchemaEntityAttribute, CedarSchemaEntityType, PrimitiveType, PrimitiveTypeKind, +}; use super::{ - entity_types::{ - CedarSchemaEntityAttribute, CedarSchemaEntityType, PrimitiveType, PrimitiveTypeKind, - }, CedarSchemaEntities, CedarSchemaJson, CedarSchemaRecord, CedarType, GetCedarTypeError, }; -use serde::{de, ser::SerializeMap, Deserialize, Serialize}; -use serde_json::{json, Value}; -use std::collections::{HashMap, HashSet}; +use crate::authz::entities::CEDAR_POLICY_SEPARATOR; +use crate::common::cedar_schema::cedar_json::SchemaDefinedType; type AttrName = String; @@ -61,6 +60,7 @@ impl Action<'_> { if let Some(ctx_entities) = &self.context_entities { for attr in ctx_entities.iter() { + println!("attr: {:?}", attr); if let CedarType::TypeName(type_name) = &attr.kind { let id = match id_mapping.get(&attr.key) { Some(val) => val, @@ -80,10 +80,20 @@ impl Action<'_> { #[derive(Debug, thiserror::Error)] pub enum BuildJsonCtxError { /// If an entity reference is provided but the ID is missing from `id_mapping`. - #[error("An entity reference for `{0}` is required by the schema but an ID was not provided via the `id_mapping`")] + /// + /// This is usually caused by: + /// - disabling workload AuthZ but having a Workload entity in the context schema + /// - disabling user AuthZ but referencing User entity in the context schema + #[error( + "An entity reference for `{0}` is required by the schema but an ID was not provided via \ + the `id_mapping`" + )] MissingIdMapping(String), /// If a non-entity attribute is provided but the value is missing from `value_mapping`. - #[error("A non-entity attribute for `{0}` is required by the schema but a value was not provided via the `value_mapping`")] + #[error( + "A non-entity attribute for `{0}` is required by the schema but a value was not provided \ + via the `value_mapping`" + )] MissingValueMapping(String), } @@ -323,18 +333,18 @@ pub enum FindActionError { #[cfg(test)] mod test { - use super::ActionSchema; - use crate::common::cedar_schema::cedar_json::{ - action::RecordOrType, - entity_types::{ - CedarSchemaEntityAttribute, CedarSchemaEntityType, EntityType, PrimitiveType, - PrimitiveTypeKind, - }, - CedarSchemaRecord, - }; + use std::collections::{HashMap, HashSet}; + use serde::Deserialize; use serde_json::{json, Value}; - use std::collections::{HashMap, HashSet}; + + use super::ActionSchema; + use crate::common::cedar_schema::cedar_json::action::RecordOrType; + use crate::common::cedar_schema::cedar_json::entity_types::{ + CedarSchemaEntityAttribute, CedarSchemaEntityType, EntityType, PrimitiveType, + PrimitiveTypeKind, + }; + use crate::common::cedar_schema::cedar_json::CedarSchemaRecord; type ActionType = String; #[derive(Deserialize, Debug, PartialEq)] diff --git a/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/entity_types.rs b/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/entity_types.rs index e50b1f8657f..9d855ed6449 100644 --- a/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/entity_types.rs +++ b/jans-cedarling/cedarling/src/common/cedar_schema/cedar_json/entity_types.rs @@ -1,13 +1,12 @@ -/* - * 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. - */ +// 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::{CedarType, GetCedarTypeError}; use std::collections::HashMap; +use super::{CedarType, GetCedarTypeError}; + /// CedarSchemaEntityShape hold shape of an entity. #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, PartialEq)] pub struct CedarSchemaEntityShape { @@ -220,7 +219,6 @@ impl EntityType { /// Describes the Set element /// Set ::= '"type": "Set", "element": ' TypeJson -// // "type": "Set" checked during deserialization #[derive(Debug, Clone, serde::Deserialize, PartialEq, serde::Serialize, Hash)] pub struct SetEntityType { diff --git a/jans-cedarling/cedarling/src/common/cedar_schema/mod.rs b/jans-cedarling/cedarling/src/common/cedar_schema/mod.rs index 41f504cecbf..dda9cef224f 100644 --- a/jans-cedarling/cedarling/src/common/cedar_schema/mod.rs +++ b/jans-cedarling/cedarling/src/common/cedar_schema/mod.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. pub(crate) use cedar_json::CedarSchemaJson; pub(crate) mod cedar_json; @@ -198,10 +196,8 @@ mod deserialize { mod tests { use test_utils::assert_eq; - use crate::common::policy_store::AgamaPolicyStore; - use crate::common::policy_store::PolicyStore; - use super::*; + use crate::common::policy_store::{AgamaPolicyStore, PolicyStore}; #[test] fn test_read_ok() { @@ -292,7 +288,10 @@ mod deserialize { let err = policy_result.unwrap_err(); let msg = err.to_string(); assert!( - msg.contains("unable to parse cedar policy schema: error parsing schema: unexpected end of input"), + msg.contains( + "unable to parse cedar policy schema: error parsing schema: unexpected end of \ + input" + ), "{err:?}" ); } @@ -306,7 +305,8 @@ mod deserialize { let err_msg = policy_result.unwrap_err().to_string(); assert_eq!( err_msg, - "policy_stores.a1bf93115de86de760ee0bea1d529b521489e5a11747: unable to parse cedar policy schema: failed to resolve type: User_TypeNotExist at line 8 column 5" + "policy_stores.a1bf93115de86de760ee0bea1d529b521489e5a11747: unable to parse \ + cedar policy schema: failed to resolve type: User_TypeNotExist at line 8 column 5" ); } } diff --git a/jans-cedarling/cedarling/src/common/mod.rs b/jans-cedarling/cedarling/src/common/mod.rs index 99f70aece1d..f66f26e5ede 100644 --- a/jans-cedarling/cedarling/src/common/mod.rs +++ b/jans-cedarling/cedarling/src/common/mod.rs @@ -1,9 +1,8 @@ -/* - * 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. - */ +// 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. + //! # Common //! This package provides the core data models for the *Cedarling* application, //! defining the structures and types essential for its functionality and is used in more than one module. diff --git a/jans-cedarling/cedarling/src/common/policy_store.rs b/jans-cedarling/cedarling/src/common/policy_store.rs index 9b665c68b22..48ca81d04d3 100644 --- a/jans-cedarling/cedarling/src/common/policy_store.rs +++ b/jans-cedarling/cedarling/src/common/policy_store.rs @@ -1,21 +1,23 @@ -/* - * 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. - */ +// 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. mod claim_mapping; #[cfg(test)] mod test; mod token_entity_metadata; -use super::cedar_schema::CedarSchema; +use std::collections::HashMap; +use std::fmt; +use std::sync::LazyLock; + use cedar_policy::PolicyId; use semver::Version; use serde::{Deserialize, Deserializer}; -use std::{collections::HashMap, fmt, sync::LazyLock}; -pub use token_entity_metadata::{AccessTokenEntityMetadata, ClaimMappings, TokenEntityMetadata}; +pub use token_entity_metadata::{ClaimMappings, TokenEntityMetadata}; + +use super::cedar_schema::CedarSchema; /// This is the top-level struct in compliance with the Agama Lab Policy Designer format. #[derive(Debug, Clone, serde::Deserialize, PartialEq)] @@ -101,7 +103,7 @@ pub struct TrustedIssuer { /// Metadata for access tokens issued by the trusted issuer. #[serde(default)] - pub access_tokens: AccessTokenEntityMetadata, + pub access_tokens: TokenEntityMetadata, /// Metadata for ID tokens issued by the trusted issuer. #[serde(default)] @@ -137,120 +139,43 @@ impl Default for &TrustedIssuer { } } -/// Structure define the source from where role mappings are retrieved. -#[derive(Debug)] -pub struct RoleMapping<'a> { - pub kind: TokenKind, - pub mapping_field: &'a str, -} - -// By default we will search role in the User token -impl Default for RoleMapping<'_> { - fn default() -> Self { - Self { - kind: TokenKind::Userinfo, - mapping_field: "role", +impl TrustedIssuer { + /// Retrieves the claim that defines the `Role` for a given token type. + pub fn role_mapping(&self, token_kind: TokenKind) -> Option<&str> { + match token_kind { + TokenKind::Access => self.access_tokens.role_mapping.as_deref(), + TokenKind::Id => self.id_tokens.role_mapping.as_deref(), + TokenKind::Userinfo => self.userinfo_tokens.role_mapping.as_deref(), + TokenKind::Transaction => self.tx_tokens.role_mapping.as_deref(), } } -} - -/// Structure define the source from where user mappings are retrieved. -pub struct UserMapping<'a> { - pub kind: TokenKind, - pub mapping_field: &'a str, -} -// By default we will search role in the User token -impl Default for UserMapping<'_> { - fn default() -> Self { - Self { - kind: TokenKind::Userinfo, - mapping_field: "sub", + /// Retrieves the claim that defines the `User` for a given token type. + pub fn user_mapping(&self, token_kind: TokenKind) -> Option<&str> { + match token_kind { + TokenKind::Access => self.access_tokens.user_id.as_deref(), + TokenKind::Id => self.id_tokens.user_id.as_deref(), + TokenKind::Userinfo => self.userinfo_tokens.user_id.as_deref(), + TokenKind::Transaction => self.tx_tokens.user_id.as_deref(), } } -} - -impl TrustedIssuer { - /// Retrieves the available `RoleMapping` from the token metadata. - /// - /// Checks each token metadata and returns all found in a `role_mapping` field. - pub fn get_role_mapping(&self) -> Option> { - let mut role_mapping_vec = Vec::new(); - - if let Some(role_mapping) = &self.access_tokens.entity_metadata.role_mapping { - role_mapping_vec.push(RoleMapping { - kind: TokenKind::Access, - mapping_field: role_mapping.as_str(), - }); - } - if let Some(role_mapping) = &self.id_tokens.role_mapping { - role_mapping_vec.push(RoleMapping { - kind: TokenKind::Id, - mapping_field: role_mapping.as_str(), - }); - } - - if let Some(role_mapping) = &self.userinfo_tokens.role_mapping { - role_mapping_vec.push(RoleMapping { - kind: TokenKind::Userinfo, - mapping_field: role_mapping.as_str(), - }); - } - - if let Some(role_mapping) = &self.tx_tokens.role_mapping { - role_mapping_vec.push(RoleMapping { - kind: TokenKind::Transaction, - mapping_field: role_mapping.as_str(), - }); - }; - - if !role_mapping_vec.is_empty() { - Some(role_mapping_vec) - } else { - None + pub fn claim_mapping(&self, token_kind: TokenKind) -> &ClaimMappings { + match token_kind { + TokenKind::Access => &self.access_tokens.claim_mapping, + TokenKind::Id => &self.id_tokens.claim_mapping, + TokenKind::Userinfo => &self.userinfo_tokens.claim_mapping, + TokenKind::Transaction => &self.tx_tokens.claim_mapping, } } - /// Retrieves the available `user id` mapping from the token metadata. - /// - /// Checks each token metadata and returns the first one found with a `role_mapping` field. - /// - /// The checks happen in this order: - /// 1. access_token - /// 2. id_token - /// 3. userinfo_token - /// 4. tx_token - pub fn get_user_id_mapping(&self) -> Option { - if let Some(user_mapping) = &self.access_tokens.entity_metadata.user_id { - return Some(UserMapping { - kind: TokenKind::Access, - mapping_field: user_mapping.as_str(), - }); - } - - if let Some(user_mapping) = &self.id_tokens.user_id { - return Some(UserMapping { - kind: TokenKind::Id, - mapping_field: user_mapping.as_str(), - }); - } - - if let Some(user_mapping) = &self.userinfo_tokens.user_id { - return Some(UserMapping { - kind: TokenKind::Userinfo, - mapping_field: user_mapping.as_str(), - }); - } - - if let Some(user_mapping) = &self.tx_tokens.user_id { - return Some(UserMapping { - kind: TokenKind::Transaction, - mapping_field: user_mapping.as_str(), - }); + pub fn token_metadata(&self, token_kind: TokenKind) -> &TokenEntityMetadata { + match token_kind { + TokenKind::Access => self.tokens_metadata().access_tokens, + TokenKind::Id => self.tokens_metadata().id_tokens, + TokenKind::Userinfo => self.tokens_metadata().userinfo_tokens, + TokenKind::Transaction => self.tokens_metadata().tx_tokens, } - - None } pub fn tokens_metadata(&self) -> TokensMetadata<'_> { @@ -266,7 +191,7 @@ impl TrustedIssuer { // Hold reference to tokens metadata pub struct TokensMetadata<'a> { /// Metadata for access tokens issued by the trusted issuer. - pub access_tokens: &'a AccessTokenEntityMetadata, + pub access_tokens: &'a TokenEntityMetadata, /// Metadata for ID tokens issued by the trusted issuer. pub id_tokens: &'a TokenEntityMetadata, @@ -316,11 +241,10 @@ impl<'de> Deserialize<'de> for TokenKind { "id_token" => Ok(TokenKind::Id), "userinfo_token" => Ok(TokenKind::Userinfo), "access_token" => Ok(TokenKind::Access), - _ => Err(serde::de::Error::unknown_variant(&token_kind, &[ - "access_token", - "id_token", - "userinfo_token", - ])), + _ => Err(serde::de::Error::unknown_variant( + &token_kind, + &["access_token", "id_token", "userinfo_token"], + )), } } } @@ -451,7 +375,6 @@ struct RawPolicy { pub struct PoliciesContainer { /// HasMap to store raw policy info /// Is used to get policy description by ID - // // In HasMap ID is ID of policy raw_policy_info: HashMap, diff --git a/jans-cedarling/cedarling/src/common/policy_store/claim_mapping.rs b/jans-cedarling/cedarling/src/common/policy_store/claim_mapping.rs index dd4e6e45294..ac32b442762 100644 --- a/jans-cedarling/cedarling/src/common/policy_store/claim_mapping.rs +++ b/jans-cedarling/cedarling/src/common/policy_store/claim_mapping.rs @@ -1,15 +1,14 @@ -/* -* 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. -*/ +// 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 regex; use regex::Regex; use serde::{de, Deserialize}; use serde_json::Value; -use std::collections::HashMap; /// Structure for storing `claim mappings` /// @@ -266,13 +265,14 @@ impl RegexFieldMappingType { #[cfg(test)] mod test { - use super::*; - use super::{ClaimMapping, RegexMapping}; - use crate::common::policy_store::claim_mapping::RegexFieldMapping; - use serde_json::json; use std::collections::HashMap; + + use serde_json::json; use test_utils::assert_eq; + use super::{ClaimMapping, RegexMapping, *}; + use crate::common::policy_store::claim_mapping::RegexFieldMapping; + /// Tests if a token entity metadata with a RegEx parser can be parsed /// from a JSON string #[test] diff --git a/jans-cedarling/cedarling/src/common/policy_store/test.rs b/jans-cedarling/cedarling/src/common/policy_store/test.rs index 3ceb56627c4..97e75f30218 100644 --- a/jans-cedarling/cedarling/src/common/policy_store/test.rs +++ b/jans-cedarling/cedarling/src/common/policy_store/test.rs @@ -1,21 +1,18 @@ -/* - * 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::parse_option_string; -use super::AgamaPolicyStore; -use super::ParsePolicySetMessage; -use super::PolicyStore; -use crate::common::policy_store::parse_cedar_version; +// 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::str::FromStr; + use base64::prelude::*; use serde::Deserialize; use serde_json::json; -use std::str::FromStr; use test_utils::assert_eq; +use super::{parse_option_string, AgamaPolicyStore, ParsePolicySetMessage, PolicyStore}; +use crate::common::policy_store::parse_cedar_version; + /// Tests successful deserialization of a valid policy store JSON. #[test] fn test_policy_store_deserialization_success() { @@ -157,7 +154,13 @@ fn test_broken_policy_parsing_error_in_policy_store() { // TODO: this isn't really a human readable format but the current plan is to fetch it from // a which will respond with the policy encoded in base64. This could probably be improved // in the future once the structure of the project is clearer. - assert_eq!(err_msg, "policy_stores.ba1f39115ed86ed760ee0bea1d529b52189e5a117474: Errors encountered while parsing policies: [Error(\"unable to decode policy with id: 840da5d85403f35ea76519ed1a18a33989f855bf1cf8, error: unable to decode policy_content from human readable format: unexpected token `)`\")] at line 8 column 5") + assert_eq!( + err_msg, + "policy_stores.ba1f39115ed86ed760ee0bea1d529b52189e5a117474: Errors encountered while \ + parsing policies: [Error(\"unable to decode policy with id: \ + 840da5d85403f35ea76519ed1a18a33989f855bf1cf8, error: unable to decode policy_content \ + from human readable format: unexpected token `)`\")] at line 8 column 5" + ) } /// Tests that a valid version string is accepted. diff --git a/jans-cedarling/cedarling/src/common/policy_store/token_entity_metadata.rs b/jans-cedarling/cedarling/src/common/policy_store/token_entity_metadata.rs index 1a84cb54420..4f2cb945b49 100644 --- a/jans-cedarling/cedarling/src/common/policy_store/token_entity_metadata.rs +++ b/jans-cedarling/cedarling/src/common/policy_store/token_entity_metadata.rs @@ -1,32 +1,13 @@ -/* -* This software is available under the Apache-2.0 license. +// 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. -* See https://www.apache.org/licenses/LICENSE-2.0.txt for full text. -* -* Copyright (c) 2024, Gluu, Inc. -*/ +use serde::Deserialize; pub use super::claim_mapping::ClaimMappings; use super::parse_option_string; -use serde::Deserialize; - -/// Represents metadata related to access tokens issued by a trusted issuer. -/// -/// This struct includes information on whether the access token is trusted, the principal -/// identifier, and additional entity metadata. -#[derive(Deserialize, Clone, PartialEq, Default, Debug)] -pub struct AccessTokenEntityMetadata { - /// Indicates if the access token is trusted. - #[serde(default)] - pub trusted: bool, - #[serde(default, deserialize_with = "parse_option_string")] - /// An optional string representing the principal identifier (e.g., `jti`). - pub principal_identifier: Option, - - /// Additional metadata associated with the access token. - #[serde(default, flatten)] - pub entity_metadata: TokenEntityMetadata, -} /// Structure for storing mapping JWT claims to `cedar-policy` custom defined types in the `schema`. /// @@ -35,7 +16,13 @@ pub struct AccessTokenEntityMetadata { /// a `ClaimMapping` struct. #[derive(Debug, PartialEq, Clone, Default, Deserialize)] pub struct TokenEntityMetadata { - /// An optional user identifier extracted from the token metadata. + /// Indicates if the access token is trusted. + #[serde(default)] + pub trusted: bool, + #[serde(default, deserialize_with = "parse_option_string")] + /// An optional string representing the principal identifier (e.g., `jti`). + pub principal_identifier: Option, + /// The claim used to create the user id #[serde(deserialize_with = "parse_option_string", default)] pub user_id: Option, /// An optional string indicating the role mapping for the user. @@ -50,9 +37,10 @@ pub struct TokenEntityMetadata { #[cfg(test)] mod test { - use super::TokenEntityMetadata; use serde_json::json; + use super::TokenEntityMetadata; + /// Test deserialization of `TokenEntityMetadata` from JSON. #[test] fn can_parse_from_json() { @@ -75,13 +63,14 @@ mod test { "Failed to parse JSON object with user_id and role_mapping into TokenEntityMetadata", ); assert_eq!( - parsed, - TokenEntityMetadata { - user_id: Some("sub".into()), - role_mapping: None, - claim_mapping: Default::default() - }, - "Expected JSON with user_id and empty role_mapping to be parsed into TokenEntityMetadata" + parsed, + TokenEntityMetadata { + user_id: Some("sub".into()), + role_mapping: None, + ..Default::default() + }, + "Expected JSON with user_id and empty role_mapping to be parsed into \ + TokenEntityMetadata" ); } @@ -107,13 +96,14 @@ mod test { "Failed to parse YAML object with user_id and role_mapping into TokenEntityMetadata", ); assert_eq!( - parsed, - TokenEntityMetadata { - user_id: Some("sub".into()), - role_mapping: None, - claim_mapping: Default::default() - }, - "Expected YAML with user_id and empty role_mapping to be parsed into TokenEntityMetadata" + parsed, + TokenEntityMetadata { + user_id: Some("sub".into()), + role_mapping: None, + ..Default::default() + }, + "Expected YAML with user_id and empty role_mapping to be parsed into \ + TokenEntityMetadata" ); } } diff --git a/jans-cedarling/cedarling/src/http/blocking.rs b/jans-cedarling/cedarling/src/http/blocking.rs index 5ed80c6a17d..90a8a0ad1fb 100644 --- a/jans-cedarling/cedarling/src/http/blocking.rs +++ b/jans-cedarling/cedarling/src/http/blocking.rs @@ -1,13 +1,14 @@ -/* - * 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. - */ +// 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::thread::sleep; +use std::time::Duration; -use super::{HttpClientError, HttpGet, Response}; use reqwest::blocking::Client; -use std::{thread::sleep, time::Duration}; + +use super::{HttpClientError, HttpGet, Response}; /// A wrapper around `reqwest::blocking::Client` providing HTTP request functionality /// with retry logic. diff --git a/jans-cedarling/cedarling/src/http/mod.rs b/jans-cedarling/cedarling/src/http/mod.rs index 0e516dac544..85cc01ef862 100644 --- a/jans-cedarling/cedarling/src/http/mod.rs +++ b/jans-cedarling/cedarling/src/http/mod.rs @@ -1,18 +1,17 @@ -/* -* 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. -cfg*/ +// 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. #[cfg(not(target_family = "wasm"))] mod blocking; #[cfg(target_family = "wasm")] mod wasm; -use serde::Deserialize; use std::time::Duration; +use serde::Deserialize; + trait HttpGet { /// Sends a GET request to the specified URI fn get(&self, uri: &str) -> Result; @@ -76,13 +75,14 @@ pub enum HttpClientError { #[cfg(test)] mod test { - use crate::http::{HttpClient, HttpClientError}; + use std::time::Duration; use mockito::Server; use serde_json::json; - use std::time::Duration; use test_utils::assert_eq; + use crate::http::{HttpClient, HttpClientError}; + #[test] fn can_fetch() { let mut mock_server = Server::new(); diff --git a/jans-cedarling/cedarling/src/http/wasm.rs b/jans-cedarling/cedarling/src/http/wasm.rs index aa154b82e5f..b17d28a5c58 100644 --- a/jans-cedarling/cedarling/src/http/wasm.rs +++ b/jans-cedarling/cedarling/src/http/wasm.rs @@ -1,13 +1,12 @@ -/* - * 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. - */ +// 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::{HttpClientError, HttpGet, Response}; use std::time::Duration; +use super::{HttpClientError, HttpGet, Response}; + /// A wrapper around `reqwest::blocking::Client` providing HTTP request functionality /// with retry logic. /// diff --git a/jans-cedarling/cedarling/src/init/mod.rs b/jans-cedarling/cedarling/src/init/mod.rs index 9dcc1b414d1..e1adb14cc4c 100644 --- a/jans-cedarling/cedarling/src/init/mod.rs +++ b/jans-cedarling/cedarling/src/init/mod.rs @@ -1,9 +1,8 @@ -/* - * 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. - */ +// 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. + //! # Init Engine //! Part of Cedarling that main purpose is: //! - read boostrap properties diff --git a/jans-cedarling/cedarling/src/init/policy_store.rs b/jans-cedarling/cedarling/src/init/policy_store.rs index 6ebc5fa1ca5..c4259c872e6 100644 --- a/jans-cedarling/cedarling/src/init/policy_store.rs +++ b/jans-cedarling/cedarling/src/init/policy_store.rs @@ -1,17 +1,16 @@ -/* - * 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. - */ +// 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 crate::bootstrap_config::policy_store_config::{PolicyStoreConfig, PolicyStoreSource}; -use crate::common::policy_store::{AgamaPolicyStore, PolicyStoreWithID}; -use crate::http::{HttpClient, HttpClientError}; use std::path::Path; use std::time::Duration; use std::{fs, io}; +use crate::bootstrap_config::policy_store_config::{PolicyStoreConfig, PolicyStoreSource}; +use crate::common::policy_store::{AgamaPolicyStore, PolicyStoreWithID}; +use crate::http::{HttpClient, HttpClientError}; + /// Errors that can occur when loading a policy store. #[derive(Debug, thiserror::Error)] pub enum PolicyStoreLoadError { diff --git a/jans-cedarling/cedarling/src/init/service_config.rs b/jans-cedarling/cedarling/src/init/service_config.rs index a21ac4a6ea5..9abbdd643e8 100644 --- a/jans-cedarling/cedarling/src/init/service_config.rs +++ b/jans-cedarling/cedarling/src/init/service_config.rs @@ -1,14 +1,13 @@ -/* - * 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. - */ +// 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 bootstrap_config::BootstrapConfig; use super::policy_store::{load_policy_store, PolicyStoreLoadError}; use crate::bootstrap_config; use crate::common::policy_store::PolicyStoreWithID; -use bootstrap_config::BootstrapConfig; /// Configuration that hold validated infomation from bootstrap config #[derive(Clone)] diff --git a/jans-cedarling/cedarling/src/init/service_factory.rs b/jans-cedarling/cedarling/src/init/service_factory.rs index 368d4e263da..1403a34f245 100644 --- a/jans-cedarling/cedarling/src/init/service_factory.rs +++ b/jans-cedarling/cedarling/src/init/service_factory.rs @@ -1,21 +1,18 @@ -/* - * 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. - */ +// 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. //! Module to lazily initialize internal cedarling services use std::sync::Arc; -use crate::bootstrap_config::BootstrapConfig; -use crate::common::policy_store::PolicyStoreWithID; -use crate::jwt::{JwtService, JwtServiceInitError}; - use super::service_config::ServiceConfig; use crate::authz::{Authz, AuthzConfig}; +use crate::bootstrap_config::BootstrapConfig; use crate::common::app_types; +use crate::common::policy_store::PolicyStoreWithID; +use crate::jwt::{JwtService, JwtServiceInitError}; use crate::log; #[derive(Clone)] diff --git a/jans-cedarling/cedarling/src/jwt/issuers_store.rs b/jans-cedarling/cedarling/src/jwt/issuers_store.rs index f7b1dbf3161..6d0e818e109 100644 --- a/jans-cedarling/cedarling/src/jwt/issuers_store.rs +++ b/jans-cedarling/cedarling/src/jwt/issuers_store.rs @@ -1,11 +1,10 @@ -/* - * 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, sync::Arc}; +// 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 std::sync::Arc; use url::Url; diff --git a/jans-cedarling/cedarling/src/jwt/jwk_store.rs b/jans-cedarling/cedarling/src/jwt/jwk_store.rs index 7be87adc4f9..a02b693c595 100644 --- a/jans-cedarling/cedarling/src/jwt/jwk_store.rs +++ b/jans-cedarling/cedarling/src/jwt/jwk_store.rs @@ -1,22 +1,22 @@ -/* - * 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. - */ +// 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, HashSet}; +use std::fmt::Debug; +use std::sync::Arc; -use super::{KeyId, TrustedIssuerId}; -use crate::common::policy_store::TrustedIssuer; -use crate::http::{HttpClient, HttpClientError}; use jsonwebtoken::jwk::Jwk; use jsonwebtoken::DecodingKey; use serde::Deserialize; use serde_json::Value; -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use std::sync::Arc; use time::OffsetDateTime; +use super::{KeyId, TrustedIssuerId}; +use crate::common::policy_store::TrustedIssuer; +use crate::http::{HttpClient, HttpClientError}; + #[derive(Deserialize)] struct OpenIdConfig { issuer: String, @@ -88,6 +88,7 @@ impl JwkStore { serde_json::from_value::(jwks).map_err(JwkStoreError::DecodeJwk)?; Self::new_from_jwks(store_id, jwks) } + /// Creates a JwkStore from a [`String`] pub fn new_from_jwks_str(store_id: Arc, jwks: &str) -> Result { let jwks = @@ -229,13 +230,19 @@ struct IntermediateJwks { #[cfg(test)] mod test { - use crate::{common::policy_store::TrustedIssuer, http::HttpClient, jwt::jwk_store::JwkStore}; - use jsonwebtoken::{jwk::JwkSet, DecodingKey}; + use std::collections::HashMap; + use std::time::Duration; + + use jsonwebtoken::jwk::JwkSet; + use jsonwebtoken::DecodingKey; use mockito::Server; use serde_json::json; - use std::{collections::HashMap, time::Duration}; use time::OffsetDateTime; + use crate::common::policy_store::TrustedIssuer; + use crate::http::HttpClient; + use crate::jwt::jwk_store::JwkStore; + #[test] fn can_load_from_jwkset() { let kid1 = "a50f6e70ef4b548a5fd9142eecd1fb8f54dce9ee"; diff --git a/jans-cedarling/cedarling/src/jwt/key_service.rs b/jans-cedarling/cedarling/src/jwt/key_service.rs index 50e0fa8b1e1..0f794d77bad 100644 --- a/jans-cedarling/cedarling/src/jwt/key_service.rs +++ b/jans-cedarling/cedarling/src/jwt/key_service.rs @@ -1,19 +1,19 @@ -/* - * 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. - */ +// 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 std::sync::Arc; +use std::time::Duration; -use super::{ - jwk_store::{JwkStore, JwkStoreError}, - TrustedIssuerId, -}; -use crate::common::policy_store::TrustedIssuer; -use crate::http::{HttpClient, HttpClientError}; use jsonwebtoken::DecodingKey; use serde_json::{json, Value}; -use std::{collections::HashMap, sync::Arc, time::Duration}; + +use super::jwk_store::{JwkStore, JwkStoreError}; +use super::TrustedIssuerId; +use crate::common::policy_store::TrustedIssuer; +use crate::http::{HttpClient, HttpClientError}; pub struct DecodingKeyWithIss<'a> { /// The decoding key used to validate JWT signatures. @@ -127,10 +127,12 @@ pub enum KeyServiceError { mod test { use std::collections::HashMap; - use crate::{common::policy_store::TrustedIssuer, jwt::key_service::KeyService}; use mockito::Server; use serde_json::json; + use crate::common::policy_store::TrustedIssuer; + use crate::jwt::key_service::KeyService; + #[test] fn can_load_from_str() { let kid1 = "a50f6e70ef4b548a5fd9142eecd1fb8f54dce9ee"; diff --git a/jans-cedarling/cedarling/src/jwt/mod.rs b/jans-cedarling/cedarling/src/jwt/mod.rs index 48d0bfcea37..97d88d9d822 100644 --- a/jans-cedarling/cedarling/src/jwt/mod.rs +++ b/jans-cedarling/cedarling/src/jwt/mod.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. //! # `JwtEngine` //! @@ -18,17 +16,19 @@ mod jwk_store; mod key_service; #[cfg(test)] mod test_utils; +mod token; mod validator; -use crate::common::policy_store::TrustedIssuer; -use crate::{IdTokenTrustMode, JwtConfig}; -use key_service::{KeyService, KeyServiceError}; -use serde::de::DeserializeOwned; use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use validator::{JwtValidator, JwtValidatorConfig, JwtValidatorError}; pub use jsonwebtoken::Algorithm; +use key_service::{KeyService, KeyServiceError}; +pub use token::{Token, TokenClaim, TokenClaimTypeError, TokenClaims, TokenStr}; +use validator::{JwtValidator, JwtValidatorConfig, JwtValidatorError}; + +use crate::common::policy_store::TrustedIssuer; +use crate::{IdTokenTrustMode, JwtConfig}; /// Type alias for Trusted Issuers' ID. type TrustedIssuerId = Arc; @@ -44,15 +44,25 @@ pub enum JwtProcessingError { InvalidIdToken(#[source] JwtValidatorError), #[error("Invalid Userinfo token: {0}")] InvalidUserinfoToken(#[source] JwtValidatorError), - #[error("Validation failed: id_token audience does not match the access_token client_id. id_token.aud: {0:?}, access_token.client_id: {1:?}")] + #[error( + "Validation failed: id_token audience does not match the access_token client_id. \ + id_token.aud: {0:?}, access_token.client_id: {1:?}" + )] IdTokenAudienceMismatch(String, String), - #[error("Validation failed: Userinfo token subject does not match the id_token subject. userinfo_token.sub: {0:?}, id_token.sub: {1:?}")] + #[error( + "Validation failed: Userinfo token subject does not match the id_token subject. \ + userinfo_token.sub: {0:?}, id_token.sub: {1:?}" + )] UserinfoSubMismatch(String, String), #[error( - "Validation failed: Userinfo token audience ({0}) does not match the access_token client_id ({1})." + "Validation failed: Userinfo token audience ({0}) does not match the access_token \ + client_id ({1})." )] UserinfoAudienceMismatch(String, String), - #[error("CEDARLING_ID_TOKEN_TRUST_MODE is set to 'Strict', but the {0} is missing a required claim: {1}")] + #[error( + "CEDARLING_ID_TOKEN_TRUST_MODE is set to 'Strict', but the {0} is missing a required \ + claim: {1}" + )] MissingClaimsInStrictMode(&'static str, &'static str), #[error("Failed to deserialize from Value to String: {0}")] StringDeserialization(#[from] serde_json::Error), @@ -60,9 +70,15 @@ pub enum JwtProcessingError { #[derive(Debug, thiserror::Error)] pub enum JwtServiceInitError { - #[error("Failed to initialize Key Service for JwtService due to a conflictig config: both a local JWKS and trusted issuers was provided.")] + #[error( + "Failed to initialize Key Service for JwtService due to a conflictig config: both a local \ + JWKS and trusted issuers was provided." + )] ConflictingJwksConfig, - #[error("Failed to initialize Key Service for JwtService due to a missing config: no local JWKS or trusted issuers was provided.")] + #[error( + "Failed to initialize Key Service for JwtService due to a missing config: no local JWKS \ + or trusted issuers was provided." + )] MissingJwksConfig, #[error("Failed to initialize Key Service: {0}")] KeyService(#[from] KeyServiceError), @@ -76,6 +92,9 @@ pub struct JwtService { access_tkn_validator: JwtValidator, id_tkn_validator: JwtValidator, userinfo_tkn_validator: JwtValidator, + // TODO: implement the usage of this bootstrap property in + // the authz module. + #[allow(dead_code)] id_token_trust_mode: IdTokenTrustMode, } @@ -158,118 +177,53 @@ impl JwtService { }) } - pub fn process_tokens<'a, A, I, U>( + pub fn process_token<'a>( &'a self, - access_token: &'a str, - id_token: &'a str, - userinfo_token: Option<&'a str>, - ) -> Result, JwtProcessingError> - where - A: DeserializeOwned, - I: DeserializeOwned, - U: DeserializeOwned, - { - let access_token = self - .access_tkn_validator - .process_jwt(access_token) - .map_err(JwtProcessingError::InvalidAccessToken)?; - let id_token = self - .id_tkn_validator - .process_jwt(id_token) - .map_err(JwtProcessingError::InvalidIdToken)?; - let userinfo_token = userinfo_token - .map(|jwt| self.userinfo_tkn_validator.process_jwt(jwt)) - .transpose() - .map_err(JwtProcessingError::InvalidUserinfoToken)?; - - // Additional checks for STRICT MODE - if self.id_token_trust_mode == IdTokenTrustMode::Strict { - // Check if id_token.sub == access_token.client_id - let id_tkn_aud = - id_token - .claims - .get("aud") - .ok_or(JwtProcessingError::MissingClaimsInStrictMode( - "id_token", "aud", - ))?; - let access_tkn_client_id = access_token.claims.get("client_id").ok_or( - JwtProcessingError::MissingClaimsInStrictMode("access_token", "client_id"), - )?; - if id_tkn_aud != access_tkn_client_id { - Err(JwtProcessingError::IdTokenAudienceMismatch( - serde_json::from_value::(id_tkn_aud.clone())?, - serde_json::from_value::(access_tkn_client_id.clone())?, - ))? - } - - // If userinfo token is present, check if: - // 1. userinfo_token.sub == id_token.sub - // 2. userinfo_token.aud == access_token.client_id - if let Some(token) = &userinfo_token { - let id_tkn_sub = id_token.claims.get("sub").ok_or( - JwtProcessingError::MissingClaimsInStrictMode("ID Token", "sub"), - )?; - let usrinfo_sub = token.claims.get("sub").ok_or( - JwtProcessingError::MissingClaimsInStrictMode("Userinfo Token", "sub"), - )?; - if usrinfo_sub != id_tkn_sub { - Err(JwtProcessingError::UserinfoSubMismatch( - serde_json::from_value::(usrinfo_sub.clone())?, - serde_json::from_value::(id_tkn_sub.clone())?, - ))? - } - - let usrinfo_aud = token.claims.get("aud").ok_or( - JwtProcessingError::MissingClaimsInStrictMode("Userinfo Token", "aud"), - )?; - if usrinfo_aud != access_tkn_client_id { - Err(JwtProcessingError::UserinfoAudienceMismatch( - serde_json::from_value::(usrinfo_aud.clone())?, - serde_json::from_value::(access_tkn_client_id.clone())?, - ))? - } - } + token: TokenStr<'a>, + ) -> Result, JwtProcessingError> { + match token { + TokenStr::Access(tkn_str) => { + let token = self + .access_tkn_validator + .process_jwt(tkn_str) + .map_err(JwtProcessingError::InvalidAccessToken)?; + let claims = serde_json::from_value::(token.claims)?; + Ok(Token::new_access(claims, token.trusted_iss)) + }, + TokenStr::Id(tkn_str) => { + let token = self + .id_tkn_validator + .process_jwt(tkn_str) + .map_err(JwtProcessingError::InvalidIdToken)?; + let claims = serde_json::from_value::(token.claims)?; + Ok(Token::new_id(claims, token.trusted_iss)) + }, + TokenStr::Userinfo(tkn_str) => { + let token = self + .userinfo_tkn_validator + .process_jwt(tkn_str) + .map_err(JwtProcessingError::InvalidUserinfoToken)?; + let claims = serde_json::from_value::(token.claims)?; + Ok(Token::new_userinfo(claims, token.trusted_iss)) + }, } - - let userinfo_token = match userinfo_token { - Some(token) => token, - None => unimplemented!("Having no userinfo token is not yet supported."), - }; - - Ok(ProcessTokensResult { - access_token: serde_json::from_value::(access_token.claims)?, - id_token: serde_json::from_value::(id_token.claims)?, - userinfo_token: serde_json::from_value::(userinfo_token.claims)?, - // we just assume that all the tokens have the same issuer so we get the - // issuer from the access token. - // this behavior might be changed in future - trusted_issuer: access_token.trusted_iss, - }) } } -#[derive(Debug)] -pub struct ProcessTokensResult<'a, A, I, U> { - pub access_token: A, - pub id_token: I, - pub userinfo_token: U, - pub trusted_issuer: Option<&'a TrustedIssuer>, -} - #[cfg(test)] mod test { - use super::test_utils::*; - use super::JwtService; - use crate::IdTokenTrustMode; - use crate::JwtConfig; - use crate::TokenValidationConfig; + use std::collections::HashSet; + use jsonwebtoken::Algorithm; use serde_json::json; - use serde_json::Value; - use std::collections::HashSet; + use test_utils::assert_eq; + + use super::test_utils::*; + use super::{JwtService, Token, TokenClaims, TokenStr}; + use crate::{IdTokenTrustMode, JwtConfig, TokenValidationConfig}; #[test] - pub fn can_validate_tokens() { + pub fn can_validate_token() { // Generate token let keys = generate_keypair_hs256(Some("some_hs256_key")).expect("Should generate keys"); let access_tkn_claims = json!({ @@ -318,8 +272,28 @@ mod test { ) .expect("Should create JwtService"); - jwt_service - .process_tokens::(&access_tkn, &id_tkn, Some(&userinfo_tkn)) - .expect("Should process JWTs"); + // Test access_token + let access_tkn = jwt_service + .process_token(TokenStr::Access(&access_tkn)) + .expect("Should process access_token"); + let expected_claims = serde_json::from_value::(access_tkn_claims) + .expect("Should create expected access_token claims"); + assert_eq!(access_tkn, Token::new_access(expected_claims.into(), None)); + + // Test id_token + let id_tkn = jwt_service + .process_token(TokenStr::Id(&id_tkn)) + .expect("Should process id_token"); + let expected_claims = serde_json::from_value::(id_tkn_claims) + .expect("Should create expected id_token claims"); + assert_eq!(id_tkn, Token::new_id(expected_claims, None)); + + // Test userinfo_token + let userinfo_tkn = jwt_service + .process_token(TokenStr::Userinfo(&userinfo_tkn)) + .expect("Should process userinfo_token"); + let expected_claims = serde_json::from_value::(userinfo_tkn_claims) + .expect("Should create expected userinfo_token claims"); + assert_eq!(userinfo_tkn, Token::new_userinfo(expected_claims, None)); } } diff --git a/jans-cedarling/cedarling/src/jwt/test_utils.rs b/jans-cedarling/cedarling/src/jwt/test_utils.rs index 7b33ee3f885..33f35ed4cf1 100644 --- a/jans-cedarling/cedarling/src/jwt/test_utils.rs +++ b/jans-cedarling/cedarling/src/jwt/test_utils.rs @@ -1,13 +1,10 @@ -/* - * 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. - */ +// 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 jsonwebkey as jwk; -use jsonwebtoken as jwt; use serde::Serialize; +use {jsonwebkey as jwk, jsonwebtoken as jwt}; /// A pair of encoding and decoding keys. pub struct KeyPair { diff --git a/jans-cedarling/cedarling/src/jwt/token.rs b/jans-cedarling/cedarling/src/jwt/token.rs new file mode 100644 index 00000000000..58a28f31b57 --- /dev/null +++ b/jans-cedarling/cedarling/src/jwt/token.rs @@ -0,0 +1,220 @@ +// 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 serde::Deserialize; +use serde_json::Value; + +use crate::common::policy_store::{ClaimMappings, TokenEntityMetadata, TokenKind, TrustedIssuer}; + +const DEFAULT_USER_ID_SRC_CLAIM: &str = "sub"; +const DEFAULT_ROLE_SRC_CLAIM: &str = "role"; + +pub enum TokenStr<'a> { + Access(&'a str), + Id(&'a str), + Userinfo(&'a str), +} + +#[derive(Debug, PartialEq)] +pub struct Token<'a> { + pub kind: TokenKind, + pub iss: Option<&'a TrustedIssuer>, + claims: TokenClaims, +} + +impl<'a> Token<'a> { + pub fn new_access(claims: TokenClaims, iss: Option<&'a TrustedIssuer>) -> Token<'a> { + Self { + kind: TokenKind::Access, + iss, + claims, + } + } + + pub fn new_id(claims: TokenClaims, iss: Option<&'a TrustedIssuer>) -> Token<'a> { + Self { + kind: TokenKind::Id, + iss, + claims, + } + } + + pub fn new_userinfo(claims: TokenClaims, iss: Option<&'a TrustedIssuer>) -> Token<'a> { + Self { + kind: TokenKind::Userinfo, + iss, + claims, + } + } + + pub fn metadata(&self) -> &TokenEntityMetadata { + self.iss.unwrap_or_default().token_metadata(self.kind) + } + + pub fn user_mapping(&self) -> &str { + self.iss + .unwrap_or_default() + .user_mapping(self.kind) + .unwrap_or(DEFAULT_USER_ID_SRC_CLAIM) + } + + pub fn claim_mapping(&self) -> &ClaimMappings { + self.iss.unwrap_or_default().claim_mapping(self.kind) + } + + pub fn role_mapping(&self) -> &str { + self.iss + .unwrap_or_default() + .role_mapping(self.kind) + .unwrap_or(DEFAULT_ROLE_SRC_CLAIM) + } + + pub fn get_claim(&self, name: &str) -> Option { + self.claims.get_claim(name) + } + + pub fn logging_info(&'a self, claim: &'a str) -> HashMap<&'a str, &'a serde_json::Value> { + self.claims.logging_info(claim) + } + + pub fn claims(&self) -> &TokenClaims { + &self.claims + } +} + +/// A struct holding information on a decoded JWT. +#[derive(Debug, PartialEq, Default, Deserialize)] +pub struct TokenClaims { + #[serde(flatten)] + claims: HashMap, +} + +impl From> for TokenClaims { + fn from(claims: HashMap) -> Self { + Self { claims } + } +} + +impl TokenClaims { + pub fn new(claims: HashMap) -> Self { + Self { claims } + } + + pub fn from_json_map(map: serde_json::Map) -> Self { + Self::new(HashMap::from_iter(map)) + } + + pub fn get_claim(&self, name: &str) -> Option { + self.claims.get(name).map(|value| TokenClaim { + key: name.to_string(), + value, + }) + } + + pub fn logging_info<'a>(&'a self, claim: &'a str) -> HashMap<&'a str, &'a serde_json::Value> { + let claim = if !claim.is_empty() { claim } else { "jti" }; + + let iter = [self.claims.get(claim).map(|value| (claim, value))] + .into_iter() + .flatten(); + + HashMap::from_iter(iter) + } +} + +pub struct TokenClaim<'a> { + key: String, + value: &'a serde_json::Value, +} + +impl TokenClaim<'_> { + pub fn key(&self) -> &str { + &self.key + } + + pub fn value(&self) -> &serde_json::Value { + self.value + } + + pub fn as_i64(&self) -> Result { + self.value + .as_i64() + .ok_or(TokenClaimTypeError::type_mismatch( + &self.key, "i64", self.value, + )) + } + + pub fn as_str(&self) -> Result<&str, TokenClaimTypeError> { + self.value + .as_str() + .ok_or(TokenClaimTypeError::type_mismatch( + &self.key, "String", self.value, + )) + } + + pub fn as_bool(&self) -> Result { + self.value + .as_bool() + .ok_or(TokenClaimTypeError::type_mismatch( + &self.key, "bool", self.value, + )) + } + + pub fn as_array(&self) -> Result, TokenClaimTypeError> { + self.value + .as_array() + .map(|array| { + array + .iter() + .enumerate() + .map(|(i, v)| { + TokenClaim { + // show current key and index in array + key: format!("{}[{}]", self.key, i), + value: v, + } + }) + .collect() + }) + .ok_or(TokenClaimTypeError::type_mismatch( + &self.key, "Array", self.value, + )) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("type mismatch for key '{key}'. expected: '{expected_type}', but found: '{actual_type}'")] +pub struct TokenClaimTypeError { + pub key: String, + pub expected_type: String, + pub actual_type: String, +} + +impl TokenClaimTypeError { + /// Returns the JSON type name of the given value. + pub fn json_value_type_name(value: &Value) -> String { + match value { + Value::Null => "null".to_string(), + Value::Bool(_) => "bool".to_string(), + Value::Number(_) => "number".to_string(), + Value::String(_) => "string".to_string(), + Value::Array(_) => "array".to_string(), + Value::Object(_) => "object".to_string(), + } + } + + /// Constructs a `TypeMismatch` error with detailed information about the expected and actual types. + fn type_mismatch(key: &str, expected_type_name: &str, got_value: &Value) -> Self { + let got_value_type_name = Self::json_value_type_name(got_value); + + Self { + key: key.to_string(), + expected_type: expected_type_name.to_string(), + actual_type: got_value_type_name, + } + } +} diff --git a/jans-cedarling/cedarling/src/jwt/validator.rs b/jans-cedarling/cedarling/src/jwt/validator.rs index 4b73110ca47..55464cf42e5 100644 --- a/jans-cedarling/cedarling/src/jwt/validator.rs +++ b/jans-cedarling/cedarling/src/jwt/validator.rs @@ -1,28 +1,27 @@ -/* - * 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. - */ +// 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. mod config; #[cfg(test)] mod test; -use super::issuers_store::TrustedIssuersStore; -use super::key_service::KeyService; -use crate::common::policy_store::TrustedIssuer; +use std::collections::HashMap; +use std::sync::Arc; + use base64::prelude::*; pub use config::*; -use jsonwebtoken::{self as jwt}; -use jsonwebtoken::{decode_header, Algorithm, Validation}; +use jsonwebtoken::{self as jwt, decode_header, Algorithm, Validation}; use serde_json::Value; -use std::collections::HashMap; -use std::sync::Arc; use url::Url; +use super::issuers_store::TrustedIssuersStore; +use super::key_service::KeyService; +use crate::common::policy_store::TrustedIssuer; + type IssuerId = String; -pub type TokenClaims = Value; +type TokenClaims = Value; /// Validates Json Web Tokens. pub struct JwtValidator { @@ -156,23 +155,27 @@ impl JwtValidator { }; let decode_result = jsonwebtoken::decode::(jwt, decoding_key.key, validation) - .map_err(|e| match e.kind() { - jsonwebtoken::errors::ErrorKind::InvalidToken => JwtValidatorError::InvalidShape, - jsonwebtoken::errors::ErrorKind::InvalidSignature => { - JwtValidatorError::InvalidSignature(e) - }, - jsonwebtoken::errors::ErrorKind::ExpiredSignature => { - JwtValidatorError::ExpiredToken - }, - jsonwebtoken::errors::ErrorKind::ImmatureSignature => { - JwtValidatorError::ImmatureToken - }, - jsonwebtoken::errors::ErrorKind::Base64(decode_error) => { - JwtValidatorError::DecodeJwt(decode_error.to_string()) - }, - // the jsonwebtoken crate placed all it's errors onto a single enum, even the errors - // that wouldn't be returned when we call `decode`. - _ => JwtValidatorError::Unexpected(e), + .map_err(|e| { + match e.kind() { + jsonwebtoken::errors::ErrorKind::InvalidToken => { + JwtValidatorError::InvalidShape + }, + jsonwebtoken::errors::ErrorKind::InvalidSignature => { + JwtValidatorError::InvalidSignature(e) + }, + jsonwebtoken::errors::ErrorKind::ExpiredSignature => { + JwtValidatorError::ExpiredToken + }, + jsonwebtoken::errors::ErrorKind::ImmatureSignature => { + JwtValidatorError::ImmatureToken + }, + jsonwebtoken::errors::ErrorKind::Base64(decode_error) => { + JwtValidatorError::DecodeJwt(decode_error.to_string()) + }, + // the jsonwebtoken crate placed all it's errors onto a single enum, even the errors + // that wouldn't be returned when we call `decode`. + _ => JwtValidatorError::Unexpected(e), + } })?; Ok(ProcessedJwt { diff --git a/jans-cedarling/cedarling/src/jwt/validator/config.rs b/jans-cedarling/cedarling/src/jwt/validator/config.rs index b65f86edb2d..5014568458b 100644 --- a/jans-cedarling/cedarling/src/jwt/validator/config.rs +++ b/jans-cedarling/cedarling/src/jwt/validator/config.rs @@ -1,17 +1,15 @@ -/* - * 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. - */ +// 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, HashSet}; +use std::sync::Arc; + +use jsonwebtoken::Algorithm; use super::IssuerId; use crate::common::policy_store::TrustedIssuer; -use jsonwebtoken::Algorithm; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; /// Validation options related to JSON Web Tokens (JWT). /// diff --git a/jans-cedarling/cedarling/src/jwt/validator/test.rs b/jans-cedarling/cedarling/src/jwt/validator/test.rs index 31c5da0cb2b..97f17b162b1 100644 --- a/jans-cedarling/cedarling/src/jwt/validator/test.rs +++ b/jans-cedarling/cedarling/src/jwt/validator/test.rs @@ -1,19 +1,19 @@ -/* - * 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. - */ +// 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::HashSet; +use std::sync::Arc; + +use jsonwebtoken::Algorithm; +use serde_json::json; +use test_utils::assert_eq; use super::super::test_utils::*; use super::{JwtValidator, JwtValidatorConfig, JwtValidatorError}; use crate::jwt::key_service::KeyService; use crate::jwt::validator::ProcessedJwt; -use jsonwebtoken::Algorithm; -use serde_json::json; -use std::collections::HashSet; -use std::sync::Arc; -use test_utils::assert_eq; #[test] fn can_decode_jwt() { diff --git a/jans-cedarling/cedarling/src/lib.rs b/jans-cedarling/cedarling/src/lib.rs index 4611528d7d3..465de0eab1b 100644 --- a/jans-cedarling/cedarling/src/lib.rs +++ b/jans-cedarling/cedarling/src/lib.rs @@ -1,9 +1,8 @@ -/* - * 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. - */ +// 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. + #![deny(missing_docs)] //! # Cedarling //! The Cedarling is a performant local authorization service that runs the Rust Cedar Engine. @@ -29,31 +28,29 @@ mod tests; use std::sync::Arc; pub use authz::request::{Request, ResourceData}; +#[cfg(test)] +use authz::AuthorizeEntitiesData; use authz::Authz; pub use authz::{AuthorizeError, AuthorizeResult}; pub use bootstrap_config::*; +use common::app_types; use init::service_config::{ServiceConfig, ServiceConfigError}; use init::service_factory::ServiceInitError; use init::ServiceFactory; - -use common::app_types; use log::interface::LogWriter; -use log::LogEntry; -use log::LogType; +use log::{LogEntry, LogType}; pub use log::{LogLevel, LogStorage}; -pub use crate::authz::entities::CedarPolicyCreateTypeError; - -#[cfg(test)] -use authz::AuthorizeEntitiesData; +pub use crate::authz::entities::CreateCedarEntityError; #[doc(hidden)] pub mod bindings { + pub use cedar_policy; + pub use super::log::{ AuthorizationLogInfo, Decision, Diagnostics, LogEntry, PolicyEvaluationError, }; pub use crate::common::policy_store::PolicyStore; - pub use cedar_policy; } /// Errors that can occur during initialization Cedarling. @@ -120,7 +117,7 @@ impl Cedarling { request: &Request, ) -> Result { let tokens = self.authz.decode_tokens(request)?; - self.authz.authorize_entities_data(request, &tokens) + self.authz.build_entities(request, &tokens) } } diff --git a/jans-cedarling/cedarling/src/lock/mod.rs b/jans-cedarling/cedarling/src/lock/mod.rs index 9ccfc4999b2..cace9e1cb68 100644 --- a/jans-cedarling/cedarling/src/lock/mod.rs +++ b/jans-cedarling/cedarling/src/lock/mod.rs @@ -1,9 +1,8 @@ -/* - * 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. - */ +// 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. + //! # Lock Engine //! Lock Engine - it is enterprise feature. //! Janssen Lock (or just "Lock") provides a centralized control plane for domains to use Cedar to secure a network of distributed applications and audit the activity of both people and software. diff --git a/jans-cedarling/cedarling/src/log/interface.rs b/jans-cedarling/cedarling/src/log/interface.rs index 498b915a0cb..cd529e1f422 100644 --- a/jans-cedarling/cedarling/src/log/interface.rs +++ b/jans-cedarling/cedarling/src/log/interface.rs @@ -1,17 +1,15 @@ -/* - * 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. - */ +// 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. //! Log interface //! Contains the interface for logging. And getting log information from storage. -use super::LogEntry; -use super::LogLevel; use uuid7::Uuid; +use super::{LogEntry, LogLevel}; + /// Log Writer /// interface for logging events pub(crate) trait LogWriter { @@ -32,7 +30,6 @@ pub(crate) trait Loggable: serde::Serialize { fn get_log_level(&self) -> Option; /// check if entry can log to logger - /// // default implementation of method // is used to avoid boilerplate code fn can_log(&self, logger_level: LogLevel) -> bool { diff --git a/jans-cedarling/cedarling/src/log/log_entry.rs b/jans-cedarling/cedarling/src/log/log_entry.rs index b60f64404fd..78639461369 100644 --- a/jans-cedarling/cedarling/src/log/log_entry.rs +++ b/jans-cedarling/cedarling/src/log/log_entry.rs @@ -1,29 +1,23 @@ -/* - * 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. - */ +// 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. //! # Log entry //! The module contains structs for logging events. -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fmt::Display; - use std::hash::Hash; -use uuid7::uuid7; -use uuid7::Uuid; +use uuid7::{uuid7, Uuid}; +use super::interface::Loggable; +use super::LogLevel; use crate::bootstrap_config::AuthorizationConfig; use crate::common::app_types::{self, ApplicationName}; use crate::common::policy_store::PoliciesContainer; -use super::interface::Loggable; -use super::LogLevel; - /// ISO-8601 time format for [`chrono`] /// example: 2024-11-27T10:10:50.654Z const ISO8601: &str = "%Y-%m-%dT%H:%M:%S%.3fZ"; @@ -35,7 +29,6 @@ pub struct LogEntry { /// it is unwrap to flatten structure #[serde(flatten)] pub base: BaseLogEntry, - /// message of the event pub msg: String, /// name of application from [bootstrap properties](https://github.com/JanssenProject/jans/wiki/Cedarling-Nativity-Plan#bootstrap-properties) @@ -136,7 +129,7 @@ pub struct AuthorizationLogInfo { // It allow deserialize json to flatten structure. /// Person authorize info #[serde(flatten)] - pub person_authorize_info: Option, + pub person_authorize_info: Option, /// Workload authorize info #[serde(flatten)] pub workload_authorize_info: Option, @@ -147,24 +140,30 @@ pub struct AuthorizationLogInfo { /// Person authorize info #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct PersonAuthorizeInfo { +pub struct UserAuthorizeInfo { /// cedar-policy user/person principal - pub person_principal: String, + #[serde(rename = "person_principal")] + pub principal: String, /// cedar-policy user/person diagnostics information - pub person_diagnostics: Diagnostics, + #[serde(rename = "person_diagnostics")] + pub diagnostics: Diagnostics, /// cedar-policy user/person decision - pub person_decision: Decision, + #[serde(rename = "person_decision")] + pub decision: Decision, } /// Workload authorize info #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct WorkloadAuthorizeInfo { /// cedar-policy workload principal - pub workload_principal: String, + #[serde(rename = "workload_principal")] + pub principal: String, + #[serde(rename = "workload_diagnostics")] /// cedar-policy workload diagnostics information - pub workload_diagnostics: Diagnostics, + pub diagnostics: Diagnostics, /// cedar-policy workload decision - pub workload_decision: Decision, + #[serde(rename = "workload_decision")] + pub decision: Decision, } /// Cedar-policy decision of the authorization @@ -281,12 +280,12 @@ pub struct DecisionLogEntry<'a> { pub principal: PrincipalLogEntry, /// A list of claims, specified by the CEDARLING_DECISION_LOG_USER_CLAIMS property, that must be present in the Cedar User entity #[serde(rename = "User")] - #[serde(skip_serializing_if = "HashMap::is_empty")] - pub user: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option>, /// A list of claims, specified by the CEDARLING_DECISION_LOG_WORKLOAD_CLAIMS property, that must be present in the Cedar Workload entity #[serde(rename = "Workload")] - #[serde(skip_serializing_if = "HashMap::is_empty")] - pub workload: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub workload: Option>, /// If this Cedarling has registered with a Lock Server, what is the client_id it received #[serde(skip_serializing_if = "Option::is_none")] pub lock_client_id: Option, @@ -363,7 +362,6 @@ impl Loggable for BaseLogEntry { } /// Describes what principal is was executed -// // is used only for logging #[derive(Debug, Clone, PartialEq)] pub enum PrincipalLogEntry { @@ -417,8 +415,8 @@ impl serde::Serialize for PrincipalLogEntry { #[derive(Debug, Clone, PartialEq, serde::Serialize)] pub struct LogTokensInfo<'a> { - pub id_token: HashMap<&'a str, &'a serde_json::Value>, + pub id_token: Option>, #[serde(rename = "Userinfo")] - pub userinfo: HashMap<&'a str, &'a serde_json::Value>, - pub access: HashMap<&'a str, &'a serde_json::Value>, + pub userinfo: Option>, + pub access: Option>, } diff --git a/jans-cedarling/cedarling/src/log/log_level.rs b/jans-cedarling/cedarling/src/log/log_level.rs index e3b47e7d40f..55f3cc8be66 100644 --- a/jans-cedarling/cedarling/src/log/log_level.rs +++ b/jans-cedarling/cedarling/src/log/log_level.rs @@ -1,13 +1,12 @@ -/* - * 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. - */ +// 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 serde::{Deserialize, Serialize}; use std::str::FromStr; +use serde::{Deserialize, Serialize}; + /// Log levels /// Fatal level is the highest, trace is lowest #[derive( diff --git a/jans-cedarling/cedarling/src/log/log_strategy.rs b/jans-cedarling/cedarling/src/log/log_strategy.rs index f3627a47f71..c2c96134fc3 100644 --- a/jans-cedarling/cedarling/src/log/log_strategy.rs +++ b/jans-cedarling/cedarling/src/log/log_strategy.rs @@ -1,17 +1,14 @@ -/* - * 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. - */ +// 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::interface::{LogStorage, LogWriter, Loggable}; use super::memory_logger::MemoryLogger; use super::nop_logger::NopLogger; use super::stdout_logger::StdOutLogger; use super::LogEntry; -use crate::bootstrap_config::log_config::LogConfig; -use crate::bootstrap_config::log_config::LogTypeConfig; +use crate::bootstrap_config::log_config::{LogConfig, LogTypeConfig}; /// LogStrategy implements strategy pattern for logging. /// It is used to provide a single point of access for logging and same api for different loggers. diff --git a/jans-cedarling/cedarling/src/log/memory_logger.rs b/jans-cedarling/cedarling/src/log/memory_logger.rs index e3d771eb060..bb0f1aacb4b 100644 --- a/jans-cedarling/cedarling/src/log/memory_logger.rs +++ b/jans-cedarling/cedarling/src/log/memory_logger.rs @@ -1,16 +1,16 @@ -/* - * 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. - */ +// 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::sync::Mutex; +use std::time::Duration; + +use sparkv::{Config as ConfigSparKV, SparKV}; use super::interface::{LogStorage, LogWriter, Loggable}; -use super::LogEntry; -use super::LogLevel; +use super::{LogEntry, LogLevel}; use crate::bootstrap_config::log_config::MemoryLogConfig; -use sparkv::{Config as ConfigSparKV, SparKV}; -use std::{sync::Mutex, time::Duration}; const STORAGE_MUTEX_EXPECT_MESSAGE: &str = "MemoryLogger storage mutex should unlock"; const STORAGE_JSON_PARSE_EXPECT_MESSAGE: &str = @@ -93,10 +93,11 @@ impl LogStorage for MemoryLogger { #[cfg(test)] mod tests { + use test_utils::assert_eq; + use super::super::{AuthorizationLogInfo, LogEntry, LogType}; use super::*; use crate::common::app_types; - use test_utils::assert_eq; fn create_memory_logger() -> MemoryLogger { let config = MemoryLogConfig { log_ttl: 60 }; diff --git a/jans-cedarling/cedarling/src/log/mod.rs b/jans-cedarling/cedarling/src/log/mod.rs index a3d20f39582..80747e88ad8 100644 --- a/jans-cedarling/cedarling/src/log/mod.rs +++ b/jans-cedarling/cedarling/src/log/mod.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. //! # Log Engine //! Log Engine is responsible for log all authz and init events. @@ -67,10 +65,11 @@ mod test; use std::sync::Arc; -use crate::bootstrap_config::log_config::LogConfig; pub use interface::LogStorage; pub(crate) use log_strategy::LogStrategy; +use crate::bootstrap_config::log_config::LogConfig; + /// Type alias for logger that is used in application pub(crate) type Logger = Arc; diff --git a/jans-cedarling/cedarling/src/log/nop_logger.rs b/jans-cedarling/cedarling/src/log/nop_logger.rs index c1a3d7ac7a0..a1093a0b157 100644 --- a/jans-cedarling/cedarling/src/log/nop_logger.rs +++ b/jans-cedarling/cedarling/src/log/nop_logger.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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::interface::LogWriter; diff --git a/jans-cedarling/cedarling/src/log/stdout_logger.rs b/jans-cedarling/cedarling/src/log/stdout_logger.rs index b1b8c1748d6..68b0c55f50c 100644 --- a/jans-cedarling/cedarling/src/log/stdout_logger.rs +++ b/jans-cedarling/cedarling/src/log/stdout_logger.rs @@ -1,16 +1,13 @@ -/* - * 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. - */ +// 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::io::Write; +use std::sync::{Arc, Mutex}; use super::interface::{LogWriter, Loggable}; use super::LogLevel; -use std::{ - io::Write, - sync::{Arc, Mutex}, -}; /// A logger that write to std output. pub(crate) struct StdOutLogger { @@ -94,12 +91,11 @@ impl Write for TestWriter { #[cfg(test)] mod tests { - use crate::common::app_types::PdpID; + use std::io::Write; use super::super::{LogEntry, LogType}; - use super::*; - use std::io::Write; + use crate::common::app_types::PdpID; #[test] fn write_log_ok() { diff --git a/jans-cedarling/cedarling/src/log/test.rs b/jans-cedarling/cedarling/src/log/test.rs index 17b3f3024bd..10fb54ab8a2 100644 --- a/jans-cedarling/cedarling/src/log/test.rs +++ b/jans-cedarling/cedarling/src/log/test.rs @@ -1,16 +1,22 @@ +// 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. + //! Log unit test module //! Contains unit tests for the main code flow with the `LogStrategy`` //! `LogStrategy` wraps all other logger implementations. use std::io::Write; -use super::*; -use crate::{common::app_types, log::stdout_logger::TestWriter}; use interface::{LogWriter, Loggable}; use nop_logger::NopLogger; use stdout_logger::StdOutLogger; +use super::*; use crate::bootstrap_config::log_config; +use crate::common::app_types; +use crate::log::stdout_logger::TestWriter; #[test] fn test_new_log_strategy_off() { diff --git a/jans-cedarling/cedarling/src/tests/cases_authorize_different_principals.rs b/jans-cedarling/cedarling/src/tests/cases_authorize_different_principals.rs index 364584e81a1..6d38674724d 100644 --- a/jans-cedarling/cedarling/src/tests/cases_authorize_different_principals.rs +++ b/jans-cedarling/cedarling/src/tests/cases_authorize_different_principals.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. //! In this module we test authorize different action //! where not all principals can be applied @@ -11,11 +9,12 @@ //! all case scenario should have `result.is_allowed() == true` //! because we have checked different scenarios in `cases_authorize_without_check_jwt.rs` -use super::utils::*; -use crate::{cmp_decision, cmp_policy, WorkloadBoolOp}; // macros is defined in the cedarling\src\tests\utils\cedarling_util.rs use lazy_static::lazy_static; use test_utils::assert_eq; +use super::utils::*; +use crate::{cmp_decision, cmp_policy, WorkloadBoolOp}; /* macros is defined in the cedarling\src\tests\utils\cedarling_util.rs */ + static POLICY_STORE_RAW_YAML: &str = include_str!("../../../test_files/policy-store_ok_2.yaml"); lazy_static! { @@ -415,7 +414,7 @@ fn test_where_principal_workload_cant_be_applied() { assert!(matches!( result, - crate::AuthorizeError::CreateRequestWorkloadEntity(_) + crate::AuthorizeError::WorkloadRequestValidation(_) )) } @@ -439,8 +438,9 @@ fn test_where_principal_user_cant_be_applied() { .authorize(request) .expect_err("request should be parsed with error"); - assert!(matches!( - result, - crate::AuthorizeError::CreateRequestUserEntity(_) - )) + assert!( + matches!(result, crate::AuthorizeError::UserRequestValidation(_)), + "expected error UserRequestValidation, got: {}", + result + ) } diff --git a/jans-cedarling/cedarling/src/tests/cases_authorize_namespace_jans2.rs b/jans-cedarling/cedarling/src/tests/cases_authorize_namespace_jans2.rs index b1766ef3fe6..a09753429d2 100644 --- a/jans-cedarling/cedarling/src/tests/cases_authorize_namespace_jans2.rs +++ b/jans-cedarling/cedarling/src/tests/cases_authorize_namespace_jans2.rs @@ -1,14 +1,13 @@ -/* - * 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. - */ +// 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::utils::*; -use crate::{cmp_decision, cmp_policy}; // macros is defined in the cedarling\src\tests\utils\cedarling_util.rs use test_utils::assert_eq; +use super::utils::*; +use crate::{cmp_decision, cmp_policy}; /* macros is defined in the cedarling\src\tests\utils\cedarling_util.rs */ + static POLICY_STORE_RAW_YAML: &str = include_str!("../../../test_files/policy-store_ok_namespace_Jans2.yaml"); diff --git a/jans-cedarling/cedarling/src/tests/cases_authorize_without_check_jwt.rs b/jans-cedarling/cedarling/src/tests/cases_authorize_without_check_jwt.rs index a3cc11e6840..6503c477b2a 100644 --- a/jans-cedarling/cedarling/src/tests/cases_authorize_without_check_jwt.rs +++ b/jans-cedarling/cedarling/src/tests/cases_authorize_without_check_jwt.rs @@ -1,14 +1,13 @@ -/* - * 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. - */ +// 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::utils::*; -use crate::{cmp_decision, cmp_policy}; // macros is defined in the cedarling\src\tests\utils\cedarling_util.rs use test_utils::assert_eq; +use super::utils::*; +use crate::{cmp_decision, cmp_policy}; /* macros is defined in the cedarling\src\tests\utils\cedarling_util.rs */ + static POLICY_STORE_RAW_YAML: &str = include_str!("../../../test_files/policy-store_ok_2.yaml"); static POLICY_STORE_ABAC_YAML: &str = include_str!("../../../test_files/policy-store_ok_abac.yaml"); diff --git a/jans-cedarling/cedarling/src/tests/mapping_entities.rs b/jans-cedarling/cedarling/src/tests/mapping_entities.rs index 0e4d1651fdb..90e3e3ed97f 100644 --- a/jans-cedarling/cedarling/src/tests/mapping_entities.rs +++ b/jans-cedarling/cedarling/src/tests/mapping_entities.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. //! In this test cases we check mapping entities using Bootstrap properties: //! CEDARLING_MAPPING_USER @@ -12,14 +10,16 @@ //! CEDARLING_MAPPING_ACCESS_TOKEN //! CEDARLING_MAPPING_USERINFO_TOKEN -use super::utils::*; -use crate::{AuthorizeError, Cedarling}; -use crate::{CedarPolicyCreateTypeError, cmp_decision, cmp_policy}; -use cedarling_util::get_raw_config; use std::collections::HashSet; use std::sync::LazyLock; + +use cedarling_util::get_raw_config; use test_utils::assert_eq; +use super::utils::*; +use crate::common::policy_store::TokenKind; +use crate::{cmp_decision, cmp_policy, AuthorizeError, Cedarling, CreateCedarEntityError}; + static POLICY_STORE_RAW_YAML: &str = include_str!("../../../test_files/policy-store_entity_mapping.yaml"); @@ -162,7 +162,8 @@ fn test_custom_mapping() { fn test_failed_user_mapping() { let mut raw_config = get_raw_config(POLICY_STORE_RAW_YAML); - raw_config.mapping_user = Some("MappedUserNotExist".to_string()); + let entity_type = "MappedUserNotExist".to_string(); + raw_config.mapping_user = Some(entity_type.to_string()); let config = crate::BootstrapConfig::from_raw_config(&raw_config) .expect("raw config should parse without errors"); @@ -175,22 +176,38 @@ fn test_failed_user_mapping() { .authorize(request) .expect_err("request should be parsed with mapping error"); - assert!( - matches!( - err, - AuthorizeError::CreateUserEntity(CedarPolicyCreateTypeError::CouldNotFindEntity(_)) - ), - "should be error CouldNotFindEntity, got: {:?}", - err - ); + match err { + AuthorizeError::CreateUserEntity(error) => { + assert_eq!(error.errors.len(), 2, "there should be 2 errors"); + + let (token_kind, err) = &error.errors[0]; + assert_eq!(token_kind, &TokenKind::Userinfo); + assert!( + matches!(err, CreateCedarEntityError::CouldNotFindEntity(ref err) if err == &entity_type), + "expected CouldNotFindEntity({}), got: {:?}", + &entity_type, + err, + ); + + let (token_kind, err) = &error.errors[1]; + assert_eq!(token_kind, &TokenKind::Id); + assert!( + matches!(err, CreateCedarEntityError::CouldNotFindEntity(ref err) if err == &entity_type), + "expected CouldNotFindEntity({}), got: {:?}", + &entity_type, + err, + ); + }, + _ => panic!("expected error CreateWorkloadEntity"), + } } /// Check if we get error on mapping workload to undefined entity #[test] fn test_failed_workload_mapping() { let mut raw_config = get_raw_config(POLICY_STORE_RAW_YAML); - - raw_config.mapping_workload = Some("MappedWorkloadNotExist".to_string()); + let entity_type = "MappedWorkloadNotExist".to_string(); + raw_config.mapping_workload = Some(entity_type.clone()); let config = crate::BootstrapConfig::from_raw_config(&raw_config) .expect("raw config should parse without errors"); @@ -203,13 +220,41 @@ fn test_failed_workload_mapping() { .authorize(request) .expect_err("request should be parsed with mapping error"); - assert!( - matches!( - err, - AuthorizeError::CreateWorkloadEntity(CedarPolicyCreateTypeError::CouldNotFindEntity(_)) - ), - "should be error CouldNotFindEntity" - ); + match err { + AuthorizeError::CreateWorkloadEntity(error) => { + assert_eq!(error.errors.len(), 3, "there should be 3 errors"); + + // check for access token error + let (token_kind, err) = &error.errors[0]; + assert_eq!(token_kind, &TokenKind::Access); + assert!( + matches!(err, CreateCedarEntityError::CouldNotFindEntity(ref err) if err == &entity_type), + "expected CouldNotFindEntity(\"{}\"), got: {:?}", + &entity_type, + err, + ); + + // check for id token error + let (token_kind, err) = &error.errors[1]; + assert_eq!(token_kind, &TokenKind::Id); + assert!( + matches!(err, CreateCedarEntityError::CouldNotFindEntity(ref err) if err == &entity_type), + "expected CouldNotFindEntity(\"{}\"), got: {:?}", + &entity_type, + err, + ); + + // check for userinfo token error + let (token_kind, err) = &error.errors[2]; + assert_eq!(token_kind, &TokenKind::Userinfo); + assert!( + matches!(err, CreateCedarEntityError::MissingClaim(ref claim) if claim == "aud"), + "expected MissinClaim(\"aud\"), got: {:?}", + err + ); + }, + _ => panic!("expected error CreateWorkloadEntity"), + } } /// Check if we get error on mapping id_token to undefined entity @@ -233,7 +278,7 @@ fn test_failed_id_token_mapping() { assert!( matches!( err, - AuthorizeError::CreateIdTokenEntity(CedarPolicyCreateTypeError::CouldNotFindEntity(_)) + AuthorizeError::CreateIdTokenEntity(CreateCedarEntityError::CouldNotFindEntity(_)) ), "should be error CouldNotFindEntity, got: {err:?}" ); @@ -260,9 +305,7 @@ fn test_failed_access_token_mapping() { assert!( matches!( err, - AuthorizeError::CreateAccessTokenEntity( - CedarPolicyCreateTypeError::CouldNotFindEntity(_) - ) + AuthorizeError::CreateAccessTokenEntity(CreateCedarEntityError::CouldNotFindEntity(_)) ), "should be error CouldNotFindEntity" ); @@ -289,9 +332,9 @@ fn test_failed_userinfo_token_mapping() { assert!( matches!( err, - AuthorizeError::CreateUserinfoTokenEntity( - CedarPolicyCreateTypeError::CouldNotFindEntity(_) - ) + AuthorizeError::CreateUserinfoTokenEntity(CreateCedarEntityError::CouldNotFindEntity( + _ + )) ), "should be error CouldNotFindEntity" ); @@ -355,7 +398,7 @@ fn test_role_many_tokens_mapping() { let roles_left = cedarling .authorize_entities_data(&request) .expect("should get authorize_entities_data without errors") - .role_entities + .roles .into_iter() .map(|entity| entity.uid().id().escaped()) // if role successfully removed from `expected_role_ids` we filter it diff --git a/jans-cedarling/cedarling/src/tests/mod.rs b/jans-cedarling/cedarling/src/tests/mod.rs index 94a52482cfc..a97558c9c15 100644 --- a/jans-cedarling/cedarling/src/tests/mod.rs +++ b/jans-cedarling/cedarling/src/tests/mod.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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. //! This module is used for integration tests. diff --git a/jans-cedarling/cedarling/src/tests/schema_type_mapping.rs b/jans-cedarling/cedarling/src/tests/schema_type_mapping.rs index b68f9356309..1583a7670f1 100644 --- a/jans-cedarling/cedarling/src/tests/schema_type_mapping.rs +++ b/jans-cedarling/cedarling/src/tests/schema_type_mapping.rs @@ -1,13 +1,12 @@ -/* - * 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. - */ +// 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::utils::*; use test_utils::{assert_eq, SortedJson}; +use super::utils::*; + static POLICY_STORE_RAW_YAML: &str = include_str!("../../../test_files/agama-store_2.yaml"); /// Test loading policy store with mappings JWT payload to custom `cedar-entities` types in schema @@ -117,7 +116,7 @@ fn check_mapping_tokens_data() { let expected_resource = json!({"uid":{"type":"Test::Application","id":"SomeID"},"attrs":{"url":{"host":"test-casa.gluu.info","path":"/","protocol":"https"},"app_id":"1234","name":"some_app"},"parents":[]}).sorted(); assert_eq!( expected_resource, - entities.resource_entity.to_json_value().unwrap().sorted(), + entities.resource.to_json_value().unwrap().sorted(), "derived resource_entity is not equal to the expected" ); @@ -126,7 +125,7 @@ fn check_mapping_tokens_data() { let expected_user = json!({"attrs":{"email":{"domain":"example.com", "uid":"user"},"role":["Manager","Support"],"sub":"J3BmtnPPB8BjMbScWmR8cjT9gWCCTHKfSf0dkbOvhGg"},"parents":[{"id":"Manager","type":"Test::Role"},{"id":"Support","type":"Test::Role"}],"uid":{"id":"J3BmtnPPB8BjMbScWmR8cjT9gWCCTHKfSf0dkbOvhGg","type":"Test::User"}}).sorted(); assert_eq!( expected_user, - entities.user_entity.to_json_value().unwrap().sorted(), + entities.user.unwrap().to_json_value().unwrap().sorted(), "derived user_entity is not equal to the expected" ); } diff --git a/jans-cedarling/cedarling/src/tests/success_test_json.rs b/jans-cedarling/cedarling/src/tests/success_test_json.rs index 55512fefcaa..2ac61219cbe 100644 --- a/jans-cedarling/cedarling/src/tests/success_test_json.rs +++ b/jans-cedarling/cedarling/src/tests/success_test_json.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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::utils::*; diff --git a/jans-cedarling/cedarling/src/tests/utils/cedarling_util.rs b/jans-cedarling/cedarling/src/tests/utils/cedarling_util.rs index 2e2482b0b27..df7f7b295e5 100644 --- a/jans-cedarling/cedarling/src/tests/utils/cedarling_util.rs +++ b/jans-cedarling/cedarling/src/tests/utils/cedarling_util.rs @@ -1,9 +1,7 @@ -/* - * 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. - */ +// 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 crate::{AuthorizationConfig, JwtConfig, WorkloadBoolOp}; pub use crate::{ diff --git a/jans-cedarling/cedarling/src/tests/utils/mod.rs b/jans-cedarling/cedarling/src/tests/utils/mod.rs index c2471aee308..b31c1d6c11a 100644 --- a/jans-cedarling/cedarling/src/tests/utils/mod.rs +++ b/jans-cedarling/cedarling/src/tests/utils/mod.rs @@ -1,15 +1,14 @@ -/* - * 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. - */ +// 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. -pub use crate::{PolicyStoreSource, Request}; pub use cedar_policy::Decision; pub use serde::Deserialize; pub use serde_json::json; +pub use crate::{PolicyStoreSource, Request}; + pub mod cedarling_util; pub mod token_claims; pub use cedarling_util::{get_cedarling, get_cedarling_with_authorization_conf}; diff --git a/jans-cedarling/cedarling/src/tests/utils/token_claims.rs b/jans-cedarling/cedarling/src/tests/utils/token_claims.rs index ce8801d7a27..7529926f241 100644 --- a/jans-cedarling/cedarling/src/tests/utils/token_claims.rs +++ b/jans-cedarling/cedarling/src/tests/utils/token_claims.rs @@ -1,14 +1,10 @@ -/* - * 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. - */ +// 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 lazy_static::lazy_static; - -use jsonwebkey as jwk; -use jsonwebtoken as jwt; +use {jsonwebkey as jwk, jsonwebtoken as jwt}; // Represent meta information about entity from cedar-policy schema. lazy_static! { diff --git a/jans-cedarling/test_files/policy-store_ok_2.yaml b/jans-cedarling/test_files/policy-store_ok_2.yaml index 2d18a36006d..9218bbb746f 100644 --- a/jans-cedarling/test_files/policy-store_ok_2.yaml +++ b/jans-cedarling/test_files/policy-store_ok_2.yaml @@ -83,6 +83,18 @@ policy_stores: id_token: id_token, userinfo_token: Userinfo_token, }; + type ContextWithoutWorkload = { + user: User, + access_token: Access_token, + id_token: id_token, + userinfo_token: Userinfo_token, + }; + type ContextWithoutUser = { + workload: Workload, + access_token: Access_token, + id_token: id_token, + userinfo_token: Userinfo_token, + }; action "Update" appliesTo { principal: [Workload, User], resource: [Issue], @@ -91,12 +103,12 @@ policy_stores: action "UpdateForWorkload" appliesTo { principal: [Workload, User], resource: [Issue], - context: Context + context: ContextWithoutUser }; action "UpdateForUser" appliesTo { principal: [Workload, User], resource: [Issue], - context: Context + context: ContextWithoutWorkload }; action "UpdateForRole" appliesTo { principal: [Role], @@ -106,7 +118,7 @@ policy_stores: action "UpdateForUserAndRole" appliesTo { principal: [Workload, User], resource: [Issue], - context: Context + context: ContextWithoutWorkload }; action "UpdateForWorkloadAndRole" appliesTo { principal: [Workload, User], @@ -116,7 +128,7 @@ policy_stores: action "NoApplies" appliesTo { principal: [Empty], resource: [Issue], - context: Context + context: ContextWithoutWorkload }; action "AlwaysDeny" appliesTo { principal: [Workload, User],