]> git.proxmox.com Git - proxmox.git/blob - src/lib.rs
allow to configure used scopes
[proxmox.git] / src / lib.rs
1 use std::path::Path;
2
3 use anyhow::{format_err, Error};
4 use serde::{Deserialize, Serialize};
5
6 mod http_client;
7 pub use http_client::http_client;
8
9 mod auth_state;
10 pub use auth_state::*;
11
12
13 use openidconnect::{
14 //curl::http_client,
15 core::{
16 CoreProviderMetadata,
17 CoreClient,
18 CoreIdTokenClaims,
19 CoreIdTokenVerifier,
20 CoreAuthenticationFlow,
21 CoreAuthDisplay,
22 CoreAuthPrompt,
23 },
24 PkceCodeChallenge,
25 PkceCodeVerifier,
26 AuthorizationCode,
27 ClientId,
28 ClientSecret,
29 CsrfToken,
30 IssuerUrl,
31 Nonce,
32 OAuth2TokenResponse,
33 RedirectUrl,
34 Scope,
35 };
36
37 #[derive(Debug, Deserialize, Serialize, Clone)]
38 pub struct OpenIdConfig {
39 pub issuer_url: String,
40 pub client_id: String,
41 #[serde(skip_serializing_if="Option::is_none")]
42 pub client_key: Option<String>,
43 #[serde(skip_serializing_if="Option::is_none")]
44 pub scopes: Option<Vec<String>>,
45 }
46
47 pub struct OpenIdAuthenticator {
48 client: CoreClient,
49 config: OpenIdConfig,
50 }
51
52 #[derive(Debug, Deserialize, Serialize)]
53 pub struct PublicAuthState {
54 pub csrf_token: CsrfToken,
55 pub realm: String,
56 }
57
58 #[derive(Debug, Deserialize, Serialize)]
59 pub struct PrivateAuthState {
60 pub csrf_token: CsrfToken,
61 pub nonce: Nonce,
62 pub pkce_verifier: PkceCodeVerifier,
63 pub ctime: i64,
64 }
65
66 impl PrivateAuthState {
67
68 pub fn new() -> Self {
69 let nonce = Nonce::new_random();
70 let csrf_token = CsrfToken::new_random();
71 let (_pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
72
73 PrivateAuthState {
74 csrf_token,
75 nonce,
76 pkce_verifier,
77 ctime: proxmox_time::epoch_i64(),
78 }
79 }
80
81 pub fn pkce_verifier(&self) -> PkceCodeVerifier {
82 // Note: PkceCodeVerifier does not impl. clone()
83 PkceCodeVerifier::new(self.pkce_verifier.secret().to_string())
84 }
85
86 pub fn pkce_challenge(&self) -> PkceCodeChallenge {
87 PkceCodeChallenge::from_code_verifier_sha256(&self.pkce_verifier)
88 }
89
90 pub fn public_state_string(&self, realm: String) -> Result<String, Error> {
91 let pub_state = PublicAuthState {
92 csrf_token: self.csrf_token.clone(),
93 realm,
94 };
95 Ok(serde_json::to_string(&pub_state)?)
96 }
97 }
98
99 impl OpenIdAuthenticator {
100
101 pub fn discover(config: &OpenIdConfig, redirect_url: &str) -> Result<Self, Error> {
102
103 let client_id = ClientId::new(config.client_id.clone());
104 let client_key = config.client_key.clone().map(|key| ClientSecret::new(key));
105 let issuer_url = IssuerUrl::new(config.issuer_url.clone())?;
106
107 let provider_metadata = CoreProviderMetadata::discover(&issuer_url, http_client)?;
108
109 let client = CoreClient::from_provider_metadata(
110 provider_metadata,
111 client_id,
112 client_key,
113 ).set_redirect_uri(RedirectUrl::new(String::from(redirect_url))?);
114
115 Ok(Self {
116 client,
117 config: config.clone(),
118 })
119 }
120
121 pub fn authorize_url(&self, state_dir: &str, realm: &str) -> Result<String, Error> {
122
123 let private_auth_state = PrivateAuthState::new();
124 let public_auth_state = private_auth_state.public_state_string(realm.to_string())?;
125 let nonce = private_auth_state.nonce.clone();
126
127 store_auth_state(Path::new(state_dir), realm, &private_auth_state)?;
128
129 // Generate the authorization URL to which we'll redirect the user.
130 let mut request = self.client
131 .authorize_url(
132 CoreAuthenticationFlow::AuthorizationCode,
133 || CsrfToken::new(public_auth_state),
134 || nonce,
135 )
136 .set_pkce_challenge(private_auth_state.pkce_challenge());
137
138 request = request.set_display(CoreAuthDisplay::Page);
139
140 request = request.add_prompt(CoreAuthPrompt::Login);
141
142 if let Some(ref scopes) = self.config.scopes {
143 for scope in scopes.clone() {
144 request = request.add_scope(Scope::new(scope));
145 }
146 }
147
148 let (authorize_url, _csrf_state, _nonce) = request.url();
149
150 Ok(authorize_url.to_string())
151 }
152
153 pub fn verify_public_auth_state(
154 state_dir: &str,
155 state: &str,
156 ) -> Result<(String, PrivateAuthState), Error> {
157 verify_public_auth_state(Path::new(state_dir), state)
158 }
159
160 pub fn verify_authorization_code(
161 &self,
162 code: &str,
163 private_auth_state: &PrivateAuthState,
164 ) -> Result<CoreIdTokenClaims, Error> {
165
166 let code = AuthorizationCode::new(code.to_string());
167 // Exchange the code with a token.
168 let token_response = self.client
169 .exchange_code(code)
170 .set_pkce_verifier(private_auth_state.pkce_verifier())
171 .request(http_client)
172 .map_err(|err| format_err!("Failed to contact token endpoint: {}", err))?;
173
174 let id_token_verifier: CoreIdTokenVerifier = self.client.id_token_verifier();
175 let id_token_claims: &CoreIdTokenClaims = token_response
176 .extra_fields()
177 .id_token()
178 .expect("Server did not return an ID token")
179 .claims(&id_token_verifier, &private_auth_state.nonce)
180 .map_err(|err| format_err!("Failed to verify ID token: {}", err))?;
181
182 Ok(id_token_claims.clone())
183 }
184 }