Skip to main content

isb_server/auth/
actors.rs

1//! The principals that are nobody's account: an org's workspace, and a
2//! tailnet or Access agent identity an org mapped
3//! ([`super::agent_identities`]). Each has user id 0, is confined to its
4//! org or orgs, and is never a platform admin.
5
6use super::{OrgId, Principal, PrincipalKind, Role, User};
7use crate::audit::{Actor, ActorKind};
8
9/// How audit rows, history and `isb.owner` labels name a workspace.
10pub const WORKSPACE_ACTOR: &str = "workspace";
11
12impl Principal {
13    /// The token's scopes; empty for sessions and unscoped tokens.
14    pub fn scopes(&self) -> &[String] {
15        match &self.kind {
16            PrincipalKind::ApiToken { scopes, .. } => scopes,
17            _ => &[],
18        }
19    }
20
21    /// A token scoped short of `admin`: it may not change who has access.
22    pub fn restricted(&self) -> bool {
23        let s = self.scopes();
24        !s.is_empty() && !s.iter().any(|x| x == "admin")
25    }
26
27    /// This principal as an org-bound endpoint sees it. A superadmin or a
28    /// platform admin becomes an admin of `org` only (an owner stays one):
29    /// not a platform admin, no other org. Everyone else is unchanged.
30    pub fn downscoped_to(&self, org: &OrgId) -> Principal {
31        let privileged =
32            self.platform_admin || matches!(self.kind, PrincipalKind::Superadmin { .. });
33        if !privileged {
34            return self.clone();
35        }
36        let role = self
37            .role_in(org)
38            .map_or(Role::Admin, |r| r.max(Role::Admin));
39        let mut p = self.clone();
40        p.platform_admin = false;
41        p.user.platform_admin = false;
42        p.orgs = vec![(org.clone(), role)];
43        p.downscoped = Some(org.clone());
44        p
45    }
46
47    /// The principal an org's workspace token authenticates: no account
48    /// (user id 0, named `workspace`), confined to `org` with `role`.
49    pub fn workspace(org: &OrgId, name: &str, role: Role) -> Principal {
50        Principal {
51            user: User {
52                id: 0,
53                email: WORKSPACE_ACTOR.into(),
54                name: format!("workspace {name} in {org}"),
55                platform_admin: false,
56                created_at: 0,
57                disabled: false,
58                has_password: false,
59            },
60            kind: PrincipalKind::Workspace {
61                org: org.clone(),
62                name: name.to_string(),
63            },
64            orgs: vec![(org.clone(), role)],
65            platform_admin: false,
66            downscoped: None,
67        }
68    }
69
70    /// An org's workspace, rather than a person or a user's token.
71    pub fn is_workspace(&self) -> bool {
72        matches!(self.kind, PrincipalKind::Workspace { .. })
73    }
74
75    /// The principal of a tailnet or Access agent: no account (user id 0,
76    /// named after its source), confined to `orgs`, never a platform admin.
77    /// `label` is `tailnet:<login or node>` or `access:<name>`.
78    pub fn agent(label: &str, orgs: Vec<(OrgId, Role)>) -> Principal {
79        Principal {
80            user: User {
81                id: 0,
82                email: label.to_string(),
83                name: label.to_string(),
84                platform_admin: false,
85                created_at: 0,
86                disabled: false,
87                has_password: false,
88            },
89            kind: PrincipalKind::Agent {
90                label: label.to_string(),
91            },
92            orgs,
93            platform_admin: false,
94            downscoped: None,
95        }
96    }
97
98    /// A tailnet or Access agent identity mapped by an org.
99    pub fn is_agent(&self) -> bool {
100        matches!(self.kind, PrincipalKind::Agent { .. })
101    }
102}
103
104impl Actor {
105    /// The org's workspace: the row's org says which.
106    pub(crate) fn as_workspace(&mut self, name: &str) {
107        self.name = WORKSPACE_ACTOR.into();
108        self.kind = Some(ActorKind::Agent);
109        self.user_id = None;
110        self.email = None;
111        self.token_name = Some(format!("workspace:{name}"));
112    }
113
114    /// A tailnet or Access agent identity, named by its source.
115    pub(crate) fn as_agent(&mut self, label: &str) {
116        self.name = label.to_string();
117        self.kind = Some(ActorKind::Agent);
118        self.user_id = None;
119        self.email = None;
120    }
121}