Skip to content

Commit

Permalink
feat(jans-cedarling): Implement check authorization principals based …
Browse files Browse the repository at this point in the history
…on the schema for action (#10126)

* chore(jans-cedarling): move `cedar_schema.rs` file to `mod.rs`

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): refactor move types to parse cedar-policy entities to file `entity_types.rs`

Signed-off-by: Oleh Bohzok <[email protected]>

* feat(jans-cedarling): add parsing actions from json cedar-policy schema

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): update `AuthorizationLogInfo` to be more flexible and to be `authorize_info` optional for each principal

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): add update to `AuthorizationLogInfo` structure `role_authorize_info` now is vector

It allows to log many role authorize info results

Signed-off-by: Oleh Bohzok <[email protected]>

* feat(jans-cedarling): add authorize check only for defined `principal`s in `schema` for `action`

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): refactor test/utils module, split functions to different folders

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): move util macros to util test util crate

Signed-off-by: Oleh Bohzok <[email protected]>

* test(jans-cedarling): add test check when different principal can be applied to action

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): implement more easier implementation to apply principal only when it can be applied to action

Signed-off-by: Oleh Bohzok <[email protected]>

* feat(jans-cedarling): add loading namespace from policy store

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): fix test cases after changes

Signed-off-by: Oleh Bohzok <[email protected]>

* test(jans-cedarling): add test case to check if namespace different from `Jans` works correctly

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): fix clippy issues

Signed-off-by: Oleh Bohzok <[email protected]>

* chore(jans-cedarling): add better comment to macros

Signed-off-by: Oleh Bohzok <[email protected]>

---------

Signed-off-by: Oleh Bohzok <[email protected]>
  • Loading branch information
olehbozhok authored Nov 14, 2024
1 parent eac6fd1 commit 774f779
Show file tree
Hide file tree
Showing 32 changed files with 1,437 additions and 726 deletions.
7 changes: 4 additions & 3 deletions jans-cedarling/bindings/cedarling_python/cedarling_python.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class DisabledLoggingConfig:

@final
class PolicyStoreSource:
def __init__(self, json: Optional[str] = None, yaml: Optional[str] = None) -> None: ...
def __init__(self, json: Optional[str] = None,
yaml: Optional[str] = None) -> None: ...


@final
Expand Down Expand Up @@ -107,9 +108,9 @@ class ResourceData:
class AuthorizeResult:
def is_allowed(self) -> bool: ...

def workload(self) -> AuthorizeResultResponse: ...
def workload(self) -> AuthorizeResultResponse | None: ...

def person(self) -> AuthorizeResultResponse: ...
def person(self) -> AuthorizeResultResponse | None: ...

def role(self) -> AuthorizeResultResponse | None: ...

Expand Down
35 changes: 19 additions & 16 deletions jans-cedarling/bindings/cedarling_python/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,29 +196,32 @@
workload_result = authorize_result.workload()
print(f"Result of workload authorization: {workload_result.decision}")

# show diagnostic information
workload_diagnostic = workload_result.diagnostics
print("Policy ID(s) used:")
for diagnostic in workload_diagnostic.reason:
print(diagnostic)
if workload_result is not None:
# show diagnostic information
workload_diagnostic = workload_result.diagnostics
print("Policy ID(s) used:")
for diagnostic in workload_diagnostic.reason:
print(diagnostic)

print("Errors during authorization:")
for diagnostic in workload_diagnostic.errors:
print(diagnostic)
print("Errors during authorization:")
for diagnostic in workload_diagnostic.errors:
print(diagnostic)

print()

# watch on the decision for person
person_result = authorize_result.person()
print(f"Result of person authorization: {person_result.decision}")
person_diagnostic = person_result.diagnostics
print("Policy ID(s) used:")
for diagnostic in person_diagnostic.reason:
print(diagnostic)

print("Errors during authorization:")
for diagnostic in person_diagnostic.errors:
print(diagnostic)

if person_result is not None:
person_diagnostic = person_result.diagnostics
print("Policy ID(s) used:")
for diagnostic in person_diagnostic.reason:
print(diagnostic)

