risingwave_frontend/handler/
alter_user.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashSet;
16
17use pgwire::pg_response::StatementType;
18use risingwave_common::catalog::{DEFAULT_SUPER_USER_FOR_ADMIN, is_reserved_admin_user};
19use risingwave_pb::user::UserInfo;
20use risingwave_pb::user::update_user_request::UpdateField;
21use risingwave_sqlparser::ast::{AlterUserStatement, ObjectName, UserOption, UserOptions};
22
23use super::RwPgResponse;
24use crate::binder::Binder;
25use crate::catalog::CatalogError;
26use crate::error::ErrorCode::{self, InternalError, PermissionDenied};
27use crate::error::Result;
28use crate::handler::HandlerArgs;
29use crate::user::user_authentication::{
30    OAUTH_ISSUER_KEY, OAUTH_JWKS_URL_KEY, build_oauth_info, encrypted_password,
31};
32use crate::user::user_catalog::UserCatalog;
33
34fn alter_prost_user_info(
35    mut user_info: UserInfo,
36    options: &UserOptions,
37    session_user: &UserCatalog,
38) -> Result<(UserInfo, Vec<UpdateField>, Vec<String>)> {
39    let change_self_password_only = session_user.id == user_info.id
40        && options.0.len() == 1
41        && matches!(
42            &options.0[0],
43            UserOption::EncryptedPassword(_) | UserOption::Password(_) | UserOption::OAuth(_)
44        );
45    let require_admin = user_info.is_admin
46        || options
47            .0
48            .iter()
49            .any(|option| matches!(option, UserOption::Admin | UserOption::NoAdmin));
50    if !change_self_password_only && require_admin && !session_user.is_admin {
51        return Err(PermissionDenied(
52            format!("{} cannot be altered with admin option", user_info.name).to_owned(),
53        )
54        .into());
55    }
56
57    if !session_user.is_super {
58        let require_super = user_info.is_super
59            || options
60                .0
61                .iter()
62                .any(|option| matches!(option, UserOption::SuperUser | UserOption::NoSuperUser));
63        if require_super {
64            return Err(PermissionDenied(
65                "must be superuser to alter superuser roles or change superuser attribute"
66                    .to_owned(),
67            )
68            .into());
69        }
70
71        if !session_user.can_create_user && !change_self_password_only {
72            return Err(PermissionDenied("permission denied to alter user".to_owned()).into());
73        }
74    }
75
76    let mut update_fields = HashSet::new();
77    let mut notices = vec![];
78
79    for option in &options.0 {
80        match option {
81            UserOption::SuperUser => {
82                user_info.is_super = true;
83                update_fields.insert(UpdateField::Super);
84            }
85            UserOption::NoSuperUser => {
86                user_info.is_super = false;
87                update_fields.insert(UpdateField::Super);
88            }
89            UserOption::CreateDB => {
90                user_info.can_create_db = true;
91                update_fields.insert(UpdateField::CreateDb);
92            }
93            UserOption::NoCreateDB => {
94                user_info.can_create_db = false;
95                update_fields.insert(UpdateField::CreateDb);
96            }
97            UserOption::CreateUser => {
98                user_info.can_create_user = true;
99                update_fields.insert(UpdateField::CreateUser);
100            }
101            UserOption::NoCreateUser => {
102                user_info.can_create_user = false;
103                update_fields.insert(UpdateField::CreateUser);
104            }
105            UserOption::Login => {
106                user_info.can_login = true;
107                update_fields.insert(UpdateField::Login);
108            }
109            UserOption::NoLogin => {
110                user_info.can_login = false;
111                update_fields.insert(UpdateField::Login);
112            }
113            UserOption::Admin => {
114                user_info.is_admin = true;
115                update_fields.insert(UpdateField::Admin);
116            }
117            UserOption::NoAdmin => {
118                user_info.is_admin = false;
119                update_fields.insert(UpdateField::Admin);
120            }
121            UserOption::EncryptedPassword(p) => {
122                if !p.0.is_empty() {
123                    user_info.auth_info = encrypted_password(&user_info.name, &p.0);
124                } else {
125                    user_info.auth_info = None;
126                    notices
127                        .push("empty string is not a valid password, clearing password".to_owned());
128                };
129                update_fields.insert(UpdateField::AuthInfo);
130            }
131            UserOption::Password(opt) => {
132                if let Some(password) = opt
133                    && !password.0.is_empty()
134                {
135                    user_info.auth_info = encrypted_password(&user_info.name, &password.0);
136                } else {
137                    user_info.auth_info = None;
138                    notices
139                        .push("empty string is not a valid password, clearing password".to_owned());
140                }
141                update_fields.insert(UpdateField::AuthInfo);
142            }
143            UserOption::OAuth(options) => {
144                let auth_info = build_oauth_info(options).ok_or_else(|| {
145                    ErrorCode::InvalidParameterValue(format!(
146                        "{} and {} must be provided",
147                        OAUTH_JWKS_URL_KEY, OAUTH_ISSUER_KEY
148                    ))
149                })?;
150                user_info.auth_info = Some(auth_info);
151                update_fields.insert(UpdateField::AuthInfo);
152            }
153        }
154    }
155
156    if user_info.is_admin && !user_info.is_super {
157        user_info.is_super = true;
158        update_fields.insert(UpdateField::Super);
159        if options
160            .0
161            .iter()
162            .any(|option| matches!(option, UserOption::NoSuperUser))
163        {
164            notices.push("admin users must remain superusers, ignoring NOSUPERUSER".to_owned());
165        }
166    }
167
168    Ok((user_info, update_fields.into_iter().collect(), notices))
169}
170
171fn alter_rename_prost_user_info(
172    mut user_info: UserInfo,
173    new_name: ObjectName,
174    session_user: &UserCatalog,
175) -> Result<(UserInfo, Vec<UpdateField>)> {
176    if session_user.id == user_info.id {
177        return Err(InternalError("session user cannot be renamed".to_owned()).into());
178    }
179
180    if !session_user.is_super {
181        return Err(PermissionDenied("must be superuser to rename users".to_owned()).into());
182    }
183
184    let new_name = Binder::resolve_user_name(new_name)?;
185    if is_reserved_admin_user(&new_name) {
186        return Err(PermissionDenied(
187            format!("{} is reserved for admin", DEFAULT_SUPER_USER_FOR_ADMIN).to_owned(),
188        )
189        .into());
190    }
191    if is_reserved_admin_user(&user_info.name) {
192        return Err(PermissionDenied(
193            format!("{} cannot be renamed", DEFAULT_SUPER_USER_FOR_ADMIN).to_owned(),
194        )
195        .into());
196    }
197
198    user_info.name = new_name;
199    user_info.auth_info = None;
200    Ok((user_info, vec![UpdateField::Rename, UpdateField::AuthInfo]))
201}
202
203pub async fn handle_alter_user(
204    handler_args: HandlerArgs,
205    stmt: AlterUserStatement,
206) -> Result<RwPgResponse> {
207    let session = handler_args.session;
208    let (user_info, update_fields, notices) = {
209        let user_name = Binder::resolve_user_name(stmt.user_name.clone())?;
210        let user_reader = session.env().user_info_reader().read_guard();
211
212        let old_info = user_reader
213            .get_user_by_name(&user_name)
214            .ok_or(CatalogError::NotFound("user", user_name))?
215            .to_prost();
216
217        let session_user = user_reader
218            .get_user_by_name(&session.user_name())
219            .ok_or_else(|| CatalogError::NotFound("user", session.user_name().to_owned()))?;
220
221        match stmt.mode {
222            risingwave_sqlparser::ast::AlterUserMode::Options(options) => {
223                alter_prost_user_info(old_info, &options, session_user)?
224            }
225            risingwave_sqlparser::ast::AlterUserMode::Rename(new_name) => {
226                let (user_info, fields) =
227                    alter_rename_prost_user_info(old_info, new_name, session_user)?;
228                (user_info, fields, vec![])
229            }
230        }
231    };
232
233    let user_info_writer = session.user_info_writer()?;
234    user_info_writer
235        .update_user(user_info, update_fields)
236        .await?;
237    let mut response_builder = RwPgResponse::builder(StatementType::UPDATE_USER);
238    for notice in notices {
239        response_builder = response_builder.notice(notice);
240    }
241    Ok(response_builder.into())
242}
243
244#[cfg(test)]
245mod tests {
246    use std::collections::HashMap;
247
248    use risingwave_common::catalog::{
249        DEFAULT_DATABASE_NAME, DEFAULT_SUPER_USER_FOR_ADMIN, DEFAULT_SUPER_USER_FOR_ADMIN_ID,
250    };
251    use risingwave_pb::user::AuthInfo;
252    use risingwave_pb::user::auth_info::EncryptionType;
253
254    use crate::test_utils::LocalFrontend;
255
256    #[tokio::test]
257    async fn test_alter_user() {
258        let frontend = LocalFrontend::new(Default::default()).await;
259        let session = frontend.session_ref();
260        let user_info_reader = session.env().user_info_reader();
261
262        frontend.run_sql("CREATE USER userB WITH SUPERUSER NOCREATEDB PASSWORD 'md5827ccb0eea8a706c4c34a16891f84e7b'").await.unwrap();
263        frontend
264            .run_sql("ALTER USER userB RENAME TO user")
265            .await
266            .unwrap();
267        assert!(
268            user_info_reader
269                .read_guard()
270                .get_user_by_name("userB")
271                .is_none()
272        );
273        assert!(
274            user_info_reader
275                .read_guard()
276                .get_user_by_name("user")
277                .is_some()
278        );
279
280        frontend.run_sql("ALTER USER user WITH NOSUPERUSER CREATEDB PASSWORD 'md59f2fa6a30871a92249bdd2f1eeee4ef6'").await.unwrap();
281
282        let user_info = user_info_reader
283            .read_guard()
284            .get_user_by_name("user")
285            .cloned()
286            .unwrap();
287        assert!(!user_info.is_super);
288        assert!(user_info.can_create_db);
289        assert_eq!(
290            user_info.auth_info,
291            Some(AuthInfo {
292                encryption_type: EncryptionType::Md5 as i32,
293                encrypted_value: b"9f2fa6a30871a92249bdd2f1eeee4ef6".to_vec(),
294                metadata: HashMap::new(),
295            })
296        );
297    }
298
299    #[tokio::test]
300    async fn test_alter_admin_user() {
301        let frontend = LocalFrontend::new(Default::default()).await;
302        let session = frontend.session_ref();
303        let user_info_reader = session.env().user_info_reader();
304
305        // Create admin user
306        frontend
307            .run_user_sql(
308                "CREATE USER admin_user WITH ADMIN",
309                DEFAULT_DATABASE_NAME.to_owned(),
310                DEFAULT_SUPER_USER_FOR_ADMIN.to_owned(),
311                DEFAULT_SUPER_USER_FOR_ADMIN_ID,
312            )
313            .await
314            .unwrap();
315
316        let admin_user = user_info_reader
317            .read_guard()
318            .get_user_by_name("admin_user")
319            .cloned()
320            .unwrap();
321
322        // Should be both admin and superuser
323        assert!(admin_user.is_admin);
324        assert!(admin_user.is_super);
325
326        // Try to remove superuser status from admin user - should be ignored with notice
327        frontend
328            .run_user_sql(
329                "ALTER USER admin_user WITH NOSUPERUSER",
330                DEFAULT_DATABASE_NAME.to_owned(),
331                DEFAULT_SUPER_USER_FOR_ADMIN.to_owned(),
332                DEFAULT_SUPER_USER_FOR_ADMIN_ID,
333            )
334            .await
335            .unwrap();
336
337        let admin_user_after = user_info_reader
338            .read_guard()
339            .get_user_by_name("admin_user")
340            .cloned()
341            .unwrap();
342
343        assert!(admin_user_after.is_admin);
344        assert!(admin_user_after.is_super);
345
346        // Now remove admin and super status from admin user
347        frontend
348            .run_user_sql(
349                "ALTER USER admin_user WITH NOADMIN NOSUPERUSER",
350                DEFAULT_DATABASE_NAME.to_owned(),
351                DEFAULT_SUPER_USER_FOR_ADMIN.to_owned(),
352                DEFAULT_SUPER_USER_FOR_ADMIN_ID,
353            )
354            .await
355            .unwrap();
356
357        let admin_user_after = user_info_reader
358            .read_guard()
359            .get_user_by_name("admin_user")
360            .cloned()
361            .unwrap();
362
363        assert!(!admin_user_after.is_admin);
364        assert!(!admin_user_after.is_super);
365    }
366}