From 819210ae8611d3e8302b7fbed5f0be7b14c41132 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Fri, 15 Apr 2022 17:04:23 +0200 Subject: [PATCH] Userinfo endpoint is working --- src/actors/openid_sessions_actor.rs | 15 ++++++++ src/controllers/openid_controller.rs | 54 ++++++++++++++++++++++++---- src/data/mod.rs | 3 +- src/data/open_id_user_info.rs | 24 +++++++++++++ 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/data/open_id_user_info.rs diff --git a/src/actors/openid_sessions_actor.rs b/src/actors/openid_sessions_actor.rs index 30226e7..abac6c7 100644 --- a/src/actors/openid_sessions_actor.rs +++ b/src/actors/openid_sessions_actor.rs @@ -50,6 +50,10 @@ pub struct FindSessionByAuthorizationCode(pub String); #[rtype(result = "Option")] pub struct FindSessionByRefreshToken(pub String); +#[derive(Message)] +#[rtype(result = "Option")] +pub struct FindSessionByAccessToken(pub String); + #[derive(Message)] #[rtype(result = "()")] pub struct MarkAuthorizationCodeUsed(pub String); @@ -111,6 +115,17 @@ impl Handler for OpenIDSessionsActor { } } +impl Handler for OpenIDSessionsActor { + type Result = Option; + + fn handle(&mut self, msg: FindSessionByAccessToken, _ctx: &mut Self::Context) -> Self::Result { + self.session + .iter() + .find(|f| f.access_token.eq(&msg.0)) + .cloned() + } +} + impl Handler for OpenIDSessionsActor { type Result = (); diff --git a/src/controllers/openid_controller.rs b/src/controllers/openid_controller.rs index a9773f0..6fa195e 100644 --- a/src/controllers/openid_controller.rs +++ b/src/controllers/openid_controller.rs @@ -6,8 +6,9 @@ use actix_web::{HttpRequest, HttpResponse, Responder, web}; use actix_web::error::ErrorUnauthorized; use askama::Template; -use crate::actors::openid_sessions_actor; +use crate::actors::{openid_sessions_actor, users_actor}; use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID}; +use crate::actors::users_actor::UsersActor; use crate::constants::{AUTHORIZE_URI, CERT_URI, OPEN_ID_ACCESS_TOKEN_LEN, OPEN_ID_ACCESS_TOKEN_TIMEOUT, OPEN_ID_AUTHORIZATION_CODE_LEN, OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, OPEN_ID_REFRESH_TOKEN_LEN, OPEN_ID_REFRESH_TOKEN_TIMEOUT, OPEN_ID_SESSION_LEN, TOKEN_URI, USERINFO_URI}; use crate::controllers::base_controller::FatalErrorPage; use crate::data::app_config::AppConfig; @@ -16,8 +17,10 @@ use crate::data::code_challenge::CodeChallenge; use crate::data::current_user::CurrentUser; use crate::data::id_token::IdToken; use crate::data::jwt_signer::{JsonWebKey, JWTSigner}; +use crate::data::open_id_user_info::OpenIDUserInfo; use crate::data::openid_config::OpenIDConfig; use crate::data::session_identity::SessionIdentity; +use crate::data::user::User; use crate::utils::string_utils::rand_str; use crate::utils::time::time; @@ -406,21 +409,29 @@ pub struct UserInfoQuery { pub async fn user_info_post(req: HttpRequest, form: Option>, - query: web::Query) -> impl Responder { + query: web::Query, + sessions: web::Data>, + users: web::Data>) -> impl Responder { user_info(req, form .map(|f| f.0.access_token) .unwrap_or_default() .or(query.0.access_token), + sessions, + users, ).await } -pub async fn user_info_get(req: HttpRequest, query: web::Query) -> impl Responder { - user_info(req, query.0.access_token).await +pub async fn user_info_get(req: HttpRequest, query: web::Query, + sessions: web::Data>, + users: web::Data>) -> impl Responder { + user_info(req, query.0.access_token, sessions, users).await } /// Authenticate request using RFC6750 /// -async fn user_info(req: HttpRequest, token: Option) -> impl Responder { +async fn user_info(req: HttpRequest, token: Option, + sessions: web::Data>, + users: web::Data>) -> impl Responder { let token = match token { Some(t) => t, None => { @@ -443,6 +454,35 @@ async fn user_info(req: HttpRequest, token: Option) -> impl Responder { } }; - // TODO : continue - HttpResponse::Ok().body(format!("token is {}", token)) + let session: Option = sessions + .send(openid_sessions_actor::FindSessionByAccessToken(token)).await.unwrap(); + let session = match session { + None => { + return user_info_error("invalid_request", "Session not found!"); + } + Some(s) => s, + }; + + if session.access_token_expire_at < time() { + return user_info_error("invalid_request", "Access token has expired!"); + } + + let user: Option = users.send(users_actor::GetUserRequest(session.user)).await.unwrap().0; + let user = match user { + None => { + return user_info_error("invalid_request", "Failed to extract user information!"); + } + Some(u) => u, + }; + + HttpResponse::Ok() + .json(OpenIDUserInfo { + name: user.full_name(), + sub: user.uid, + given_name: user.first_name, + family_name: user.last_name, + preferred_username: user.username, + email: user.email, + email_verified: true, + }) } \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 7fcc00d..cc70b73 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -8,4 +8,5 @@ pub mod current_user; pub mod openid_config; pub mod jwt_signer; pub mod id_token; -pub mod code_challenge; \ No newline at end of file +pub mod code_challenge; +pub mod open_id_user_info; \ No newline at end of file diff --git a/src/data/open_id_user_info.rs b/src/data/open_id_user_info.rs new file mode 100644 index 0000000..af7e703 --- /dev/null +++ b/src/data/open_id_user_info.rs @@ -0,0 +1,24 @@ +/// Refer to for more information +#[derive(Debug, serde::Serialize)] +pub struct OpenIDUserInfo { + /// Subject - Identifier for the End-User at the Issuer + pub sub: String, + + /// End-User's full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the End-User's locale and preferences. + pub name: String, + + /// Given name(s) or first name(s) of the End-User. Note that in some cultures, people can have multiple given names; all can be present, with the names being separated by space characters. + pub given_name: String, + + /// Surname(s) or last name(s) of the End-User. Note that in some cultures, people can have multiple family names or no family name; all can be present, with the names being separated by space characters. + pub family_name: String, + + /// Shorthand name by which the End-User wishes to be referred to at the RP, such as janedoe or j.doe. This value MAY be any valid JSON string including special characters such as @, /, or whitespace. The RP MUST NOT rely upon this value being unique, as discussed in + pub preferred_username: String, + + /// End-User's preferred e-mail address. Its value MUST conform to the RFC 5322 RFC5322 addr-spec syntax. The RP MUST NOT rely upon this value being unique, as discussed in Section 5.7. + pub email: String, + + /// True if the End-User's e-mail address has been verified; otherwise false. When this Claim Value is true, this means that the OP took affirmative steps to ensure that this e-mail address was controlled by the End-User at the time the verification was performed. The means by which an e-mail address is verified is context-specific, and dependent upon the trust framework or contractual agreements within which the parties are operating. + pub email_verified: bool, +} \ No newline at end of file