use core::str::from_utf8;
use ockam_core::compat::fmt;
use ockam_core::compat::fmt::Debug;
use ockam_core::compat::fmt::Formatter;
use ockam_core::compat::str;
use ockam_core::compat::sync::Arc;
use ockam_core::compat::vec::vec;
use ockam_core::{RelayMessage, SecureChannelMetadata};
use ockam_core::{Result, SecureChannelLocalInfo};
use crate::expr::str;
use crate::{eval, Env, Expr};
use ockam_core::compat::format;
use ockam_core::compat::string::ToString;
use ockam_identity::{Identifier, IdentitiesAttributes};
use ockam_node::Context;
use tracing::{debug, warn};
pub const SUBJECT_KEY: &str = "subject";
pub const ABAC_HAS_CREDENTIAL_KEY: &str = "has_credential";
pub const ABAC_IDENTIFIER_KEY: &str = "identifier";
#[derive(Clone)]
pub struct Abac {
identities_attributes: Arc<IdentitiesAttributes>,
authority: Option<Identifier>,
environment: Env,
}
impl Debug for Abac {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Authority: {:?}", self.authority)
}
}
impl Abac {
pub fn new(
identities_attributes: Arc<IdentitiesAttributes>,
authority: Option<Identifier>,
environment: Env,
) -> Self {
Self {
identities_attributes,
authority,
environment,
}
}
}
impl Abac {
pub fn get_outgoing_identifier(
ctx: &Context,
relay_msg: &RelayMessage,
) -> Result<Option<Identifier>> {
let metadata = if let Some((_address, metadata)) =
ctx.find_terminal_address(relay_msg.onward_route().iter())?
{
metadata
} else {
return Ok(None);
};
if let Ok(metadata) = SecureChannelMetadata::from_terminal_address_metadata(&metadata) {
Ok(Some(metadata.their_identifier().into()))
} else {
Ok(None)
}
}
pub fn get_incoming_identifier(relay_msg: &RelayMessage) -> Option<Identifier> {
let identifier =
if let Ok(info) = SecureChannelLocalInfo::find_info(relay_msg.local_message()) {
info.their_identifier()
} else {
return None;
};
Some(identifier.into())
}
pub async fn is_identity_authorized(
&self,
identifier: &Identifier,
expression: &Expr,
) -> Result<bool> {
Self::is_identity_authorized_static(
self.identities_attributes.clone(),
&self.environment,
self.authority.as_ref(),
identifier,
expression,
)
.await
}
pub async fn is_identity_authorized_static(
identities_attributes: Arc<IdentitiesAttributes>,
environment: &Env,
authority: Option<&Identifier>,
identifier: &Identifier,
expression: &Expr,
) -> Result<bool> {
let mut environment = environment.clone();
environment.put(
subject_identifier_attribute().to_string(),
str(identifier.to_string()),
);
if let Some(authority) = authority {
match identities_attributes
.get_attributes(identifier, authority)
.await?
{
Some(attrs) => {
environment.put(
subject_has_credential_attribute().to_string(),
Expr::CONST_TRUE,
);
for (key, value) in attrs.attrs() {
let key = match from_utf8(key) {
Ok(key) => key,
Err(_) => {
warn! {
policy = %expression,
id = %identifier,
"attribute key is not utf-8"
}
continue;
}
};
if key.find(|c: char| c.is_whitespace()).is_some() {
warn! {
policy = %expression,
id = %identifier,
key = %key,
"attribute key with whitespace ignored"
}
}
match str::from_utf8(value) {
Ok(s) => {
if environment.contains(key) {
warn! {
policy = %expression,
id = %identifier,
key = %key,
"attribute already present"
}
} else {
environment
.put(format!("{}.{key}", SUBJECT_KEY), str(s.to_string()));
}
}
Err(e) => {
warn! {
policy = %expression,
id = %identifier,
key = %key,
err = %e,
"failed to interpret attribute as string"
}
}
}
}
}
None => {
environment.put(
subject_has_credential_attribute().to_string(),
Expr::CONST_FALSE,
);
}
}
}
match eval(expression, &environment) {
Ok(Expr::Bool(b)) => {
debug! {
policy = %expression,
id = %identifier,
is_authorized = %b,
"policy evaluated"
}
Ok(b)
}
Ok(x) => {
warn! {
policy = %expression,
id = %identifier,
expr = %x,
"evaluation did not yield a boolean result"
}
Ok(false)
}
Err(e) => {
warn! {
policy = %expression,
id = %identifier,
err = %e,
env = %environment,
"policy evaluation failed"
}
Ok(false)
}
}
}
}
pub fn subject_has_credential_policy_expression() -> Expr {
Expr::List(vec![
Expr::Ident("=".to_string()),
subject_has_credential_attribute(),
Expr::Bool(true),
])
}
pub fn subject_has_credential_attribute() -> Expr {
Expr::Ident(format!("{}.{}", SUBJECT_KEY, ABAC_HAS_CREDENTIAL_KEY))
}
pub fn subject_identifier_attribute() -> Expr {
Expr::Ident(format!("{}.{}", SUBJECT_KEY, ABAC_IDENTIFIER_KEY))
}