Skip to main content

tansu_storage/service/
alter_user_scram_credentials.rs

1// Copyright ⓒ 2024-2026 Peter Morgan <peter.james.morgan@gmail.com>
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 crate::{Error, Result, ScramCredential, Storage};
16use bytes::Bytes;
17use rama::{Context, Service};
18use rsasl::mechanisms::scram::tools::derive_keys;
19use sha2::{Digest, Sha256, Sha512};
20use tansu_sans_io::{
21    AlterUserScramCredentialsRequest, AlterUserScramCredentialsResponse, ApiKey, ErrorCode,
22    ScramMechanism, alter_user_scram_credentials_response::AlterUserScramCredentialsResult,
23};
24use tracing::{debug, instrument};
25
26#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
27pub struct AlterUserScramCredentialsService;
28
29impl ApiKey for AlterUserScramCredentialsService {
30    const KEY: i16 = AlterUserScramCredentialsRequest::KEY;
31}
32
33impl<G> Service<G, AlterUserScramCredentialsRequest> for AlterUserScramCredentialsService
34where
35    G: Storage,
36{
37    type Response = AlterUserScramCredentialsResponse;
38    type Error = Error;
39
40    #[instrument(skip(ctx, req))]
41    async fn serve(
42        &self,
43        ctx: Context<G>,
44        req: AlterUserScramCredentialsRequest,
45    ) -> Result<Self::Response, Self::Error> {
46        let mut results = vec![];
47
48        if let Some(deletions) = req.deletions {
49            for deletion in deletions {
50                let mechanism = ScramMechanism::try_from(deletion.mechanism)?;
51
52                results.push(
53                    ctx.state()
54                        .delete_user_scram_credential(&deletion.name, mechanism)
55                        .await
56                        .map_or(
57                            AlterUserScramCredentialsResult::default()
58                                .user(deletion.name.clone())
59                                .error_code(ErrorCode::UnsupportedSaslMechanism.into())
60                                .error_message(Some("".into())),
61                            |()| {
62                                AlterUserScramCredentialsResult::default()
63                                    .user(deletion.name.clone())
64                                    .error_code(ErrorCode::None.into())
65                                    .error_message(Some("".into()))
66                            },
67                        ),
68                );
69            }
70        }
71
72        if let Some(upsertions) = req.upsertions {
73            for upsertion in upsertions {
74                let (mechanism, stored_key, server_key) =
75                    ScramMechanism::try_from(upsertion.mechanism)
76                        .inspect(|mechanism| debug!(?mechanism))
77                        .map(|mechanism| {
78                            if mechanism == ScramMechanism::Scram256 {
79                                let (client_key, server_key) =
80                                    derive_keys::<Sha256>(&upsertion.salted_password);
81
82                                (
83                                    mechanism,
84                                    Bytes::copy_from_slice(&Sha256::digest(client_key)[..]),
85                                    Bytes::copy_from_slice(&server_key[..]),
86                                )
87                            } else {
88                                let (client_key, server_key) =
89                                    derive_keys::<Sha512>(&upsertion.salted_password);
90
91                                (
92                                    mechanism,
93                                    Bytes::copy_from_slice(&Sha512::digest(client_key)[..]),
94                                    Bytes::copy_from_slice(&server_key[..]),
95                                )
96                            }
97                        })?;
98
99                let credential = ScramCredential {
100                    salt: upsertion.salt,
101                    iterations: upsertion.iterations,
102                    stored_key,
103                    server_key,
104                };
105
106                results.push(
107                    ctx.state()
108                        .upsert_user_scram_credential(
109                            upsertion.name.as_str(),
110                            mechanism,
111                            credential,
112                        )
113                        .await
114                        .map_or(
115                            AlterUserScramCredentialsResult::default()
116                                .user(upsertion.name.clone())
117                                .error_code(ErrorCode::UnsupportedSaslMechanism.into())
118                                .error_message(Some("".into())),
119                            |()| {
120                                AlterUserScramCredentialsResult::default()
121                                    .user(upsertion.name.clone())
122                                    .error_code(ErrorCode::None.into())
123                                    .error_message(Some("".into()))
124                            },
125                        ),
126                );
127            }
128        }
129
130        Ok(AlterUserScramCredentialsResponse::default()
131            .throttle_time_ms(0)
132            .results(Some(results)))
133    }
134}