143 lines
3.5 KiB
Rust
143 lines
3.5 KiB
Rust
use crate::{MembershipRole, UserId, WorkspaceId};
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct SessionActor {
|
|
pub user_id: UserId,
|
|
pub workspace_id: WorkspaceId,
|
|
pub role: MembershipRole,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
#[allow(clippy::enum_variant_names)]
|
|
pub enum PolicyAction {
|
|
ReadWorkspace,
|
|
WriteWorkspace,
|
|
ReadWorkspaceAccess,
|
|
WriteWorkspaceAccess,
|
|
ReadOperation,
|
|
WriteOperation,
|
|
ReadAgent,
|
|
WriteAgent,
|
|
ReadPlatformApiKey,
|
|
WritePlatformApiKey,
|
|
ReadSecret,
|
|
WriteSecret,
|
|
ReadAuthProfile,
|
|
WriteAuthProfile,
|
|
ReadObservability,
|
|
ReadCapability,
|
|
}
|
|
|
|
impl PolicyAction {
|
|
pub fn is_read(self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::ReadWorkspace
|
|
| Self::ReadWorkspaceAccess
|
|
| Self::ReadOperation
|
|
| Self::ReadAgent
|
|
| Self::ReadPlatformApiKey
|
|
| Self::ReadSecret
|
|
| Self::ReadAuthProfile
|
|
| Self::ReadObservability
|
|
| Self::ReadCapability
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum PolicyScope {
|
|
Workspace(WorkspaceId),
|
|
Global,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum PolicyDecision {
|
|
Allow,
|
|
Deny { reason: &'static str },
|
|
}
|
|
|
|
pub trait PolicyEngine: Send + Sync {
|
|
fn check(
|
|
&self,
|
|
actor: &SessionActor,
|
|
action: PolicyAction,
|
|
scope: PolicyScope,
|
|
) -> PolicyDecision;
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
pub struct OwnerOnlyPolicyEngine;
|
|
|
|
impl PolicyEngine for OwnerOnlyPolicyEngine {
|
|
fn check(
|
|
&self,
|
|
actor: &SessionActor,
|
|
action: PolicyAction,
|
|
_scope: PolicyScope,
|
|
) -> PolicyDecision {
|
|
if matches!(actor.role, MembershipRole::Owner) || action.is_read() {
|
|
PolicyDecision::Allow
|
|
} else {
|
|
PolicyDecision::Deny {
|
|
reason: "write actions require owner role in community",
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{
|
|
OwnerOnlyPolicyEngine, PolicyAction, PolicyDecision, PolicyEngine, PolicyScope,
|
|
SessionActor,
|
|
};
|
|
use crate::{MembershipRole, UserId, WorkspaceId};
|
|
|
|
fn actor(role: MembershipRole) -> SessionActor {
|
|
SessionActor {
|
|
user_id: UserId::new("user_01"),
|
|
workspace_id: WorkspaceId::new("ws_01"),
|
|
role,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn owner_is_allowed_for_write_actions() {
|
|
let decision = OwnerOnlyPolicyEngine.check(
|
|
&actor(MembershipRole::Owner),
|
|
PolicyAction::WriteWorkspace,
|
|
PolicyScope::Workspace(WorkspaceId::new("ws_01")),
|
|
);
|
|
|
|
assert_eq!(decision, PolicyDecision::Allow);
|
|
}
|
|
|
|
#[test]
|
|
fn non_owner_is_allowed_for_read_actions() {
|
|
let decision = OwnerOnlyPolicyEngine.check(
|
|
&actor(MembershipRole::Viewer),
|
|
PolicyAction::ReadWorkspace,
|
|
PolicyScope::Workspace(WorkspaceId::new("ws_01")),
|
|
);
|
|
|
|
assert_eq!(decision, PolicyDecision::Allow);
|
|
}
|
|
|
|
#[test]
|
|
fn non_owner_is_denied_for_write_actions() {
|
|
let decision = OwnerOnlyPolicyEngine.check(
|
|
&actor(MembershipRole::Admin),
|
|
PolicyAction::WriteWorkspace,
|
|
PolicyScope::Workspace(WorkspaceId::new("ws_01")),
|
|
);
|
|
|
|
assert_eq!(
|
|
decision,
|
|
PolicyDecision::Deny {
|
|
reason: "write actions require owner role in community",
|
|
}
|
|
);
|
|
}
|
|
}
|