|
| 1 | +use crate::config::AppState; |
| 2 | +use crate::db; |
| 3 | +use crate::util::ugkthid_to_user; |
| 4 | +use anyhow::{anyhow, Result}; |
| 5 | +use openidconnect::core::{ |
| 6 | + CoreAuthDisplay, CoreAuthPrompt, CoreAuthenticationFlow, CoreErrorResponseType, |
| 7 | + CoreGenderClaim, CoreJsonWebKey, CoreJsonWebKeyType, CoreJsonWebKeyUse, |
| 8 | + CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm, CoreProviderMetadata, |
| 9 | + CoreRevocableToken, CoreRevocationErrorResponse, CoreTokenIntrospectionResponse, CoreTokenType, |
| 10 | +}; |
| 11 | +use serde::Serialize; |
| 12 | + |
| 13 | +use openidconnect::{ |
| 14 | + AccessTokenHash, AdditionalClaims, AuthorizationCode, Client, ClientId, ClientSecret, |
| 15 | + CsrfToken, EmptyExtraTokenFields, IdTokenFields, IssuerUrl, Nonce, RedirectUrl, Scope, |
| 16 | + StandardErrorResponse, StandardTokenResponse, |
| 17 | +}; |
| 18 | +use rocket::http::{Cookie, Cookies}; |
| 19 | +use rocket::request::Form; |
| 20 | +use rocket::response::Redirect; |
| 21 | +use rocket::State; |
| 22 | +use rocket_client_addr::ClientAddr; |
| 23 | +use serde::Deserialize; |
| 24 | +use std::env; |
| 25 | + |
| 26 | +use openidconnect::reqwest::http_client; |
| 27 | +use openidconnect::{OAuth2TokenResponse, TokenResponse}; |
| 28 | + |
| 29 | +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] |
| 30 | +pub struct KthAdditionalClaims { |
| 31 | + kthid: std::string::String, |
| 32 | +} |
| 33 | +impl AdditionalClaims for KthAdditionalClaims {} |
| 34 | + |
| 35 | +pub type KthIdTokenFields = IdTokenFields< |
| 36 | + KthAdditionalClaims, |
| 37 | + EmptyExtraTokenFields, |
| 38 | + CoreGenderClaim, |
| 39 | + CoreJweContentEncryptionAlgorithm, |
| 40 | + CoreJwsSigningAlgorithm, |
| 41 | + CoreJsonWebKeyType, |
| 42 | +>; |
| 43 | + |
| 44 | +pub type KthTokenResponse = StandardTokenResponse<KthIdTokenFields, CoreTokenType>; |
| 45 | + |
| 46 | +// EmptyAdditionalClaims, |
| 47 | +type KthClient = Client< |
| 48 | + KthAdditionalClaims, |
| 49 | + CoreAuthDisplay, |
| 50 | + CoreGenderClaim, |
| 51 | + CoreJweContentEncryptionAlgorithm, |
| 52 | + CoreJwsSigningAlgorithm, |
| 53 | + CoreJsonWebKeyType, |
| 54 | + CoreJsonWebKeyUse, |
| 55 | + CoreJsonWebKey, |
| 56 | + CoreAuthPrompt, |
| 57 | + StandardErrorResponse<CoreErrorResponseType>, |
| 58 | + KthTokenResponse, |
| 59 | + CoreTokenType, |
| 60 | + CoreTokenIntrospectionResponse, |
| 61 | + CoreRevocableToken, |
| 62 | + CoreRevocationErrorResponse, |
| 63 | +>; |
| 64 | + |
| 65 | +#[get("/login")] |
| 66 | +pub fn kth_login(cookies: Cookies) -> Redirect { |
| 67 | + match use_oidc(cookies) { |
| 68 | + Ok(redirect) => redirect, |
| 69 | + Err(err) => { |
| 70 | + println!("oidc error: {:?}", err); |
| 71 | + Redirect::to("https://queue.csc.kth.se/failed_login") |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +#[derive(FromForm, Default)] |
| 77 | +pub struct Code { |
| 78 | + code: Option<String>, |
| 79 | + #[allow(dead_code)] |
| 80 | + state: Option<String>, |
| 81 | +} |
| 82 | + |
| 83 | +#[get("/oidc-auth?<params..>")] |
| 84 | +pub fn kth_oidc_auth( |
| 85 | + mut cookies: Cookies, |
| 86 | + conn: db::DbConn, |
| 87 | + state: State<AppState>, |
| 88 | + params: Form<Code>, |
| 89 | + client_addr: &ClientAddr, |
| 90 | +) -> Redirect { |
| 91 | + println!("starting oidc auth"); |
| 92 | + // cookies.add(Cookie::new("nonce", nonce.secret().clone())); |
| 93 | + match cookies.get("nonce") { |
| 94 | + Some(nonce) => { |
| 95 | + println!("got nonce: {}", nonce.value()); |
| 96 | + match get_oidc_user(params, Nonce::new(nonce.value().to_string())) { |
| 97 | + Ok(ugkthid) => { |
| 98 | + println!("good login!"); |
| 99 | + match ugkthid_to_user(&conn, ugkthid) { |
| 100 | + Some(user) => { |
| 101 | + println!("User logged in: {:?}", user); |
| 102 | + cookies.add(Cookie::new( |
| 103 | + "userdata", |
| 104 | + json!(user.to_user_auth(&conn, &state.secret, client_addr)) |
| 105 | + .to_string(), |
| 106 | + )); |
| 107 | + } |
| 108 | + None => println!("Login failed for some reason..."), |
| 109 | + } |
| 110 | + } |
| 111 | + Err(err) => { |
| 112 | + println!("oidc error: {:?}", err); |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + None => println!("failed to get nonce"), |
| 117 | + } |
| 118 | + Redirect::to("/") |
| 119 | +} |
| 120 | + |
| 121 | +pub fn get_client() -> Result<KthClient> { |
| 122 | + let provider_metadata = CoreProviderMetadata::discover( |
| 123 | + &IssuerUrl::new("https://login.ug.kth.se/adfs".to_string())?, |
| 124 | + http_client, |
| 125 | + )?; |
| 126 | + |
| 127 | + // Create an OpenID Connect client by specifying the client ID, client secret, authorization URL |
| 128 | + // and token URL. |
| 129 | + let application_id = env::var("APPLICATION_ID").expect("OIDC need an application ID"); |
| 130 | + let client_secret = env::var("CLIENT_SECRET").expect("OIDC need a client secret"); |
| 131 | + let client = KthClient::from_provider_metadata( |
| 132 | + provider_metadata, |
| 133 | + ClientId::new(application_id), |
| 134 | + Some(ClientSecret::new(client_secret)), |
| 135 | + ) |
| 136 | + // Set the URL the user will be redirected to after the authorization process. |
| 137 | + .set_redirect_uri(RedirectUrl::new( |
| 138 | + "https://queue.csc.kth.se/oidc-auth".to_string(), |
| 139 | + )?); |
| 140 | + Ok(client) |
| 141 | +} |
| 142 | + |
| 143 | +pub fn use_oidc(mut cookies: Cookies) -> Result<Redirect> { |
| 144 | + let client = get_client()?; |
| 145 | + |
| 146 | + // Generate the full authorization URL. |
| 147 | + let (auth_url, _csrf_token, nonce) = client |
| 148 | + .authorize_url( |
| 149 | + CoreAuthenticationFlow::AuthorizationCode, |
| 150 | + CsrfToken::new_random, |
| 151 | + Nonce::new_random, |
| 152 | + ) |
| 153 | + // Set the desired scopes. |
| 154 | + .add_scope(Scope::new("kthid".to_string())) |
| 155 | + .url(); |
| 156 | + |
| 157 | + cookies.add(Cookie::new("nonce", nonce.secret().clone())); |
| 158 | + // println!("wrote nonce: {:?}", nonce.secret()); |
| 159 | + Ok(Redirect::to(auth_url.to_string())) |
| 160 | +} |
| 161 | + |
| 162 | +pub fn get_oidc_user(params: Form<Code>, nonce: Nonce) -> Result<String> { |
| 163 | + // println!("got nonce: {:?}", nonce.secret()); |
| 164 | + let client = get_client()?; |
| 165 | + let code = params |
| 166 | + .code |
| 167 | + .as_ref() |
| 168 | + .ok_or_else(|| anyhow!("got no code in request"))?; |
| 169 | + // Once the user has been redirected to the redirect URL, you'll have access to the |
| 170 | + // authorization code. For security reasons, your code should verify that the `state` |
| 171 | + // parameter returned by the server matches `csrf_state`. |
| 172 | + |
| 173 | + // Now you can exchange it for an access token and ID token. |
| 174 | + let token_response = client |
| 175 | + .exchange_code(AuthorizationCode::new(code.to_string())) |
| 176 | + .request(http_client)?; |
| 177 | + |
| 178 | + // Extract the ID token claims after verifying its authenticity and nonce. |
| 179 | + let id_token = token_response |
| 180 | + .id_token() |
| 181 | + .ok_or_else(|| anyhow!("Server did not return an ID token"))?; |
| 182 | + let claims = id_token.claims(&client.id_token_verifier(), &nonce)?; |
| 183 | + |
| 184 | + println!("Got the claims: {:?}", claims); |
| 185 | + // Verify the access token hash to ensure that the access token hasn't been substituted for |
| 186 | + // another user's. |
| 187 | + if let Some(expected_access_token_hash) = claims.access_token_hash() { |
| 188 | + let actual_access_token_hash = |
| 189 | + AccessTokenHash::from_token(token_response.access_token(), &id_token.signing_alg()?)?; |
| 190 | + if actual_access_token_hash != *expected_access_token_hash { |
| 191 | + return Err(anyhow!("Invalid access token")); |
| 192 | + } |
| 193 | + } |
| 194 | + println!("almost done now!"); |
| 195 | + |
| 196 | + // The authenticated user's identity is now available. See the IdTokenClaims struct for a |
| 197 | + // complete listing of the available claims. |
| 198 | + println!("Got kthid: {:?}", claims.additional_claims().kthid); |
| 199 | + Ok(claims.additional_claims().kthid.clone()) |
| 200 | +} |
0 commit comments