Skip to main content

openstack_keystone_core/application_credential/
service.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//     http://www.apache.org/licenses/LICENSE-2.0
5//
6// Unless required by applicable law or agreed to in writing, software
7// distributed under the License is distributed on an "AS IS" BASIS,
8// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9// See the License for the specific language governing permissions and
10// limitations under the License.
11//
12// SPDX-License-Identifier: Apache-2.0
13
14//! # Application credentials provider
15use std::collections::BTreeMap;
16use std::sync::Arc;
17
18use async_trait::async_trait;
19use base64::{Engine as _, engine::general_purpose};
20use rand::{RngExt, rng};
21use secrecy::SecretString;
22use uuid::Uuid;
23use validator::Validate;
24
25use crate::application_credential::{
26    ApplicationCredentialProviderError, backend::ApplicationCredentialBackend, types::*,
27};
28use crate::config::Config;
29use crate::keystone::ServiceState;
30use crate::plugin_manager::PluginManagerApi;
31use crate::role::{
32    RoleApi,
33    types::{Role, RoleListParameters},
34};
35
36/// Application Credential Provider.
37pub struct ApplicationCredentialService {
38    backend_driver: Arc<dyn ApplicationCredentialBackend>,
39}
40
41impl ApplicationCredentialService {
42    pub fn new<P: PluginManagerApi>(
43        config: &Config,
44        plugin_manager: &P,
45    ) -> Result<Self, ApplicationCredentialProviderError> {
46        let backend_driver = plugin_manager
47            .get_application_credential_backend(config.application_credential.driver.clone())?
48            .clone();
49        Ok(Self { backend_driver })
50    }
51}
52
53#[async_trait]
54impl ApplicationCredentialApi for ApplicationCredentialService {
55    /// Create a new application credential.
56    async fn create_application_credential(
57        &self,
58        state: &ServiceState,
59        rec: ApplicationCredentialCreate,
60    ) -> Result<ApplicationCredentialCreateResponse, ApplicationCredentialProviderError> {
61        rec.validate()?;
62        // TODO: Check app creds count
63        let mut new_rec = rec;
64        if new_rec.id.is_none() {
65            new_rec.id = Some(Uuid::new_v4().simple().to_string());
66        }
67        if let Some(ref mut rules) = new_rec.access_rules {
68            for rule in rules {
69                if rule.id.is_none() {
70                    rule.id = Some(Uuid::new_v4().simple().to_string());
71                }
72            }
73        }
74        if new_rec.secret.is_none() {
75            new_rec.secret = Some(generate_secret());
76        }
77        self.backend_driver
78            .create_application_credential(state, new_rec)
79            .await
80    }
81
82    /// Get a single application credential by ID.
83    #[tracing::instrument(level = "info", skip(self, state))]
84    async fn get_application_credential<'a>(
85        &self,
86        state: &ServiceState,
87        id: &'a str,
88    ) -> Result<Option<ApplicationCredential>, ApplicationCredentialProviderError> {
89        if let Some(mut app_cred) = self
90            .backend_driver
91            .get_application_credential(state, id)
92            .await?
93        {
94            let roles: BTreeMap<String, Role> = state
95                .provider
96                .get_role_provider()
97                .list_roles(state, &RoleListParameters::default())
98                .await?
99                .into_iter()
100                .map(|x| (x.id.clone(), x))
101                .collect();
102            for cred_role in app_cred.roles.iter_mut() {
103                if let Some(role) = roles.get(&cred_role.id) {
104                    cred_role.name = Some(role.name.clone());
105                    cred_role.domain_id = role.domain_id.clone();
106                }
107            }
108            Ok(Some(app_cred))
109        } else {
110            Ok(None)
111        }
112    }
113
114    /// List application credentials.
115    #[tracing::instrument(level = "info", skip(self, state))]
116    async fn list_application_credentials(
117        &self,
118        state: &ServiceState,
119        params: &ApplicationCredentialListParameters,
120    ) -> Result<Vec<ApplicationCredential>, ApplicationCredentialProviderError> {
121        params.validate()?;
122        let mut creds = self
123            .backend_driver
124            .list_application_credentials(state, params)
125            .await?;
126
127        let roles: BTreeMap<String, Role> = state
128            .provider
129            .get_role_provider()
130            .list_roles(state, &RoleListParameters::default())
131            .await?
132            .into_iter()
133            .map(|x| (x.id.clone(), x))
134            .collect();
135        for cred in creds.iter_mut() {
136            for cred_role in cred.roles.iter_mut() {
137                if let Some(role) = roles.get(&cred_role.id) {
138                    cred_role.name = Some(role.name.clone());
139                    cred_role.domain_id = role.domain_id.clone();
140                }
141            }
142        }
143        Ok(creds)
144    }
145}
146
147/// Generate application credential secret.
148///
149/// Use the same algorithm as the python Keystone uses:
150///
151///  - use random 64 bytes
152///  - apply base64 encoding with no padding
153pub fn generate_secret() -> SecretString {
154    const LENGTH: usize = 64;
155
156    // 1. Generate 64 cryptographically secure random bytes (Analogous to
157    //    `secrets.token_bytes(length)`)
158    let mut secret_bytes = [0u8; LENGTH];
159    rng().fill(&mut secret_bytes[..]);
160
161    // 2. Base64 URL-safe encoding (Analogous to `base64.urlsafe_b64encode(secret)`)
162    //    with stripping padding handled automatically by `URL_SAFE_NO_PAD` engine.
163    let encoded_secret = general_purpose::URL_SAFE_NO_PAD.encode(secret_bytes);
164
165    SecretString::new(encoded_secret.into())
166}