mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-10-09 12:05:49 +08:00
This commit is contained in:
parent
cce2d9c915
commit
292d1cc048
5 changed files with 57 additions and 28 deletions
|
@ -78,12 +78,20 @@ pub struct Userinfo {
|
||||||
pub updated_at: Option<i64>,
|
pub updated_at: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Nonce {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub nonce: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn issue_id_token(
|
pub fn issue_id_token(
|
||||||
&self,
|
&self,
|
||||||
subject: impl Into<String>,
|
subject: impl Into<String>,
|
||||||
issuer: impl Into<String>,
|
issuer: impl Into<String>,
|
||||||
audience: impl Into<String>,
|
audience: impl Into<String>,
|
||||||
|
nonce: Option<impl Into<String>>,
|
||||||
) -> trc::Result<String> {
|
) -> trc::Result<String> {
|
||||||
let now = now() as i64;
|
let now = now() as i64;
|
||||||
|
|
||||||
|
@ -93,7 +101,7 @@ impl Server {
|
||||||
key_id: Some("default".into()),
|
key_id: Some("default".into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
ClaimsSet::<()> {
|
ClaimsSet::<Nonce> {
|
||||||
registered: RegisteredClaims {
|
registered: RegisteredClaims {
|
||||||
issuer: Some(issuer.into()),
|
issuer: Some(issuer.into()),
|
||||||
subject: Some(subject.into()),
|
subject: Some(subject.into()),
|
||||||
|
@ -103,7 +111,9 @@ impl Server {
|
||||||
expiry: Some((now + self.core.oauth.oidc_expiry_id_token as i64).into()),
|
expiry: Some((now + self.core.oauth.oidc_expiry_id_token as i64).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
private: (),
|
private: Nonce {
|
||||||
|
nonce: nonce.map(Into::into),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_encoded(&self.core.oauth.oidc_signing_secret)
|
.into_encoded(&self.core.oauth.oidc_signing_secret)
|
||||||
|
|
|
@ -262,7 +262,8 @@ impl OAuthApiHandler for Server {
|
||||||
user_code,
|
user_code,
|
||||||
expires_in: self.core.oauth.oauth_expiry_user_code,
|
expires_in: self.core.oauth.oauth_expiry_user_code,
|
||||||
interval: 5,
|
interval: 5,
|
||||||
}).no_cache()
|
})
|
||||||
|
.no_cache()
|
||||||
.into_http_response())
|
.into_http_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ pub trait TokenHandler: Sync + Send {
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
client_id: &str,
|
client_id: &str,
|
||||||
issuer: String,
|
issuer: String,
|
||||||
|
nonce: Option<String>,
|
||||||
with_refresh_token: bool,
|
with_refresh_token: bool,
|
||||||
) -> impl Future<Output = trc::Result<OAuthResponse>> + Send;
|
) -> impl Future<Output = trc::Result<OAuthResponse>> + Send;
|
||||||
}
|
}
|
||||||
|
@ -63,10 +64,11 @@ impl TokenHandler for Server {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if grant_type.eq_ignore_ascii_case("authorization_code") {
|
if grant_type.eq_ignore_ascii_case("authorization_code") {
|
||||||
response = if let (Some(code), Some(client_id), Some(redirect_uri)) = (
|
response = if let (Some(code), Some(client_id), Some(redirect_uri), nonce) = (
|
||||||
params.get("code"),
|
params.get("code"),
|
||||||
params.get("client_id"),
|
params.get("client_id"),
|
||||||
params.get("redirect_uri"),
|
params.get("redirect_uri"),
|
||||||
|
params.get("nonce"),
|
||||||
) {
|
) {
|
||||||
// Obtain code
|
// Obtain code
|
||||||
match self
|
match self
|
||||||
|
@ -100,7 +102,13 @@ impl TokenHandler for Server {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Issue token
|
// Issue token
|
||||||
self.issue_token(oauth.account_id, &oauth.client_id, issuer, true)
|
self.issue_token(
|
||||||
|
oauth.account_id,
|
||||||
|
&oauth.client_id,
|
||||||
|
issuer,
|
||||||
|
nonce.map(Into::into),
|
||||||
|
true,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(TokenResponse::Granted)
|
.map(TokenResponse::Granted)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
|
@ -122,9 +130,11 @@ impl TokenHandler for Server {
|
||||||
} else if grant_type.eq_ignore_ascii_case("urn:ietf:params:oauth:grant-type:device_code") {
|
} else if grant_type.eq_ignore_ascii_case("urn:ietf:params:oauth:grant-type:device_code") {
|
||||||
response = TokenResponse::error(ErrorType::ExpiredToken);
|
response = TokenResponse::error(ErrorType::ExpiredToken);
|
||||||
|
|
||||||
if let (Some(device_code), Some(client_id)) =
|
if let (Some(device_code), Some(client_id), nonce) = (
|
||||||
(params.get("device_code"), params.get("client_id"))
|
params.get("device_code"),
|
||||||
{
|
params.get("client_id"),
|
||||||
|
params.get("nonce"),
|
||||||
|
) {
|
||||||
// Obtain code
|
// Obtain code
|
||||||
if let Some(auth_code) = self
|
if let Some(auth_code) = self
|
||||||
.core
|
.core
|
||||||
|
@ -157,6 +167,7 @@ impl TokenHandler for Server {
|
||||||
oauth.account_id,
|
oauth.account_id,
|
||||||
&oauth.client_id,
|
&oauth.client_id,
|
||||||
issuer,
|
issuer,
|
||||||
|
nonce.map(Into::into),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -190,6 +201,7 @@ impl TokenHandler for Server {
|
||||||
token_info.account_id,
|
token_info.account_id,
|
||||||
&token_info.client_id,
|
&token_info.client_id,
|
||||||
issuer,
|
issuer,
|
||||||
|
None,
|
||||||
token_info.expires_in
|
token_info.expires_in
|
||||||
<= self.core.oauth.oauth_expiry_refresh_token_renew,
|
<= self.core.oauth.oauth_expiry_refresh_token_renew,
|
||||||
)
|
)
|
||||||
|
@ -251,6 +263,7 @@ impl TokenHandler for Server {
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
client_id: &str,
|
client_id: &str,
|
||||||
issuer: String,
|
issuer: String,
|
||||||
|
nonce: Option<String>,
|
||||||
with_refresh_token: bool,
|
with_refresh_token: bool,
|
||||||
) -> trc::Result<OAuthResponse> {
|
) -> trc::Result<OAuthResponse> {
|
||||||
Ok(OAuthResponse {
|
Ok(OAuthResponse {
|
||||||
|
@ -276,7 +289,7 @@ impl TokenHandler for Server {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
id_token: match self.issue_id_token(account_id.to_string(), issuer, client_id) {
|
id_token: match self.issue_id_token(account_id.to_string(), issuer, client_id, nonce) {
|
||||||
Ok(id_token) => Some(id_token),
|
Ok(id_token) => Some(id_token),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trc::error!(err);
|
trc::error!(err);
|
||||||
|
|
|
@ -11,6 +11,7 @@ use biscuit::{jwk::JWKSet, SingleOrMultiple, JWT};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use common::auth::oauth::{
|
use common::auth::oauth::{
|
||||||
introspect::OAuthIntrospect,
|
introspect::OAuthIntrospect,
|
||||||
|
oidc::Nonce,
|
||||||
registration::{ClientRegistrationRequest, ClientRegistrationResponse},
|
registration::{ClientRegistrationRequest, ClientRegistrationResponse},
|
||||||
};
|
};
|
||||||
use imap_proto::ResponseType;
|
use imap_proto::ResponseType;
|
||||||
|
@ -135,6 +136,7 @@ pub async fn test(params: &mut JMAPTest) {
|
||||||
|
|
||||||
// Obtain token
|
// Obtain token
|
||||||
token_params.insert("redirect_uri".to_string(), "https://localhost".to_string());
|
token_params.insert("redirect_uri".to_string(), "https://localhost".to_string());
|
||||||
|
token_params.insert("nonce".to_string(), "abc1234".to_string());
|
||||||
let (token, refresh_token, id_token) =
|
let (token, refresh_token, id_token) =
|
||||||
unwrap_oidc_token_response(post(&metadata.token_endpoint, &token_params).await);
|
unwrap_oidc_token_response(post(&metadata.token_endpoint, &token_params).await);
|
||||||
|
|
||||||
|
@ -154,16 +156,19 @@ pub async fn test(params: &mut JMAPTest) {
|
||||||
.is_empty());
|
.is_empty());
|
||||||
|
|
||||||
// Verify ID token using the JWK set
|
// Verify ID token using the JWK set
|
||||||
let id_token = JWT::<(), biscuit::Empty>::new_encoded(&id_token)
|
let id_token = JWT::<Nonce, biscuit::Empty>::new_encoded(&id_token)
|
||||||
.decode_with_jwks(&jwk_set, None)
|
.decode_with_jwks(&jwk_set, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let claims = &id_token.payload().unwrap().registered;
|
let claims = id_token.payload().unwrap();
|
||||||
assert_eq!(claims.issuer, Some(oidc_metadata.issuer));
|
let registered_claims = &claims.registered;
|
||||||
assert_eq!(claims.subject, Some(john_int_id.to_string()));
|
let private_claims = &claims.private;
|
||||||
|
assert_eq!(registered_claims.issuer, Some(oidc_metadata.issuer));
|
||||||
|
assert_eq!(registered_claims.subject, Some(john_int_id.to_string()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
claims.audience,
|
registered_claims.audience,
|
||||||
Some(SingleOrMultiple::Single(client_id.to_string()))
|
Some(SingleOrMultiple::Single(client_id.to_string()))
|
||||||
);
|
);
|
||||||
|
assert_eq!(private_claims.nonce, Some("abc1234".to_string()));
|
||||||
|
|
||||||
// Introspect token
|
// Introspect token
|
||||||
let access_introspect: OAuthIntrospect = post_with_auth::<OAuthIntrospect>(
|
let access_introspect: OAuthIntrospect = post_with_auth::<OAuthIntrospect>(
|
||||||
|
|
|
@ -370,8 +370,8 @@ pub async fn jmap_tests() {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
webhooks::test(&mut params).await;
|
/*webhooks::test(&mut params).await;
|
||||||
/*email_query::test(&mut params, delete).await;
|
email_query::test(&mut params, delete).await;
|
||||||
email_get::test(&mut params).await;
|
email_get::test(&mut params).await;
|
||||||
email_set::test(&mut params).await;
|
email_set::test(&mut params).await;
|
||||||
email_parse::test(&mut params).await;
|
email_parse::test(&mut params).await;
|
||||||
|
@ -384,9 +384,9 @@ pub async fn jmap_tests() {
|
||||||
mailbox::test(&mut params).await;
|
mailbox::test(&mut params).await;
|
||||||
delivery::test(&mut params).await;
|
delivery::test(&mut params).await;
|
||||||
auth_acl::test(&mut params).await;
|
auth_acl::test(&mut params).await;
|
||||||
auth_limits::test(&mut params).await;
|
auth_limits::test(&mut params).await;*/
|
||||||
auth_oauth::test(&mut params).await;
|
auth_oauth::test(&mut params).await;
|
||||||
event_source::test(&mut params).await;
|
/*event_source::test(&mut params).await;
|
||||||
push_subscription::test(&mut params).await;
|
push_subscription::test(&mut params).await;
|
||||||
sieve_script::test(&mut params).await;
|
sieve_script::test(&mut params).await;
|
||||||
vacation_response::test(&mut params).await;
|
vacation_response::test(&mut params).await;
|
||||||
|
@ -394,10 +394,10 @@ pub async fn jmap_tests() {
|
||||||
websocket::test(&mut params).await;
|
websocket::test(&mut params).await;
|
||||||
quota::test(&mut params).await;
|
quota::test(&mut params).await;
|
||||||
crypto::test(&mut params).await;
|
crypto::test(&mut params).await;
|
||||||
blob::test(&mut params).await;*/
|
blob::test(&mut params).await;
|
||||||
permissions::test(¶ms).await;
|
permissions::test(¶ms).await;
|
||||||
purge::test(&mut params).await;
|
purge::test(&mut params).await;
|
||||||
enterprise::test(&mut params).await;
|
enterprise::test(&mut params).await;*/
|
||||||
|
|
||||||
if delete {
|
if delete {
|
||||||
params.temp_dir.delete();
|
params.temp_dir.delete();
|
||||||
|
|
Loading…
Add table
Reference in a new issue