Skip to content

Commit

Permalink
Formalize generated policy, preparing user role
Browse files Browse the repository at this point in the history
- Completely refactor of all relevant structs, renaming them more
  appropriately, and moved into their own module.
- Include a new user role relationship, which represents the role that
  the user has at the system, such that when a resource is at a workflow
  state, the permissions associated with that role associated with that
  state will become activated for the user.
- Clarified that Reviewer role doesn't need the global role by default
  at the base model level, as that type of grant is wider than what was
  intended.  With the user role being defined this type of grant is
  much better fit for that, and will achieve the goal of enabling the
  access whenever ``State::Pending`` is set for those resources.
- Various backend methods have been renamed to be more precise.
- Still need to provide backend methods for management of user roles.
  • Loading branch information
metatoaster committed Sep 27, 2024
1 parent 054deb0 commit 78a8001
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 189 deletions.
28 changes: 17 additions & 11 deletions pmrac/src/platform.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use pmrcore::{
ac::{
agent::Agent,
permit::ResourcePolicy,
genpolicy::Policy,
role::Role,
workflow::State,
},
Expand Down Expand Up @@ -179,26 +179,26 @@ impl Platform {
// Agent Policy management

impl Platform {
pub async fn grant_role_to_agent(
pub async fn grant_res_role_to_agent(
&self,
res: &str,
agent: impl Into<Agent>,
role: Role,
) -> Result<(), Error> {
Ok(self.ac_platform.grant_role_to_agent(
Ok(self.ac_platform.grant_res_role_to_agent(
res,
&agent.into(),
role
).await?)
}

pub async fn revoke_role_from_agent(
pub async fn revoke_res_role_from_agent(
&self,
res: &str,
agent: impl Into<Agent>,
role: Role,
) -> Result<(), Error> {
Ok(self.ac_platform.revoke_role_from_agent(
Ok(self.ac_platform.revoke_res_role_from_agent(
res,
&agent.into(),
role,
Expand Down Expand Up @@ -250,12 +250,14 @@ impl Platform {
).await?)
}

pub async fn generate_policy_for_res(
pub async fn generate_policy_for_agent_res(
&self,
agent: &Agent,
res: String,
) -> Result<ResourcePolicy, Error> {
Ok(self.ac_platform.generate_policy_for_res(
res
) -> Result<Policy, Error> {
Ok(self.ac_platform.generate_policy_for_agent_res(
agent,
res,
).await?)
}
}
Expand All @@ -270,13 +272,17 @@ impl Platform {
endpoint_group: impl AsRef<str>,
http_method: &str,
) -> Result<bool, Error> {
let agent = agent.into();
Ok(self.pmrrbac_builder
.build_with_resource_policy(
self.generate_policy_for_res(res.to_string()).await?,
self.generate_policy_for_agent_res(
&agent,
res.to_string(),
).await?,
)
.await?
.enforce(
<Agent as Into<Option<String>>>::into(agent.into()),
<Agent as Into<Option<String>>>::into(agent),
res,
endpoint_group,
http_method,
Expand Down
45 changes: 25 additions & 20 deletions pmrac/tests/test_platform.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use pmrcore::ac::{
agent::Agent,
role::Role,
workflow::State,
};
Expand Down Expand Up @@ -141,8 +142,8 @@ async fn policy() -> anyhow::Result<()> {
let state = State::Private;
let role = Role::Manager;

platform.grant_role_to_agent("/", &user, role).await?;
platform.revoke_role_from_agent("/", &user, role).await?;
platform.grant_res_role_to_agent("/", &user, role).await?;
platform.revoke_res_role_from_agent("/", &user, role).await?;
platform.assign_policy_to_wf_state(state, role, "", "GET").await?;
platform.remove_policy_from_wf_state(state, role, "", "GET").await?;

Expand All @@ -155,12 +156,12 @@ async fn resource_wf_state() -> anyhow::Result<()> {
let admin = platform.create_user("admin").await?;
let user = platform.create_user("test_user").await?;

platform.grant_role_to_agent(
platform.grant_res_role_to_agent(
"/*",
admin,
Role::Manager,
).await?;
platform.grant_role_to_agent(
platform.grant_res_role_to_agent(
"/item/1",
user,
Role::Owner,
Expand Down Expand Up @@ -195,16 +196,18 @@ async fn resource_wf_state() -> anyhow::Result<()> {
State::Private,
).await?;

let mut policy = platform.generate_policy_for_res("/item/1".into()).await?;
policy.grants.sort_unstable();
policy.policies.sort_unstable();
let mut policy = platform.generate_policy_for_agent_res(&Agent::Anonymous, "/item/1".into()).await?;
policy.res_grants.sort_unstable();
policy.role_permits.sort_unstable();
assert_eq!(policy, serde_json::from_str(r#"{
"resource": "/item/1",
"grants": [
"user_roles": [
],
"res_grants": [
{"res": "/*", "agent": "admin", "role": "Manager"},
{"res": "/item/1", "agent": "test_user", "role": "Owner"}
],
"policies": [
"role_permits": [
{"role": "Owner", "endpoint_group": "edit", "method": "GET"},
{"role": "Owner", "endpoint_group": "edit", "method": "POST"}
]
Expand All @@ -214,16 +217,18 @@ async fn resource_wf_state() -> anyhow::Result<()> {
"/item/1",
State::Published,
).await?;
let mut policy = platform.generate_policy_for_res("/item/1".into()).await?;
policy.grants.sort_unstable();
policy.policies.sort_unstable();
let mut policy = platform.generate_policy_for_agent_res(&Agent::Anonymous, "/item/1".into()).await?;
policy.res_grants.sort_unstable();
policy.role_permits.sort_unstable();
assert_eq!(policy, serde_json::from_str(r#"{
"resource": "/item/1",
"grants": [
"user_roles": [
],
"res_grants": [
{"res": "/*", "agent": "admin", "role": "Manager"},
{"res": "/item/1", "agent": "test_user", "role": "Owner"}
],
"policies": [
"role_permits": [
{"role": "Owner", "endpoint_group": "edit", "method": "GET"},
{"role": "Reader", "endpoint_group": "", "method": "GET"}
]
Expand All @@ -249,21 +254,21 @@ async fn policy_enforcement() -> anyhow::Result<()> {

let admin = platform.create_user("admin").await?;
admin.reset_password("admin", "admin").await?;
platform.grant_role_to_agent("/*", admin, Role::Manager).await?;
platform.grant_res_role_to_agent("/*", admin, Role::Manager).await?;

let reviewer = platform.create_user("reviewer").await?;
reviewer.reset_password("reviewer", "reviewer").await?;
// this makes the reviewer being able to review globally
// platform.grant_role_to_agent("/*", &reviewer, Role::Reviewer).await?;
// platform.grant_res_role_to_agent("/*", &reviewer, Role::Reviewer).await?;
// we need something actually
// Or is this something that can be expressed with casbin as part of the base model/policy?
// platform.enable_role_at_state_for_resource(Role::Reviewer, State::Pending, "/*").await?;
platform.grant_role_to_agent("/profile/reviewer", reviewer, Role::Owner).await?;
platform.grant_res_role_to_agent("/profile/reviewer", reviewer, Role::Owner).await?;
platform.set_wf_state_for_res("/profile/reviewer", State::Private).await?;

let user = platform.create_user("user").await?;
user.reset_password("user", "user").await?;
platform.grant_role_to_agent("/profile/user", user, Role::Owner).await?;
platform.grant_res_role_to_agent("/profile/user", user, Role::Owner).await?;
platform.set_wf_state_for_res("/profile/user", State::Private).await?;

let admin = platform.authenticate_user("admin", "admin").await?;
Expand All @@ -275,7 +280,7 @@ async fn policy_enforcement() -> anyhow::Result<()> {
assert!(!platform.enforce(&reviewer, "/profile/user", "", "GET").await?);

// create content owned by user
platform.grant_role_to_agent("/news/post/1", &user, Role::Owner).await?;
platform.grant_res_role_to_agent("/news/post/1", &user, Role::Owner).await?;

// editable by the user while private
platform.set_wf_state_for_res("/news/post/1", State::Private).await?;
Expand All @@ -289,7 +294,7 @@ async fn policy_enforcement() -> anyhow::Result<()> {
// moment, rather than the role in a more general way
// That said, this address the use case for assigning _specific_ reviewer for the
// task and they will have the rights required
platform.grant_role_to_agent("/news/post/1", &reviewer, Role::Reviewer).await?;
platform.grant_res_role_to_agent("/news/post/1", &reviewer, Role::Reviewer).await?;

assert!(platform.enforce(&admin, "/news/post/1", "edit", "POST").await?);
assert!(!platform.enforce(&user, "/news/post/1", "edit", "POST").await?);
Expand Down
1 change: 1 addition & 0 deletions pmrcore/src/ac.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod agent;
pub mod genpolicy;
pub mod permit;
pub mod role;
pub mod traits;
Expand Down
47 changes: 47 additions & 0 deletions pmrcore/src/ac/genpolicy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Generated Policy
//!
//! The structs provided by this module represents policies generated
//! for consumption by some security enforcer, and is not meant to be
//! persisted in some datastore.
use serde::{Deserialize, Serialize};
use crate::ac::role::Role;

/// Grants, roles and permissions associated with the given resource
/// to be passed into the security enforcer as a complete policy.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Policy {
pub resource: String,
pub user_roles: Vec<UserRole>,
pub res_grants: Vec<ResGrant>,
pub role_permits: Vec<RolePermit>,
}

/// A resource grant - the agent will have the stated role at the given
/// resource.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
pub struct ResGrant {
// this may feel redundant later, but this line signifies the exact
// res this was granted for, which may be at a higher level.
pub res: String,
pub agent: Option<String>,
pub role: Role,
}

/// This represents the endpoint_group and HTTP method that the role is
/// given the permit for.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
pub struct RolePermit {
pub role: Role,
pub endpoint_group: String,
pub method: String,
}

/// Represents the role granted to the user for the system. Roles
/// granted this way is only applicable for resources at some
/// appropriate state.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct UserRole {
pub user: String,
pub role: Role,
}
35 changes: 3 additions & 32 deletions pmrcore/src/ac/permit.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::ac::{
role::Role,
user::User,
};

// TODO use these structs to represent the underlying for editing and presentation

/// Resource grant
///
Expand All @@ -16,7 +14,7 @@ pub struct ResGrant {
pub role: String,
}

/// Workfolow policy
/// Workflow policy
///
/// For each workflow state there may be multiple roles associated with
/// the different endpoint groups and HTTP methods. This struct will
Expand All @@ -29,30 +27,3 @@ pub struct WorkflowPolicy {
pub endpoint_group: String,
pub method: String,
}

/// A grant that will be passed onto the enforcer.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
pub struct Grant {
// this may feel redundant later, but this line signifies the exact
// res this was granted for, which may be at a higher level.
pub res: String,
pub agent: Option<String>,
pub role: String,
}

/// A policy entry that will be passed onto the enforcer.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
pub struct Policy {
pub role: Role,
pub endpoint_group: String,
pub method: String,
}

/// Grants and resources associated with the resource ready to be passed
/// into the enforcer.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct ResourcePolicy {
pub resource: String,
pub grants: Vec<Grant>,
pub policies: Vec<Policy>,
}
11 changes: 6 additions & 5 deletions pmrcore/src/ac/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use async_trait::async_trait;
use crate::error::BackendError;
use super::{
agent::Agent,
permit::ResourcePolicy,
genpolicy::Policy,
role::Role,
user::User,
workflow::State,
Expand Down Expand Up @@ -39,13 +39,13 @@ pub trait UserBackend {

#[async_trait]
pub trait PolicyBackend {
async fn grant_role_to_agent(
async fn grant_res_role_to_agent(
&self,
res: &str,
agent: &Agent,
role: Role,
) -> Result<(), BackendError>;
async fn revoke_role_from_agent(
async fn revoke_res_role_from_agent(
&self,
res: &str,
agent: &Agent,
Expand Down Expand Up @@ -74,8 +74,9 @@ pub trait ResourceBackend {
res: &str,
wf_state: State,
) -> Result<(), BackendError>;
async fn generate_policy_for_res(
async fn generate_policy_for_agent_res(
&self,
agent: &Agent,
res: String,
) -> Result<ResourcePolicy, BackendError>;
) -> Result<Policy, BackendError>;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 78a8001

Please sign in to comment.