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}