OXYGENHUB
# f3e5e2bc78e9 oxygenhub / crates / hub / src / auth / roles.rs
3.7 KiB · Rust · last changed Admins disable, enable and delete accounts 1 month ago source blame history raw
1
//! Roles on repositories and organisations (hub spec §5.2, §5.5). The hub's
2
//! roles decide who may reach a repository and attempt a push; the
3
//! repository's trust policy decides which keys may move which references.
4
5
use crate::db::{self, accounts::Account, repos::RepoRow, Pool};
6
use hub_api::Visibility;
7
8
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
9
#[repr(u8)]
10
pub enum Role {
11
    Read = 1,
12
    Triage = 2,
13
    Write = 3,
14
    Maintain = 4,
15
    Admin = 5,
16
}
17
18
impl Role {
19
    pub fn parse(s: &str) -> Option<Role> {
20
        Some(match s {
21
            "read" => Role::Read,
22
            "triage" => Role::Triage,
23
            "write" => Role::Write,
24
            "maintain" => Role::Maintain,
25
            "admin" => Role::Admin,
26
            _ => return None,
27
        })
28
    }
29
30
    pub fn as_str(self) -> &'static str {
31
        match self {
32
            Role::Read => "read",
33
            Role::Triage => "triage",
34
            Role::Write => "write",
35
            Role::Maintain => "maintain",
36
            Role::Admin => "admin",
37
        }
38
    }
39
40
    pub const ALL: [Role; 5] = [Role::Read, Role::Triage, Role::Write, Role::Maintain, Role::Admin];
41
}
42
43
/// What a request wants to do with a repository.
44
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45
pub enum Access {
46
    Read,
47
    Write,
48
    Maintain,
49
    Admin,
50
}
51
52
impl Access {
53
    fn needs(self) -> Role {
54
        match self {
55
            Access::Read => Role::Read,
56
            Access::Write => Role::Write,
57
            Access::Maintain => Role::Maintain,
58
            Access::Admin => Role::Admin,
59
        }
60
    }
61
}
62
63
/// The role `account` (or nobody) holds on `repo`, from ownership,
64
/// organisation membership, collaborator entries and visibility.
65
pub async fn role_for(pool: &Pool, account: Option<&Account>, repo: &RepoRow) -> sqlx::Result<Option<Role>> {
66
    let visibility = repo.visibility();
67
    let Some(account) = account else {
68
        return Ok(if visibility == Visibility::Public { Some(Role::Read) } else { None });
69
    };
70
    if account.blocked() {
71
        return Ok(None);
72
    }
73
    if account.is_admin || account.username.eq_ignore_ascii_case(&repo.owner) {
74
        return Ok(Some(Role::Admin));
75
    }
76
    let mut best: Option<Role> = None;
77
    if let Some(org) = db::accounts::org_by_name(pool, &repo.owner).await? {
78
        match db::accounts::org_role(pool, org.id, account.id).await?.as_deref() {
79
            Some("owner") => return Ok(Some(Role::Admin)),
80
            Some(_) => best = Role::parse(&org.default_role),
81
            None => {}
82
        }
83
    }
84
    if let Some(r) = db::accounts::collaborator_role(pool, repo.id, account.id).await?.as_deref().and_then(Role::parse) {
85
        best = Some(best.map_or(r, |b| b.max(r)));
86
    }
87
    if best.is_none() && visibility != Visibility::Private {
88
        best = Some(Role::Read);
89
    }
90
    Ok(best)
91
}
92
93
/// Whether `role` permits `access`.
94
pub fn allows(role: Option<Role>, access: Access) -> bool {
95
    role.is_some_and(|r| r >= access.needs())
96
}
97
98
/// Whether an account may create repositories under `owner`.
99
pub async fn may_create_under(pool: &Pool, account: &Account, owner: &str) -> sqlx::Result<bool> {
100
    if account.blocked() {
101
        return Ok(false);
102
    }
103
    if account.username.eq_ignore_ascii_case(owner) || account.is_admin {
104
        return Ok(true);
105
    }
106
    match db::accounts::org_by_name(pool, owner).await? {
107
        Some(org) => Ok(db::accounts::org_role(pool, org.id, account.id).await?.is_some()),
108
        None => Ok(false),
109
    }
110
}
111
112
/// Whether an account administers an organisation.
113
pub async fn is_org_owner(pool: &Pool, account: &Account, org_id: i64) -> sqlx::Result<bool> {
114
    if account.is_admin {
115
        return Ok(true);
116
    }
117
    Ok(db::accounts::org_role(pool, org_id, account.id).await?.as_deref() == Some("owner"))
118
}