Skip to main content

openstack_keystone_core/federation/
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//
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//! # Federation provider
15//!
16//! Federation provider implements the functionality necessary for the user
17//! federation.
18use async_trait::async_trait;
19use std::sync::Arc;
20use uuid::Uuid;
21
22use crate::config::Config;
23use crate::federation::{FederationProviderError, backend::FederationBackend, types::*};
24use crate::keystone::ServiceState;
25use crate::plugin_manager::PluginManagerApi;
26
27pub struct FederationService {
28    backend_driver: Arc<dyn FederationBackend>,
29}
30
31impl FederationService {
32    pub fn new<P: PluginManagerApi>(
33        config: &Config,
34        plugin_manager: &P,
35    ) -> Result<Self, FederationProviderError> {
36        let backend_driver = plugin_manager
37            .get_federation_backend(config.federation.driver.clone())?
38            .clone();
39        Ok(Self { backend_driver })
40    }
41}
42
43#[async_trait]
44impl FederationApi for FederationService {
45    /// Cleanup expired resources.
46    #[tracing::instrument(level = "info", skip(self, state))]
47    async fn cleanup(&self, state: &ServiceState) -> Result<(), FederationProviderError> {
48        self.backend_driver.cleanup(state).await
49    }
50
51    /// Create new auth state.
52    #[tracing::instrument(level = "debug", skip(self, state))]
53    async fn create_auth_state(
54        &self,
55        state: &ServiceState,
56        auth_state: AuthState,
57    ) -> Result<AuthState, FederationProviderError> {
58        self.backend_driver
59            .create_auth_state(state, auth_state)
60            .await
61    }
62
63    /// Create Identity provider.
64    #[tracing::instrument(level = "debug", skip(self, state))]
65    async fn create_identity_provider(
66        &self,
67        state: &ServiceState,
68        idp: IdentityProviderCreate,
69    ) -> Result<IdentityProvider, FederationProviderError> {
70        let mut mod_idp = idp;
71        if mod_idp.id.is_none() {
72            mod_idp.id = Some(Uuid::new_v4().simple().to_string());
73        }
74
75        self.backend_driver
76            .create_identity_provider(state, mod_idp)
77            .await
78    }
79
80    /// Create mapping.
81    #[tracing::instrument(level = "debug", skip(self, state))]
82    async fn create_mapping(
83        &self,
84        state: &ServiceState,
85        mapping: Mapping,
86    ) -> Result<Mapping, FederationProviderError> {
87        let mut mod_mapping = mapping;
88        mod_mapping.id = Uuid::new_v4().into();
89        if let Some(_pid) = &mod_mapping.token_project_id {
90            // ensure domain_id is set and matches the one of the project_id.
91            if let Some(_did) = &mod_mapping.domain_id {
92                // TODO: Get the project_id and compare the domain_id
93            } else {
94                return Err(FederationProviderError::MappingTokenProjectDomainUnset);
95            }
96            // TODO: ensure current user has access to the project
97        }
98
99        self.backend_driver.create_mapping(state, mod_mapping).await
100    }
101
102    /// Delete auth state.
103    #[tracing::instrument(level = "debug", skip(self, state))]
104    async fn delete_auth_state<'a>(
105        &self,
106        state: &ServiceState,
107        id: &'a str,
108    ) -> Result<(), FederationProviderError> {
109        self.backend_driver.delete_auth_state(state, id).await
110    }
111
112    /// Delete identity provider.
113    #[tracing::instrument(level = "debug", skip(self, state))]
114    async fn delete_identity_provider<'a>(
115        &self,
116        state: &ServiceState,
117        id: &'a str,
118    ) -> Result<(), FederationProviderError> {
119        self.backend_driver
120            .delete_identity_provider(state, id)
121            .await
122    }
123
124    /// Delete identity provider.
125    #[tracing::instrument(level = "debug", skip(self, state))]
126    async fn delete_mapping<'a>(
127        &self,
128        state: &ServiceState,
129        id: &'a str,
130    ) -> Result<(), FederationProviderError> {
131        self.backend_driver.delete_mapping(state, id).await
132    }
133
134    /// Get auth state by ID.
135    #[tracing::instrument(level = "debug", skip(self, state))]
136    async fn get_auth_state<'a>(
137        &self,
138        state: &ServiceState,
139        id: &'a str,
140    ) -> Result<Option<AuthState>, FederationProviderError> {
141        self.backend_driver.get_auth_state(state, id).await
142    }
143
144    /// Get single IDP by ID.
145    #[tracing::instrument(level = "info", skip(self, state))]
146    async fn get_identity_provider<'a>(
147        &self,
148        state: &ServiceState,
149        id: &'a str,
150    ) -> Result<Option<IdentityProvider>, FederationProviderError> {
151        self.backend_driver.get_identity_provider(state, id).await
152    }
153
154    /// Get single mapping by ID.
155    #[tracing::instrument(level = "info", skip(self, state))]
156    async fn get_mapping<'a>(
157        &self,
158        state: &ServiceState,
159        id: &'a str,
160    ) -> Result<Option<Mapping>, FederationProviderError> {
161        self.backend_driver.get_mapping(state, id).await
162    }
163
164    /// List IDP.
165    #[tracing::instrument(level = "info", skip(self, state))]
166    async fn list_identity_providers(
167        &self,
168        state: &ServiceState,
169        params: &IdentityProviderListParameters,
170    ) -> Result<Vec<IdentityProvider>, FederationProviderError> {
171        self.backend_driver
172            .list_identity_providers(state, params)
173            .await
174    }
175
176    /// List mappings.
177    #[tracing::instrument(level = "info", skip(self, state))]
178    async fn list_mappings(
179        &self,
180        state: &ServiceState,
181        params: &MappingListParameters,
182    ) -> Result<Vec<Mapping>, FederationProviderError> {
183        self.backend_driver.list_mappings(state, params).await
184    }
185
186    /// Update Identity provider.
187    #[tracing::instrument(level = "debug", skip(self, state))]
188    async fn update_identity_provider<'a>(
189        &self,
190        state: &ServiceState,
191        id: &'a str,
192        idp: IdentityProviderUpdate,
193    ) -> Result<IdentityProvider, FederationProviderError> {
194        self.backend_driver
195            .update_identity_provider(state, id, idp)
196            .await
197    }
198
199    /// Update mapping
200    #[tracing::instrument(level = "debug", skip(self, state))]
201    async fn update_mapping<'a>(
202        &self,
203        state: &ServiceState,
204        id: &'a str,
205        mapping: MappingUpdate,
206    ) -> Result<Mapping, FederationProviderError> {
207        let current = self
208            .backend_driver
209            .get_mapping(state, id)
210            .await?
211            .ok_or_else(|| FederationProviderError::MappingNotFound(id.to_string()))?;
212
213        if let Some(_new_idp_id) = &mapping.idp_id {
214            // TODO: Check the new idp_id domain escaping
215        }
216
217        if let Some(_pid) = &mapping.token_project_id {
218            // ensure domain_id is set and matches the one of the project_id.
219            if let Some(_did) = &current.domain_id {
220                // TODO: Get the project_id and compare the domain_id
221            } else {
222                return Err(FederationProviderError::MappingTokenProjectDomainUnset);
223            }
224            // TODO: ensure current user has access to the project
225        }
226        // TODO: Pass current to the backend to skip re-fetching
227        self.backend_driver.update_mapping(state, id, mapping).await
228    }
229}