kanidmd_lib/idm/authsession/
handler_oauth2_client.rs1use super::{CredState, BAD_AUTH_TYPE_MSG, BAD_OAUTH2_CSRF_STATE_MSG};
2use crate::idm::account::OAuth2AccountCredential;
3use crate::idm::authentication::{AuthCredential, AuthExternal};
4use crate::idm::oauth2::PkceS256Secret;
5use crate::idm::oauth2_client::OAuth2ClientProvider;
6use crate::prelude::*;
7use crate::utils;
8use crate::value::{AuthType, SessionExtMetadata};
9use kanidm_proto::oauth2::{
10    AccessTokenRequest, AccessTokenResponse, AuthorisationRequest, GrantTypeReq, ResponseType,
11};
12use std::collections::BTreeSet;
13use std::fmt;
14
15pub struct CredHandlerOAuth2Client {
16    provider_id: Uuid,
18    provider_name: String,
19
20    user_id: String,
22    user_cred_id: Uuid,
23
24    request_scopes: BTreeSet<String>,
25    client_id: String,
26    client_basic_secret: String,
27    client_redirect_url: Url,
28    authorisation_endpoint: Url,
29    token_endpoint: Url,
30    pkce_secret: PkceS256Secret,
31    csrf_state: String,
32}
33
34impl fmt::Debug for CredHandlerOAuth2Client {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        f.debug_struct("CredHandlerOauth2Trust")
37            .field("provider_id", &self.provider_id)
38            .field("provider_name", &self.provider_name)
39            .field("user_id", &self.user_id)
40            .field("client_id", &self.client_id)
41            .field("authorisation_endpoint", &self.authorisation_endpoint)
42            .field("token_endpoint", &self.token_endpoint)
43            .finish()
44    }
45}
46
47impl CredHandlerOAuth2Client {
48    pub fn new(
49        client_provider: &OAuth2ClientProvider,
50        client_user_cred: &OAuth2AccountCredential,
51    ) -> Self {
52        let pkce_secret = PkceS256Secret::default();
53        let csrf_state = utils::password_from_random();
54
55        CredHandlerOAuth2Client {
56            provider_id: client_provider.uuid,
57            provider_name: client_provider.name.clone(),
58            request_scopes: client_provider.request_scopes.clone(),
59            user_id: client_user_cred.user_id.to_string(),
60            user_cred_id: client_user_cred.cred_id,
61            client_id: client_provider.client_id.clone(),
62            client_basic_secret: client_provider.client_basic_secret.clone(),
63            client_redirect_url: client_provider.client_redirect_uri.clone(),
64            authorisation_endpoint: client_provider.authorisation_endpoint.clone(),
65            token_endpoint: client_provider.token_endpoint.clone(),
66            pkce_secret,
67            csrf_state,
68        }
69    }
70
71    pub fn start_auth_request(&self) -> (Url, AuthorisationRequest) {
72        let pkce_request = self.pkce_secret.to_request();
73
74        (
75            self.authorisation_endpoint.clone(),
76            AuthorisationRequest {
77                redirect_uri: self.client_redirect_url.clone(),
78                response_type: ResponseType::Code,
79                response_mode: None,
80                client_id: self.client_id.clone(),
81                state: Some(self.csrf_state.clone()),
82                pkce_request: Some(pkce_request),
83                scope: self.request_scopes.clone(),
84                nonce: None,
85                oidc_ext: Default::default(),
86                max_age: None,
87                unknown_keys: Default::default(),
88            },
89        )
90    }
91
92    pub fn validate(&self, cred: &AuthCredential, current_time: Duration) -> CredState {
93        match cred {
94            AuthCredential::OAuth2AuthorisationResponse { code, state } => {
95                self.validate_authorisation_response(code, state.as_deref())
96            }
97            AuthCredential::OAuth2AccessTokenResponse { response } => {
98                self.validate_access_token_response(response, current_time)
99            }
100            _ => CredState::Denied(BAD_AUTH_TYPE_MSG),
101        }
102    }
103
104    fn validate_authorisation_response(&self, code: &str, state: Option<&str>) -> CredState {
105        let csrf_valid = state.map(|s| s == self.csrf_state).unwrap_or_default();
108
109        if !csrf_valid {
110            return CredState::Denied(BAD_OAUTH2_CSRF_STATE_MSG);
111        }
112
113        let code_verifier = Some(self.pkce_secret.verifier().to_string());
114
115        let grant_type_req = GrantTypeReq::AuthorizationCode {
116            code: code.into(),
117            redirect_uri: self.client_redirect_url.clone(),
118            code_verifier,
119        };
120
121        let request = AccessTokenRequest::from(grant_type_req);
122
123        CredState::External(AuthExternal::OAuth2AccessTokenRequest {
124            token_url: self.token_endpoint.clone(),
125            client_id: self.client_id.clone(),
126            client_secret: self.client_basic_secret.clone(),
127            request,
128        })
129    }
130
131    fn validate_access_token_response(
132        &self,
133        response: &AccessTokenResponse,
134        current_time: Duration,
135    ) -> CredState {
136        let cred_id = self.user_cred_id;
139        let access_expires_at = current_time + Duration::from_secs(response.expires_in as u64);
140
141        let ext_session_metadata = SessionExtMetadata::OAuth2 {
144            access_token: response.access_token.clone(),
145            refresh_token: response.refresh_token.clone(),
146            access_expires_at,
147        };
148
149        CredState::Success {
150            auth_type: AuthType::OAuth2Trust,
151            cred_id,
152            ext_session_metadata,
153        }
154    }
155}