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