From 4f8afcf3e150fcf5b33bb6ecfc511fb24cf08e4f Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 10 May 2025 13:29:31 +0100 Subject: [PATCH] chore: Fix most clippy issue, format & typos --- Cargo.toml | 6 ++ src/api/client/mod.rs | 4 +- src/api/client/oidc/authorize.rs | 39 +++++----- src/api/client/oidc/discovery.rs | 102 +++++++++++++------------- src/api/client/oidc/login.rs | 31 +++----- src/api/client/oidc/mod.rs | 42 +++++------ src/api/client/oidc/register.rs | 35 ++++----- src/api/client/oidc/token.rs | 27 ++++--- src/api/client/well_known.rs | 21 +++--- src/core/config/check.rs | 12 ++-- src/service/oidc/mod.rs | 45 +++++------- src/service/services.rs | 4 +- src/web/oidc.rs | 12 ++-- src/web/oidc/authorize.rs | 6 +- src/web/oidc/consent.rs | 21 ++---- src/web/oidc/error.rs | 106 ++++++++++++++------------- src/web/oidc/login.rs | 47 +++++------- src/web/oidc/request.rs | 119 +++++++++++++++---------------- src/web/oidc/response.rs | 99 ++++++++++++------------- 19 files changed, 369 insertions(+), 409 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0978a45c..c73520ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -998,6 +998,12 @@ let_underscore_future = { level = "allow", priority = 1 } # rust doesnt understand conduwuit's custom log macros literal_string_with_formatting_args = { level = "allow", priority = 1 } +<<<<<<< HEAD needless_raw_string_hashes = "allow" +||||||| parent of fd972f11 (chore: Fix most clippy issue, format & typos) +======= + +needless_raw_string_hashes = "allow" +>>>>>>> fd972f11 (chore: Fix most clippy issue, format & typos) diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs index 6e1869be..ed384705 100644 --- a/src/api/client/mod.rs +++ b/src/api/client/mod.rs @@ -2,7 +2,6 @@ pub(super) mod account; pub(super) mod account_data; pub(super) mod alias; pub(super) mod appservice; -pub(super) mod oidc; pub(super) mod backup; pub(super) mod capabilities; pub(super) mod context; @@ -14,6 +13,7 @@ pub(super) mod media; pub(super) mod media_legacy; pub(super) mod membership; pub(super) mod message; +pub(super) mod oidc; pub(super) mod openid; pub(super) mod presence; pub(super) mod profile; @@ -45,7 +45,6 @@ pub(super) use account::*; pub(super) use account_data::*; pub(super) use alias::*; pub(super) use appservice::*; -pub(super) use oidc::*; pub(super) use backup::*; pub(super) use capabilities::*; pub(super) use context::*; @@ -58,6 +57,7 @@ pub(super) use media_legacy::*; pub(super) use membership::*; pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room}; pub(super) use message::*; +pub(super) use oidc::*; pub(super) use openid::*; pub(super) use presence::*; pub(super) use profile::*; diff --git a/src/api/client/oidc/authorize.rs b/src/api/client/oidc/authorize.rs index 697e684b..56de2f85 100644 --- a/src/api/client/oidc/authorize.rs +++ b/src/api/client/oidc/authorize.rs @@ -1,10 +1,12 @@ -use conduwuit_web::oidc::{oidc_consent_form, oidc_login_form, AuthorizationQuery, OidcRequest, OidcResponse}; +use axum::extract::{Query, State}; +use conduwuit::{Result, err}; +use conduwuit_web::oidc::{ + AuthorizationQuery, OidcRequest, OidcResponse, oidc_consent_form, oidc_login_form, +}; use oxide_auth::{ endpoint::{OwnerConsent, Solicitation}, frontends::simple::endpoint::FnSolicitor, }; -use axum::extract::{Query, State}; -use conduwuit::{Result, err}; use percent_encoding::percent_decode_str; /// # `GET /_matrix/client/unstable/org.matrix.msc2964/authorize` @@ -24,12 +26,12 @@ pub(crate) async fn authorize( // Enforce MSC2964's restrictions on OAuth2 flow. let Ok(scope) = percent_decode_str(&query.scope).decode_utf8() else { return Err(err!(Request(Unknown("the scope could not be percent-decoded")))); - } ; + }; //if ! scope.contains("urn:matrix:api:*") { - if ! scope.contains("urn:matrix:org.matrix.msc2967.client:api:*") { + if !scope.contains("urn:matrix:org.matrix.msc2967.client:api:*") { return Err(err!(Request(Unknown("the scope does not include the client API")))); } - if ! scope.contains("urn:matrix:org.matrix.msc2967.client:device:") { + if !scope.contains("urn:matrix:org.matrix.msc2967.client:device:") { return Err(err!(Request(Unknown("the scope does not include a device ID")))); } if query.code_challenge_method != "S256" { @@ -48,22 +50,23 @@ pub(crate) async fn authorize( | None => { return Ok(oidc_login_form(hostname, &query)); }, - | Some(token) => if services.users.find_from_token(token).await.is_err() { - return Ok(oidc_login_form(hostname, &query)); - } + | Some(token) => + if services.users.find_from_token(token).await.is_err() { + return Ok(oidc_login_form(hostname, &query)); + }, } // TODO register the device ID ? services .oidc .endpoint() - .with_solicitor(oidc_consent_form(hostname, &query)) + .with_solicitor(oidc_consent_form(hostname, &query)) .authorization_flow() .execute(oauth) .map_err(|err| err!("authorization failed: {err:?}")) } -/// Wether a user allows their device to access this homeserver's resources. +/// Whether a user allows their device to access this homeserver's resources. #[derive(serde::Deserialize)] pub(crate) struct Allowance { allow: Option, @@ -85,14 +88,12 @@ pub(crate) async fn authorize_consent( services .oidc .endpoint() - .with_solicitor( - FnSolicitor(move |_: &mut _, solicitation: Solicitation<'_>| - match allowed { - | false => OwnerConsent::Denied, - | true => OwnerConsent::Authorized(solicitation.pre_grant().client_id.clone()) - } - ) - ) + .with_solicitor(FnSolicitor( + move |_: &mut _, solicitation: Solicitation<'_>| match allowed { + | false => OwnerConsent::Denied, + | true => OwnerConsent::Authorized(solicitation.pre_grant().client_id.clone()), + }, + )) .authorization_flow() .execute(oauth) .map_err(|err| err!(Request(Unknown("consent request failed: {err:?}")))) diff --git a/src/api/client/oidc/discovery.rs b/src/api/client/oidc/discovery.rs index 2d9835b4..a91430a6 100644 --- a/src/api/client/oidc/discovery.rs +++ b/src/api/client/oidc/discovery.rs @@ -3,71 +3,76 @@ /// [MSC2965]: https://github.com/matrix-org/matrix-spec-proposals/pull/2965 use axum::extract::State; use conduwuit::Result; -use ruma::serde::Raw; -use ruma::api::client::{ - error::{ - Error as ClientError, - ErrorKind as ClientErrorKind, - ErrorBody as ClientErrorBody, - }, - discovery::get_authorization_server_metadata::{ - self, - msc2965::{ - AccountManagementAction, - AuthorizationServerMetadata, - CodeChallengeMethod, - GrantType, - Prompt, - ResponseMode, - ResponseType, - Response, +use ruma::{ + api::client::{ + discovery::get_authorization_server_metadata::{ + self, + msc2965::{ + AccountManagementAction, AuthorizationServerMetadata, CodeChallengeMethod, + GrantType, Prompt, Response, ResponseMode, ResponseType, + }, + }, + error::{ + Error as ClientError, ErrorBody as ClientErrorBody, ErrorKind as ClientErrorKind, }, }, + serde::Raw, }; -use crate::{conduwuit::Error, Ruma, RumaResponse}; + +use crate::{Ruma, RumaResponse, conduwuit::Error}; /// # `GET /_matrix/client/unstable/org.matrix.msc2965/auth_metadata` /// -/// If `globals.auth.enable_oidc_login` is set, advertise this homeserver's OAuth2 endpoints. -/// Otherwise, MSC2965 requires that the homeserver responds with 404/M_UNRECOGNIZED. +/// If `globals.auth.enable_oidc_login` is set, advertise this homeserver's +/// OAuth2 endpoints. Otherwise, MSC2965 requires that the homeserver responds +/// with 404/M_UNRECOGNIZED. pub(crate) async fn get_auth_metadata( State(services): State, _body: Ruma, ) -> Result> { - let unrecognized_error = Err(Error::Ruma( - ClientError::new( - http::StatusCode::NOT_FOUND, - ClientErrorBody::Standard { - kind: ClientErrorKind::Unrecognized, - message: "This homeserver doesn't do OIDC authentication.".to_string() - } - ) - )); + let unrecognized_error = Err(Error::Ruma(ClientError::new( + http::StatusCode::NOT_FOUND, + ClientErrorBody::Standard { + kind: ClientErrorKind::Unrecognized, + message: "This homeserver doesn't do OIDC authentication.".to_owned(), + }, + ))); let Some(ref auth) = services.server.config.auth else { return unrecognized_error; }; - if ! auth.enable_oidc_login { + if !auth.enable_oidc_login { return unrecognized_error; - }; + } // Advertise this homeserver's access URL as the issuer URL. // Unwrap all Url::parse() calls because the issuer URL is validated at startup. let issuer = services.server.config.well_known.client.as_ref().unwrap(); - let account_management_uri = auth - .enable_oidc_account_management - .then_some(issuer.join("/_matrix/client/unstable/org.matrix.msc2964/account").unwrap()); + let account_management_uri = auth.enable_oidc_account_management.then_some( + issuer + .join("/_matrix/client/unstable/org.matrix.msc2964/account") + .unwrap(), + ); let metadata = AuthorizationServerMetadata { issuer: issuer.clone(), - authorization_endpoint: - issuer.join("/_matrix/client/unstable/org.matrix.msc2964/authorize").unwrap(), - device_authorization_endpoint: - Some(issuer.join("/_matrix/client/unstable/org.matrix.msc2964/device").unwrap()), - token_endpoint: - issuer.join("/_matrix/client/unstable/org.matrix.msc2964/token").unwrap(), - registration_endpoint: - Some(issuer.join("/_matrix/client/unstable/org.matrix.msc2964/device/register").unwrap()), - revocation_endpoint: - issuer.join("_matrix/client/unstable/org.matrix.msc2964/revoke").unwrap(), + authorization_endpoint: issuer + .join("/_matrix/client/unstable/org.matrix.msc2964/authorize") + .unwrap(), + device_authorization_endpoint: Some( + issuer + .join("/_matrix/client/unstable/org.matrix.msc2964/device") + .unwrap(), + ), + token_endpoint: issuer + .join("/_matrix/client/unstable/org.matrix.msc2964/token") + .unwrap(), + registration_endpoint: Some( + issuer + .join("/_matrix/client/unstable/org.matrix.msc2964/device/register") + .unwrap(), + ), + revocation_endpoint: issuer + .join("_matrix/client/unstable/org.matrix.msc2964/revoke") + .unwrap(), response_types_supported: [ResponseType::Code].into(), grant_types_supported: [GrantType::AuthorizationCode, GrantType::RefreshToken].into(), response_modes_supported: [ResponseMode::Fragment, ResponseMode::Query].into(), @@ -77,11 +82,12 @@ pub(crate) async fn get_auth_metadata( AccountManagementAction::Profile, AccountManagementAction::SessionView, AccountManagementAction::SessionEnd, - ].into(), + ] + .into(), prompt_values_supported: match services.server.config.allow_registration { | true => vec![Prompt::Create], - | false => vec![] - } + | false => vec![], + }, }; let metadata = Raw::new(&metadata).expect("authorization server metadata should serialize"); diff --git a/src/api/client/oidc/login.rs b/src/api/client/oidc/login.rs index f5f8308f..91454e7f 100644 --- a/src/api/client/oidc/login.rs +++ b/src/api/client/oidc/login.rs @@ -1,16 +1,7 @@ -use conduwuit_web::oidc::{ - oidc_consent_form, - AuthorizationQuery, - LoginError, - LoginQuery, - OidcRequest, - OidcResponse, -}; use axum::extract::State; -use conduwuit::{ - Result, - err, - utils::hash::verify_password, +use conduwuit::{Result, err, utils::hash::verify_password}; +use conduwuit_web::oidc::{ + AuthorizationQuery, LoginError, LoginQuery, OidcRequest, OidcResponse, oidc_consent_form, }; use ruma::user_id::UserId; @@ -25,17 +16,15 @@ pub(crate) async fn oidc_login( State(services): State, request: OidcRequest, ) -> Result { - let query: LoginQuery = request.clone().try_into().map_err(|LoginError(err)| + let query: LoginQuery = request.clone().try_into().map_err(|LoginError(err)| { err!(Request(InvalidParam("Cannot process login form. {err}"))) - )?; + })?; // Only accept local usernames. Mostly to simplify things at first. - let user_id = UserId::parse_with_server_name( - query.username.clone(), - &services.config.server_name - ) - .map_err(|e| err!(Request(InvalidUsername("Username is invalid: {e}"))))?; + let user_id = + UserId::parse_with_server_name(query.username.clone(), &services.config.server_name) + .map_err(|e| err!(Request(InvalidUsername("Username is invalid: {e}"))))?; - if ! services.users.exists(&user_id).await { + if !services.users.exists(&user_id).await { return Err(err!(Request(Unknown("unknown username")))); } tracing::info!("logging in: {user_id:?}"); @@ -48,7 +37,7 @@ pub(crate) async fn oidc_login( if valid_hash.is_empty() { return Err(err!(Request(UserDeactivated("the user's hash was not found")))); } - if let Err(_) = verify_password(&query.password, &valid_hash) { + if verify_password(&query.password, &valid_hash).is_err() { return Err(err!(Request(InvalidParam("password does not match")))); } tracing::info!("{user_id:?} passed, forwarding to consent page"); diff --git a/src/api/client/oidc/mod.rs b/src/api/client/oidc/mod.rs index 3303f9a2..1b43ad23 100644 --- a/src/api/client/oidc/mod.rs +++ b/src/api/client/oidc/mod.rs @@ -1,25 +1,27 @@ -/// OIDC -/// -/// Stands for OpenID Connect, and is an authentication scheme relying on OAuth2. -/// The [MSC2964] Matrix Spec Proposal describes an authentication process based -/// on the OIDC flow, with restrictions. See the [sample flow] for details on -/// what's expected. -/// -/// This module implements the needed endpoints. It relies on the [oxide-auth] -/// crate, and the [`service::oidc`] and [`web::oidc`] modules. -/// -/// [MSC2964]: https://github.com/matrix-org/matrix-spec-proposals/pull/2964 -/// [oxide-auth]: https://docs.rs/oxide-auth -/// [sample flow]: https://github.com/sandhose/matrix-spec-proposals/blob/msc/sandhose/oauth2-profile/proposals/2964-oauth2-profile.md#sample-flow +//! OIDC +//! +//! Stands for OpenID Connect, and is an authentication scheme relying on +//! OAuth2. The [MSC2964] Matrix Spec Proposal describes an authentication +//! process based on the OIDC flow, with restrictions. See the [sample flow] for +//! details on what's expected. +//! +//! This module implements the needed endpoints. It relies on the [oxide-auth] +//! crate, and the [`service::oidc`] and [`web::oidc`] modules. +//! +//! [MSC2964]: https://github.com/matrix-org/matrix-spec-proposals/pull/2964 +//! [oxide-auth]: https://docs.rs/oxide-auth +//! [sample flow]: https://github.com/sandhose/matrix-spec-proposals/blob/msc/sandhose/oauth2-profile/proposals/2964-oauth2-profile.md#sample-flow +mod authorize; mod discovery; mod login; -mod authorize; -mod token; mod register; +mod token; -pub(crate) use self::discovery::get_auth_metadata; -pub(crate) use self::login::oidc_login; -pub(crate) use self::authorize::{authorize, authorize_consent}; -pub(crate) use self::register::register_client; -pub(crate) use self::token::token; +pub(crate) use self::{ + authorize::{authorize, authorize_consent}, + discovery::get_auth_metadata, + login::oidc_login, + register::register_client, + token::token, +}; diff --git a/src/api/client/oidc/register.rs b/src/api/client/oidc/register.rs index 7848b3e1..f4996d3c 100644 --- a/src/api/client/oidc/register.rs +++ b/src/api/client/oidc/register.rs @@ -1,22 +1,20 @@ -use oxide_auth::primitives::prelude::Client; -use axum::{ - Json, - extract::State, -}; +use axum::{Json, extract::State}; use conduwuit::{Result, err}; -use ruma::DeviceId; +use oxide_auth::primitives::prelude::Client; use reqwest::Url; +use ruma::DeviceId; /// The required parameters to register a new client for OAuth2 application. #[derive(serde::Deserialize, Clone)] pub(crate) struct ClientQuery { /// Human-readable name. - client_name: String, - /// A public page that tells more about the client. All other links must be within. - client_uri: Url, + client_name: String, + /// A public page that tells more about the client. All other links must be + /// within. + client_uri: Url, /// Redirect URIs declared by the client. At least one. redirect_uris: Vec, - /// Must be ["code"]. + /// Must be `["code"]`. response_types: Vec, /// Must include "authorization_type" and "refresh_token". grant_types: Vec, @@ -51,8 +49,9 @@ pub(crate) struct ClientResponse { /// # `GET /_matrix/client/unstable/org.matrix.msc2964/device/register` /// -/// Register a client, as specified in [MSC2966]. This client, "device" in OIDC parlance, -/// will have the right to submit [super::authorize::authorize] requests. +/// Register a client, as specified in [MSC2966]. This client, "device" in OIDC +/// parlance, will have the right to submit [super::authorize::authorize] +/// requests. /// /// [MSC2966]: https://github.com/matrix-org/matrix-spec-proposals/pull/2966 pub(crate) async fn register_client( @@ -66,15 +65,17 @@ pub(crate) async fn register_client( }; let device_id = DeviceId::new(); let scope = format!( - "urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:{}", - device_id + "urn:matrix:org.matrix.msc2967.client:api:* \ + urn:matrix:org.matrix.msc2967.client:device:{device_id}" ); // TODO check if the users service needs an update. //services.users.update_device_metadata(); services.oidc.register_client(&Client::public( - &device_id.to_string(), + device_id.as_ref(), redirect_uri.into(), - scope.parse().expect("device ID should parse in Matrix scope"), + scope + .parse() + .expect("device ID should parse in Matrix scope"), ))?; Ok(Json(ClientResponse { @@ -88,6 +89,6 @@ pub(crate) async fn register_client( token_endpoint_auth_method: client.token_endpoint_auth_method.clone(), response_types: client.response_types.clone(), grant_types: client.grant_types.clone(), - application_type: client.application_type.clone(), + application_type: client.application_type, })) } diff --git a/src/api/client/oidc/token.rs b/src/api/client/oidc/token.rs index 80809c98..3301eedc 100644 --- a/src/api/client/oidc/token.rs +++ b/src/api/client/oidc/token.rs @@ -1,7 +1,7 @@ -use conduwuit_web::oidc::{OidcRequest, OidcResponse}; -use conduwuit::{Result, err}; -use oxide_auth::endpoint::QueryParameter; use axum::extract::State; +use conduwuit::{Result, err}; +use conduwuit_web::oidc::{OidcRequest, OidcResponse}; +use oxide_auth::endpoint::QueryParameter; /// # `POST /_matrix/client/unstable/org.matrix.msc2964/token` /// @@ -20,17 +20,14 @@ pub(crate) async fn token( let endpoint = services.oidc.endpoint(); match grant_type.as_deref() { - | Some("authorization_code") => - endpoint - .access_token_flow() - .execute(oauth) - .map_err(|err| err!(Request(Unknown("token grant failed: {err:?}")))), - | Some("refresh_token") => - endpoint - .refresh_flow() - .execute(oauth) - .map_err(|err| err!(Request(Unknown("token refresh failed: {err:?}")))), - | other => - Err(err!(Request(Unknown("unsupported grant type: {other:?}")))), + | Some("authorization_code") => endpoint + .access_token_flow() + .execute(oauth) + .map_err(|err| err!(Request(Unknown("token grant failed: {err:?}")))), + | Some("refresh_token") => endpoint + .refresh_flow() + .execute(oauth) + .map_err(|err| err!(Request(Unknown("token refresh failed: {err:?}")))), + | other => Err(err!(Request(Unknown("unsupported grant type: {other:?}")))), } } diff --git a/src/api/client/well_known.rs b/src/api/client/well_known.rs index 698eb470..35ce52f9 100644 --- a/src/api/client/well_known.rs +++ b/src/api/client/well_known.rs @@ -3,10 +3,7 @@ use conduwuit::{Error, Result}; use ruma::api::client::{ discovery::{ discover_homeserver::{ - self, - HomeserverInfo, - SlidingSyncProxyInfo, - AuthenticationServerInfo, + self, AuthenticationServerInfo, HomeserverInfo, SlidingSyncProxyInfo, }, discover_support::{self, Contact}, }, @@ -32,16 +29,14 @@ pub(crate) async fn well_known_client( identity_server: None, sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url.clone() }), tile_server: None, - authentication: services.config.auth.as_ref().and_then(|auth| - auth.enable_oidc_login.then_some( - AuthenticationServerInfo::new( + authentication: services.config.auth.as_ref().and_then(|auth| { + auth.enable_oidc_login + .then_some(AuthenticationServerInfo::new( client_url.clone(), - auth.enable_oidc_account_management.then_some( - format!("{client_url}/account") - ) - ) - ) - ) + auth.enable_oidc_account_management + .then_some(format!("{client_url}/account")), + )) + }), }) } diff --git a/src/core/config/check.rs b/src/core/config/check.rs index 4d4d4a5f..267325a0 100644 --- a/src/core/config/check.rs +++ b/src/core/config/check.rs @@ -272,13 +272,11 @@ pub fn check(config: &Config) -> Result { } if let Some(auth) = &config.auth { - if auth.enable_oidc_login { - if config.well_known.client.is_none() { - return Err!(Config( - "auth.enable_oidc_login", - "Oidc authentication is enabled but the well-known client is not set." - )) - } + if auth.enable_oidc_login && config.well_known.client.is_none() { + return Err!(Config( + "auth.enable_oidc_login", + "Oidc authentication is enabled but the well-known client is not set." + )); } } diff --git a/src/service/oidc/mod.rs b/src/service/oidc/mod.rs index b2eb99d9..93224eda 100644 --- a/src/service/oidc/mod.rs +++ b/src/service/oidc/mod.rs @@ -1,33 +1,26 @@ -/// OIDC service. -/// -/// Provides the registrar, authorizer and issuer needed by [api::client::oidc]. -/// The whole OAuth2 flow is taken care of by [oxide-auth]. -/// -/// TODO this service would need a dedicated space in the database. -/// -/// [oxide-auth]: https://docs.rs/oxide-auth +//! OIDC service. +//! +//! Provides the registrar, authorizer and issuer needed by [api::client::oidc]. +//! The whole OAuth2 flow is taken care of by [oxide-auth]. +//! +//! TODO this service would need a dedicated space in the database. +//! +//! [oxide-auth]: https://docs.rs/oxide-auth +use std::sync::{Arc, Mutex}; + +use async_trait::async_trait; use conduwuit::Result; use oxide_auth::{ frontends::simple::endpoint::{Generic, Vacant}, primitives::{ prelude::{ - AuthMap, - Authorizer, - Client, - ClientMap, - Issuer, - RandomGenerator, - Registrar, - TokenMap, + AuthMap, Authorizer, Client, ClientMap, Issuer, RandomGenerator, Registrar, TokenMap, }, registrar::RegisteredUrl, }, }; -use async_trait::async_trait; -use std::sync::{Arc, Mutex}; - pub struct Service { registrar: Mutex, authorizer: Mutex>, @@ -36,17 +29,14 @@ pub struct Service { #[async_trait] impl crate::Service for Service { - fn build(_args: crate::Args<'_>) -> Result> { - Ok(Arc::new(Self::preconfigured())) - } + fn build(_args: crate::Args<'_>) -> Result> { Ok(Arc::new(Self::preconfigured())) } fn name(&self) -> &str { crate::service::make_name(std::module_path!()) } } impl Service { pub fn register_client(&self, client: &Client) -> Result<()> { - self - .registrar + self.registrar .lock() .expect("lockable registrar") .register_client(client.clone()); @@ -54,8 +44,9 @@ impl Service { Ok(()) } + #[must_use] pub fn preconfigured() -> Self { - Service { + Self { registrar: Mutex::new( vec![Client::public( "LocalClient", @@ -80,7 +71,9 @@ impl Service { } /// The oxide-auth carry-all endpoint. - pub fn endpoint(&self) -> Generic { + pub fn endpoint( + &self, + ) -> Generic { Generic { registrar: self.registrar.lock().unwrap(), authorizer: self.authorizer.lock().unwrap(), diff --git a/src/service/services.rs b/src/service/services.rs index 52beb898..d0d3bdfc 100644 --- a/src/service/services.rs +++ b/src/service/services.rs @@ -13,9 +13,9 @@ use crate::{ account_data, admin, announcements, appservice, client, config, emergency, federation, globals, key_backups, manager::Manager, - media, moderation, presence, pusher, resolver, rooms, sending, server_keys, service, + media, moderation, oidc, presence, pusher, resolver, rooms, sending, server_keys, service, service::{Args, Map, Service}, - sync, transaction_ids, uiaa, users, oidc, + sync, transaction_ids, uiaa, users, }; pub struct Services { diff --git a/src/web/oidc.rs b/src/web/oidc.rs index dfda7103..080f482e 100644 --- a/src/web/oidc.rs +++ b/src/web/oidc.rs @@ -1,20 +1,19 @@ -use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use askama::Template; +use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode}; + // Imports needed by askama templates. -use crate::{ - VERSION_EXTRA, GIT_REMOTE_WEB_URL, GIT_REMOTE_COMMIT_URL, -}; +use crate::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, VERSION_EXTRA}; mod authorize; mod consent; mod error; mod login; -mod response; mod request; +mod response; pub use authorize::AuthorizationQuery; pub use consent::oidc_consent_form; pub use error::OidcError; -pub use login::{LoginQuery, LoginError, oidc_login_form}; +pub use login::{LoginError, LoginQuery, oidc_login_form}; pub use request::OidcRequest; pub use response::OidcResponse; @@ -35,7 +34,6 @@ pub(crate) struct LoginPageTemplate<'a> { response_mode: &'a str, } - /// The parameters for the OIDC consent page template. #[derive(Template)] #[template(path = "consent.html.j2")] diff --git a/src/web/oidc/authorize.rs b/src/web/oidc/authorize.rs index b0c47dae..43a7c176 100644 --- a/src/web/oidc/authorize.rs +++ b/src/web/oidc/authorize.rs @@ -1,4 +1,5 @@ use url::Url; + use super::LoginQuery; /// The set of parameters required for an OIDC authorization request. @@ -27,8 +28,8 @@ impl From for AuthorizationQuery { response_mode, .. } = value; - - AuthorizationQuery { + + Self { client_id, redirect_uri, scope, @@ -40,4 +41,3 @@ impl From for AuthorizationQuery { } } } - diff --git a/src/web/oidc/consent.rs b/src/web/oidc/consent.rs index 231f36e7..4759ef32 100644 --- a/src/web/oidc/consent.rs +++ b/src/web/oidc/consent.rs @@ -1,20 +1,14 @@ -use super::{ - encode, - ConsentPageTemplate, - AuthorizationQuery, - OidcResponse, -}; use askama::Template; use oxide_auth::frontends::simple::request::{Body, Status}; +use super::{AuthorizationQuery, ConsentPageTemplate, OidcResponse, encode}; + /// A web consent solicitor form for the OIDC authentication flow. /// /// Asks the resource owner for their consent to let a client access their data /// on this server. -pub fn oidc_consent_form( - hostname: &str, - query: &AuthorizationQuery, -) -> OidcResponse { +#[must_use] +pub fn oidc_consent_form(hostname: &str, query: &AuthorizationQuery) -> OidcResponse { // The target request route. let route = "/_matrix/client/unstable/org.matrix.msc2964/authorize"; let nonce = rand::random::().to_string(); @@ -30,12 +24,7 @@ pub fn oidc_consent_form( } /// Render the html contents of the user consent page. -fn consent_page( - hostname: &str, - query: &AuthorizationQuery, - route: &str, - nonce: &str, -) -> String { +fn consent_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &str) -> String { let template = ConsentPageTemplate { nonce, hostname, diff --git a/src/web/oidc/error.rs b/src/web/oidc/error.rs index 81513814..8a167623 100644 --- a/src/web/oidc/error.rs +++ b/src/web/oidc/error.rs @@ -1,80 +1,78 @@ -use super::OidcRequest; use axum::{ - http::{header::InvalidHeaderValue, StatusCode}, - response::{IntoResponse, Response}, + http::{StatusCode, header::InvalidHeaderValue}, + response::{IntoResponse, Response}, }; use oxide_auth::frontends::{dev::OAuthError, simple::endpoint::Error}; +use super::OidcRequest; + #[derive(Debug)] /// The error type for Oxide Auth operations pub enum OidcError { - /// Errors occuring in Endpoint operations - Endpoint(OAuthError), - /// Errors occuring in Endpoint operations - Header(InvalidHeaderValue), - /// Errors with the request encoding - Encoding, - /// Request body could not be parsed as a form - Form, - /// Request query was absent or could not be parsed - Query, - /// Request query was absent or could not be parsed - Body, - /// The Authorization header was invalid - Authorization, - /// General internal server error - InternalError(Option), + /// Errors occurring in Endpoint operations + Endpoint(OAuthError), + /// Errors occurring in Endpoint operations + Header(InvalidHeaderValue), + /// Errors with the request encoding + Encoding, + /// Request body could not be parsed as a form + Form, + /// Request query was absent or could not be parsed + Query, + /// Request query was absent or could not be parsed + Body, + /// The Authorization header was invalid + Authorization, + /// General internal server error + InternalError(Option), } impl std::fmt::Display for OidcError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - OidcError::Endpoint(ref e) => write!(f, "Endpoint, {}", e), - OidcError::Header(ref e) => write!(f, "Couldn't set header, {}", e), - OidcError::Encoding => write!(f, "Error decoding request"), - OidcError::Form => write!(f, "Request is not a form"), - OidcError::Query => write!(f, "No query present"), - OidcError::Body => write!(f, "No body present"), - OidcError::Authorization => write!(f, "Request has invalid Authorization headers"), - OidcError::InternalError(None) => write!(f, "An internal server error occured"), - OidcError::InternalError(Some(ref e)) => write!(f, "An internal server error occured: {}", e), - } - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + | Self::Endpoint(ref e) => write!(f, "Endpoint, {e}"), + | Self::Header(ref e) => write!(f, "Couldn't set header, {e}"), + | Self::Encoding => write!(f, "Error decoding request"), + | Self::Form => write!(f, "Request is not a form"), + | Self::Query => write!(f, "No query present"), + | Self::Body => write!(f, "No body present"), + | Self::Authorization => write!(f, "Request has invalid Authorization headers"), + | Self::InternalError(None) => write!(f, "An internal server error occurred"), + | Self::InternalError(Some(ref e)) => + write!(f, "An internal server error occurred: {e}"), + } + } } impl std::error::Error for OidcError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match *self { - OidcError::Endpoint(ref e) => e.source(), - OidcError::Header(ref e) => e.source(), - _ => None, - } - } + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + | Self::Endpoint(ref e) => e.source(), + | Self::Header(ref e) => e.source(), + | _ => None, + } + } } impl IntoResponse for OidcError { - fn into_response(self) -> Response { - (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() - } + fn into_response(self) -> Response { + (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() + } } impl From> for OidcError { - fn from(e: Error) -> Self { - match e { - Error::Web(e) => e, - Error::OAuth(e) => e.into(), - } - } + fn from(e: Error) -> Self { + match e { + | Error::Web(e) => e, + | Error::OAuth(e) => e.into(), + } + } } impl From for OidcError { - fn from(e: OAuthError) -> Self { - OidcError::Endpoint(e) - } + fn from(e: OAuthError) -> Self { Self::Endpoint(e) } } impl From for OidcError { - fn from(e: InvalidHeaderValue) -> Self { - Self::Header(e) - } + fn from(e: InvalidHeaderValue) -> Self { Self::Header(e) } } diff --git a/src/web/oidc/login.rs b/src/web/oidc/login.rs index 0e1f18ad..678bf1f2 100644 --- a/src/web/oidc/login.rs +++ b/src/web/oidc/login.rs @@ -1,17 +1,13 @@ -use super::{ - AuthorizationQuery, - LoginPageTemplate, - OidcRequest, - OidcResponse, -}; +use std::str::FromStr; + use askama::Template; use oxide_auth::{ endpoint::QueryParameter, frontends::simple::request::{Body, Status}, }; use url::Url; -use std::str::FromStr; +use super::{AuthorizationQuery, LoginPageTemplate, OidcRequest, OidcResponse}; /// The set of query parameters a client needs to get authorization. #[derive(serde::Deserialize, Debug, Clone)] @@ -38,40 +34,40 @@ impl TryFrom for LoginQuery { let body = value.body().expect("body in OidcRequest"); let Some(username) = body.unique_value("username") else { - return Err(LoginError("missing field: username".to_string())); + return Err(LoginError("missing field: username".to_owned())); }; let Some(password) = body.unique_value("password") else { - return Err(LoginError("missing field: password".to_string())); + return Err(LoginError("missing field: password".to_owned())); }; let Some(client_id) = body.unique_value("client_id") else { - return Err(LoginError("missing field: client_id".to_string())); + return Err(LoginError("missing field: client_id".to_owned())); }; let Some(redirect_uri) = body.unique_value("redirect_uri") else { - return Err(LoginError("missing field: redirect_uri".to_string())); + return Err(LoginError("missing field: redirect_uri".to_owned())); }; let Some(scope) = body.unique_value("scope") else { - return Err(LoginError("missing field: scope".to_string())); + return Err(LoginError("missing field: scope".to_owned())); }; let Some(state) = body.unique_value("state") else { - return Err(LoginError("missing field: state".to_string())); + return Err(LoginError("missing field: state".to_owned())); }; let Some(code_challenge) = body.unique_value("code_challenge") else { - return Err(LoginError("missing field: code_challenge".to_string())); + return Err(LoginError("missing field: code_challenge".to_owned())); }; let Some(code_challenge_method) = body.unique_value("code_challenge_method") else { - return Err(LoginError("missing field: code_challenge_method".to_string())); + return Err(LoginError("missing field: code_challenge_method".to_owned())); }; let Some(response_type) = body.unique_value("response_type") else { - return Err(LoginError("missing field: response_type".to_string())); + return Err(LoginError("missing field: response_type".to_owned())); }; let Some(response_mode) = body.unique_value("response_mode") else { - return Err(LoginError("missing field: response_mode".to_string())); + return Err(LoginError("missing field: response_mode".to_owned())); }; let Ok(redirect_uri) = Url::from_str(&redirect_uri) else { - return Err(LoginError("invalid field: redirect_uri".to_string())); + return Err(LoginError("invalid field: redirect_uri".to_owned())); }; - Ok(LoginQuery { + Ok(Self { username: username.to_string(), password: password.to_string(), client_id: client_id.to_string(), @@ -89,10 +85,8 @@ impl TryFrom for LoginQuery { /// A web login form for the OIDC authentication flow. /// /// The returned `OidcResponse` handles CSP headers to allow that form. -pub fn oidc_login_form( - hostname: &str, - query: &AuthorizationQuery, -) -> OidcResponse { +#[must_use] +pub fn oidc_login_form(hostname: &str, query: &AuthorizationQuery) -> OidcResponse { // The target request route. let route = "/_matrix/client/unstable/org.matrix.msc2964/login"; let nonce = rand::random::().to_string(); @@ -108,12 +102,7 @@ pub fn oidc_login_form( } /// Render the html contents of the login page. -fn login_page( - hostname: &str, - query: &AuthorizationQuery, - route: &str, - nonce: &str, -) -> String { +fn login_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &str) -> String { let template = LoginPageTemplate { nonce, hostname, diff --git a/src/web/oidc/request.rs b/src/web/oidc/request.rs index b8301eb1..c0a1b18c 100644 --- a/src/web/oidc/request.rs +++ b/src/web/oidc/request.rs @@ -1,11 +1,13 @@ -use super::{OidcError, OidcResponse}; -use oxide_auth::endpoint::{NormalizedParameter, QueryParameter, WebRequest}; +use std::borrow::Cow; + use async_trait::async_trait; use axum::{ extract::{Form, FromRequest, FromRequestParts, Query, Request}, http::header, }; -use std::borrow::Cow; +use oxide_auth::endpoint::{NormalizedParameter, QueryParameter, WebRequest}; + +use super::{OidcError, OidcResponse}; /// An OIDC authentication request. /// @@ -16,84 +18,79 @@ use std::borrow::Cow; /// [oxide-auth-axum]: https://docs.rs/oxide-auth-axum #[derive(Clone, Debug)] pub struct OidcRequest { - pub(crate) auth: Option, - pub(crate) query: Option, - pub(crate) body: Option, + pub(crate) auth: Option, + pub(crate) query: Option, + pub(crate) body: Option, } impl OidcRequest { - /// Fetch the authorization header from the request - pub fn authorization_header(&self) -> Option<&str> { - self.auth.as_deref() - } + /// Fetch the authorization header from the request + #[must_use] + pub fn authorization_header(&self) -> Option<&str> { self.auth.as_deref() } - /// Fetch the query for this request - pub fn query(&self) -> Option<&NormalizedParameter> { - self.query.as_ref() - } + /// Fetch the query for this request + #[must_use] + pub fn query(&self) -> Option<&NormalizedParameter> { self.query.as_ref() } - /// Fetch the query mutably - pub fn query_mut(&mut self) -> Option<&mut NormalizedParameter> { - self.query.as_mut() - } + /// Fetch the query mutably + pub fn query_mut(&mut self) -> Option<&mut NormalizedParameter> { self.query.as_mut() } - /// Fetch the body of the request - pub fn body(&self) -> Option<&NormalizedParameter> { - self.body.as_ref() - } + /// Fetch the body of the request + #[must_use] + pub fn body(&self) -> Option<&NormalizedParameter> { self.body.as_ref() } } impl WebRequest for OidcRequest { - type Error = OidcError; - type Response = OidcResponse; + type Error = OidcError; + type Response = OidcResponse; - fn query(&mut self) -> Result, Self::Error> { - self.query - .as_ref() - .map(|q| Cow::Borrowed(q as &dyn QueryParameter)) - .ok_or(OidcError::Query) - } + fn query(&mut self) -> Result, Self::Error> { + self.query + .as_ref() + .map(|q| Cow::Borrowed(q as &dyn QueryParameter)) + .ok_or(OidcError::Query) + } - fn urlbody(&mut self) -> Result, Self::Error> { - self.body - .as_ref() - .map(|b| Cow::Borrowed(b as &dyn QueryParameter)) - .ok_or(OidcError::Body) - } + fn urlbody(&mut self) -> Result, Self::Error> { + self.body + .as_ref() + .map(|b| Cow::Borrowed(b as &dyn QueryParameter)) + .ok_or(OidcError::Body) + } - fn authheader(&mut self) -> Result>, Self::Error> { - Ok(self.auth.as_deref().map(Cow::Borrowed)) - } + fn authheader(&mut self) -> Result>, Self::Error> { + Ok(self.auth.as_deref().map(Cow::Borrowed)) + } } #[async_trait] impl FromRequest for OidcRequest where - S: Send + Sync, + S: Send + Sync, { - type Rejection = OidcError; + type Rejection = OidcError; - async fn from_request(req: Request, state: &S) -> Result { - let mut all_auth = req.headers().get_all(header::AUTHORIZATION).iter(); - let optional = all_auth.next(); + async fn from_request(req: Request, state: &S) -> Result { + let mut all_auth = req.headers().get_all(header::AUTHORIZATION).iter(); + let optional = all_auth.next(); - let auth = if all_auth.next().is_some() { - return Err(OidcError::Authorization); - } else { - optional.and_then(|hv| hv.to_str().ok().map(str::to_owned)) - }; + let auth = if all_auth.next().is_some() { + return Err(OidcError::Authorization); + } else { + optional.and_then(|hv| hv.to_str().ok().map(str::to_owned)) + }; - let (mut parts, body) = req.into_parts(); - let query = Query::from_request_parts(&mut parts, state) - .await - .ok() - .map(|q: Query| q.0); + let (mut parts, body) = req.into_parts(); + let query = Query::from_request_parts(&mut parts, state) + .await + .ok() + .map(|q: Query| q.0); - let req = Request::from_parts(parts, body); - let body = Form::from_request(req, state) - .await - .ok() - .map(|b: Form| b.0); + let req = Request::from_parts(parts, body); + let body = Form::from_request(req, state) + .await + .ok() + .map(|b: Form| b.0); // If the query is empty and the body has a request, copy it over // because login forms are POST requests but OAuth flow expects @@ -110,6 +107,6 @@ where }, }; - Ok(Self { auth, query, body }) - } + Ok(Self { auth, query, body }) + } } diff --git a/src/web/oidc/response.rs b/src/web/oidc/response.rs index 87a61b00..1dcbd904 100644 --- a/src/web/oidc/response.rs +++ b/src/web/oidc/response.rs @@ -1,15 +1,16 @@ -use super::{oidc_consent_form, LoginQuery, OidcError, OidcRequest}; +use axum::{ + body::Body, + http::{Response, header}, + response::IntoResponse, +}; use oxide_auth::{ endpoint::{OwnerConsent, OwnerSolicitor, Solicitation, WebRequest, WebResponse}, frontends::simple::request::{Body as OAuthRequestBody, Status}, }; -use axum::{ - body::Body, - http::{header, Response}, - response::IntoResponse, -}; use url::Url; +use super::{LoginQuery, OidcError, OidcRequest, oidc_consent_form}; + /// A Web response that can be processed by the OIDC authentication flow before /// being sent over. #[derive(Default, Clone, Debug)] @@ -22,9 +23,9 @@ pub struct OidcResponse { } impl OidcResponse { - /// Instanciate from a response body. Used to send login or consent forms. + /// Instantiate from a response body. Used to send login or consent forms. pub fn from_body(body: &str) -> Result { - let mut result = OidcResponse::default(); + let mut result = Self::default(); result.body_text(body)?; Ok(result) @@ -33,17 +34,19 @@ impl OidcResponse { impl IntoResponse for OidcResponse { fn into_response(self) -> Response { - let body = self.body.expect("body").as_str().to_string(); - let response = Response::builder() + let body = self.body.expect("body").as_str().to_owned(); + + Response::builder() .header(header::CONTENT_TYPE, "text/html") .header( header::CONTENT_SECURITY_POLICY, - format!("default-src 'nonce-{}'; form-action https://eon.presentmatter.one/;", self.nonce) + format!( + "default-src 'nonce-{}'; form-action https://eon.presentmatter.one/;", + self.nonce + ), ) .body(body.into()) - .unwrap(); - - response + .unwrap() } } @@ -62,10 +65,7 @@ impl OwnerSolicitor for OidcResponse { .try_into() .expect("login query from OidcRequest"); - OwnerConsent::InProgress(oidc_consent_form( - hostname, - &query.into(), - )) + OwnerConsent::InProgress(oidc_consent_form(hostname, &query.into())) } } @@ -80,39 +80,40 @@ impl WebResponse for OidcResponse { Ok(()) } - /// A response which will redirect the user-agent to which the response is issued. - fn redirect(&mut self, url: Url) -> Result<(), Self::Error> { - self.status = Status::Redirect; - self.location = Some(url); - self.www_authenticate = None; - Ok(()) - } + /// A response which will redirect the user-agent to which the response is + /// issued. + fn redirect(&mut self, url: Url) -> Result<(), Self::Error> { + self.status = Status::Redirect; + self.location = Some(url); + self.www_authenticate = None; + Ok(()) + } - /// Set the response status to 400. - fn client_error(&mut self) -> Result<(), Self::Error> { - self.status = Status::BadRequest; - self.location = None; - self.www_authenticate = None; - Ok(()) - } + /// Set the response status to 400. + fn client_error(&mut self) -> Result<(), Self::Error> { + self.status = Status::BadRequest; + self.location = None; + self.www_authenticate = None; + Ok(()) + } - /// Set the response status to 401 and add a `WWW-Authenticate` header. - fn unauthorized(&mut self, header_value: &str) -> Result<(), Self::Error> { - self.status = Status::Unauthorized; - self.location = None; - self.www_authenticate = Some(header_value.to_owned()); - Ok(()) - } + /// Set the response status to 401 and add a `WWW-Authenticate` header. + fn unauthorized(&mut self, header_value: &str) -> Result<(), Self::Error> { + self.status = Status::Unauthorized; + self.location = None; + self.www_authenticate = Some(header_value.to_owned()); + Ok(()) + } - /// A pure text response with no special media type set. - fn body_text(&mut self, text: &str) -> Result<(), Self::Error> { - self.body = Some(OAuthRequestBody::Text(text.to_owned())); - Ok(()) - } + /// A pure text response with no special media type set. + fn body_text(&mut self, text: &str) -> Result<(), Self::Error> { + self.body = Some(OAuthRequestBody::Text(text.to_owned())); + Ok(()) + } - /// Json repsonse data, with media type `aplication/json. - fn body_json(&mut self, data: &str) -> Result<(), Self::Error> { - self.body = Some(OAuthRequestBody::Json(data.to_owned())); - Ok(()) - } + /// Json response data, with media type `aplication/json. + fn body_json(&mut self, data: &str) -> Result<(), Self::Error> { + self.body = Some(OAuthRequestBody::Json(data.to_owned())); + Ok(()) + } }