use alkcall::core::Identity;
use crate::registry::{AccessAction, RepoRecord, Visibility};
pub fn authorize(record: &RepoRecord, identity: Option<&Identity>, action: AccessAction) -> bool {
match action {
AccessAction::Read if record.visibility == Visibility::Public => true,
AccessAction::Read | AccessAction::Write | AccessAction::Manage => {
identity_holds(record, identity, action)
}
}
}
pub fn authorize_repo_op(record: &RepoRecord, identity: Option<&Identity>) -> bool {
match identity {
None => false,
Some(identity) => {
identity.scopes.iter().any(|scope| scope == GIT_ADMIN_SCOPE)
|| authorize(record, Some(identity), AccessAction::Manage)
}
}
}
pub const GIT_ADMIN_SCOPE: &str = "git:admin";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthorizedRepo {
record: RepoRecord,
action: AccessAction,
identity_id: Option<String>,
}
impl AuthorizedRepo {
pub fn authorize(
record: RepoRecord,
identity: Option<&Identity>,
action: AccessAction,
) -> Option<Self> {
if authorize(&record, identity, action) {
Some(Self {
record,
action,
identity_id: identity.map(|identity| identity.id.clone()),
})
} else {
None
}
}
pub fn record(&self) -> &RepoRecord {
&self.record
}
pub fn action(&self) -> AccessAction {
self.action
}
pub fn identity_id(&self) -> Option<&str> {
self.identity_id.as_deref()
}
}
fn identity_holds(record: &RepoRecord, identity: Option<&Identity>, action: AccessAction) -> bool {
let Some(identity) = identity else {
return false;
};
let Some(held) = record.grants.get(&identity.id) else {
return false;
};
held.iter().any(|grant| *grant >= action)
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
fn identity(id: &str) -> Identity {
Identity {
id: id.to_string(),
scopes: Vec::new(),
resources: HashMap::new(),
}
}
fn record(visibility: Visibility, grants: &[(&str, &[AccessAction])]) -> RepoRecord {
RepoRecord {
repo_id: "alkdev/alkgit".to_string(),
visibility,
grants: grants
.iter()
.map(|(key, actions)| ((*key).to_string(), actions.to_vec()))
.collect(),
}
}
#[test]
fn public_read_allows_anyone_including_anonymous() {
let public = record(Visibility::Public, &[]);
assert!(authorize(&public, None, AccessAction::Read));
let no_grants = identity("u-7731");
assert!(authorize(&public, Some(&no_grants), AccessAction::Read));
let with_grants = record(
Visibility::Public,
&[("u-7731", &[AccessAction::Read, AccessAction::Write])],
);
assert!(authorize(
&with_grants,
Some(&no_grants),
AccessAction::Read
));
}
#[test]
fn public_write_is_authenticated_and_granted_on_public_repos_too() {
let public_empty = record(Visibility::Public, &[]);
assert!(!authorize(&public_empty, None, AccessAction::Write));
let stranger = identity("stranger");
assert!(!authorize(
&public_empty,
Some(&stranger),
AccessAction::Write
));
let public_read_only = record(Visibility::Public, &[("stranger", &[AccessAction::Read])]);
assert!(!authorize(
&public_read_only,
Some(&stranger),
AccessAction::Write
));
}
#[test]
fn private_read_denies_anonymous_and_the_grantless() {
let private = record(Visibility::Private, &[]);
assert!(!authorize(&private, None, AccessAction::Read));
let holder = identity("u-7731");
assert!(!authorize(&private, Some(&holder), AccessAction::Read));
}
#[test]
fn private_read_is_satisfied_by_every_grant_tier() {
for actions in [
&[AccessAction::Read][..],
&[AccessAction::Write],
&[AccessAction::Manage],
&[AccessAction::Read, AccessAction::Write],
] {
let private = record(Visibility::Private, &[("u-7731", actions)]);
let holder = identity("u-7731");
assert!(authorize(&private, Some(&holder), AccessAction::Read));
}
}
#[test]
fn private_read_denies_an_identity_without_an_entry() {
let private = record(Visibility::Private, &[("other", &[AccessAction::Manage])]);
let stranger = identity("u-7731");
assert!(!authorize(&private, Some(&stranger), AccessAction::Read));
}
#[test]
fn write_requires_a_write_or_manage_grant() {
for visibility in [Visibility::Public, Visibility::Private] {
let empty = record(visibility, &[]);
let holder = identity("u-7731");
assert!(!authorize(&empty, None, AccessAction::Write));
assert!(!authorize(&empty, Some(&holder), AccessAction::Write));
let read_only = record(visibility, &[("u-7731", &[AccessAction::Read])]);
assert!(!authorize(&read_only, Some(&holder), AccessAction::Write));
}
}
#[test]
fn write_is_satisfied_by_write_and_manage_grants() {
for visibility in [Visibility::Public, Visibility::Private] {
for actions in [
&[AccessAction::Write][..],
&[AccessAction::Manage],
&[AccessAction::Read, AccessAction::Write],
&[
AccessAction::Read,
AccessAction::Write,
AccessAction::Manage,
],
] {
let granted = record(visibility, &[("u-7731", actions)]);
let holder = identity("u-7731");
assert!(authorize(&granted, Some(&holder), AccessAction::Write));
}
}
}
#[test]
fn manage_denies_anonymous_and_lower_tiers() {
for visibility in [Visibility::Public, Visibility::Private] {
let empty = record(visibility, &[]);
let holder = identity("u-7731");
assert!(!authorize(&empty, None, AccessAction::Manage));
assert!(!authorize(&empty, Some(&holder), AccessAction::Manage));
let lower = record(
visibility,
&[("u-7731", &[AccessAction::Read, AccessAction::Write])],
);
assert!(!authorize(&lower, Some(&holder), AccessAction::Manage));
}
}
#[test]
fn manage_requires_exactly_a_manage_grant() {
for visibility in [Visibility::Public, Visibility::Private] {
let granted = record(
visibility,
&[("u-7731", &[AccessAction::Read, AccessAction::Manage])],
);
let holder = identity("u-7731");
assert!(authorize(&granted, Some(&holder), AccessAction::Manage));
let write_only = record(visibility, &[("u-7731", &[AccessAction::Write])]);
assert!(!authorize(&write_only, Some(&holder), AccessAction::Manage));
}
}
#[test]
fn stored_grant_sets_need_not_be_closed_under_monotonicity() {
let sparse = record(Visibility::Private, &[("u-7731", &[AccessAction::Manage])]);
let holder = identity("u-7731");
assert!(authorize(&sparse, Some(&holder), AccessAction::Read));
assert!(authorize(&sparse, Some(&holder), AccessAction::Write));
assert!(authorize(&sparse, Some(&holder), AccessAction::Manage));
let empty_entry = record(Visibility::Private, &[("u-7731", &[])]);
assert!(!authorize(&empty_entry, Some(&holder), AccessAction::Read));
assert!(!authorize(&empty_entry, Some(&holder), AccessAction::Write));
assert!(!authorize(
&empty_entry,
Some(&holder),
AccessAction::Manage
));
}
#[test]
fn the_marker_is_constructible_only_through_the_checked_path() {
let private = record(Visibility::Private, &[("u-7731", &[AccessAction::Write])]);
let holder = identity("u-7731");
let stranger = identity("stranger");
assert_eq!(
AuthorizedRepo::authorize(private.clone(), None, AccessAction::Read),
None
);
assert_eq!(
AuthorizedRepo::authorize(private.clone(), Some(&stranger), AccessAction::Read),
None
);
assert_eq!(
AuthorizedRepo::authorize(private.clone(), Some(&holder), AccessAction::Manage),
None
);
assert!(
AuthorizedRepo::authorize(private.clone(), Some(&holder), AccessAction::Read).is_some()
);
assert!(AuthorizedRepo::authorize(private, Some(&holder), AccessAction::Write).is_some());
}
#[test]
fn the_marker_carries_the_checked_authorization_inputs() {
let public = record(Visibility::Public, &[]);
let anonymous = AuthorizedRepo::authorize(public.clone(), None, AccessAction::Read)
.expect("anonymous public read passes");
assert_eq!(anonymous.record(), &public);
assert_eq!(anonymous.action(), AccessAction::Read);
assert_eq!(anonymous.identity_id(), None);
let private = record(Visibility::Private, &[("u-7731", &[AccessAction::Manage])]);
let holder = identity("u-7731");
let identified = AuthorizedRepo::authorize(private, Some(&holder), AccessAction::Write)
.expect("the manage grant satisfies write");
assert_eq!(identified.record().repo_id, "alkdev/alkgit");
assert_eq!(identified.action(), AccessAction::Write);
assert_eq!(identified.identity_id(), Some("u-7731"));
}
fn scoped(id: &str, scopes: &[&str]) -> Identity {
Identity {
id: id.to_string(),
scopes: scopes.iter().map(|scope| (*scope).to_string()).collect(),
resources: HashMap::new(),
}
}
#[test]
fn the_op_gate_denies_anonymous() {
for visibility in [Visibility::Public, Visibility::Private] {
assert!(!authorize_repo_op(&record(visibility, &[]), None));
assert!(!authorize_repo_op(
&record(visibility, &[("u-7731", &[AccessAction::Manage])]),
None
));
}
}
#[test]
fn the_op_gate_denies_read_and_write_grants() {
for actions in [
&[AccessAction::Read][..],
&[AccessAction::Write],
&[AccessAction::Read, AccessAction::Write],
] {
for visibility in [Visibility::Public, Visibility::Private] {
let granted = record(visibility, &[("u-7731", actions)]);
let holder = identity("u-7731");
assert!(!authorize_repo_op(&granted, Some(&holder)));
}
}
}
#[test]
fn the_op_gate_grants_a_manage_grant() {
for visibility in [Visibility::Public, Visibility::Private] {
let granted = record(visibility, &[("u-7731", &[AccessAction::Manage])]);
let holder = identity("u-7731");
assert!(authorize_repo_op(&granted, Some(&holder)));
}
}
#[test]
fn the_op_gate_grants_the_admin_scope_without_any_grant() {
for visibility in [Visibility::Public, Visibility::Private] {
let grantless = record(visibility, &[]);
let admin = scoped("u-admin", &[GIT_ADMIN_SCOPE]);
assert!(authorize_repo_op(&grantless, Some(&admin)));
}
}
#[test]
fn the_op_gate_grants_the_admin_scope_on_a_private_repo() {
let private = record(Visibility::Private, &[]);
let admin = scoped("u-admin", &[GIT_ADMIN_SCOPE]);
assert!(authorize_repo_op(&private, Some(&admin)));
}
#[test]
fn the_op_gate_other_scopes_do_not_satisfy_the_admin_scope() {
let grantless = record(Visibility::Private, &[]);
let repo_create = scoped("u-7731", &["git:repo:create"]);
assert!(!authorize_repo_op(&grantless, Some(&repo_create)));
let mixed = scoped("u-7731", &["git:repo:create", GIT_ADMIN_SCOPE]);
assert!(authorize_repo_op(&grantless, Some(&mixed)));
}
#[test]
fn the_op_gate_manage_grant_monotonicity_holds_through_the_composition() {
let sparse = record(
Visibility::Private,
&[("u-7731", &[AccessAction::Read, AccessAction::Manage])],
);
let holder = identity("u-7731");
assert!(authorize_repo_op(&sparse, Some(&holder)));
let via_authorize = authorize(&sparse, Some(&holder), AccessAction::Manage);
assert!(via_authorize);
assert_eq!(authorize_repo_op(&sparse, Some(&holder)), via_authorize);
}
#[test]
fn the_op_gate_denies_an_empty_scopes_identity_on_a_grantless_record() {
let grantless = record(Visibility::Private, &[]);
let holder = identity("u-7731");
assert!(!authorize_repo_op(&grantless, Some(&holder)));
}
#[test]
fn the_admin_scope_is_platform_capability_the_grant_is_per_repo() {
let granted = record(Visibility::Private, &[("u-7731", &[AccessAction::Manage])]);
let holder = identity("u-7731");
assert!(authorize_repo_op(&granted, Some(&holder)));
let grantless = record(Visibility::Private, &[]);
assert!(!authorize_repo_op(&grantless, Some(&holder)));
}
}