use serde::{Deserialize, Serialize};
use super::context::{Action, LaunchContext};
use super::scope::{ScopeActions, ScopeContext, SmartScope, SmartScopeSet};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScopeDecision {
Allow,
AllowWithPatientContext { patient_id: String },
Deny,
MissingPatientContext,
}
pub fn authorize(
scopes: &SmartScopeSet,
launch: &LaunchContext,
resource_type: &str,
action: Action,
) -> ScopeDecision {
let mut best = ScopeDecision::Deny;
for s in scopes.resource_scopes() {
let SmartScope::Resource {
context,
resource_type: filter,
actions,
} = s
else {
continue;
};
if !filter.matches(resource_type) {
continue;
}
if !action_permitted(*actions, action) {
continue;
}
let candidate = match context {
ScopeContext::User | ScopeContext::System => ScopeDecision::Allow,
ScopeContext::Patient => match &launch.patient_id {
Some(pid) => ScopeDecision::AllowWithPatientContext {
patient_id: pid.clone(),
},
None => ScopeDecision::MissingPatientContext,
},
};
best = elevate(best, candidate);
if matches!(best, ScopeDecision::Allow) {
return best;
}
}
best
}
fn action_permitted(granted: ScopeActions, requested: Action) -> bool {
match requested {
Action::Read => granted.read,
Action::Write => granted.write,
}
}
fn elevate(current: ScopeDecision, candidate: ScopeDecision) -> ScopeDecision {
use ScopeDecision::{Allow, AllowWithPatientContext, Deny, MissingPatientContext};
match (¤t, &candidate) {
(Allow, _) | (_, Allow) => Allow,
(AllowWithPatientContext { .. }, _) => current,
(_, AllowWithPatientContext { .. }) => candidate,
(MissingPatientContext, _) | (_, MissingPatientContext) => MissingPatientContext,
_ => Deny,
}
}