You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-29 22:01:14 +03:00
Support signed userinfo responses
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
// Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -82,6 +82,9 @@ pub struct Client<T: StorageBackend> {
|
||||
/// Client
|
||||
pub id_token_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
|
||||
/// JWS alg algorithm REQUIRED for signing UserInfo Responses.
|
||||
pub userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
|
||||
/// Requested authentication method for the token endpoint
|
||||
pub token_endpoint_auth_method: Option<OAuthClientAuthenticationMethod>,
|
||||
|
||||
@ -112,6 +115,7 @@ impl<S: StorageBackendMarker> From<Client<S>> for Client<()> {
|
||||
tos_uri: c.tos_uri,
|
||||
jwks: c.jwks,
|
||||
id_token_signed_response_alg: c.id_token_signed_response_alg,
|
||||
userinfo_signed_response_alg: c.userinfo_signed_response_alg,
|
||||
token_endpoint_auth_method: c.token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg: c.token_endpoint_auth_signing_alg,
|
||||
initiate_login_uri: c.initiate_login_uri,
|
||||
|
@ -59,14 +59,20 @@ use oauth2_types::{
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{PgConnection, PgPool, Postgres, Transaction};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use crate::views::{LoginRequest, PostAuthAction, ReauthRequest, RegisterRequest};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RouteError {
|
||||
#[error(transparent)]
|
||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error(transparent)]
|
||||
Anyhow(anyhow::Error),
|
||||
#[error("could not find client")]
|
||||
ClientNotFound,
|
||||
#[error("invalid redirect uri")]
|
||||
InvalidRedirectUri,
|
||||
}
|
||||
|
||||
@ -218,6 +224,7 @@ fn resolve_response_mode(
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
pub(crate) async fn get(
|
||||
Extension(templates): Extension<Templates>,
|
||||
Extension(pool): Extension<PgPool>,
|
||||
@ -413,7 +420,10 @@ pub(crate) async fn get(
|
||||
|
||||
let response = match res {
|
||||
Ok(r) => r,
|
||||
Err(_e) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||
Err(err) => {
|
||||
tracing::error!(%err);
|
||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||
}
|
||||
};
|
||||
|
||||
Ok((cookie_jar, response).into_response())
|
||||
|
@ -105,7 +105,8 @@ pub(crate) async fn get(
|
||||
|
||||
let subject_types_supported = Some(vec![SubjectType::Public]);
|
||||
|
||||
let id_token_signing_alg_values_supported = jwt_signing_alg_values_supported;
|
||||
let id_token_signing_alg_values_supported = jwt_signing_alg_values_supported.clone();
|
||||
let userinfo_signing_alg_values_supported = jwt_signing_alg_values_supported;
|
||||
|
||||
let display_values_supported = Some(vec![Display::Page]);
|
||||
|
||||
@ -148,6 +149,7 @@ pub(crate) async fn get(
|
||||
userinfo_endpoint,
|
||||
subject_types_supported,
|
||||
id_token_signing_alg_values_supported,
|
||||
userinfo_signing_alg_values_supported,
|
||||
display_values_supported,
|
||||
claim_types_supported,
|
||||
claims_supported,
|
||||
|
@ -75,6 +75,7 @@ pub(crate) async fn post(
|
||||
body.jwks_uri.as_ref(),
|
||||
body.jwks.as_ref(),
|
||||
body.id_token_signed_response_alg,
|
||||
body.userinfo_signed_response_alg,
|
||||
body.token_endpoint_auth_method,
|
||||
body.token_endpoint_auth_signing_alg,
|
||||
body.initiate_login_uri.as_ref(),
|
||||
|
@ -317,7 +317,13 @@ async fn authorization_code_grant(
|
||||
claims::AT_HASH.insert(&mut claims, hash(Sha256::new(), &access_token_str)?)?;
|
||||
claims::C_HASH.insert(&mut claims, hash(Sha256::new(), &grant.code)?)?;
|
||||
|
||||
let header = key_store.prepare_header(JsonWebSignatureAlg::Rs256).await?;
|
||||
let header = key_store
|
||||
.prepare_header(
|
||||
client
|
||||
.id_token_signed_response_alg
|
||||
.unwrap_or(JsonWebSignatureAlg::Rs256),
|
||||
)
|
||||
.await?;
|
||||
let id_token = DecodedJsonWebToken::new(header, claims);
|
||||
let id_token = id_token.sign(key_store).await?;
|
||||
|
||||
|
@ -12,12 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
extract::Extension,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
Json, TypedHeader,
|
||||
};
|
||||
use mas_axum_utils::{internal_error, user_authorization::UserAuthorization};
|
||||
use headers::ContentType;
|
||||
use hyper::StatusCode;
|
||||
use mas_axum_utils::{internal_error, user_authorization::UserAuthorization, UrlBuilder};
|
||||
use mas_jose::{DecodedJsonWebToken, SigningKeystore, StaticKeystore};
|
||||
use mime::Mime;
|
||||
use oauth2_types::scope;
|
||||
use serde::Serialize;
|
||||
use serde_with::skip_serializing_none;
|
||||
@ -32,10 +38,21 @@ struct UserInfo {
|
||||
email_verified: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SignedUserInfo {
|
||||
iss: String,
|
||||
aud: String,
|
||||
#[serde(flatten)]
|
||||
user_info: UserInfo,
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
Extension(url_builder): Extension<UrlBuilder>,
|
||||
Extension(pool): Extension<PgPool>,
|
||||
Extension(key_store): Extension<Arc<StaticKeystore>>,
|
||||
user_authorization: UserAuthorization,
|
||||
) -> Result<impl IntoResponse, Response> {
|
||||
) -> Result<Response, Response> {
|
||||
// TODO: error handling
|
||||
let mut conn = pool
|
||||
.acquire()
|
||||
.await
|
||||
@ -48,7 +65,7 @@ pub async fn get(
|
||||
.map_err(IntoResponse::into_response)?;
|
||||
|
||||
let user = session.browser_session.user;
|
||||
let mut res = UserInfo {
|
||||
let mut user_info = UserInfo {
|
||||
sub: user.sub,
|
||||
username: user.username,
|
||||
email: None,
|
||||
@ -57,10 +74,36 @@ pub async fn get(
|
||||
|
||||
if session.scope.contains(&scope::EMAIL) {
|
||||
if let Some(email) = user.primary_email {
|
||||
res.email_verified = Some(email.confirmed_at.is_some());
|
||||
res.email = Some(email.email);
|
||||
user_info.email_verified = Some(email.confirmed_at.is_some());
|
||||
user_info.email = Some(email.email);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Json(res))
|
||||
if let Some(alg) = session.client.userinfo_signed_response_alg {
|
||||
let header = key_store
|
||||
.prepare_header(alg)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.map_err(IntoResponse::into_response)?;
|
||||
|
||||
let user_info = SignedUserInfo {
|
||||
iss: url_builder.oidc_issuer().to_string(),
|
||||
aud: session.client.client_id,
|
||||
user_info,
|
||||
};
|
||||
|
||||
let user_info = DecodedJsonWebToken::new(header, user_info);
|
||||
let user_info = user_info
|
||||
.sign(key_store.as_ref())
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.map_err(IntoResponse::into_response)?;
|
||||
|
||||
let token = user_info.serialize();
|
||||
let application_jwt: Mime = "application/jwt".parse().unwrap();
|
||||
let content_type = ContentType::from(application_jwt);
|
||||
Ok((TypedHeader(content_type), token).into_response())
|
||||
} else {
|
||||
Ok(Json(user_info).into_response())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
-- Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
-- you may not use this file except in compliance with the License.
|
||||
-- You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
ALTER TABLE oauth2_clients
|
||||
DROP COLUMN "userinfo_signed_response_alg" TEXT;
|
@ -0,0 +1,16 @@
|
||||
-- Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
-- you may not use this file except in compliance with the License.
|
||||
-- You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
ALTER TABLE oauth2_clients
|
||||
ADD COLUMN "userinfo_signed_response_alg" TEXT;
|
@ -45,6 +45,7 @@ pub struct OAuth2ClientLookup {
|
||||
jwks_uri: Option<String>,
|
||||
jwks: Option<serde_json::Value>,
|
||||
id_token_signed_response_alg: Option<String>,
|
||||
userinfo_signed_response_alg: Option<String>,
|
||||
token_endpoint_auth_method: Option<String>,
|
||||
token_endpoint_auth_signing_alg: Option<String>,
|
||||
initiate_login_uri: Option<String>,
|
||||
@ -153,6 +154,15 @@ impl TryInto<Client<PostgresqlBackend>> for OAuth2ClientLookup {
|
||||
source,
|
||||
})?;
|
||||
|
||||
let userinfo_signed_response_alg = self
|
||||
.userinfo_signed_response_alg
|
||||
.map(|s| s.parse())
|
||||
.transpose()
|
||||
.map_err(|source| ClientFetchError::ParseField {
|
||||
field: "userinfo_signed_response_alg",
|
||||
source,
|
||||
})?;
|
||||
|
||||
let token_endpoint_auth_method = self
|
||||
.token_endpoint_auth_method
|
||||
.map(|s| s.parse())
|
||||
@ -214,6 +224,7 @@ impl TryInto<Client<PostgresqlBackend>> for OAuth2ClientLookup {
|
||||
tos_uri,
|
||||
jwks,
|
||||
id_token_signed_response_alg,
|
||||
userinfo_signed_response_alg,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg,
|
||||
initiate_login_uri,
|
||||
@ -245,6 +256,7 @@ pub async fn lookup_client(
|
||||
c.jwks_uri,
|
||||
c.jwks,
|
||||
c.id_token_signed_response_alg,
|
||||
c.userinfo_signed_response_alg,
|
||||
c.token_endpoint_auth_method,
|
||||
c.token_endpoint_auth_signing_alg,
|
||||
c.initiate_login_uri
|
||||
@ -286,6 +298,7 @@ pub async fn lookup_client_by_client_id(
|
||||
c.jwks_uri,
|
||||
c.jwks,
|
||||
c.id_token_signed_response_alg,
|
||||
c.userinfo_signed_response_alg,
|
||||
c.token_endpoint_auth_method,
|
||||
c.token_endpoint_auth_signing_alg,
|
||||
c.initiate_login_uri
|
||||
@ -320,6 +333,7 @@ pub async fn insert_client(
|
||||
jwks_uri: Option<&Url>,
|
||||
jwks: Option<&JsonWebKeySet>,
|
||||
id_token_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
token_endpoint_auth_method: Option<OAuthClientAuthenticationMethod>,
|
||||
token_endpoint_auth_signing_alg: Option<JsonWebSignatureAlg>,
|
||||
initiate_login_uri: Option<&Url>,
|
||||
@ -334,6 +348,7 @@ pub async fn insert_client(
|
||||
let jwks = jwks.map(serde_json::to_value).transpose().unwrap(); // TODO
|
||||
let jwks_uri = jwks_uri.map(Url::as_str);
|
||||
let id_token_signed_response_alg = id_token_signed_response_alg.map(|v| v.to_string());
|
||||
let userinfo_signed_response_alg = userinfo_signed_response_alg.map(|v| v.to_string());
|
||||
let token_endpoint_auth_method = token_endpoint_auth_method.map(|v| v.to_string());
|
||||
let token_endpoint_auth_signing_alg = token_endpoint_auth_signing_alg.map(|v| v.to_string());
|
||||
let initiate_login_uri = initiate_login_uri.map(Url::as_str);
|
||||
@ -355,11 +370,12 @@ pub async fn insert_client(
|
||||
jwks_uri,
|
||||
jwks,
|
||||
id_token_signed_response_alg,
|
||||
userinfo_signed_response_alg,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg,
|
||||
initiate_login_uri)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
|
||||
RETURNING id
|
||||
"#,
|
||||
client_id,
|
||||
@ -376,6 +392,7 @@ pub async fn insert_client(
|
||||
jwks_uri,
|
||||
jwks,
|
||||
id_token_signed_response_alg,
|
||||
userinfo_signed_response_alg,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg,
|
||||
initiate_login_uri,
|
||||
|
Reference in New Issue
Block a user