Start to build userinfo endpoint

This commit is contained in:
Pierre HUBERT 2022-04-14 18:39:18 +02:00
parent 27cee8d3a0
commit b867016a71
4 changed files with 65 additions and 4 deletions

View File

@ -46,6 +46,7 @@ pub const TEMPORARY_PASSWORDS_LEN: usize = 20;
pub const AUTHORIZE_URI: &str = "/openid/authorize"; pub const AUTHORIZE_URI: &str = "/openid/authorize";
pub const TOKEN_URI: &str = "/openid/token"; pub const TOKEN_URI: &str = "/openid/token";
pub const CERT_URI: &str = "/openid/jwks_uri"; pub const CERT_URI: &str = "/openid/jwks_uri";
pub const USERINFO_URI: &str = "/openid/userinfo";
/// Open ID constants /// Open ID constants
pub const OPEN_ID_SESSION_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); pub const OPEN_ID_SESSION_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);

View File

@ -8,7 +8,7 @@ use askama::Template;
use crate::actors::openid_sessions_actor; use crate::actors::openid_sessions_actor;
use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID}; use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID};
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}; 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::controllers::base_controller::FatalErrorPage;
use crate::data::app_config::AppConfig; use crate::data::app_config::AppConfig;
use crate::data::client::{ClientID, ClientManager}; use crate::data::client::{ClientID, ClientManager};
@ -26,7 +26,7 @@ pub async fn get_configuration(app_conf: web::Data<AppConfig>) -> impl Responder
issuer: app_conf.full_url("/"), issuer: app_conf.full_url("/"),
authorization_endpoint: app_conf.full_url(AUTHORIZE_URI), authorization_endpoint: app_conf.full_url(AUTHORIZE_URI),
token_endpoint: app_conf.full_url(TOKEN_URI), token_endpoint: app_conf.full_url(TOKEN_URI),
userinfo_endpoint: app_conf.full_url("openid/userinfo"), userinfo_endpoint: app_conf.full_url(USERINFO_URI),
jwks_uri: app_conf.full_url(CERT_URI), jwks_uri: app_conf.full_url(CERT_URI),
scopes_supported: vec!["openid", "profile", "email"], scopes_supported: vec!["openid", "profile", "email"],
response_types_supported: vec!["code", "id_token", "token id_token"], response_types_supported: vec!["code", "id_token", "token id_token"],
@ -387,4 +387,62 @@ struct CertsResponse {
pub async fn cert_uri(jwt_signer: web::Data<JWTSigner>) -> impl Responder { pub async fn cert_uri(jwt_signer: web::Data<JWTSigner>) -> impl Responder {
HttpResponse::Ok().json(CertsResponse { keys: vec![jwt_signer.get_json_web_key()] }) HttpResponse::Ok().json(CertsResponse { keys: vec![jwt_signer.get_json_web_key()] })
}
fn user_info_error(err: &str, description: &str) -> HttpResponse {
HttpResponse::Unauthorized()
.insert_header(("WWW-Authenticate", format!(
"Bearer error=\"{}\", error_description=\"{}\"",
err,
description
)))
.finish()
}
#[derive(serde::Deserialize)]
pub struct UserInfoQuery {
access_token: Option<String>,
}
pub async fn user_info_post(req: HttpRequest,
form: Option<web::Form<UserInfoQuery>>,
query: web::Query<UserInfoQuery>) -> impl Responder {
user_info(req,
form
.map(|f| f.0.access_token)
.unwrap_or_default()
.or(query.0.access_token),
).await
}
pub async fn user_info_get(req: HttpRequest, query: web::Query<UserInfoQuery>) -> impl Responder {
user_info(req, query.0.access_token).await
}
/// Authenticate request using RFC6750 <https://datatracker.ietf.org/doc/html/rfc6750>///
async fn user_info(req: HttpRequest, token: Option<String>) -> impl Responder {
let token = match token {
Some(t) => t,
None => {
let token = match req.headers().get("Authorization") {
None => return user_info_error("invalid_request", "Missing access token!"),
Some(t) => t
};
let token = match token.to_str() {
Err(_) => return user_info_error("invalid_request", "Failed to decode token!"),
Ok(t) => t,
};
let token = match token.strip_prefix("Bearer ") {
None => return user_info_error("invalid_request", "Header token does not start with 'Bearer '!"),
Some(t) => t,
};
token.to_string()
}
};
// TODO : continue <https://openid.net/specs/openid-connect-core-1_0.html#RFC6749>
HttpResponse::Ok().body(format!("token is {}", token))
} }

View File

@ -136,6 +136,8 @@ async fn main() -> std::io::Result<()> {
.route(AUTHORIZE_URI, web::get().to(openid_controller::authorize)) .route(AUTHORIZE_URI, web::get().to(openid_controller::authorize))
.route(TOKEN_URI, web::post().to(openid_controller::token)) .route(TOKEN_URI, web::post().to(openid_controller::token))
.route(CERT_URI, web::get().to(openid_controller::cert_uri)) .route(CERT_URI, web::get().to(openid_controller::cert_uri))
.route(USERINFO_URI, web::post().to(openid_controller::user_info_post))
.route(USERINFO_URI, web::get().to(openid_controller::user_info_get))
}) })
.bind(listen_address)? .bind(listen_address)?
.run() .run()

View File

@ -13,7 +13,7 @@ use actix_web::body::EitherBody;
use actix_web::http::{header, Method}; use actix_web::http::{header, Method};
use askama::Template; use askama::Template;
use crate::constants::{ADMIN_ROUTES, AUTHENTICATED_ROUTES, AUTHORIZE_URI, TOKEN_URI}; use crate::constants::{ADMIN_ROUTES, AUTHENTICATED_ROUTES, AUTHORIZE_URI, TOKEN_URI, USERINFO_URI};
use crate::controllers::base_controller::{FatalErrorPage, redirect_user_for_login}; use crate::controllers::base_controller::{FatalErrorPage, redirect_user_for_login};
use crate::data::app_config::AppConfig; use crate::data::app_config::AppConfig;
use crate::data::session_identity::{SessionIdentity, SessionIdentityData, SessionStatus}; use crate::data::session_identity::{SessionIdentity, SessionIdentityData, SessionStatus};
@ -91,7 +91,7 @@ impl<S, B> Service<ServiceRequest> for AuthInnerMiddleware<S>
// Check if POST request comes from another website (block invalid origins) // Check if POST request comes from another website (block invalid origins)
let origin = req.headers().get(header::ORIGIN); let origin = req.headers().get(header::ORIGIN);
if req.method() == Method::POST && req.path() != TOKEN_URI { if req.method() == Method::POST && req.path() != TOKEN_URI && req.path() != USERINFO_URI {
if let Some(o) = origin { if let Some(o) = origin {
if !o.to_str().unwrap_or("bad").eq(&config.website_origin) { if !o.to_str().unwrap_or("bad").eq(&config.website_origin) {
log::warn!( log::warn!(