Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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
Loading