Skip to main content

mz_sql/session/
user.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::{BTreeMap, BTreeSet};
11use std::sync::LazyLock;
12
13use mz_auth::AuthenticatorKind;
14use mz_auth::user::ExternalUserMetadata;
15use mz_repr::role_id::RoleId;
16use mz_repr::user::InternalUserMetadata;
17use serde::Serialize;
18
19pub const SYSTEM_USER_NAME: &str = "mz_system";
20pub static SYSTEM_USER: LazyLock<User> = LazyLock::new(|| User {
21    name: SYSTEM_USER_NAME.into(),
22    external_metadata: None,
23    internal_metadata: None,
24    authenticator_kind: None,
25    groups: None,
26});
27
28pub const SUPPORT_USER_NAME: &str = "mz_support";
29pub static SUPPORT_USER: LazyLock<User> = LazyLock::new(|| User {
30    name: SUPPORT_USER_NAME.into(),
31    external_metadata: None,
32    internal_metadata: None,
33    authenticator_kind: None,
34    groups: None,
35});
36
37pub const ANALYTICS_USER_NAME: &str = "mz_analytics";
38pub static ANALYTICS_USER: LazyLock<User> = LazyLock::new(|| User {
39    name: ANALYTICS_USER_NAME.into(),
40    external_metadata: None,
41    internal_metadata: None,
42    authenticator_kind: None,
43    groups: None,
44});
45
46pub static INTERNAL_USER_NAMES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
47    [&SYSTEM_USER, &SUPPORT_USER, &ANALYTICS_USER]
48        .into_iter()
49        .map(|user| user.name.clone())
50        .collect()
51});
52
53pub static INTERNAL_USER_NAME_TO_DEFAULT_CLUSTER: LazyLock<BTreeMap<String, String>> =
54    LazyLock::new(|| {
55        [
56            (&SYSTEM_USER, "mz_system"),
57            (&SUPPORT_USER, "mz_catalog_server"),
58            (&ANALYTICS_USER, "mz_analytics"),
59        ]
60        .into_iter()
61        .map(|(user, cluster)| (user.name.clone(), cluster.to_string()))
62        .collect()
63    });
64
65pub static HTTP_DEFAULT_USER: LazyLock<User> = LazyLock::new(|| User {
66    name: "anonymous_http_user".into(),
67    external_metadata: None,
68    internal_metadata: None,
69    authenticator_kind: None,
70    groups: None,
71});
72
73/// Identifies a user.
74#[derive(Debug, Clone, Serialize)]
75pub struct User {
76    /// The name of the user within the system.
77    pub name: String,
78    /// Metadata about this user in an external system.
79    pub external_metadata: Option<ExternalUserMetadata>,
80    /// Metadata about this user stored in the catalog,
81    /// such as its role's `SUPERUSER` attribute.
82    pub internal_metadata: Option<InternalUserMetadata>,
83    /// The authenticator that authenticated this user.
84    /// If `None`, the user hasn't been authenticated.
85    pub authenticator_kind: Option<AuthenticatorKind>,
86    /// Groups extracted from JWT claims during OIDC authentication.
87    /// None for non-OIDC connections or when the group claim is absent.
88    pub groups: Option<Vec<String>>,
89}
90
91impl From<&User> for mz_pgwire_common::UserMetadata {
92    fn from(user: &User) -> mz_pgwire_common::UserMetadata {
93        mz_pgwire_common::UserMetadata {
94            is_admin: user.is_external_admin(),
95            should_limit_connections: user.limit_max_connections(),
96        }
97    }
98}
99
100impl PartialEq for User {
101    fn eq(&self, other: &User) -> bool {
102        self.name == other.name
103    }
104}
105
106impl User {
107    /// Returns whether this is an internal user.
108    pub fn is_internal(&self) -> bool {
109        INTERNAL_USER_NAMES.contains(&self.name)
110    }
111
112    /// Returns whether this user is an admin in an external system.
113    pub fn is_external_admin(&self) -> bool {
114        self.external_metadata
115            .as_ref()
116            .map(|metadata| metadata.admin)
117            .clone()
118            .unwrap_or(false)
119    }
120
121    pub fn is_internal_admin(&self) -> bool {
122        self.internal_metadata
123            .as_ref()
124            .map(|metadata| metadata.superuser)
125            .clone()
126            .unwrap_or(false)
127    }
128
129    /// Returns whether this user is a superuser.
130    pub fn is_superuser(&self) -> bool {
131        matches!(self.kind(), UserKind::Superuser)
132    }
133
134    /// Returns whether this is user is the `mz_system` user.
135    pub fn is_system_user(&self) -> bool {
136        self == &*SYSTEM_USER
137    }
138
139    /// Returns whether we should limit this user's connections to max_connections
140    pub fn limit_max_connections(&self) -> bool {
141        !self.is_internal()
142    }
143
144    /// Returns the kind of user this is.
145    pub fn kind(&self) -> UserKind {
146        if self.is_external_admin() || self.is_system_user() || self.is_internal_admin() {
147            UserKind::Superuser
148        } else {
149            UserKind::Regular
150        }
151    }
152}
153
154#[derive(Debug, Copy, Clone)]
155pub enum UserKind {
156    Regular,
157    Superuser,
158}
159
160pub const MZ_SYSTEM_ROLE_ID: RoleId = RoleId::System(1);
161pub const MZ_SUPPORT_ROLE_ID: RoleId = RoleId::System(2);
162pub const MZ_ANALYTICS_ROLE_ID: RoleId = RoleId::System(3);
163/// Sentinel role ID for JWT group-sync-managed role memberships.
164/// Not a login role — exists only to distinguish sync grants from manual grants.
165pub const MZ_JWT_SYNC_ROLE_ID: RoleId = RoleId::System(4);
166pub const JWT_SYNC_ROLE_NAME: &str = "mz_jwt_sync";
167pub const MZ_MONITOR_ROLE_ID: RoleId = RoleId::Predefined(1);
168pub const MZ_MONITOR_REDACTED_ROLE_ID: RoleId = RoleId::Predefined(2);
169
170/// Metadata about a Session's role.
171///
172/// Modeled after PostgreSQL role hierarchy:
173/// <https://github.com/postgres/postgres/blob/9089287aa037fdecb5a52cec1926e5ae9569e9f9/src/backend/utils/init/miscinit.c#L461-L493>
174#[derive(Debug, Clone)]
175pub struct RoleMetadata {
176    /// The role that initiated the database context. Fixed for the duration of the connection.
177    pub authenticated_role: RoleId,
178    /// Initially the same as `authenticated_role`, but can be changed by SET SESSION AUTHORIZATION
179    /// (not yet implemented). Used to determine what roles can be used for SET ROLE
180    /// (not yet implemented).
181    pub session_role: RoleId,
182    /// The role of the current execution context. This role is used for all normal privilege
183    /// checks.
184    pub current_role: RoleId,
185}
186
187impl RoleMetadata {
188    /// Returns a RoleMetadata with all fields set to `id`.
189    pub fn new(id: RoleId) -> RoleMetadata {
190        RoleMetadata {
191            authenticated_role: id,
192            session_role: id,
193            current_role: id,
194        }
195    }
196}