Allow APIs to be used with external directories (closes #1815)
This commit is contained in:
@@ -7,7 +7,8 @@
|
|||||||
use std::{net::IpAddr, sync::Arc};
|
use std::{net::IpAddr, sync::Arc};
|
||||||
|
|
||||||
use directory::{
|
use directory::{
|
||||||
Directory, Permission, Permissions, Principal, QueryBy, core::secret::verify_secret_hash,
|
Directory, Permission, Permissions, Principal, QueryBy, Type,
|
||||||
|
backend::internal::lookup::DirectoryStore, core::secret::verify_secret_hash,
|
||||||
};
|
};
|
||||||
use jmap_proto::types::collection::Collection;
|
use jmap_proto::types::collection::Collection;
|
||||||
use mail_send::Credentials;
|
use mail_send::Credentials;
|
||||||
@@ -62,6 +63,7 @@ pub struct AuthRequest<'x> {
|
|||||||
session_id: u64,
|
session_id: u64,
|
||||||
remote_ip: IpAddr,
|
remote_ip: IpAddr,
|
||||||
return_member_of: bool,
|
return_member_of: bool,
|
||||||
|
allow_api_access: bool,
|
||||||
directory: Option<&'x Directory>,
|
directory: Option<&'x Directory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,48 +126,62 @@ impl Server {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Then check if the credentials match the fallback admin or master user
|
// Then check if the credentials match the fallback admin or master user
|
||||||
match (
|
if let Credentials::Plain { username, secret } = &req.credentials {
|
||||||
&self.core.jmap.fallback_admin,
|
match (&self.core.jmap.fallback_admin, &self.core.jmap.master_user) {
|
||||||
&self.core.jmap.master_user,
|
(Some((fallback_admin, fallback_pass)), _) if username == fallback_admin => {
|
||||||
&req.credentials,
|
if verify_secret_hash(fallback_pass, secret).await? {
|
||||||
) {
|
|
||||||
(Some((fallback_admin, fallback_pass)), _, Credentials::Plain { username, secret })
|
|
||||||
if username == fallback_admin =>
|
|
||||||
{
|
|
||||||
if verify_secret_hash(fallback_pass, secret).await? {
|
|
||||||
trc::event!(
|
|
||||||
Auth(trc::AuthEvent::Success),
|
|
||||||
AccountName = username.clone(),
|
|
||||||
SpanId = req.session_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(Principal::fallback_admin(fallback_pass));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, Some((master_user, master_pass)), Credentials::Plain { username, secret })
|
|
||||||
if username.ends_with(master_user) =>
|
|
||||||
{
|
|
||||||
if verify_secret_hash(master_pass, secret).await? {
|
|
||||||
let username = username.strip_suffix(master_user).unwrap();
|
|
||||||
let username = username.strip_suffix('%').unwrap_or(username);
|
|
||||||
|
|
||||||
if let Some(principal) = directory
|
|
||||||
.query(QueryBy::Name(username), req.return_member_of)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
trc::event!(
|
trc::event!(
|
||||||
Auth(trc::AuthEvent::Success),
|
Auth(trc::AuthEvent::Success),
|
||||||
AccountName = username.to_string(),
|
AccountName = username.clone(),
|
||||||
SpanId = req.session_id,
|
SpanId = req.session_id,
|
||||||
AccountId = principal.id(),
|
|
||||||
Type = principal.typ().as_str(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return Ok(principal);
|
return Ok(Principal::fallback_admin(fallback_pass));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, Some((master_user, master_pass))) if username.ends_with(master_user) => {
|
||||||
|
if verify_secret_hash(master_pass, secret).await? {
|
||||||
|
let username = username.strip_suffix(master_user).unwrap();
|
||||||
|
let username = username.strip_suffix('%').unwrap_or(username);
|
||||||
|
|
||||||
|
if let Some(principal) = directory
|
||||||
|
.query(QueryBy::Name(username), req.return_member_of)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
trc::event!(
|
||||||
|
Auth(trc::AuthEvent::Success),
|
||||||
|
AccountName = username.to_string(),
|
||||||
|
SpanId = req.session_id,
|
||||||
|
AccountId = principal.id(),
|
||||||
|
Type = principal.typ().as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(principal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Validate API credentials
|
||||||
|
if req.allow_api_access {
|
||||||
|
if let Ok(Some(principal)) = self
|
||||||
|
.store()
|
||||||
|
.query(QueryBy::Credentials(&req.credentials), req.return_member_of)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if principal.typ == Type::ApiKey {
|
||||||
|
trc::event!(
|
||||||
|
Auth(trc::AuthEvent::Success),
|
||||||
|
AccountName = principal.name().to_string(),
|
||||||
|
AccountId = principal.id(),
|
||||||
|
SpanId = req.session_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(principal);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
@@ -205,6 +221,7 @@ impl<'x> AuthRequest<'x> {
|
|||||||
remote_ip,
|
remote_ip,
|
||||||
return_member_of: true,
|
return_member_of: true,
|
||||||
directory: None,
|
directory: None,
|
||||||
|
allow_api_access: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +250,11 @@ impl<'x> AuthRequest<'x> {
|
|||||||
self.directory = Some(directory);
|
self.directory = Some(directory);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_api_access(mut self, allow_api_access: bool) -> Self {
|
||||||
|
self.allow_api_access = allow_api_access;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CacheItemWeight for AccessToken {
|
impl CacheItemWeight for AccessToken {
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ impl Authenticator for Server {
|
|||||||
.is_http_authenticated_request_allowed(&access_token)
|
.is_http_authenticated_request_allowed(&access_token)
|
||||||
.await
|
.await
|
||||||
.map(|in_flight| (in_flight, access_token));
|
.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +84,14 @@ impl Authenticator for Server {
|
|||||||
|
|
||||||
// Authenticate
|
// Authenticate
|
||||||
let access_token = self
|
let access_token = self
|
||||||
.authenticate(&AuthRequest::from_credentials(
|
.authenticate(
|
||||||
credentials,
|
&AuthRequest::from_credentials(
|
||||||
session.session_id,
|
credentials,
|
||||||
session.remote_ip,
|
session.session_id,
|
||||||
))
|
session.remote_ip,
|
||||||
|
)
|
||||||
|
.with_api_access(allow_api_access),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Cache credentials
|
// Cache credentials
|
||||||
@@ -153,8 +159,8 @@ fn decode_plain_auth(token: &str) -> Option<Credentials<String>> {
|
|||||||
|
|
||||||
fn decode_bearer_token(token: &str, allow_api_access: bool) -> Option<Credentials<String>> {
|
fn decode_bearer_token(token: &str, allow_api_access: bool) -> Option<Credentials<String>> {
|
||||||
if allow_api_access {
|
if allow_api_access {
|
||||||
if let Some(token) = token.strip_prefix("api_") {
|
if let Some(token) = token.strip_prefix("api_").and_then(decode_plain_auth) {
|
||||||
return decode_plain_auth(token);
|
return Some(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user