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 pgwire::pg_response::{PgResponse, StatementType};
16use risingwave_pb::user::UserInfo;
17use risingwave_pb::user::update_user_request::UpdateField;
18use risingwave_sqlparser::ast::{AlterUserStatement, ObjectName, UserOption, UserOptions};
19
20use super::RwPgResponse;
21use crate::binder::Binder;
22use crate::catalog::CatalogError;
23use crate::error::ErrorCode::{self, InternalError, PermissionDenied};
24use crate::error::Result;
25use crate::handler::HandlerArgs;
26use crate::user::user_authentication::{
27    OAUTH_ISSUER_KEY, OAUTH_JWKS_URL_KEY, build_oauth_info, encrypted_password,
28};
29use crate::user::user_catalog::UserCatalog;
30
31fn alter_prost_user_info(
32    mut user_info: UserInfo,
33    options: &UserOptions,
34    session_user: &UserCatalog,
35) -> Result<(UserInfo, Vec<UpdateField>)> {
36    if !session_user.is_super {
37        let require_super = user_info.is_super
38            || options
39                .0
40                .iter()
41                .any(|option| matches!(option, UserOption::SuperUser | UserOption::NoSuperUser));
42        if require_super {
43            return Err(PermissionDenied(
44                "must be superuser to alter superuser roles or change superuser attribute"
45                    .to_owned(),
46            )
47            .into());
48        }
49
50        let change_self_password = session_user.id == user_info.id
51            && options.0.len() == 1
52            && matches!(
53                &options.0[0],
54                UserOption::EncryptedPassword(_) | UserOption::Password(_)
55            );
56        if !session_user.can_create_user && !change_self_password {
57            return Err(PermissionDenied("Do not have the privilege".to_owned()).into());
58        }
59    }
60
61    let mut update_fields = Vec::new();
62    for option in &options.0 {
63        match option {
64            UserOption::SuperUser => {
65                user_info.is_super = true;
66                update_fields.push(UpdateField::Super);
67            }
68            UserOption::NoSuperUser => {
69                user_info.is_super = false;
70                update_fields.push(UpdateField::Super);
71            }
72            UserOption::CreateDB => {
73                user_info.can_create_db = true;
74                update_fields.push(UpdateField::CreateDb);
75            }
76            UserOption::NoCreateDB => {
77                user_info.can_create_db = false;
78                update_fields.push(UpdateField::CreateDb);
79            }
80            UserOption::CreateUser => {
81                user_info.can_create_user = true;
82                update_fields.push(UpdateField::CreateUser);
83            }
84            UserOption::NoCreateUser => {
85                user_info.can_create_user = false;
86                update_fields.push(UpdateField::CreateUser);
87            }
88            UserOption::Login => {
89                user_info.can_login = true;
90                update_fields.push(UpdateField::Login);
91            }
92            UserOption::NoLogin => {
93                user_info.can_login = false;
94                update_fields.push(UpdateField::Login);
95            }
96            UserOption::EncryptedPassword(p) => {
97                // TODO: Behaviour of PostgreSQL: Notice when password is empty string.
98                if !p.0.is_empty() {
99                    user_info.auth_info = encrypted_password(&user_info.name, &p.0);
100                } else {
101                    user_info.auth_info = None;
102                };
103                update_fields.push(UpdateField::AuthInfo);
104            }
105            UserOption::Password(opt) => {
106                // TODO: Behaviour of PostgreSQL: Notice when password is empty string.
107                if let Some(password) = opt
108                    && !password.0.is_empty()
109                {
110                    user_info.auth_info = encrypted_password(&user_info.name, &password.0);
111                } else {
112                    user_info.auth_info = None;
113                }
114                update_fields.push(UpdateField::AuthInfo);
115            }
116            UserOption::OAuth(options) => {
117                let auth_info = build_oauth_info(options).ok_or_else(|| {
118                    ErrorCode::InvalidParameterValue(format!(
119                        "{} and {} must be provided",
120                        OAUTH_JWKS_URL_KEY, OAUTH_ISSUER_KEY
121                    ))
122                })?;
123                user_info.auth_info = Some(auth_info);
124                update_fields.push(UpdateField::AuthInfo)
125            }
126        }
127    }
128    Ok((user_info, update_fields))
129}
130
131fn alter_rename_prost_user_info(
132    mut user_info: UserInfo,
133    new_name: ObjectName,
134    session_user: &UserCatalog,
135) -> Result<(UserInfo, Vec<UpdateField>)> {
136    if session_user.id == user_info.id {
137        return Err(InternalError("session user cannot be renamed".to_owned()).into());
138    }
139
140    if !session_user.is_super {
141        if user_info.is_super {
142            return Err(
143                PermissionDenied("must be superuser to rename superusers".to_owned()).into(),
144            );
145        }
146
147        if !session_user.can_create_user {
148            return Err(
149                PermissionDenied("Do not have the privilege to rename user".to_owned()).into(),
150            );
151        }
152    }
153
154    user_info.name = Binder::resolve_user_name(new_name)?;
155    user_info.auth_info = None;
156    Ok((user_info, vec![UpdateField::Rename, UpdateField::AuthInfo]))
157}
158
159pub async fn handle_alter_user(
160    handler_args: HandlerArgs,
161    stmt: AlterUserStatement,
162) -> Result<RwPgResponse> {
163    let session = handler_args.session;
164    let (user_info, update_fields) = {
165        let user_name = Binder::resolve_user_name(stmt.user_name.clone())?;
166        let user_reader = session.env().user_info_reader().read_guard();
167
168        let old_info = user_reader
169            .get_user_by_name(&user_name)
170            .ok_or(CatalogError::NotFound("user", user_name))?
171            .to_prost();
172
173        let session_user = user_reader
174            .get_user_by_name(&session.user_name())
175            .ok_or_else(|| CatalogError::NotFound("user", session.user_name().to_owned()))?;
176
177        match stmt.mode {
178            risingwave_sqlparser::ast::AlterUserMode::Options(options) => {
179                alter_prost_user_info(old_info, &options, session_user)?
180            }
181            risingwave_sqlparser::ast::AlterUserMode::Rename(new_name) => {
182                alter_rename_prost_user_info(old_info, new_name, session_user)?
183            }
184        }
185    };
186
187    let user_info_writer = session.user_info_writer()?;
188    user_info_writer
189        .update_user(user_info, update_fields)
190        .await?;
191    Ok(PgResponse::empty_result(StatementType::UPDATE_USER))
192}
193
194#[cfg(test)]
195mod tests {
196    use std::collections::HashMap;
197
198    use risingwave_pb::user::AuthInfo;
199    use risingwave_pb::user::auth_info::EncryptionType;
200
201    use crate::test_utils::LocalFrontend;
202
203    #[tokio::test]
204    async fn test_alter_user() {
205        let frontend = LocalFrontend::new(Default::default()).await;
206        let session = frontend.session_ref();
207        let user_info_reader = session.env().user_info_reader();
208
209        frontend.run_sql("CREATE USER userB WITH SUPERUSER NOCREATEDB PASSWORD 'md5827ccb0eea8a706c4c34a16891f84e7b'").await.unwrap();
210        frontend
211            .run_sql("ALTER USER userB RENAME TO user")
212            .await
213            .unwrap();
214        assert!(
215            user_info_reader
216                .read_guard()
217                .get_user_by_name("userB")
218                .is_none()
219        );
220        assert!(
221            user_info_reader
222                .read_guard()
223                .get_user_by_name("user")
224                .is_some()
225        );
226
227        frontend.run_sql("ALTER USER user WITH NOSUPERUSER CREATEDB PASSWORD 'md59f2fa6a30871a92249bdd2f1eeee4ef6'").await.unwrap();
228
229        let user_info = user_info_reader
230            .read_guard()
231            .get_user_by_name("user")
232            .cloned()
233            .unwrap();
234        assert!(!user_info.is_super);
235        assert!(user_info.can_create_db);
236        assert_eq!(
237            user_info.auth_info,
238            Some(AuthInfo {
239                encryption_type: EncryptionType::Md5 as i32,
240                encrypted_value: b"9f2fa6a30871a92249bdd2f1eeee4ef6".to_vec(),
241                metadata: HashMap::new(),
242            })
243        );
244    }
245}