Skip to main content

openstack_keystone_core/identity/
mod.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//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13// SPDX-License-Identifier: Apache-2.0
14
15//! # Identity provider
16//!
17//! Following identity concepts are covered by the identity provider:
18//!
19//! ## Group
20//!
21//! An Identity service API v3 entity. Groups are a collection of users
22//! owned by a domain. A group role, granted to a domain or project, applies to
23//! all users in the group. Adding or removing users to or from a group grants
24//! or revokes their role and authentication to the associated domain or
25//! project.
26//!
27//! ## User
28//!
29//! A digital representation of a person, system, or service that uses
30//! OpenStack cloud services. The Identity service validates that incoming
31//! requests are made by the user who claims to be making the call. Users have a
32//! login and can access resources by using assigned tokens. Users can be
33//! directly assigned to a particular project and behave as if they are
34//! contained in that project.
35
36use async_trait::async_trait;
37use chrono::{DateTime, Utc};
38use std::collections::HashSet;
39
40pub mod backend;
41pub mod error;
42#[cfg(any(test, feature = "mock"))]
43pub mod mock;
44pub mod types;
45#[cfg(any(test, feature = "mock"))]
46pub use mock::MockIdentityProvider;
47pub mod service;
48
49use crate::auth::AuthenticatedInfo;
50use crate::config::Config;
51use crate::identity::types::*;
52use crate::keystone::ServiceState;
53use crate::plugin_manager::PluginManagerApi;
54use service::IdentityService;
55
56pub use error::IdentityProviderError;
57pub use types::IdentityApi;
58
59/// Identity provider.
60pub enum IdentityProvider {
61    Service(IdentityService),
62    #[cfg(any(test, feature = "mock"))]
63    Mock(MockIdentityProvider),
64}
65
66impl IdentityProvider {
67    pub fn new<P: PluginManagerApi>(
68        config: &Config,
69        plugin_manager: &P,
70    ) -> Result<Self, IdentityProviderError> {
71        Ok(Self::Service(IdentityService::new(config, plugin_manager)?))
72    }
73}
74
75#[async_trait]
76impl IdentityApi for IdentityProvider {
77    #[tracing::instrument(skip(self, state))]
78    async fn add_user_to_group<'a>(
79        &self,
80        state: &ServiceState,
81        user_id: &'a str,
82        group_id: &'a str,
83    ) -> Result<(), IdentityProviderError> {
84        match self {
85            Self::Service(provider) => provider.add_user_to_group(state, user_id, group_id).await,
86            #[cfg(any(test, feature = "mock"))]
87            Self::Mock(provider) => provider.add_user_to_group(state, user_id, group_id).await,
88        }
89    }
90
91    #[tracing::instrument(skip(self, state))]
92    async fn add_user_to_group_expiring<'a>(
93        &self,
94        state: &ServiceState,
95        user_id: &'a str,
96        group_id: &'a str,
97        idp_id: &'a str,
98    ) -> Result<(), IdentityProviderError> {
99        match self {
100            Self::Service(provider) => {
101                provider
102                    .add_user_to_group_expiring(state, user_id, group_id, idp_id)
103                    .await
104            }
105            #[cfg(any(test, feature = "mock"))]
106            Self::Mock(provider) => {
107                provider
108                    .add_user_to_group_expiring(state, user_id, group_id, idp_id)
109                    .await
110            }
111        }
112    }
113
114    #[tracing::instrument(skip(self, state))]
115    async fn add_users_to_groups<'a>(
116        &self,
117        state: &ServiceState,
118        memberships: Vec<(&'a str, &'a str)>,
119    ) -> Result<(), IdentityProviderError> {
120        match self {
121            Self::Service(provider) => provider.add_users_to_groups(state, memberships).await,
122            #[cfg(any(test, feature = "mock"))]
123            Self::Mock(provider) => provider.add_users_to_groups(state, memberships).await,
124        }
125    }
126
127    #[tracing::instrument(skip(self, state))]
128    async fn add_users_to_groups_expiring<'a>(
129        &self,
130        state: &ServiceState,
131        memberships: Vec<(&'a str, &'a str)>,
132        idp_id: &'a str,
133    ) -> Result<(), IdentityProviderError> {
134        match self {
135            Self::Service(provider) => {
136                provider
137                    .add_users_to_groups_expiring(state, memberships, idp_id)
138                    .await
139            }
140            #[cfg(any(test, feature = "mock"))]
141            Self::Mock(provider) => {
142                provider
143                    .add_users_to_groups_expiring(state, memberships, idp_id)
144                    .await
145            }
146        }
147    }
148
149    /// Authenticate user with the password auth method.
150    #[tracing::instrument(skip(self, state, auth))]
151    async fn authenticate_by_password(
152        &self,
153        state: &ServiceState,
154        auth: &UserPasswordAuthRequest,
155    ) -> Result<AuthenticatedInfo, IdentityProviderError> {
156        match self {
157            Self::Service(provider) => provider.authenticate_by_password(state, auth).await,
158            #[cfg(any(test, feature = "mock"))]
159            Self::Mock(provider) => provider.authenticate_by_password(state, auth).await,
160        }
161    }
162
163    /// Create group.
164    #[tracing::instrument(skip(self, state))]
165    async fn create_group(
166        &self,
167        state: &ServiceState,
168        group: GroupCreate,
169    ) -> Result<Group, IdentityProviderError> {
170        match self {
171            Self::Service(provider) => provider.create_group(state, group).await,
172            #[cfg(any(test, feature = "mock"))]
173            Self::Mock(provider) => provider.create_group(state, group).await,
174        }
175    }
176
177    /// Create service account.
178    #[tracing::instrument(skip(self, state))]
179    async fn create_service_account(
180        &self,
181        state: &ServiceState,
182        sa: ServiceAccountCreate,
183    ) -> Result<ServiceAccount, IdentityProviderError> {
184        match self {
185            Self::Service(provider) => provider.create_service_account(state, sa).await,
186            #[cfg(any(test, feature = "mock"))]
187            Self::Mock(provider) => provider.create_service_account(state, sa).await,
188        }
189    }
190
191    /// Create user.
192    #[tracing::instrument(skip(self, state))]
193    async fn create_user(
194        &self,
195        state: &ServiceState,
196        user: UserCreate,
197    ) -> Result<UserResponse, IdentityProviderError> {
198        match self {
199            Self::Service(provider) => provider.create_user(state, user).await,
200            #[cfg(any(test, feature = "mock"))]
201            Self::Mock(provider) => provider.create_user(state, user).await,
202        }
203    }
204
205    /// Delete group.
206    #[tracing::instrument(skip(self, state))]
207    async fn delete_group<'a>(
208        &self,
209        state: &ServiceState,
210        group_id: &'a str,
211    ) -> Result<(), IdentityProviderError> {
212        match self {
213            Self::Service(provider) => provider.delete_group(state, group_id).await,
214            #[cfg(any(test, feature = "mock"))]
215            Self::Mock(provider) => provider.delete_group(state, group_id).await,
216        }
217    }
218
219    /// Delete user.
220    #[tracing::instrument(skip(self, state))]
221    async fn delete_user<'a>(
222        &self,
223        state: &ServiceState,
224        user_id: &'a str,
225    ) -> Result<(), IdentityProviderError> {
226        match self {
227            Self::Service(provider) => provider.delete_user(state, user_id).await,
228            #[cfg(any(test, feature = "mock"))]
229            Self::Mock(provider) => provider.delete_user(state, user_id).await,
230        }
231    }
232
233    /// Get a service account by ID.
234    #[tracing::instrument(skip(self, state))]
235    async fn get_service_account<'a>(
236        &self,
237        state: &ServiceState,
238        user_id: &'a str,
239    ) -> Result<Option<ServiceAccount>, IdentityProviderError> {
240        match self {
241            Self::Service(provider) => provider.get_service_account(state, user_id).await,
242            #[cfg(any(test, feature = "mock"))]
243            Self::Mock(provider) => provider.get_service_account(state, user_id).await,
244        }
245    }
246
247    /// Get single user.
248    #[tracing::instrument(skip(self, state))]
249    async fn get_user<'a>(
250        &self,
251        state: &ServiceState,
252        user_id: &'a str,
253    ) -> Result<Option<UserResponse>, IdentityProviderError> {
254        match self {
255            Self::Service(provider) => provider.get_user(state, user_id).await,
256            #[cfg(any(test, feature = "mock"))]
257            Self::Mock(provider) => provider.get_user(state, user_id).await,
258        }
259    }
260
261    /// Get `domain_id` of a user.
262    ///
263    /// When the caching is enabled check for the cached value there. When no
264    /// data is present for the key - invoke the backend driver and place
265    /// the new value into the cache. Other operations (`get_user`,
266    /// `delete_user`) update the cache with `delete_user` purging the value
267    /// from the cache.
268    async fn get_user_domain_id<'a>(
269        &self,
270        state: &ServiceState,
271        user_id: &'a str,
272    ) -> Result<String, IdentityProviderError> {
273        match self {
274            Self::Service(provider) => provider.get_user_domain_id(state, user_id).await,
275            #[cfg(any(test, feature = "mock"))]
276            Self::Mock(provider) => provider.get_user_domain_id(state, user_id).await,
277        }
278    }
279
280    /// Find federated user by `idp_id` and `unique_id`.
281    #[tracing::instrument(skip(self, state))]
282    async fn find_federated_user<'a>(
283        &self,
284        state: &ServiceState,
285        idp_id: &'a str,
286        unique_id: &'a str,
287    ) -> Result<Option<UserResponse>, IdentityProviderError> {
288        match self {
289            Self::Service(provider) => provider.find_federated_user(state, idp_id, unique_id).await,
290            #[cfg(any(test, feature = "mock"))]
291            Self::Mock(provider) => provider.find_federated_user(state, idp_id, unique_id).await,
292        }
293    }
294
295    /// List users.
296    #[tracing::instrument(skip(self, state))]
297    async fn list_users(
298        &self,
299        state: &ServiceState,
300        params: &UserListParameters,
301    ) -> Result<Vec<UserResponse>, IdentityProviderError> {
302        match self {
303            Self::Service(provider) => provider.list_users(state, params).await,
304            #[cfg(any(test, feature = "mock"))]
305            Self::Mock(provider) => provider.list_users(state, params).await,
306        }
307    }
308
309    /// List groups.
310    #[tracing::instrument(skip(self, state))]
311    async fn list_groups(
312        &self,
313        state: &ServiceState,
314        params: &GroupListParameters,
315    ) -> Result<Vec<Group>, IdentityProviderError> {
316        match self {
317            Self::Service(provider) => provider.list_groups(state, params).await,
318            #[cfg(any(test, feature = "mock"))]
319            Self::Mock(provider) => provider.list_groups(state, params).await,
320        }
321    }
322
323    /// Get single group.
324    #[tracing::instrument(skip(self, state))]
325    async fn get_group<'a>(
326        &self,
327        state: &ServiceState,
328        group_id: &'a str,
329    ) -> Result<Option<Group>, IdentityProviderError> {
330        match self {
331            Self::Service(provider) => provider.get_group(state, group_id).await,
332            #[cfg(any(test, feature = "mock"))]
333            Self::Mock(provider) => provider.get_group(state, group_id).await,
334        }
335    }
336
337    /// List groups a user is a member of.
338    #[tracing::instrument(skip(self, state))]
339    async fn list_groups_of_user<'a>(
340        &self,
341        state: &ServiceState,
342        user_id: &'a str,
343    ) -> Result<Vec<Group>, IdentityProviderError> {
344        match self {
345            Self::Service(provider) => provider.list_groups_of_user(state, user_id).await,
346            #[cfg(any(test, feature = "mock"))]
347            Self::Mock(provider) => provider.list_groups_of_user(state, user_id).await,
348        }
349    }
350
351    #[tracing::instrument(skip(self, state))]
352    async fn remove_user_from_group<'a>(
353        &self,
354        state: &ServiceState,
355        user_id: &'a str,
356        group_id: &'a str,
357    ) -> Result<(), IdentityProviderError> {
358        match self {
359            Self::Service(provider) => {
360                provider
361                    .remove_user_from_group(state, user_id, group_id)
362                    .await
363            }
364            #[cfg(any(test, feature = "mock"))]
365            Self::Mock(provider) => {
366                provider
367                    .remove_user_from_group(state, user_id, group_id)
368                    .await
369            }
370        }
371    }
372
373    #[tracing::instrument(skip(self, state))]
374    async fn remove_user_from_group_expiring<'a>(
375        &self,
376        state: &ServiceState,
377        user_id: &'a str,
378        group_id: &'a str,
379        idp_id: &'a str,
380    ) -> Result<(), IdentityProviderError> {
381        match self {
382            Self::Service(provider) => {
383                provider
384                    .remove_user_from_group_expiring(state, user_id, group_id, idp_id)
385                    .await
386            }
387            #[cfg(any(test, feature = "mock"))]
388            Self::Mock(provider) => {
389                provider
390                    .remove_user_from_group_expiring(state, user_id, group_id, idp_id)
391                    .await
392            }
393        }
394    }
395
396    #[tracing::instrument(skip(self, state))]
397    async fn remove_user_from_groups<'a>(
398        &self,
399        state: &ServiceState,
400        user_id: &'a str,
401        group_ids: HashSet<&'a str>,
402    ) -> Result<(), IdentityProviderError> {
403        match self {
404            Self::Service(provider) => {
405                provider
406                    .remove_user_from_groups(state, user_id, group_ids)
407                    .await
408            }
409            #[cfg(any(test, feature = "mock"))]
410            Self::Mock(provider) => {
411                provider
412                    .remove_user_from_groups(state, user_id, group_ids)
413                    .await
414            }
415        }
416    }
417
418    #[tracing::instrument(skip(self, state))]
419    async fn remove_user_from_groups_expiring<'a>(
420        &self,
421        state: &ServiceState,
422        user_id: &'a str,
423        group_ids: HashSet<&'a str>,
424        idp_id: &'a str,
425    ) -> Result<(), IdentityProviderError> {
426        match self {
427            Self::Service(provider) => {
428                provider
429                    .remove_user_from_groups_expiring(state, user_id, group_ids, idp_id)
430                    .await
431            }
432            #[cfg(any(test, feature = "mock"))]
433            Self::Mock(provider) => {
434                provider
435                    .remove_user_from_groups_expiring(state, user_id, group_ids, idp_id)
436                    .await
437            }
438        }
439    }
440
441    #[tracing::instrument(skip(self, state))]
442    async fn set_user_groups<'a>(
443        &self,
444        state: &ServiceState,
445        user_id: &'a str,
446        group_ids: HashSet<&'a str>,
447    ) -> Result<(), IdentityProviderError> {
448        match self {
449            Self::Service(provider) => provider.set_user_groups(state, user_id, group_ids).await,
450            #[cfg(any(test, feature = "mock"))]
451            Self::Mock(provider) => provider.set_user_groups(state, user_id, group_ids).await,
452        }
453    }
454
455    #[tracing::instrument(skip(self, state))]
456    async fn set_user_groups_expiring<'a>(
457        &self,
458        state: &ServiceState,
459        user_id: &'a str,
460        group_ids: HashSet<&'a str>,
461        idp_id: &'a str,
462        last_verified: Option<&'a DateTime<Utc>>,
463    ) -> Result<(), IdentityProviderError> {
464        match self {
465            Self::Service(provider) => {
466                provider
467                    .set_user_groups_expiring(state, user_id, group_ids, idp_id, last_verified)
468                    .await
469            }
470            #[cfg(any(test, feature = "mock"))]
471            Self::Mock(provider) => {
472                provider
473                    .set_user_groups_expiring(state, user_id, group_ids, idp_id, last_verified)
474                    .await
475            }
476        }
477    }
478}