use either::Either;
use std::collections::{BTreeMap, HashMap};
use ockam::identity::utils::now;
use ockam::identity::Identifier;
use ockam::identity::{AttributesEntry, IdentitiesAttributes};
use ockam_core::compat::sync::Arc;
use ockam_core::Result;
use crate::authenticator::common::EnrollerAccessControlChecks;
use crate::authenticator::{AuthorityMember, AuthorityMembersRepository};
pub const OCKAM_ROLE_ATTRIBUTE_KEY: &str = "ockam-role";
pub const OCKAM_ROLE_ATTRIBUTE_ENROLLER_VALUE: &str = "enroller";
pub const OCKAM_TLS_ATTRIBUTE_KEY: &str = "ockam-tls-certificate";
pub struct DirectAuthenticatorError(pub String);
pub type DirectAuthenticatorResult<T> = Either<T, DirectAuthenticatorError>;
pub struct DirectAuthenticator {
authority: Identifier,
members: Arc<dyn AuthorityMembersRepository>,
identities_attributes: Arc<IdentitiesAttributes>,
account_authority: Option<AccountAuthorityInfo>,
}
#[derive(Clone)]
pub struct AccountAuthorityInfo {
account_authority: Identifier,
project_identifier: String,
enforce_admin_checks: bool,
}
impl AccountAuthorityInfo {
pub fn new(
account_authority: Identifier,
project_identifier: String,
enforce_admin_checks: bool,
) -> Self {
Self {
account_authority,
project_identifier,
enforce_admin_checks,
}
}
pub fn account_authority(&self) -> &Identifier {
&self.account_authority
}
pub fn project_identifier(&self) -> String {
self.project_identifier.clone()
}
pub fn enforce_admin_checks(&self) -> bool {
self.enforce_admin_checks
}
}
impl DirectAuthenticator {
pub fn new(
authority: &Identifier,
members: Arc<dyn AuthorityMembersRepository>,
identities_attributes: Arc<IdentitiesAttributes>,
account_authority: Option<AccountAuthorityInfo>,
) -> Self {
Self {
authority: authority.clone(),
members,
identities_attributes,
account_authority,
}
}
#[instrument(skip_all, fields(enroller = %enroller, identifier = %identifier))]
pub async fn add_member(
&self,
enroller: &Identifier,
identifier: &Identifier,
attributes: &BTreeMap<String, String>,
) -> Result<DirectAuthenticatorResult<()>> {
let check = EnrollerAccessControlChecks::check_identifier(
&self.authority,
self.members.clone(),
self.identities_attributes.clone(),
enroller,
&self.account_authority,
)
.await?;
if !check.is_enroller {
warn!(
"{} is trying to add member {}, but {} is not an enroller",
enroller, identifier, enroller
);
return Ok(Either::Right(DirectAuthenticatorError(format!(
"Non-enroller {} is trying to add a member {}",
enroller, identifier
))));
}
let attrs = attributes
.iter()
.map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec()))
.collect();
if EnrollerAccessControlChecks::check_bin_attributes_is_enroller(&attrs) {
if !check.is_admin {
warn!(
"Not pre trusted enroller {} is trying to create an enroller {}",
enroller, identifier
);
return Ok(Either::Right(DirectAuthenticatorError(
"Not pre trusted enroller is trying to create an enroller".to_string(),
)));
}
}
let member =
AuthorityMember::new(identifier.clone(), attrs, enroller.clone(), now()?, false);
if let Err(err) = self.members.add_member(&self.authority, member).await {
warn!("Error adding member {} directly: {}", identifier, err);
return Ok(Either::Right(DirectAuthenticatorError(
"Error adding member".to_string(),
)));
}
info!(
"Successfully added a member {} by {}. Attributes: {:?}",
identifier, enroller, attributes
);
Ok(Either::Left(()))
}
#[instrument(skip_all, fields(enroller = %enroller))]
pub async fn show_member(
&self,
enroller: &Identifier,
identifier: &Identifier,
) -> Result<DirectAuthenticatorResult<AttributesEntry>> {
let check = EnrollerAccessControlChecks::check_identifier(
&self.authority,
self.members.clone(),
self.identities_attributes.clone(),
enroller,
&self.account_authority,
)
.await?;
if !check.is_enroller {
warn!("Non-enroller {} is trying to retrieve a member", enroller);
return Ok(Either::Right(DirectAuthenticatorError(
"Non-enroller is trying to retrieve a member".to_string(),
)));
}
match self.members.get_member(&self.authority, identifier).await? {
Some(member) => {
let entry = AttributesEntry::new(
member.attributes().clone(),
member.added_at(),
None,
Some(member.added_by().clone()),
);
Ok(Either::Left(entry))
}
None => {
warn!("Member {} not found", identifier);
Ok(Either::Right(DirectAuthenticatorError(format!(
"Member {} not found",
identifier
))))
}
}
}
#[instrument(skip_all, fields(enroller = %enroller))]
pub async fn list_members(
&self,
enroller: &Identifier,
) -> Result<DirectAuthenticatorResult<HashMap<Identifier, AttributesEntry>>> {
let check = EnrollerAccessControlChecks::check_identifier(
&self.authority,
self.members.clone(),
self.identities_attributes.clone(),
enroller,
&self.account_authority,
)
.await?;
if !check.is_enroller {
warn!("Non-enroller {} is trying to list members", enroller);
return Ok(Either::Right(DirectAuthenticatorError(
"Non-enroller is trying to list members".to_string(),
)));
}
let all_members = self.members.get_members(&self.authority).await?;
let mut res = HashMap::<Identifier, AttributesEntry>::default();
for member in all_members {
let entry = AttributesEntry::new(
member.attributes().clone(),
member.added_at(),
None,
Some(member.added_by().clone()),
);
res.insert(member.identifier().clone(), entry);
}
Ok(Either::Left(res))
}
#[instrument(skip_all, fields(enroller = %enroller))]
pub async fn delete_all_members(
&self,
enroller: &Identifier,
) -> Result<DirectAuthenticatorResult<()>> {
match self.list_members(enroller).await? {
Either::Left(members) => {
for member in members.keys() {
if member != enroller {
_ = self.delete_member(enroller, member).await?
}
}
Ok(Either::Left(()))
}
Either::Right(e) => Ok(Either::Right(e)),
}
}
#[instrument(skip_all, fields(enroller = %enroller, identifier = %identifier))]
pub async fn delete_member(
&self,
enroller: &Identifier,
identifier: &Identifier,
) -> Result<DirectAuthenticatorResult<()>> {
let check_enroller = EnrollerAccessControlChecks::check_identifier(
&self.authority,
self.members.clone(),
self.identities_attributes.clone(),
enroller,
&self.account_authority,
)
.await?;
if !check_enroller.is_enroller {
warn!(
"Non-enroller {} is trying to delete member {}",
enroller, identifier
);
return Ok(Either::Right(DirectAuthenticatorError(
"Non-enroller is trying to delete a member".to_string(),
)));
}
let check_member = EnrollerAccessControlChecks::check_identifier(
&self.authority,
self.members.clone(),
self.identities_attributes.clone(),
identifier,
&self.account_authority,
)
.await?;
if check_member.is_pre_trusted {
warn!(
"Enroller {} is trying to delete a pre trusted identity {}",
enroller, identifier
);
return Ok(Either::Right(DirectAuthenticatorError(
"Enroller is trying to delete a pre trusted identity".to_string(),
)));
}
if check_member.is_enroller && !check_enroller.is_admin {
warn!(
"Not admin {} is trying to delete enroller {}",
enroller, identifier
);
return Ok(Either::Right(DirectAuthenticatorError(
"Not admin is trying to delete an enroller".to_string(),
)));
}
self.members
.delete_member(&self.authority, identifier)
.await?;
info!("Successfully deleted member {}", identifier);
Ok(Either::Left(()))
}
}