Allow local access tokens to be used with OIDC backends (closes #1311 closes stalwartlabs/webadmin#52)

This commit is contained in:
mdecimus
2025-07-25 21:06:23 +02:00
parent 02f6a114e0
commit 654f296d45
37 changed files with 369 additions and 278 deletions

View File

@@ -4,16 +4,15 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::Arc;
use common::auth::AccessToken;
use common::{HttpAuthCache, Server, auth::AuthRequest, listener::limiter::InFlight};
use http_proto::{HttpRequest, HttpSessionData};
use hyper::header;
use mail_parser::decoders::base64::base64_decode;
use mail_send::Credentials;
use common::auth::AccessToken;
use std::future::Future;
use std::sync::Arc;
use std::time::{Duration, Instant};
pub trait Authenticator: Sync + Send {
fn authenticate_headers(
@@ -34,19 +33,20 @@ impl Authenticator for Server {
if let Some((mechanism, token)) = req.authorization() {
// Check if the credentials are cached
if let Some(http_cache) = self.inner.cache.http_auth.get(token) {
let access_token = self.get_access_token(http_cache.account_id).await?;
// Make sure the revision is still valid
if access_token.revision == http_cache.revision {
// Enforce authenticated rate limit
return self
.is_http_authenticated_request_allowed(&access_token)
.await
.map(|in_flight| (in_flight, access_token));
} else {
// If the revision is not valid, remove the cached credentials
self.inner.cache.http_auth.remove(token);
if http_cache.expires <= Instant::now() {
let access_token = self.get_access_token(http_cache.account_id).await?;
if access_token.revision == http_cache.revision {
// Enforce authenticated rate limit
return self
.is_http_authenticated_request_allowed(&access_token)
.await
.map(|in_flight| (in_flight, access_token));
}
}
// If the revision is not valid, remove the cached credentials
self.inner.cache.http_auth.remove(token);
}
let credentials = if mechanism.eq_ignore_ascii_case("basic") {
@@ -100,6 +100,8 @@ impl Authenticator for Server {
HttpAuthCache {
account_id: access_token.primary_id(),
revision: access_token.revision,
expires: Instant::now()
+ Duration::from_secs(self.core.oauth.oauth_expiry_token),
},
);

View File

@@ -12,7 +12,7 @@ use common::{
};
use directory::{
Permission, QueryBy, Type,
Permission, QueryParams, Type,
backend::internal::{
PrincipalField, PrincipalSet, lookup::DirectoryStore, manage::ManageDirectory,
},
@@ -113,7 +113,7 @@ impl ClientRegistrationHandler for Server {
// Fetch client registration
let found_registration = if let Some(client) = self
.store()
.query(QueryBy::Name(client_id), false)
.query(QueryParams::name(client_id).with_return_member_of(false))
.await
.caused_by(trc::location!())?
.filter(|p| p.typ() == Type::OauthClient)

View File

@@ -4,19 +4,16 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt::Write;
use common::{Server, manager::webadmin::Resource};
use directory::QueryBy;
use directory::QueryParams;
use http_proto::*;
use quick_xml::Reader;
use quick_xml::events::Event;
use std::fmt::Write;
use std::future::Future;
use trc::AddContext;
use utils::url_params::UrlParams;
use http_proto::*;
use std::future::Future;
pub trait Autoconfig: Sync + Send {
fn handle_autoconfig_request(
&self,
@@ -211,7 +208,7 @@ impl Autoconfig for Server {
.core
.storage
.directory
.query(QueryBy::Id(id), false)
.query(QueryParams::id(id).with_return_member_of(false))
.await
{
if principal

View File

@@ -6,7 +6,7 @@
use common::{KV_BAYES_MODEL_USER, Server, auth::AccessToken};
use directory::{
DirectoryInner, Permission, QueryBy, Type,
DirectoryInner, Permission, QueryBy, QueryParams, Type,
backend::internal::{
PrincipalAction, PrincipalField, PrincipalSet, PrincipalUpdate, PrincipalValue,
SpecialSecrets,
@@ -476,7 +476,7 @@ impl PrincipalManager for Server {
let principal = self
.store()
.query(QueryBy::Id(account_id), true)
.query(QueryParams::id(account_id).with_return_member_of(true))
.await?
.ok_or_else(|| trc::ManageEvent::NotFound.into_err())?;
@@ -707,7 +707,7 @@ impl PrincipalManager for Server {
if access_token.primary_id() != u32::MAX {
let principal = self
.directory()
.query(QueryBy::Id(access_token.primary_id()), false)
.query(QueryParams::id(access_token.primary_id()).with_return_member_of(false))
.await?
.ok_or_else(|| trc::ManageEvent::NotFound.into_err())?;
@@ -803,7 +803,16 @@ impl PrincipalManager for Server {
}
// Make sure the current directory supports updates
self.assert_supported_directory(false)?;
if requests.iter().any(|r| {
matches!(
r,
AccountAuthRequest::SetPassword { .. }
| AccountAuthRequest::EnableOtpAuth { .. }
| AccountAuthRequest::DisableOtpAuth { .. }
)
}) {
self.assert_supported_directory(false)?;
}
// Build actions
let mut actions = Vec::with_capacity(requests.len());