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