Skip to content

Commit

Permalink
refactor(jans-cedarling): update AuthZ interface to use tokens for …
Browse files Browse the repository at this point in the history
…all JWTs sent as input (#10521)

* refactor(jans-cedarling): update AuthZ interface to use tokens for all JWTs sent as input

Signed-off-by: rmarinn <[email protected]>

* docs(jans-cedarling): update docs for AuthZ interface

Signed-off-by: rmarinn <[email protected]>

* fix(jans-cedarling): incorrect error message

Signed-off-by: rmarinn <[email protected]>

* fix(jans-cedarling): remove workload entity creation using userinfo_token

Signed-off-by: rmarinn <[email protected]>

* fix(jans-cedarling): broken tests

- fix the broken tests due to removing the creation of workload entity
  using the userinfo token

Signed-off-by: rmarinn <[email protected]>

* fix(jans-cedarling): update .pyi file to match with rust define structs

Signed-off-by: rmarinn <[email protected]>

* chore: remove static version

Signed-off-by: SafinWasi <[email protected]>

* chore(jans-cedarling): update incorrect python docstring

Signed-off-by: rmarinn <[email protected]>

* chore(jans-cedarling): update PYTHON_TYPES.md

Signed-off-by: rmarinn <[email protected]>

---------

Signed-off-by: rmarinn <[email protected]>
Signed-off-by: SafinWasi <[email protected]>
Co-authored-by: SafinWasi <[email protected]>
  • Loading branch information
rmarinn and SafinWasi authored Jan 3, 2025
1 parent 3c00152 commit cec0b70
Show file tree
Hide file tree
Showing 21 changed files with 335 additions and 255 deletions.
37 changes: 19 additions & 18 deletions docs/cedarling/cedarling-authz.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,25 @@ this is a sample request from a hypothetical application:

```js
input = {
"access_token": "eyJhbGc....",
"id_token": "eyJjbGc...",
"userinfo_token": "eyJjbGc...",
"tx_token": "eyJjbGc...",
"action": "View",
"resource": {
"id": "ticket-10101",
"type" : "Ticket",
"owner": "[email protected]",
"org_id": "Acme"
},
"context": {
"ip_address": "54.9.21.201",
"network_type": "VPN",
"user_agent": "Chrome 125.0.6422.77 (Official Build) (arm64)",
"time": "1719266610.98636",
}
}
"tokens": {
"access_token": "eyJhbGc....",
"id_token": "eyJjbGc...",
"userinfo_token": "eyJjbGc...",
},
"action": "View",
"resource": {
"id": "ticket-10101",
"type" : "Ticket",
"owner": "[email protected]",
"org_id": "Acme"
},
"context": {
"ip_address": "54.9.21.201",
"network_type": "VPN",
"user_agent": "Chrome 125.0.6422.77 (Official Build) (arm64)",
"time": "1719266610.98636",
}
}

decision_result = authz(input)
```
Expand Down
6 changes: 2 additions & 4 deletions jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,10 @@ authorization data with access token, action, resource, and context.

Attributes
----------
:param tokens: A class containing the JWTs what will be used for the request.
: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.
:param context: Python dictionary with additional context.

Example
-------
Expand Down
19 changes: 13 additions & 6 deletions jans-cedarling/bindings/cedarling_python/cedarling_python.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,28 @@ class Cedarling:

@final
class Request:
tokens: Tokens
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,
id_token: str,
userinfo_token: str,
tokens: Tokens,
action: str,
resource: ResourceData,
context: Dict[str, Any]) -> None: ...

@final
class Tokens:
access_token: str | None
id_token: str | None
userinfo_token: str | None

def __init__(self,
access_token: str | None,
id_token: str | None,
userinfo_token: str | None) -> None: ...


@final
class ResourceData:
Expand Down
6 changes: 2 additions & 4 deletions jans-cedarling/bindings/cedarling_python/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# Copyright (c) 2024, Gluu, Inc.

from cedarling_python import BootstrapConfig
from cedarling_python import BootstrapConfig, Tokens
from cedarling_python import Cedarling
from cedarling_python import ResourceData, Request
import time
Expand Down Expand Up @@ -189,9 +189,7 @@
action = 'Jans::Action::"Read"'

request = Request(
access_token,
id_token,
userinfo_token,
tokens=Tokens(access_token, id_token, userinfo_token),
action=action,
resource=resource, context=context)

Expand Down
1 change: 0 additions & 1 deletion jans-cedarling/bindings/cedarling_python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ build-backend = "maturin"

