use std::sync::Arc;
use kcode_k1_access::{
AccessCheck, Authorizations, K1Access, SubsystemId, Target, TxId, ViewerSubject,
};
use kcode_k1_access_profiles::K1AccessProfiles;
use kcode_k1_groups::{ALL_MODELS, ALL_USERS};
use kcode_k1_persons::K1Persons;
pub use kcode_k1_access::{AccessId, RequestPrincipal};
pub use kcode_k1_access_profiles::ProfileSelection;
pub use kcode_k1_persons::{PersonId, PersonView};
const PERSON_SUBSYSTEM: &str = "k1-person";
const NOT_VISIBLE: &str = "principal cannot access person";
const NOT_UPDATER: &str = "principal must be able to view and manage person";
const TARGET_UNAVAILABLE: &str = "person access target is unavailable";
const INVALID_TARGET: &str = "person access target must contain exactly 12 person bytes";
const PERSON_UNAVAILABLE: &str = "person is unavailable";
const NONCANONICAL_TARGET: &str = "person access target is no longer canonical";
const MISMATCHED_TARGET: &str = "person access target does not match supplied person";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SubmittedPerson {
pub access_id: AccessId,
pub person_id: PersonId,
}
pub struct K1AccessPersons {
access: Arc<K1Access>,
profiles: Arc<K1AccessProfiles>,
persons: Arc<K1Persons>,
subsystem: SubsystemId,
}
impl K1AccessPersons {
pub fn open(
access: Arc<K1Access>,
profiles: Arc<K1AccessProfiles>,
persons: Arc<K1Persons>,
) -> Result<Self, String> {
Ok(Self {
access,
profiles,
persons,
subsystem: SubsystemId::from_str(PERSON_SUBSYSTEM)?,
})
}
pub fn create(
&self,
principal: RequestPrincipal,
profile: ProfileSelection,
name: String,
) -> Result<SubmittedPerson, String> {
let resolved = self
.profiles
.resolve(principal, profile)?
.into_authorizations();
let mut viewers = resolved.viewers().to_vec();
viewers.extend([
ViewerSubject::Group(ALL_USERS),
ViewerSubject::Group(ALL_MODELS),
]);
let authorizations = Authorizations::new(resolved.owners().to_vec(), viewers)?;
let person_id = self.persons.create(name)?;
let target = Target::new(self.subsystem, person_id.as_tx_id().as_bytes().to_vec());
let access_id = self.access.create(target, authorizations)?.access_id();
Ok(SubmittedPerson {
access_id,
person_id,
})
}
pub fn read(
&self,
principal: RequestPrincipal,
access_id: AccessId,
) -> Result<PersonView, String> {
self.read_person(visible_person_id(self.access.check(
principal,
access_id,
self.subsystem,
)?)?)
}
pub fn read_person(&self, person_id: PersonId) -> Result<PersonView, String> {
self.persons
.read(person_id)?
.ok_or_else(|| PERSON_UNAVAILABLE.to_owned())
}
pub fn update(
&self,
principal: RequestPrincipal,
access_id: AccessId,
name: String,
) -> Result<(), String> {
self.update_checked(principal, access_id, None, name)
}
pub fn update_person(
&self,
principal: RequestPrincipal,
person_id: PersonId,
access_id: AccessId,
name: String,
) -> Result<(), String> {
self.update_checked(principal, access_id, Some(person_id), name)
}
fn update_checked(
&self,
principal: RequestPrincipal,
access_id: AccessId,
expected_person_id: Option<PersonId>,
name: String,
) -> Result<(), String> {
let check = self.access.check(principal, access_id, self.subsystem)?;
if !check.can_view() || !check.can_manage() {
return Err(NOT_UPDATER.to_owned());
}
let person_id = target_person_id(&check)?;
if expected_person_id.is_some_and(|expected| expected != person_id) {
return Err(MISMATCHED_TARGET.to_owned());
}
self.update_canonical_person(person_id, name)
}
fn update_canonical_person(&self, person_id: PersonId, name: String) -> Result<(), String> {
let current = self.read_person(person_id)?;
if current.person_id != person_id {
return Err(NONCANONICAL_TARGET.to_owned());
}
self.persons.update(person_id, name)
}
}
fn visible_person_id(check: AccessCheck) -> Result<PersonId, String> {
if !check.can_view() {
return Err(NOT_VISIBLE.to_owned());
}
target_person_id(&check)
}
fn target_person_id(check: &AccessCheck) -> Result<PersonId, String> {
let target = check
.target()
.ok_or_else(|| TARGET_UNAVAILABLE.to_owned())?;
let bytes: [u8; 12] = target
.object_id()
.try_into()
.map_err(|_| INVALID_TARGET.to_owned())?;
Ok(PersonId::from_tx_id(TxId::from_bytes(bytes)))
}