kanidmd_lib/idm/
oauth2_client.rs1use crate::idm::server::IdmServerProxyWriteTransaction;
2use crate::prelude::*;
3use std::collections::BTreeSet;
4use std::fmt;
5
6pub const OAUTH2_CLIENT_AUTHORISATION_RESPONSE_PATH: &str = "/ui/login/oauth2_landing";
10
11#[derive(Clone)]
12pub struct OAuth2ClientProvider {
13    pub(crate) name: String,
14    pub(crate) uuid: Uuid,
15    pub(crate) client_id: String,
16    pub(crate) client_basic_secret: String,
17    pub(crate) client_redirect_uri: Url,
19    pub(crate) request_scopes: BTreeSet<String>,
20    pub(crate) authorisation_endpoint: Url,
21    pub(crate) token_endpoint: Url,
22}
23
24impl fmt::Debug for OAuth2ClientProvider {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        f.debug_struct("OAuth2ClientProvider")
27            .field("provider_id", &self.name)
28            .field("provider_name", &self.uuid)
29            .field("client_id", &self.client_id)
30            .finish()
31    }
32}
33
34impl OAuth2ClientProvider {
35    #[cfg(test)]
36    pub fn new_test<'a, I: IntoIterator<Item = &'a str>>(
37        client_id: &str,
38        domain: &str,
39        request_scopes: I,
40    ) -> Self {
41        let mut client_redirect_uri =
43            Url::parse("https://idm.example.com").expect("invalid test data");
44        client_redirect_uri.set_path(OAUTH2_CLIENT_AUTHORISATION_RESPONSE_PATH);
45
46        let mut domain = Url::parse(domain).expect("invalid test data");
47
48        domain.set_path("/oauth2/authorise");
49        let authorisation_endpoint = domain.clone();
50
51        domain.set_path("/oauth2/token");
52        let token_endpoint = domain.clone();
53
54        let client_basic_secret = crate::utils::password_from_random();
55
56        let request_scopes = request_scopes.into_iter().map(String::from).collect();
57
58        Self {
59            name: "test_client_provider".to_string(),
60            uuid: Uuid::new_v4(),
61            client_id: client_id.to_string(),
62            client_basic_secret,
63            client_redirect_uri,
64            request_scopes,
65            authorisation_endpoint,
66            token_endpoint,
67        }
68    }
69}
70
71impl IdmServerProxyWriteTransaction<'_> {
72    #[instrument(level = "debug", skip_all)]
73    pub(crate) fn reload_oauth2_client_providers(&mut self) -> Result<(), OperationError> {
74        let oauth2_client_provider_entries = self.qs_write.internal_search(filter!(f_eq(
75            Attribute::Class,
76            EntryClass::OAuth2Client.into(),
77        )))?;
78
79        let mut oauth2_client_provider_structs =
81            Vec::with_capacity(oauth2_client_provider_entries.len());
82
83        let mut client_redirect_uri = self.origin.clone();
84        client_redirect_uri.set_path(OAUTH2_CLIENT_AUTHORISATION_RESPONSE_PATH);
85
86        for provider_entry in oauth2_client_provider_entries {
87            let uuid = provider_entry.get_uuid();
88            trace!(?uuid, "Checking OAuth2 Provider configuration");
89
90            let name = provider_entry
91                .get_ava_single_iname(Attribute::Name)
92                .map(str::to_string)
93                .ok_or(OperationError::InvalidValueState)?;
94
95            let client_id = provider_entry
96                .get_ava_single_utf8(Attribute::OAuth2ClientId)
97                .map(str::to_string)
98                .ok_or(OperationError::InvalidValueState)?;
99
100            let client_basic_secret = provider_entry
101                .get_ava_single_utf8(Attribute::OAuth2ClientSecret)
102                .map(str::to_string)
103                .ok_or(OperationError::InvalidValueState)?;
104
105            let authorisation_endpoint = provider_entry
106                .get_ava_single_url(Attribute::OAuth2AuthorisationEndpoint)
107                .cloned()
108                .ok_or(OperationError::InvalidValueState)?;
109
110            let token_endpoint = provider_entry
111                .get_ava_single_url(Attribute::OAuth2TokenEndpoint)
112                .cloned()
113                .ok_or(OperationError::InvalidValueState)?;
114
115            let request_scopes = provider_entry
116                .get_ava_as_oauthscopes(Attribute::OAuth2RequestScopes)
117                .ok_or(OperationError::InvalidValueState)?
118                .map(str::to_string)
119                .collect();
120
121            let provider = OAuth2ClientProvider {
122                name,
123                uuid,
124                client_id,
125                client_basic_secret,
126                client_redirect_uri: client_redirect_uri.clone(),
127                request_scopes,
128                authorisation_endpoint,
129                token_endpoint,
130            };
131
132            oauth2_client_provider_structs.push((uuid, provider));
133        }
134
135        self.oauth2_client_providers.clear();
137
138        self.oauth2_client_providers
140            .extend(oauth2_client_provider_structs);
141
142        Ok(())
144    }
145}