[project]
name = "cedarling_python"
version = "0.0.0"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Rust",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub fn register_entities(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<decision::Decision>()?;
m.add_class::<resource_data::ResourceData>()?;
m.add_class::<request::Request>()?;
m.add_class::<request::Tokens>()?;
m.add_class::<authorize_result_response::AuthorizeResultResponse>()?;
m.add_class::<authorize_result::AuthorizeResult>()?;

Expand Down
71 changes: 54 additions & 17 deletions jans-cedarling/bindings/cedarling_python/src/authorize/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ use serde_pyobject::from_pyobject;
///
/// Attributes
/// ----------
/// :param tokens: A class containing the JWTs what will be used for the request.
/// :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
/// -------
Expand All @@ -35,12 +33,7 @@ use serde_pyobject::from_pyobject;
/// ```
#[pyclass(get_all, set_all)]
pub struct Request {
/// Access token raw value
pub access_token: Option<String>,
/// Id token raw value
pub id_token: Option<String>,
/// Userinfo token raw value
pub userinfo_token: Option<String>,
pub tokens: Tokens,
/// cedar_policy action
pub action: String,
/// cedar_policy resource data
Expand All @@ -49,14 +42,39 @@ pub struct Request {
pub context: Py<PyDict>,
}

/// Tokens
/// =======
///
/// A Python wrapper for the Rust `cedarling::Token` struct. Contains the JWTs
/// that will be used for the AuthZ request.
///
/// Attributes
/// ----------
/// :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
/// -------
/// ```python
/// tokens = Request("your_access_tkn", "your_id_tkn", "your_userinfo_tkn")
/// ```
#[derive(Clone)]
#[pyclass(get_all, set_all)]
pub struct Tokens {
/// Access token raw value
pub access_token: Option<String>,
/// Id token raw value
pub id_token: Option<String>,
/// Userinfo token raw value
pub userinfo_token: Option<String>,
}

#[pymethods]
impl Request {
impl Tokens {
#[new]
#[pyo3(signature = (action, resource, context, access_token=None, id_token=None, userinfo_token=None))]
#[pyo3(signature = (access_token, id_token, userinfo_token))]
fn new(
action: String,
resource: ResourceData,
context: Py<PyDict>,
access_token: Option<String>,
id_token: Option<String>,
userinfo_token: Option<String>,
Expand All @@ -65,13 +83,34 @@ impl Request {
access_token,
id_token,
userinfo_token,
}
}
}

#[pymethods]
impl Request {
#[new]
#[pyo3(signature = (tokens, action, resource, context))]
fn new(tokens: Tokens, action: String, resource: ResourceData, context: Py<PyDict>) -> Self {
Self {
tokens,
action,
resource,
context,
}
}
}

impl From<Tokens> for cedarling::Tokens {
fn from(tokens: Tokens) -> Self {
Self {
access_token: tokens.access_token,
id_token: tokens.id_token,
userinfo_token: tokens.userinfo_token,
}
}
}

impl Request {
pub fn to_cedarling(&self) -> Result<cedarling::Request, PyErr> {
let context = Python::with_gil(|py| -> Result<serde_json::Value, PyErr> {
Expand All @@ -82,9 +121,7 @@ impl Request {
})?;

Ok(cedarling::Request {
access_token: self.access_token.clone(),
id_token: self.id_token.clone(),
userinfo_token: self.userinfo_token.clone(),
tokens: self.tokens.clone().into(),
action: self.action.clone(),
resource: self.resource.clone().into(),
context,
Expand Down
10 changes: 3 additions & 7 deletions jans-cedarling/bindings/cedarling_python/tests/test_authorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# Copyright (c) 2024, Gluu, Inc.

from cedarling_python import Cedarling
from cedarling_python import Cedarling, Tokens
from cedarling_python import ResourceData, Request, authorize_errors
from config import load_bootstrap_config

Expand Down Expand Up @@ -115,9 +115,7 @@ def test_authorize_ok():
})

request = Request(
access_token=ACCESS_TOKEN,
id_token=ID_TOKEN,
userinfo_token=USERINFO_TOKEN,
tokens=Tokens(ACCESS_TOKEN, ID_TOKEN, USERINFO_TOKEN),
action='Jans::Action::"Update"',
context={},
resource=resource,
Expand Down Expand Up @@ -172,9 +170,7 @@ def raise_authorize_error(bootstrap_config):
})

request = Request(
access_token=ACCESS_TOKEN,
id_token=ID_TOKEN,
userinfo_token=USERINFO_TOKEN,
tokens=Tokens(ACCESS_TOKEN, ID_TOKEN, USERINFO_TOKEN),
action='Jans::Action::"Update"',
context={}, resource=resource)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet};
use cedarling::{
AuthorizationConfig, BootstrapConfig, Cedarling, IdTokenTrustMode, JwtConfig, LogConfig,
LogLevel, LogTypeConfig, PolicyStoreConfig, PolicyStoreSource, Request, ResourceData,
TokenValidationConfig, WorkloadBoolOp,
TokenValidationConfig, Tokens, WorkloadBoolOp,
};
use jsonwebtoken::Algorithm;

Expand Down Expand Up @@ -61,9 +61,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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: Some(access_token),
id_token: Some(id_token),
userinfo_token: Some(userinfo_token),
tokens: Tokens {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::collections::HashMap;

use cedarling::{
AuthorizationConfig, BootstrapConfig, Cedarling, JwtConfig, LogConfig, LogLevel, LogTypeConfig,
PolicyStoreConfig, PolicyStoreSource, Request, ResourceData, WorkloadBoolOp,
PolicyStoreConfig, PolicyStoreSource, Request, ResourceData, Tokens, WorkloadBoolOp,
};

static POLICY_STORE_RAW: &str = include_str!("../../test_files/policy-store_ok.yaml");
Expand Down Expand Up @@ -112,9 +112,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let userinfo_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FkbWluLXVpLXRlc3QuZ2x1dS5vcmciLCJzdWIiOiJib0c4ZGZjNU1LVG4zN283Z3NkQ2V5cUw4THBXUXRnb080MW0xS1p3ZHEwIiwiY2xpZW50X2lkIjoiNWI0NDg3YzQtOGRiMS00MDlkLWE2NTMtZjkwN2I4MDk0MDM5IiwiYXVkIjoiNWI0NDg3YzQtOGRiMS00MDlkLWE2NTMtZjkwN2I4MDk0MDM5IiwidXNlcm5hbWUiOiJhZG1pbkBnbHV1Lm9yZyIsIm5hbWUiOiJEZWZhdWx0IEFkbWluIFVzZXIiLCJlbWFpbCI6ImFkbWluQGdsdXUub3JnIiwiY291bnRyeSI6IlVTIiwianRpIjoidXNyaW5mb190a25fanRpIn0.NoR53vPZFpfb4vFk85JH9RPx7CHsaJMZwrH3fnB-N60".to_string();

let result = cedarling.authorize(Request {
access_token: Some(access_token),
id_token: Some(id_token),
userinfo_token: Some(userinfo_token),
tokens: Tokens {
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 {
Expand Down
2 changes: 1 addition & 1 deletion jans-cedarling/cedarling/src/authz/entities/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,6 @@ pub enum CreateCedarEntityError {
UnavailableToken,

/// Missing claim
#[error("{0} Entity creation failed: no available token to build the entity from")]
#[error("missing claim: {0}")]
MissingClaim(String),
}
3 changes: 1 addition & 2 deletions jans-cedarling/cedarling/src/authz/entities/workload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ pub fn create_workload_entity(
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),
Expand Down Expand Up @@ -233,7 +232,7 @@ mod test {
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);
assert_eq!(result.errors.len(), 2);
for (_tkn_kind, err) in result.errors.iter() {
assert!(
matches!(err, CreateCedarEntityError::UnavailableToken),
Expand Down
11 changes: 7 additions & 4 deletions jans-cedarling/cedarling/src/authz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ use std::time::Instant;
pub use authorize_result::AuthorizeResult;
use cedar_policy::{ContextJsonError, Entities, Entity, EntityUid};
use entities::{
CEDAR_POLICY_SEPARATOR, CreateCedarEntityError, CreateUserEntityError,
CreateWorkloadEntityError, DecodedTokens, ResourceEntityError, RoleEntityError,
create_resource_entity, create_role_entities, create_token_entities, create_user_entity,
create_workload_entity,
create_workload_entity, CreateCedarEntityError, CreateUserEntityError,
CreateWorkloadEntityError, DecodedTokens, ResourceEntityError, RoleEntityError,
CEDAR_POLICY_SEPARATOR,
};
use merge_json::{MergeError, merge_json_values};
use merge_json::{merge_json_values, MergeError};
use request::Request;
use serde_json::Value;

Expand Down Expand Up @@ -87,16 +87,19 @@ impl Authz {
request: &'a Request,
) -> Result<DecodedTokens<'a>, AuthorizeError> {
let access_token = request
.tokens
.access_token
.as_ref()
.map(|tkn| self.config.jwt_service.process_token(TokenStr::Access(tkn)))
.transpose()?;
let id_token = request
.tokens
.id_token
.as_ref()
.map(|tkn| self.config.jwt_service.process_token(TokenStr::Id(tkn)))
.transpose()?;
let userinfo_token = request
.tokens
.userinfo_token
.as_ref()
.map(|tkn| {
Expand Down
Loading

0 comments on commit cec0b70

Please sign in to comment.