print("Errors during authorization:")
for diagnostic in person_diagnostic.errors:
print(diagnostic)


# watch on the decision for role if present
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ impl AuthorizeResult {
}

/// Get the decision value for workload
fn workload(&self) -> AuthorizeResultResponse {
self.inner.workload.clone().into()
fn workload(&self) -> Option<AuthorizeResultResponse> {
self.inner.workload.clone().map(|v| v.into())
}

/// Get the decision value for person/user
fn person(&self) -> AuthorizeResultResponse {
self.inner.person.clone().into()
fn person(&self) -> Option<AuthorizeResultResponse> {
self.inner.person.clone().map(|v| v.into())
}

/// Get the decision value for role
Expand Down
35 changes: 25 additions & 10 deletions jans-cedarling/cedarling/src/authz/authorize_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use cedar_policy::Decision;
/// based on the [Request](crate::models::request::Request) and policy store
pub struct AuthorizeResult {
/// Result of authorization where principal is `Jans::Workload`
pub workload: cedar_policy::Response,
pub workload: Option<cedar_policy::Response>,
/// Result of authorization where principal is `Jans::User`
pub person: cedar_policy::Response,
pub person: Option<cedar_policy::Response>,
/// Result of authorization where principal is `Jans::Role`
pub role: Option<cedar_policy::Response>,
}
Expand All @@ -22,16 +22,31 @@ impl AuthorizeResult {
/// `workload` (i.e., primary principal) needs to permit the request and
/// additional conditions (either `person` or `role`) must also indicate allowance.
pub fn is_allowed(&self) -> bool {
let role_decision = self
.role
let workload_allowed = self
.workload
.as_ref()
.map(|response| response.decision() == Decision::Allow);

let person_allowed = self
.person
.as_ref()
.map(|result| result.decision())
.unwrap_or(Decision::Deny);
.map(|response| response.decision() == Decision::Allow);

let workload_allowed = self.workload.decision() == Decision::Allow;
let person_or_role_allowed =
self.person.decision() == Decision::Allow || role_decision == Decision::Allow;
let role_allowed = self
.role
.as_ref()
.map(|response| response.decision() == Decision::Allow);

workload_allowed && person_or_role_allowed
// cover each possible case when any of value is Some or None
match (workload_allowed, person_allowed, role_allowed) {
(None, None, None) => false,
(None, None, Some(role)) => role,
(None, Some(person), None) => person,
(None, Some(person), Some(role)) => person || role,
(Some(workload), None, None) => workload,
(Some(workload), None, Some(role)) => workload && role,
(Some(workload), Some(person), None) => workload && person,
(Some(workload), Some(person), Some(role)) => workload && (person || role),
}
}
}
61 changes: 32 additions & 29 deletions jans-cedarling/cedarling/src/authz/entities/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,28 @@ use std::{
str::FromStr,
};

use crate::authz::token_data::{GetTokenClaimValue, Payload, TokenPayload};
use crate::common::cedar_schema::{
cedar_json::{CedarSchemaRecord, CedarType, GetCedarTypeError},
cedar_json::{CedarSchemaEntityShape, CedarSchemaRecord, CedarType, GetCedarTypeError},
CedarSchemaJson,
};
use crate::{
authz::token_data::{GetTokenClaimValue, Payload, TokenPayload},
common::cedar_schema::cedar_json::CedarSchemaEntityShape,
};

use cedar_policy::{EntityId, EntityTypeName, EntityUid, RestrictedExpression};

use super::trait_as_expression::AsExpression;

const CEDAR_POLICY_SEPARATOR: &str = "::";
pub const CEDAR_POLICY_SEPARATOR: &str = "::";

/// Meta information about an entity type.
/// Is used to store in `static` variable.
pub(crate) struct EntityMetadata<'a> {
pub entity_type: &'a str,
pub entity_type: EntityParsedTypeName<'a>,
pub entity_id_data_key: &'a str,
}

impl<'a> EntityMetadata<'a> {
/// create new instance of EntityMetadata.
pub fn new(entity_type: &'a str, entity_id_data_key: &'a str) -> Self {
pub fn new(entity_type: EntityParsedTypeName<'a>, entity_id_data_key: &'a str) -> Self {
Self {
entity_type,
entity_id_data_key,
Expand All @@ -51,12 +48,11 @@ impl<'a> EntityMetadata<'a> {
parents: HashSet<EntityUid>,
) -> Result<cedar_policy::Entity, CedarPolicyCreateTypeError> {
let entity_uid = build_entity_uid(
self.entity_type,
self.entity_type.full_type_name().as_str(),
data.get_payload(self.entity_id_data_key)?.as_str()?,
)?;

let parsed_typename = parse_namespace_and_typename(self.entity_type);
create_entity(entity_uid, &parsed_typename, schema, data, parents)
create_entity(entity_uid, &self.entity_type, schema, data, parents)
}
}

Expand All @@ -79,42 +75,51 @@ pub(crate) fn build_entity_uid(
/// Analog to the internal cedar_policy type `InternalName`
pub(crate) struct EntityParsedTypeName<'a> {
pub typename: &'a str,
path: Vec<&'a str>,
pub namespace: &'a str,
}
impl<'a> EntityParsedTypeName<'a> {
pub fn namespace(&self) -> String {
self.path.join(CEDAR_POLICY_SEPARATOR)
pub fn new(typename: &'a str, namespace: &'a str) -> Self {
EntityParsedTypeName {
typename,
namespace,
}
}

pub fn full_type_name(&self) -> String {
if self.namespace.is_empty() {
self.typename.to_string()
} else {
[self.namespace, self.typename].join(CEDAR_POLICY_SEPARATOR)
}
}
}

/// Parse entity type name and namespace from entity type string.
pub fn parse_namespace_and_typename(entity_type: &str) -> EntityParsedTypeName {
let mut raw_path: Vec<&str> = entity_type.split(CEDAR_POLICY_SEPARATOR).collect();
/// return (typename, namespace)
pub fn parse_namespace_and_typename(raw_entity_type: &str) -> (&str, String) {
let mut raw_path: Vec<&str> = raw_entity_type.split(CEDAR_POLICY_SEPARATOR).collect();
let typename = raw_path.pop().unwrap_or_default();
EntityParsedTypeName {
typename,
path: raw_path,
}
let namespace = raw_path.join(CEDAR_POLICY_SEPARATOR);
(typename, namespace)
}

/// fetch the schema record for a given entity type from the cedar schema json
fn fetch_schema_record<'a>(
entity_namespace: &str,
entity_typename: &str,
entity_info: &EntityParsedTypeName,
schema: &'a CedarSchemaJson,
) -> Result<&'a CedarSchemaEntityShape, CedarPolicyCreateTypeError> {
let entity_shape = schema
.entity_schema(entity_namespace, entity_typename)
.entity_schema(entity_info.namespace, entity_info.typename)
.ok_or(CedarPolicyCreateTypeError::CouldNotFindEntity(
entity_typename.to_string(),
entity_info.typename.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_typename.to_string(),
entity_info.typename.to_string(),
));
};
}
Expand Down Expand Up @@ -180,12 +185,10 @@ pub fn create_entity<'a>(
data: &'a TokenPayload,
parents: HashSet<EntityUid>,
) -> Result<cedar_policy::Entity, CedarPolicyCreateTypeError> {
let entity_namespace = parsed_typename.namespace();

// fetch the schema entity shape from the json-schema.
let schema_shape = fetch_schema_record(&entity_namespace, parsed_typename.typename, schema)?;
let schema_shape = fetch_schema_record(parsed_typename, schema)?;

let attrs = build_entity_attributes(schema_shape, data, &entity_namespace)?;
let attrs = build_entity_attributes(schema_shape, data, parsed_typename.namespace)?;

let entity_uid_string = entity_uid.to_string();
cedar_policy::Entity::new(entity_uid, attrs, parents)
Expand Down
21 changes: 0 additions & 21 deletions jans-cedarling/cedarling/src/authz/entities/meta.rs

This file was deleted.

Loading

0 comments on commit 774f779

Please sign in to comment.