mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-15 05:06:53 +00:00
chore: Clippy and formatting
This commit is contained in:
11 files changed
+224
-197
No files matched your search
@@ -76,7 +76,8 @@ pub(crate) async fn upload_keys_route(
|
||||
if deser_device_keys.device_id != sender_device {
|
||||
return Err!(Request(Unknown(
|
||||
"Device ID in keys uploaded ({}) does not match your own device ID ({})",
|
||||
deser_device_keys.device_id, sender_device,
|
||||
deser_device_keys.device_id,
|
||||
sender_device,
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::extract::{Query, State};
|
||||
use conduwuit::{Result, err, utils::ReadyExt};
|
||||
use conduwuit_web::oidc::{
|
||||
oidc_consent_form, oidc_login_form, AuthorizationQuery, OidcRequest, OidcResponse,
|
||||
AuthorizationQuery, OidcRequest, OidcResponse, oidc_consent_form, oidc_login_form,
|
||||
};
|
||||
use oxide_auth::{
|
||||
endpoint::{OwnerConsent, Solicitation},
|
||||
@@ -41,15 +41,15 @@ pub(crate) async fn authorize(
|
||||
|
||||
// Redirect to the login page if no token or token not known.
|
||||
let hostname = services.config.server_name.host();
|
||||
let Some(token) = oauth.authorization_header() else {
|
||||
return Ok(oidc_login_form(hostname, &query));
|
||||
};
|
||||
let Some(token) = oauth.authorization_header() else {
|
||||
return Ok(oidc_login_form(hostname, &query));
|
||||
};
|
||||
|
||||
tracing::debug!("submitting OIDC authorisation for token : {token:#?}");
|
||||
// Get the user id from the token and add it to the query.
|
||||
let (owner_id, _) = services.oidc.user_and_device_from_token(token).await?;
|
||||
let mut query_with_user_id = query.clone();
|
||||
query_with_user_id.username = Some(owner_id.localpart().to_string());
|
||||
let mut query_with_user_id = query.clone();
|
||||
query_with_user_id.username = Some(owner_id.localpart().to_owned());
|
||||
|
||||
services
|
||||
.oidc
|
||||
@@ -88,47 +88,49 @@ pub(crate) async fn authorize_consent(
|
||||
};
|
||||
let server_name = services.globals.server_name();
|
||||
let owner_id = UserId::parse_with_server_name(owner_id.clone(), server_name)
|
||||
.map_err(|err|
|
||||
err!(Request(InvalidUsername("invalid username {owner_id:?}: {err}")))
|
||||
)?;
|
||||
let Some(matrix_client) = services.oidc
|
||||
.map_err(|err| err!(Request(InvalidUsername("invalid username {owner_id:?}: {err}"))))?;
|
||||
let Some(matrix_client) = services
|
||||
.oidc
|
||||
.client_from_client_id(&query.client_id)
|
||||
.await? else {
|
||||
return Err(err!(Request(Unknown("no client has registered client_id {:?}", query.client_id))));
|
||||
.await?
|
||||
else {
|
||||
return Err(err!(Request(Unknown(
|
||||
"no client has registered client_id {:?}",
|
||||
query.client_id
|
||||
))));
|
||||
};
|
||||
let scope = query.scope
|
||||
.parse()
|
||||
.map_err(|err|
|
||||
err!(Request(Unknown("could not parse scope {:?}: {}", query.scope, err)))
|
||||
)?;
|
||||
let device_id = services.oidc.device_id_from_scope(scope)?;
|
||||
let scope = query.scope.parse().map_err(|err| {
|
||||
err!(Request(Unknown("could not parse scope {:?}: {}", query.scope, err)))
|
||||
})?;
|
||||
let device_id = services.oidc.device_id_from_scope(&scope)?;
|
||||
// Check that the device is registered in the owner devices list.
|
||||
// Note that this is _not_ the OIDC client registration.
|
||||
let device_is_registered_with_owner = services
|
||||
.users
|
||||
.all_device_ids(&owner_id)
|
||||
.ready_any(|v| v == device_id)
|
||||
.await;
|
||||
if ! device_is_registered_with_owner {
|
||||
.await;
|
||||
if !device_is_registered_with_owner {
|
||||
// TODO get the client's IP from the request.
|
||||
let client_ip = None;
|
||||
services.oidc.register_device(
|
||||
&query.client_id,
|
||||
(&owner_id, &device_id),
|
||||
matrix_client.name.as_deref(),
|
||||
client_ip,
|
||||
).await?;
|
||||
services
|
||||
.oidc
|
||||
.register_device(
|
||||
&query.client_id,
|
||||
(&owner_id, &device_id),
|
||||
matrix_client.name.as_deref(),
|
||||
client_ip,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
services
|
||||
.oidc
|
||||
.endpoint()
|
||||
.with_solicitor(FnSolicitor(
|
||||
move |_: &mut _, _: Solicitation<'_>| match allow.clone() {
|
||||
| None => OwnerConsent::Denied,
|
||||
| Some(user_id) => OwnerConsent::Authorized(user_id),
|
||||
},
|
||||
))
|
||||
.with_solicitor(FnSolicitor(move |_: &mut _, _: Solicitation<'_>| match allow.clone() {
|
||||
| None => OwnerConsent::Denied,
|
||||
| Some(user_id) => OwnerConsent::Authorized(user_id),
|
||||
}))
|
||||
.authorization_flow()
|
||||
.execute(oauth)
|
||||
.map_err(|err| err!(Request(Unknown("consent request failed: {err:?}"))))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Result, err, utils::hash::verify_password};
|
||||
use conduwuit_web::oidc::{
|
||||
LoginError, LoginQuery, OidcRequest, OidcResponse, oidc_consent_form,
|
||||
};
|
||||
use conduwuit_web::oidc::{LoginError, LoginQuery, OidcRequest, OidcResponse, oidc_consent_form};
|
||||
use ruma::user_id::UserId;
|
||||
|
||||
//#[axum::debug_handler]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use axum::{Json, extract::State};
|
||||
use conduwuit::{Result, err};
|
||||
use conduwuit_service::oidc::normalize_redirect;
|
||||
use oxide_auth::primitives::prelude::Client;
|
||||
use reqwest::Url;
|
||||
use ruma::{DeviceId, ClientSecret, identifiers_validation};
|
||||
use conduwuit_service::oidc::normalize_redirect;
|
||||
use ruma::{ClientSecret, DeviceId, identifiers_validation};
|
||||
|
||||
/// The required parameters to register a new client for OAuth2 application.
|
||||
/// See the required metadata in OAuth2 authorization grant flow in [MSC2966].
|
||||
@@ -22,8 +22,9 @@ pub(crate) struct ClientQuery {
|
||||
response_types: Vec<String>,
|
||||
/// Must include the literals "authorization_code" and "refresh_token".
|
||||
grant_types: Vec<String>,
|
||||
/// How the client intends to authenticate its requests. Can be "none", meaning
|
||||
/// that the client will negotiate its token with the "authorization code" flow.
|
||||
/// How the client intends to authenticate its requests. Can be "none",
|
||||
/// meaning that the client will negotiate its token with the
|
||||
/// "authorization code" flow.
|
||||
token_endpoint_auth_method: String,
|
||||
/// Link to the logo.
|
||||
logo_uri: Option<Url>,
|
||||
@@ -42,8 +43,8 @@ pub(crate) struct ClientResponse {
|
||||
client_id: String,
|
||||
/// If the client is private, the secret it authenticates itself with.
|
||||
client_secret: Option<String>,
|
||||
/// If there's a `client_secret`, its expiration date in seconds since 1970-01-01T00:00.
|
||||
/// Some(0) means no expiration date.
|
||||
/// If there's a `client_secret`, its expiration date in seconds since
|
||||
/// 1970-01-01T00:00. Some(0) means no expiration date.
|
||||
client_secret_expires_at: Option<u32>,
|
||||
client_name: String,
|
||||
/// Points to the "about" page of the client.
|
||||
@@ -51,10 +52,10 @@ pub(crate) struct ClientResponse {
|
||||
logo_uri: Option<Url>,
|
||||
tos_uri: Option<Url>,
|
||||
policy_uri: Option<Url>,
|
||||
/// Registered redirect uris, which will be matched against when authenticating.
|
||||
/// If a localhost address, must contain instances of oxide-auth's
|
||||
/// `RegisteredUrl::IgnorePortOnLocalhost` to let authorization flow through any port over
|
||||
/// localhost.
|
||||
/// Registered redirect uris, which will be matched against when
|
||||
/// authenticating. If a localhost address, must contain instances of
|
||||
/// oxide-auth's `RegisteredUrl::IgnorePortOnLocalhost` to let
|
||||
/// authorization flow through any port over localhost.
|
||||
redirect_uris: Vec<Url>,
|
||||
token_endpoint_auth_method: String,
|
||||
response_types: Vec<String>,
|
||||
@@ -74,20 +75,18 @@ pub(crate) async fn register_client(
|
||||
Json(client): Json<ClientQuery>,
|
||||
) -> Result<Json<ClientResponse>> {
|
||||
tracing::trace!("processing OIDC device register request for client: {client:#?}");
|
||||
if client.redirect_uris.len() == 0 {
|
||||
return Err(err!(Request(Unknown(
|
||||
"the client's registration request should contain at least a redirect_uri"
|
||||
))));
|
||||
if client.redirect_uris.is_empty() {
|
||||
return Err(err!(Request(Unknown(
|
||||
"the client's registration request should contain at least a redirect_uri"
|
||||
))));
|
||||
}
|
||||
let mut redirect_uris = client.redirect_uris.clone();
|
||||
let redirect_uri = redirect_uris.pop().expect("at least one redirect_uri");
|
||||
let redirect_uri = normalize_redirect(redirect_uri);
|
||||
let remaining_uris = redirect_uris
|
||||
.into_iter()
|
||||
.map(|url| normalize_redirect(url))
|
||||
.collect();
|
||||
let remaining_uris = redirect_uris.into_iter().map(normalize_redirect).collect();
|
||||
let device_id = DeviceId::new();
|
||||
// Only provide a default scope, we'll test the client's proposed scope for consent anyway.
|
||||
// Only provide a default scope, we'll test the client's proposed scope for
|
||||
// consent anyway.
|
||||
let scope = "default".parse().unwrap();
|
||||
// TODO check if the users service needs an update.
|
||||
//services.users.update_device_metadata();
|
||||
@@ -96,12 +95,12 @@ pub(crate) async fn register_client(
|
||||
// it's a public client. This is usually the case in Matrix.
|
||||
let is_private = client.token_endpoint_auth_method != "none";
|
||||
let client_secret = match is_private {
|
||||
true => {
|
||||
| true => {
|
||||
let secret = ClientSecret::new();
|
||||
identifiers_validation::client_secret::validate(secret.as_str())?;
|
||||
Some(secret.to_string())
|
||||
},
|
||||
false => None
|
||||
| false => None,
|
||||
};
|
||||
let registration = match is_private {
|
||||
| true => &Client::confidential(
|
||||
@@ -109,15 +108,15 @@ pub(crate) async fn register_client(
|
||||
redirect_uri,
|
||||
scope,
|
||||
client_secret.as_ref().unwrap().as_bytes(),
|
||||
).with_additional_redirect_uris(remaining_uris),
|
||||
| _ => &Client::public(
|
||||
device_id.as_ref(),
|
||||
redirect_uri,
|
||||
scope,
|
||||
).with_additional_redirect_uris(remaining_uris)
|
||||
)
|
||||
.with_additional_redirect_uris(remaining_uris),
|
||||
| _ => &Client::public(device_id.as_ref(), redirect_uri, scope)
|
||||
.with_additional_redirect_uris(remaining_uris),
|
||||
};
|
||||
tracing::trace!("registering OIDC device : {registration:#?}");
|
||||
services.oidc.register_client(Some(client.client_name.clone()), ®istration);
|
||||
services
|
||||
.oidc
|
||||
.register_client(Some(client.client_name.clone()), registration);
|
||||
|
||||
let client_response = ClientResponse {
|
||||
client_id: device_id.to_string(),
|
||||
|
||||
@@ -434,14 +434,14 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "userroomid_notificationcount",
|
||||
..descriptor::RANDOM
|
||||
},
|
||||
Descriptor {
|
||||
name: "client_registrar",
|
||||
..descriptor::RANDOM
|
||||
},
|
||||
Descriptor {
|
||||
name: "deviceid_clientidmap",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "client_registrar",
|
||||
..descriptor::RANDOM
|
||||
},
|
||||
Descriptor {
|
||||
name: "deviceid_clientidmap",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "userroomid_invitesender",
|
||||
..descriptor::RANDOM_SMALL
|
||||
|
||||
+106
-103
@@ -5,11 +5,15 @@
|
||||
//!
|
||||
//! [oxide-auth]: https://docs.rs/oxide-auth
|
||||
|
||||
use std::{borrow::Cow, sync::{Arc, Mutex}};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{err, Result};
|
||||
use conduwuit::{Result, err};
|
||||
use conduwuit_core::utils;
|
||||
use database::{Deserialized, Json, Map};
|
||||
use oxide_auth::{
|
||||
endpoint::{PreGrant, Scope},
|
||||
frontends::simple::endpoint::{Generic, Vacant},
|
||||
@@ -24,25 +28,17 @@ use oxide_auth::{
|
||||
},
|
||||
};
|
||||
use ruma::{
|
||||
api::client::device::Device,
|
||||
MilliSecondsSinceUnixEpoch,
|
||||
OwnedDeviceId,
|
||||
OwnedUserId,
|
||||
UserId,
|
||||
MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, UserId, api::client::device::Device,
|
||||
};
|
||||
use database::{Deserialized, Json, Map};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{globals, Dep};
|
||||
|
||||
use crate::{Dep, globals};
|
||||
|
||||
pub const SCOPE_PREFIX_DEVICE: &str = "urn:matrix:org.matrix.msc2967.client:device:";
|
||||
pub const SCOPE_PREFIX_API : &str = "urn:matrix:org.matrix.msc2967.client:api:";
|
||||
|
||||
static PASSWORD_POLICY: Lazy<Argon2> = Lazy::new(Argon2::default);
|
||||
pub const SCOPE_PREFIX_API: &str = "urn:matrix:org.matrix.msc2967.client:api:";
|
||||
|
||||
static PASSWORD_POLICY: std::sync::LazyLock<Argon2> = std::sync::LazyLock::new(Argon2::default);
|
||||
|
||||
/// A client app that connects to continuwuity via OIDC, as recorded in the
|
||||
/// database.
|
||||
@@ -116,14 +112,12 @@ impl Service {
|
||||
let client = client.clone().encode(&*PASSWORD_POLICY);
|
||||
let client_id = client.client_id.clone();
|
||||
let oidc_client = OidcClient {
|
||||
name: display_name.clone(),
|
||||
name: display_name,
|
||||
// Matrix clients have no device_id at registration time.
|
||||
device_id: None,
|
||||
client,
|
||||
};
|
||||
self.db
|
||||
.client_registrar
|
||||
.put(client_id, Json(oidc_client));
|
||||
self.db.client_registrar.put(client_id, Json(oidc_client));
|
||||
}
|
||||
|
||||
/// Register a device in the main continuwuity database. This should only
|
||||
@@ -139,17 +133,15 @@ impl Service {
|
||||
let device_key = (user_id, device_id);
|
||||
let device = Device {
|
||||
device_id: device_id.into(),
|
||||
display_name: display_name.map(|n| n.to_string()),
|
||||
display_name: display_name.map(ToOwned::to_owned),
|
||||
last_seen_ip: client_ip,
|
||||
last_seen_ts: Some(MilliSecondsSinceUnixEpoch::now()),
|
||||
};
|
||||
increment(&self.db.userid_devicelistversion, user_id.as_bytes())
|
||||
.await;
|
||||
self.db
|
||||
.userdeviceid_metadata
|
||||
.put(device_key, Json(device));
|
||||
increment(&self.db.userid_devicelistversion, user_id.as_bytes()).await;
|
||||
self.db.userdeviceid_metadata.put(device_key, Json(device));
|
||||
|
||||
let mut client : OidcClient = self.db
|
||||
let mut client: OidcClient = self
|
||||
.db
|
||||
.client_registrar
|
||||
.get(client_id)
|
||||
.await?
|
||||
@@ -157,11 +149,11 @@ impl Service {
|
||||
client.device_id = Some(device_id.to_string());
|
||||
self.db
|
||||
.client_registrar
|
||||
.put(client_id.to_string(), Json(client));
|
||||
.put(client_id.to_owned(), Json(client));
|
||||
|
||||
self.db
|
||||
.deviceid_clientidmap
|
||||
.put(device_id, client_id.to_string());
|
||||
.put(device_id, client_id.to_owned());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -169,14 +161,12 @@ impl Service {
|
||||
fn grant_from_token(&self, token: &str) -> Option<Grant> {
|
||||
let issuer = self.issuer.lock().expect("lockable issuer");
|
||||
|
||||
issuer.recover_token(token)
|
||||
issuer
|
||||
.recover_token(token)
|
||||
.expect("infallible recover_token implementation")
|
||||
}
|
||||
|
||||
pub async fn client_from_client_id(
|
||||
&self,
|
||||
client_id: &str,
|
||||
) -> Result<Option<OidcClient>> {
|
||||
pub async fn client_from_client_id(&self, client_id: &str) -> Result<Option<OidcClient>> {
|
||||
self.db
|
||||
.client_registrar
|
||||
.get(client_id)
|
||||
@@ -188,7 +178,8 @@ impl Service {
|
||||
&self,
|
||||
device_id: OwnedDeviceId,
|
||||
) -> Result<Option<OidcClient>> {
|
||||
let client_id: String = self.db
|
||||
let client_id: String = self
|
||||
.db
|
||||
.deviceid_clientidmap
|
||||
.get(&device_id)
|
||||
.await?
|
||||
@@ -201,19 +192,14 @@ impl Service {
|
||||
.deserialized()
|
||||
}
|
||||
|
||||
pub fn device_id_from_scope(&self, scope: Scope) -> Result<OwnedDeviceId> {
|
||||
let Some(device_id) = scope
|
||||
.iter()
|
||||
.find(|s| s.starts_with(SCOPE_PREFIX_DEVICE)) else {
|
||||
tracing::warn!("device_id not found in scope {scope:?}");
|
||||
return Err(err!(Request(InvalidParam("something went wrong with the scope"))));
|
||||
};
|
||||
pub fn device_id_from_scope(&self, scope: &Scope) -> Result<OwnedDeviceId> {
|
||||
let Some(device_id) = scope.iter().find(|s| s.starts_with(SCOPE_PREFIX_DEVICE)) else {
|
||||
tracing::warn!("device_id not found in scope {scope:?}");
|
||||
return Err(err!(Request(InvalidParam("something went wrong with the scope"))));
|
||||
};
|
||||
let device_id = device_id.replace(SCOPE_PREFIX_DEVICE, "");
|
||||
|
||||
OwnedDeviceId::try_from(device_id.clone())
|
||||
.map_err(|err|
|
||||
err!(Request(InvalidParam("invalid device_id {device_id:?}: {err}")))
|
||||
)
|
||||
Ok(device_id.into())
|
||||
}
|
||||
|
||||
pub async fn user_and_device_from_token(
|
||||
@@ -224,17 +210,18 @@ impl Service {
|
||||
return Err(err!(Request(MissingToken("unknown token: {token:?}"))));
|
||||
};
|
||||
let server_name = self.services.globals.server_name();
|
||||
let owner_id = UserId::parse_with_server_name(owner_id.clone(), server_name)
|
||||
.map_err(|err|
|
||||
let owner_id =
|
||||
UserId::parse_with_server_name(owner_id.clone(), server_name).map_err(|err| {
|
||||
err!(Request(InvalidUsername("invalid username {owner_id:?}: {err}")))
|
||||
)?;
|
||||
let client = self.client_from_client_id(&client_id)
|
||||
})?;
|
||||
let client = self
|
||||
.client_from_client_id(&client_id)
|
||||
.await?
|
||||
.expect("validated client_id");
|
||||
let Some(device_id) = client.device_id else {
|
||||
return Err(err!(Request(Unknown("this client has no device_id yet"))));
|
||||
};
|
||||
let device_id = OwnedDeviceId::from(device_id.to_string());
|
||||
let device_id = OwnedDeviceId::from(device_id);
|
||||
|
||||
Ok((owner_id, device_id))
|
||||
}
|
||||
@@ -264,95 +251,111 @@ async fn increment(db: &Arc<Map>, key: &[u8]) {
|
||||
|
||||
/// Substitute "127.0.0.1" and "[::1]" for "localhost" to let oxide-auth compare
|
||||
/// them ignoring their port.
|
||||
pub fn normalize_redirect_hostname(url: Url) -> Url {
|
||||
let mut new_url = url.clone();
|
||||
let new_host = url.host_str().map(|h|
|
||||
h.replace("127.0.0.1", "localhost").replace("[::1]", "localhost")
|
||||
);
|
||||
new_url.set_host(new_host.as_deref()).expect("replaceable redirect hostname");
|
||||
pub fn normalize_redirect_hostname(url: &mut Url) {
|
||||
let new_host = url.host_str().map(|h| {
|
||||
h.replace("127.0.0.1", "localhost")
|
||||
.replace("[::1]", "localhost")
|
||||
});
|
||||
|
||||
new_url
|
||||
url.set_host(new_host.as_deref())
|
||||
.expect("replaceable redirect hostname");
|
||||
}
|
||||
|
||||
/// If `url` is a localhost (either 'localhost', '127.0.0.1' or '[::1]'), wrap
|
||||
/// it in an `IgnorePortOnLocalhost`, so that oxide-auth ignores the port when
|
||||
/// comparing it with the registered ones.
|
||||
pub fn normalize_redirect(url: Url) -> RegisteredUrl {
|
||||
let new_url = normalize_redirect_hostname(url);
|
||||
#[must_use]
|
||||
pub fn normalize_redirect(mut url: Url) -> RegisteredUrl {
|
||||
normalize_redirect_hostname(&mut url);
|
||||
|
||||
match new_url.host_str() {
|
||||
Some("localhost") => RegisteredUrl::IgnorePortOnLocalhost(new_url.into()),
|
||||
_ => RegisteredUrl::Semantic(new_url)
|
||||
match url.host_str() {
|
||||
| Some("localhost") => RegisteredUrl::IgnorePortOnLocalhost(url.into()),
|
||||
| _ => RegisteredUrl::Semantic(url),
|
||||
}
|
||||
}
|
||||
|
||||
/// Let this service act as an oxide-auth `Registrar`.
|
||||
impl Registrar for Service {
|
||||
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
|
||||
let client_handle = self.db
|
||||
fn bound_redirect<'a>(
|
||||
&self,
|
||||
bound: ClientUrl<'a>,
|
||||
) -> Result<BoundClient<'a>, RegistrarError> {
|
||||
let client_handle = self
|
||||
.db
|
||||
.client_registrar
|
||||
.get_blocking(bound.client_id.as_ref())
|
||||
.map_err(|_| RegistrarError::Unspecified)?;
|
||||
let oidc_client: OidcClient = client_handle.deserialized()
|
||||
let oidc_client: OidcClient = client_handle
|
||||
.deserialized()
|
||||
.map_err(|_| RegistrarError::Unspecified)?;
|
||||
let client = oidc_client.client;
|
||||
// Perform exact matching as motivated in the rfc, but substitute
|
||||
// Perform exact matching as motivated in the rfc, but substitute
|
||||
// "127.0.0.1" and "[::1]" for "localhost" to let oxide-auth ignore
|
||||
// their port.
|
||||
let redirect_uri = bound.redirect_uri;
|
||||
let normalized_uri = redirect_uri
|
||||
.clone()
|
||||
.map(|u| normalize_redirect(u.to_url()));
|
||||
let redirect_uri = match normalized_uri {
|
||||
None => client.redirect_uri.clone(),
|
||||
Some(url) => {
|
||||
let original = std::iter::once(&client.redirect_uri);
|
||||
let alternatives = client.additional_redirect_uris.iter();
|
||||
if original
|
||||
.chain(alternatives)
|
||||
.any(|registered| *registered == url)
|
||||
{
|
||||
let normalized_uri = redirect_uri.clone().map(|u| normalize_redirect(u.to_url()));
|
||||
let redirect_uri = match normalized_uri {
|
||||
| None => client.redirect_uri,
|
||||
| Some(url) => {
|
||||
let original = std::iter::once(&client.redirect_uri);
|
||||
let alternatives = client.additional_redirect_uris.iter();
|
||||
if original
|
||||
.chain(alternatives)
|
||||
.any(|registered| *registered == url)
|
||||
{
|
||||
// If normalized_uri is Some(url), so is redirect_uri, so unwrap().
|
||||
redirect_uri.unwrap().into_owned().into()
|
||||
} else {
|
||||
tracing::debug!("the request's redirect url didn't match any registered. bound: {:?}, in client {:#?}", url, client);
|
||||
return Err(RegistrarError::Unspecified);
|
||||
}
|
||||
}
|
||||
};
|
||||
redirect_uri.unwrap().into_owned().into()
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"the request's redirect url didn't match any registered. bound: {:?}, \
|
||||
in client {:#?}",
|
||||
url,
|
||||
client
|
||||
);
|
||||
return Err(RegistrarError::Unspecified);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(BoundClient {
|
||||
client_id: bound.client_id,
|
||||
redirect_uri: Cow::Owned(redirect_uri),
|
||||
})
|
||||
Ok(BoundClient {
|
||||
client_id: bound.client_id,
|
||||
redirect_uri: Cow::Owned(redirect_uri),
|
||||
})
|
||||
}
|
||||
|
||||
fn negotiate(&self, bound: BoundClient<'_>, _scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
|
||||
let client_handle = self.db
|
||||
fn negotiate(
|
||||
&self,
|
||||
bound: BoundClient<'_>,
|
||||
_scope: Option<Scope>,
|
||||
) -> Result<PreGrant, RegistrarError> {
|
||||
let client_handle = self
|
||||
.db
|
||||
.client_registrar
|
||||
.get_blocking(bound.client_id.as_ref())
|
||||
.map_err(|_| RegistrarError::Unspecified)?;
|
||||
let oidc_client: OidcClient = client_handle.deserialized()
|
||||
let oidc_client: OidcClient = client_handle
|
||||
.deserialized()
|
||||
.map_err(|_| RegistrarError::Unspecified)?;
|
||||
|
||||
Ok(PreGrant {
|
||||
client_id: bound.client_id.into_owned(),
|
||||
redirect_uri: bound.redirect_uri.into_owned(),
|
||||
Ok(PreGrant {
|
||||
client_id: bound.client_id.into_owned(),
|
||||
redirect_uri: bound.redirect_uri.into_owned(),
|
||||
// Always use the client's scope.
|
||||
scope: oidc_client.client.default_scope.clone(),
|
||||
})
|
||||
}
|
||||
scope: oidc_client.client.default_scope,
|
||||
})
|
||||
}
|
||||
|
||||
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
|
||||
let client_handle = self.db
|
||||
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
|
||||
let client_handle = self
|
||||
.db
|
||||
.client_registrar
|
||||
.get_blocking(client_id)
|
||||
.map_err(|_| RegistrarError::Unspecified)?;
|
||||
let oidc_client: OidcClient = client_handle.deserialized()
|
||||
let oidc_client: OidcClient = client_handle
|
||||
.deserialized()
|
||||
.map_err(|_| RegistrarError::Unspecified)?;
|
||||
let client = oidc_client.client;
|
||||
|
||||
RegisteredClient::new(&client, &*PASSWORD_POLICY)
|
||||
.check_authentication(passphrase)
|
||||
}
|
||||
RegisteredClient::new(&client, &*PASSWORD_POLICY).check_authentication(passphrase)
|
||||
}
|
||||
}
|
||||
+30
-16
@@ -272,7 +272,14 @@ impl Service {
|
||||
|
||||
/// Find out which user an access token belongs to.
|
||||
pub async fn find_from_token(&self, token: &str) -> Result<(OwnedUserId, OwnedDeviceId)> {
|
||||
if self.services.server.config.auth.as_ref().is_some_and(|auth| auth.enable_oidc_login) {
|
||||
if self
|
||||
.services
|
||||
.server
|
||||
.config
|
||||
.auth
|
||||
.as_ref()
|
||||
.is_some_and(|auth| auth.enable_oidc_login)
|
||||
{
|
||||
self.services.oidc.user_and_device_from_token(token).await
|
||||
} else {
|
||||
self.db.token_userdeviceid.get(token).await.deserialized()
|
||||
@@ -542,22 +549,29 @@ impl Service {
|
||||
// Only existing devices should be able to call this, but we shouldn't assert
|
||||
// either...
|
||||
let key = (user_id, device_id);
|
||||
if self.services.server.config.auth.as_ref().is_some_and(|auth| auth.enable_oidc_login) {
|
||||
if self.services.oidc.client_from_device_id(device_id.into()).await?.is_none() {
|
||||
return Err!(Database(error!(
|
||||
?user_id,
|
||||
?device_id,
|
||||
"Device has no metadata."
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
if self.db.userdeviceid_metadata.qry(&key).await.is_err() {
|
||||
return Err!(Database(error!(
|
||||
?user_id,
|
||||
?device_id,
|
||||
"User does not exist or device has no metadata."
|
||||
)));
|
||||
if self
|
||||
.services
|
||||
.server
|
||||
.config
|
||||
.auth
|
||||
.as_ref()
|
||||
.is_some_and(|auth| auth.enable_oidc_login)
|
||||
{
|
||||
if self
|
||||
.services
|
||||
.oidc
|
||||
.client_from_device_id(device_id.into())
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
return Err!(Database(error!(?user_id, ?device_id, "Device has no metadata.")));
|
||||
}
|
||||
} else if self.db.userdeviceid_metadata.qry(&key).await.is_err() {
|
||||
return Err!(Database(error!(
|
||||
?user_id,
|
||||
?device_id,
|
||||
"User does not exist or device has no metadata."
|
||||
)));
|
||||
}
|
||||
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
use askama::Template;
|
||||
use conduwuit_build_metadata::version_tag;
|
||||
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
|
||||
|
||||
// Imports needed by askama templates.
|
||||
use crate::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL};
|
||||
use conduwuit_build_metadata::version_tag;
|
||||
|
||||
mod authorize;
|
||||
mod consent;
|
||||
|
||||
@@ -26,8 +26,15 @@ pub fn oidc_consent_form(hostname: &str, query: &AuthorizationQuery) -> OidcResp
|
||||
|
||||
/// Render the html contents of the user consent page.
|
||||
fn consent_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &str) -> String {
|
||||
let response_mode = &query.response_mode.clone().unwrap_or("fragment".to_string());
|
||||
let user_id = query.username.clone().expect("user_id in authorization query");
|
||||
let response_mode = query
|
||||
.response_mode
|
||||
.as_deref()
|
||||
.unwrap_or("fragment")
|
||||
.to_owned();
|
||||
let user_id = query
|
||||
.username
|
||||
.clone()
|
||||
.expect("user_id in authorization query");
|
||||
let template = ConsentPageTemplate {
|
||||
nonce,
|
||||
hostname,
|
||||
|
||||
@@ -64,7 +64,9 @@ impl TryFrom<OidcRequest> for LoginQuery {
|
||||
};
|
||||
// response_mode is not strictly needed : it must be the literal "fragment"
|
||||
// when over https. It's required by the spec but Fractal doesn't provide it.
|
||||
let response_mode = body.unique_value("response_mode").unwrap_or(Cow::Borrowed("fragment"));
|
||||
let response_mode = body
|
||||
.unique_value("response_mode")
|
||||
.unwrap_or(Cow::Borrowed("fragment"));
|
||||
let client_secret = body.unique_value("client_secret").map(|s| s.to_string());
|
||||
|
||||
Ok(Self {
|
||||
@@ -104,7 +106,7 @@ pub fn oidc_login_form(hostname: &str, query: &AuthorizationQuery) -> OidcRespon
|
||||
|
||||
/// Render the html contents of the login page.
|
||||
fn login_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &str) -> String {
|
||||
let response_mode = &query.response_mode.clone().unwrap_or("fragment".to_string());
|
||||
let response_mode = query.response_mode.as_deref().unwrap_or("fragment");
|
||||
let template = LoginPageTemplate {
|
||||
nonce,
|
||||
hostname,
|
||||
|
||||
@@ -28,7 +28,8 @@ impl IntoResponse for OidcResponse {
|
||||
| Some(nonce) => &format!("default-src 'nonce-{nonce}';"),
|
||||
| None => "default-src 'none';",
|
||||
};
|
||||
let csp_form_action = "form-action 'self' http://localhost http://127.0.0.1 http://[::1];";
|
||||
let csp_form_action =
|
||||
"form-action 'self' http://localhost http://127.0.0.1 http://[::1];";
|
||||
let content_csp = format!("{csp_src} {csp_form_action}");
|
||||
let content_type = match self.body {
|
||||
| Some(OAuthRequestBody::Json(_)) => "application/json",
|
||||
|
||||
Reference in new issue
Block a user