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
Allow to validate client metadata
According to OpenID Connect Dynamic Client Registration Spec 1.0. Introduce VerifiedClientMetadata.
This commit is contained in:
committed by
Quentin Gliech
parent
a543af4de3
commit
e202c3dd6d
@ -16,13 +16,13 @@ use std::sync::Arc;
|
||||
|
||||
use axum::{response::IntoResponse, Extension, Json};
|
||||
use hyper::StatusCode;
|
||||
use mas_iana::oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod};
|
||||
use mas_policy::{PolicyFactory, Violation};
|
||||
use mas_storage::oauth2::client::insert_client;
|
||||
use oauth2_types::{
|
||||
errors::{INVALID_CLIENT_METADATA, INVALID_REDIRECT_URI, SERVER_ERROR},
|
||||
registration::{ClientMetadata, ClientRegistrationResponse},
|
||||
requests::GrantType,
|
||||
registration::{
|
||||
ClientMetadata, ClientMetadataVerificationError, ClientRegistrationResponse, Localized,
|
||||
},
|
||||
};
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use sqlx::PgPool;
|
||||
@ -53,6 +53,18 @@ impl From<sqlx::Error> for RouteError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientMetadataVerificationError> for RouteError {
|
||||
fn from(e: ClientMetadataVerificationError) -> Self {
|
||||
match e {
|
||||
ClientMetadataVerificationError::MissingRedirectUris
|
||||
| ClientMetadataVerificationError::RedirectUriWithFragment(_) => {
|
||||
Self::InvalidRedirectUri
|
||||
}
|
||||
_ => Self::InvalidClientMetadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: there is probably a better way to do achieve this. ClientError only
|
||||
// works for static strings
|
||||
#[derive(serde::Serialize)]
|
||||
@ -111,55 +123,18 @@ pub(crate) async fn post(
|
||||
) -> Result<impl IntoResponse, RouteError> {
|
||||
info!(?body, "Client registration");
|
||||
|
||||
// Let's validate a bunch of things on the client body first
|
||||
for uri in &body.redirect_uris {
|
||||
if uri.fragment().is_some() {
|
||||
return Err(RouteError::InvalidRedirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the client did not send both a jwks and a jwks_uri
|
||||
if body.jwks_uri.is_some() && body.jwks.is_some() {
|
||||
return Err(RouteError::InvalidClientMetadata);
|
||||
}
|
||||
|
||||
// Check that the grant_types and the response_types are coherent
|
||||
let has_implicit = body.grant_types.contains(&GrantType::Implicit);
|
||||
let has_authorization_code = body.grant_types.contains(&GrantType::AuthorizationCode);
|
||||
let has_both = has_implicit && has_authorization_code;
|
||||
|
||||
for response_type in &body.response_types {
|
||||
let is_ok = match response_type {
|
||||
OAuthAuthorizationEndpointResponseType::Code => has_authorization_code,
|
||||
OAuthAuthorizationEndpointResponseType::CodeIdToken
|
||||
| OAuthAuthorizationEndpointResponseType::CodeIdTokenToken
|
||||
| OAuthAuthorizationEndpointResponseType::CodeToken => has_both,
|
||||
OAuthAuthorizationEndpointResponseType::IdToken
|
||||
| OAuthAuthorizationEndpointResponseType::IdTokenToken
|
||||
| OAuthAuthorizationEndpointResponseType::Token => has_implicit,
|
||||
OAuthAuthorizationEndpointResponseType::None => true,
|
||||
};
|
||||
|
||||
if !is_ok {
|
||||
return Err(RouteError::InvalidClientMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// If the private_key_jwt auth method is used, check that we actually have a
|
||||
// JWKS for that client
|
||||
if body.token_endpoint_auth_method == Some(OAuthClientAuthenticationMethod::PrivateKeyJwt)
|
||||
&& body.jwks_uri.is_none()
|
||||
&& body.jwks.is_none()
|
||||
{
|
||||
return Err(RouteError::InvalidClientMetadata);
|
||||
}
|
||||
// Validate the body
|
||||
let metadata = body.validate()?;
|
||||
|
||||
let mut policy = policy_factory.instantiate().await?;
|
||||
let res = policy.evaluate_client_registration(&body).await?;
|
||||
let res = policy.evaluate_client_registration(&metadata).await?;
|
||||
if !res.valid() {
|
||||
return Err(RouteError::PolicyDenied(res.violations));
|
||||
}
|
||||
|
||||
// Contacts was checked by the policy
|
||||
let contacts = metadata.contacts.as_deref().unwrap_or_default();
|
||||
|
||||
// Grab a txn
|
||||
let mut txn = pool.begin().await?;
|
||||
|
||||
@ -173,23 +148,26 @@ pub(crate) async fn post(
|
||||
insert_client(
|
||||
&mut txn,
|
||||
&client_id,
|
||||
&body.redirect_uris,
|
||||
metadata.redirect_uris(),
|
||||
None,
|
||||
&body.response_types,
|
||||
&body.grant_types,
|
||||
&body.contacts,
|
||||
body.client_name.as_deref(),
|
||||
body.logo_uri.as_ref(),
|
||||
body.client_uri.as_ref(),
|
||||
body.policy_uri.as_ref(),
|
||||
body.tos_uri.as_ref(),
|
||||
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(),
|
||||
metadata.response_types(),
|
||||
metadata.grant_types(),
|
||||
contacts,
|
||||
metadata
|
||||
.client_name
|
||||
.as_ref()
|
||||
.map(|l| l.non_localized().as_ref()),
|
||||
metadata.logo_uri.as_ref().map(Localized::non_localized),
|
||||
metadata.client_uri.as_ref().map(Localized::non_localized),
|
||||
metadata.policy_uri.as_ref().map(Localized::non_localized),
|
||||
metadata.tos_uri.as_ref().map(Localized::non_localized),
|
||||
metadata.jwks_uri.as_ref(),
|
||||
metadata.jwks.as_ref(),
|
||||
metadata.id_token_signed_response_alg,
|
||||
metadata.userinfo_signed_response_alg,
|
||||
metadata.token_endpoint_auth_method,
|
||||
metadata.token_endpoint_auth_signing_alg,
|
||||
metadata.initiate_login_uri.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(clippy::all, clippy::str_to_string, rustdoc::broken_intra_doc_links)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use mas_iana::oauth::OAuthAuthorizationEndpointResponseType;
|
||||
|
||||
|
@ -778,7 +778,7 @@ pub enum ProviderMetadataVerificationError {
|
||||
/// The given endpoint is missing auth signing algorithm values, but they
|
||||
/// are required because it supports at least one of the `client_secret_jwt`
|
||||
/// or `private_key_jwt` authentication methods.
|
||||
#[error("{0} auth signing algorithm values contain `none`")]
|
||||
#[error("{0} missing auth signing algorithm values")]
|
||||
MissingAuthSigningAlgValues(&'static str),
|
||||
|
||||
/// `none` is in the given endpoint's signing algorithm values, but is not
|
||||
|
@ -1,168 +0,0 @@
|
||||
// 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.
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use mas_iana::{
|
||||
jose::{JsonWebEncryptionAlg, JsonWebSignatureAlg},
|
||||
oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
|
||||
};
|
||||
use mas_jose::JsonWebKeySet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, skip_serializing_none, DurationSeconds, TimestampSeconds};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
oidc::{ApplicationType, SubjectType},
|
||||
requests::GrantType,
|
||||
};
|
||||
|
||||
fn default_response_types() -> Vec<OAuthAuthorizationEndpointResponseType> {
|
||||
vec![OAuthAuthorizationEndpointResponseType::Code]
|
||||
}
|
||||
|
||||
fn default_grant_types() -> Vec<GrantType> {
|
||||
vec![GrantType::AuthorizationCode]
|
||||
}
|
||||
|
||||
const fn default_application_type() -> ApplicationType {
|
||||
ApplicationType::Web
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct ClientMetadata {
|
||||
pub redirect_uris: Vec<Url>,
|
||||
|
||||
#[serde(default = "default_response_types")]
|
||||
pub response_types: Vec<OAuthAuthorizationEndpointResponseType>,
|
||||
|
||||
#[serde(default = "default_grant_types")]
|
||||
pub grant_types: Vec<GrantType>,
|
||||
|
||||
#[serde(default = "default_application_type")]
|
||||
pub application_type: ApplicationType,
|
||||
|
||||
#[serde(default)]
|
||||
pub contacts: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub client_name: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub logo_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub client_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub policy_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub tos_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub jwks_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub jwks: Option<JsonWebKeySet>,
|
||||
|
||||
#[serde(default)]
|
||||
pub sector_identifier_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub subject_type: Option<SubjectType>,
|
||||
|
||||
#[serde(default)]
|
||||
pub token_endpoint_auth_method: Option<OAuthClientAuthenticationMethod>,
|
||||
|
||||
#[serde(default)]
|
||||
pub token_endpoint_auth_signing_alg: Option<JsonWebSignatureAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub id_token_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub id_token_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub id_token_encrypted_response_enc: Option<JsonWebEncryptionAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub userinfo_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub userinfo_encrypted_response_enc: Option<JsonWebEncryptionAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub request_object_signing_alg: Option<JsonWebSignatureAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub request_object_encryption_alg: Option<JsonWebEncryptionAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub request_object_encryption_enc: Option<JsonWebEncryptionAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "Option<DurationSeconds<i64>>")]
|
||||
pub default_max_age: Option<Duration>,
|
||||
|
||||
#[serde(default)]
|
||||
pub require_auth_time: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub default_acr_values: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub initiate_login_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub request_uris: Option<Vec<Url>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub require_signed_request_object: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub require_pushed_authorization_requests: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub introspection_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub introspection_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
||||
|
||||
#[serde(default)]
|
||||
pub introspection_encrypted_response_enc: Option<JsonWebEncryptionAlg>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct ClientRegistrationResponse {
|
||||
pub client_id: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub client_secret: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
|
||||
pub client_id_issued_at: Option<DateTime<Utc>>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
|
||||
pub client_secret_expires_at: Option<DateTime<Utc>>,
|
||||
}
|
469
crates/oauth2-types/src/registration/client_metadata_serde.rs
Normal file
469
crates/oauth2-types/src/registration/client_metadata_serde.rs
Normal file
@ -0,0 +1,469 @@
|
||||
// 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.
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use chrono::Duration;
|
||||
use language_tags::LanguageTag;
|
||||
use mas_iana::{
|
||||
jose::{JsonWebEncryptionAlg, JsonWebEncryptionEnc, JsonWebSignatureAlg},
|
||||
oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
|
||||
};
|
||||
use mas_jose::JsonWebKeySet;
|
||||
use serde::{
|
||||
de::{DeserializeOwned, Error},
|
||||
ser::SerializeMap,
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use serde_with::{serde_as, skip_serializing_none, DurationSeconds};
|
||||
use url::Url;
|
||||
|
||||
use super::{ClientMetadata, Localized, VerifiedClientMetadata};
|
||||
use crate::{
|
||||
oidc::{ApplicationType, SubjectType},
|
||||
requests::GrantType,
|
||||
};
|
||||
|
||||
impl<T> Localized<T> {
|
||||
fn serialize<M>(&self, map: &mut M, field_name: &str) -> Result<(), M::Error>
|
||||
where
|
||||
M: SerializeMap,
|
||||
T: Serialize,
|
||||
{
|
||||
map.serialize_entry(field_name, &self.non_localized)?;
|
||||
|
||||
for (lang, localized) in &self.localized {
|
||||
map.serialize_entry(&format!("{field_name}#{lang}"), localized)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deserialize(
|
||||
map: &mut HashMap<String, HashMap<Option<LanguageTag>, Value>>,
|
||||
field_name: &'static str,
|
||||
) -> Result<Option<Self>, serde_json::Error>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let map = match map.remove(field_name) {
|
||||
Some(map) => map,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let mut non_localized = None;
|
||||
let mut localized = HashMap::with_capacity(map.len() - 1);
|
||||
|
||||
for (k, v) in map {
|
||||
let value = serde_json::from_value(v)?;
|
||||
|
||||
if let Some(lang) = k {
|
||||
localized.insert(lang, value);
|
||||
} else {
|
||||
non_localized = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
let non_localized = non_localized.ok_or_else(|| {
|
||||
serde_json::Error::custom(format!(
|
||||
"missing non-localized variant of field '{field_name}'"
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(Some(Localized {
|
||||
non_localized,
|
||||
localized,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ClientMetadataSerdeHelper {
|
||||
redirect_uris: Option<Vec<Url>>,
|
||||
response_types: Option<Vec<OAuthAuthorizationEndpointResponseType>>,
|
||||
grant_types: Option<Vec<GrantType>>,
|
||||
application_type: Option<ApplicationType>,
|
||||
contacts: Option<Vec<String>>,
|
||||
jwks_uri: Option<Url>,
|
||||
jwks: Option<JsonWebKeySet>,
|
||||
sector_identifier_uri: Option<Url>,
|
||||
subject_type: Option<SubjectType>,
|
||||
token_endpoint_auth_method: Option<OAuthClientAuthenticationMethod>,
|
||||
token_endpoint_auth_signing_alg: Option<JsonWebSignatureAlg>,
|
||||
id_token_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
id_token_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
||||
id_token_encrypted_response_enc: Option<JsonWebEncryptionEnc>,
|
||||
userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
userinfo_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
||||
userinfo_encrypted_response_enc: Option<JsonWebEncryptionEnc>,
|
||||
request_object_signing_alg: Option<JsonWebSignatureAlg>,
|
||||
request_object_encryption_alg: Option<JsonWebEncryptionAlg>,
|
||||
request_object_encryption_enc: Option<JsonWebEncryptionEnc>,
|
||||
#[serde_as(as = "Option<DurationSeconds<i64>>")]
|
||||
default_max_age: Option<Duration>,
|
||||
require_auth_time: Option<bool>,
|
||||
default_acr_values: Option<Vec<String>>,
|
||||
initiate_login_uri: Option<Url>,
|
||||
request_uris: Option<Vec<Url>>,
|
||||
require_signed_request_object: Option<bool>,
|
||||
require_pushed_authorization_requests: Option<bool>,
|
||||
introspection_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||
introspection_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
||||
introspection_encrypted_response_enc: Option<JsonWebEncryptionEnc>,
|
||||
#[serde(flatten)]
|
||||
extra: ClientMetadataLocalizedFields,
|
||||
}
|
||||
|
||||
impl From<VerifiedClientMetadata> for ClientMetadataSerdeHelper {
|
||||
fn from(metadata: VerifiedClientMetadata) -> Self {
|
||||
let VerifiedClientMetadata {
|
||||
inner:
|
||||
ClientMetadata {
|
||||
redirect_uris,
|
||||
response_types,
|
||||
grant_types,
|
||||
application_type,
|
||||
contacts,
|
||||
client_name,
|
||||
logo_uri,
|
||||
client_uri,
|
||||
policy_uri,
|
||||
tos_uri,
|
||||
jwks_uri,
|
||||
jwks,
|
||||
sector_identifier_uri,
|
||||
subject_type,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg,
|
||||
id_token_signed_response_alg,
|
||||
id_token_encrypted_response_alg,
|
||||
id_token_encrypted_response_enc,
|
||||
userinfo_signed_response_alg,
|
||||
userinfo_encrypted_response_alg,
|
||||
userinfo_encrypted_response_enc,
|
||||
request_object_signing_alg,
|
||||
request_object_encryption_alg,
|
||||
request_object_encryption_enc,
|
||||
default_max_age,
|
||||
require_auth_time,
|
||||
default_acr_values,
|
||||
initiate_login_uri,
|
||||
request_uris,
|
||||
require_signed_request_object,
|
||||
require_pushed_authorization_requests,
|
||||
introspection_signed_response_alg,
|
||||
introspection_encrypted_response_alg,
|
||||
introspection_encrypted_response_enc,
|
||||
},
|
||||
} = metadata;
|
||||
|
||||
ClientMetadataSerdeHelper {
|
||||
redirect_uris,
|
||||
response_types,
|
||||
grant_types,
|
||||
application_type,
|
||||
contacts,
|
||||
jwks_uri,
|
||||
jwks,
|
||||
sector_identifier_uri,
|
||||
subject_type,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg,
|
||||
id_token_signed_response_alg,
|
||||
id_token_encrypted_response_alg,
|
||||
id_token_encrypted_response_enc,
|
||||
userinfo_signed_response_alg,
|
||||
userinfo_encrypted_response_alg,
|
||||
userinfo_encrypted_response_enc,
|
||||
request_object_signing_alg,
|
||||
request_object_encryption_alg,
|
||||
request_object_encryption_enc,
|
||||
default_max_age,
|
||||
require_auth_time,
|
||||
default_acr_values,
|
||||
initiate_login_uri,
|
||||
request_uris,
|
||||
require_signed_request_object,
|
||||
require_pushed_authorization_requests,
|
||||
introspection_signed_response_alg,
|
||||
introspection_encrypted_response_alg,
|
||||
introspection_encrypted_response_enc,
|
||||
extra: ClientMetadataLocalizedFields {
|
||||
client_name,
|
||||
logo_uri,
|
||||
client_uri,
|
||||
policy_uri,
|
||||
tos_uri,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientMetadataSerdeHelper> for ClientMetadata {
|
||||
fn from(metadata: ClientMetadataSerdeHelper) -> Self {
|
||||
let ClientMetadataSerdeHelper {
|
||||
redirect_uris,
|
||||
response_types,
|
||||
grant_types,
|
||||
application_type,
|
||||
contacts,
|
||||
jwks_uri,
|
||||
jwks,
|
||||
sector_identifier_uri,
|
||||
subject_type,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg,
|
||||
id_token_signed_response_alg,
|
||||
id_token_encrypted_response_alg,
|
||||
id_token_encrypted_response_enc,
|
||||
userinfo_signed_response_alg,
|
||||
userinfo_encrypted_response_alg,
|
||||
userinfo_encrypted_response_enc,
|
||||
request_object_signing_alg,
|
||||
request_object_encryption_alg,
|
||||
request_object_encryption_enc,
|
||||
default_max_age,
|
||||
require_auth_time,
|
||||
default_acr_values,
|
||||
initiate_login_uri,
|
||||
request_uris,
|
||||
require_signed_request_object,
|
||||
require_pushed_authorization_requests,
|
||||
introspection_signed_response_alg,
|
||||
introspection_encrypted_response_alg,
|
||||
introspection_encrypted_response_enc,
|
||||
extra:
|
||||
ClientMetadataLocalizedFields {
|
||||
client_name,
|
||||
logo_uri,
|
||||
client_uri,
|
||||
policy_uri,
|
||||
tos_uri,
|
||||
},
|
||||
} = metadata;
|
||||
|
||||
ClientMetadata {
|
||||
redirect_uris,
|
||||
response_types,
|
||||
grant_types,
|
||||
application_type,
|
||||
contacts,
|
||||
client_name,
|
||||
logo_uri,
|
||||
client_uri,
|
||||
policy_uri,
|
||||
tos_uri,
|
||||
jwks_uri,
|
||||
jwks,
|
||||
sector_identifier_uri,
|
||||
subject_type,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg,
|
||||
id_token_signed_response_alg,
|
||||
id_token_encrypted_response_alg,
|
||||
id_token_encrypted_response_enc,
|
||||
userinfo_signed_response_alg,
|
||||
userinfo_encrypted_response_alg,
|
||||
userinfo_encrypted_response_enc,
|
||||
request_object_signing_alg,
|
||||
request_object_encryption_alg,
|
||||
request_object_encryption_enc,
|
||||
default_max_age,
|
||||
require_auth_time,
|
||||
default_acr_values,
|
||||
initiate_login_uri,
|
||||
request_uris,
|
||||
require_signed_request_object,
|
||||
require_pushed_authorization_requests,
|
||||
introspection_signed_response_alg,
|
||||
introspection_encrypted_response_alg,
|
||||
introspection_encrypted_response_enc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientMetadataLocalizedFields {
|
||||
client_name: Option<Localized<String>>,
|
||||
logo_uri: Option<Localized<Url>>,
|
||||
client_uri: Option<Localized<Url>>,
|
||||
policy_uri: Option<Localized<Url>>,
|
||||
tos_uri: Option<Localized<Url>>,
|
||||
}
|
||||
|
||||
impl Serialize for ClientMetadataLocalizedFields {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
|
||||
if let Some(client_name) = &self.client_name {
|
||||
client_name.serialize(&mut map, "client_name")?;
|
||||
}
|
||||
|
||||
if let Some(logo_uri) = &self.logo_uri {
|
||||
logo_uri.serialize(&mut map, "logo_uri")?;
|
||||
}
|
||||
|
||||
if let Some(client_uri) = &self.client_uri {
|
||||
client_uri.serialize(&mut map, "client_uri")?;
|
||||
}
|
||||
|
||||
if let Some(policy_uri) = &self.policy_uri {
|
||||
policy_uri.serialize(&mut map, "policy_uri")?;
|
||||
}
|
||||
|
||||
if let Some(tos_uri) = &self.tos_uri {
|
||||
tos_uri.serialize(&mut map, "tos_uri")?;
|
||||
}
|
||||
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientMetadataLocalizedFields {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let map = HashMap::<Cow<'de, str>, Value>::deserialize(deserializer)?;
|
||||
let mut new_map: HashMap<String, HashMap<Option<LanguageTag>, Value>> = HashMap::new();
|
||||
|
||||
for (k, v) in map {
|
||||
let (prefix, lang) = if let Some((prefix, lang)) = k.split_once('#') {
|
||||
let lang = LanguageTag::parse(lang).map_err(|_| {
|
||||
D::Error::invalid_value(serde::de::Unexpected::Str(lang), &"language tag")
|
||||
})?;
|
||||
(prefix.to_owned(), Some(lang))
|
||||
} else {
|
||||
(k.into_owned(), None)
|
||||
};
|
||||
|
||||
new_map.entry(prefix).or_default().insert(lang, v);
|
||||
}
|
||||
|
||||
let client_name =
|
||||
Localized::deserialize(&mut new_map, "client_name").map_err(D::Error::custom)?;
|
||||
|
||||
let logo_uri =
|
||||
Localized::deserialize(&mut new_map, "logo_uri").map_err(D::Error::custom)?;
|
||||
|
||||
let client_uri =
|
||||
Localized::deserialize(&mut new_map, "client_uri").map_err(D::Error::custom)?;
|
||||
|
||||
let policy_uri =
|
||||
Localized::deserialize(&mut new_map, "policy_uri").map_err(D::Error::custom)?;
|
||||
|
||||
let tos_uri = Localized::deserialize(&mut new_map, "tos_uri").map_err(D::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
client_name,
|
||||
logo_uri,
|
||||
client_uri,
|
||||
policy_uri,
|
||||
tos_uri,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize_localized_fields() {
|
||||
let metadata = serde_json::json!({
|
||||
"redirect_uris": ["http://localhost/oidc"],
|
||||
"client_name": "Postbox",
|
||||
"client_name#fr": "Boîte à lettres",
|
||||
"client_uri": "https://localhost/",
|
||||
"client_uri#fr": "https://localhost/fr",
|
||||
"client_uri#de": "https://localhost/de",
|
||||
});
|
||||
|
||||
let metadata: ClientMetadata = serde_json::from_value(metadata).unwrap();
|
||||
|
||||
let name = metadata.client_name.unwrap();
|
||||
assert_eq!(name.non_localized(), "Postbox");
|
||||
assert_eq!(
|
||||
name.get(Some(&LanguageTag::parse("fr").unwrap())).unwrap(),
|
||||
"Boîte à lettres"
|
||||
);
|
||||
assert_eq!(name.get(Some(&LanguageTag::parse("de").unwrap())), None);
|
||||
|
||||
let client_uri = metadata.client_uri.unwrap();
|
||||
assert_eq!(client_uri.non_localized().as_ref(), "https://localhost/");
|
||||
assert_eq!(
|
||||
client_uri
|
||||
.get(Some(&LanguageTag::parse("fr").unwrap()))
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
"https://localhost/fr"
|
||||
);
|
||||
assert_eq!(
|
||||
client_uri
|
||||
.get(Some(&LanguageTag::parse("de").unwrap()))
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
"https://localhost/de"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_localized_fields() {
|
||||
let client_name = Localized::new(
|
||||
"Postbox".to_owned(),
|
||||
[(
|
||||
LanguageTag::parse("fr").unwrap(),
|
||||
"Boîte à lettres".to_owned(),
|
||||
)],
|
||||
);
|
||||
let client_uri = Localized::new(
|
||||
Url::parse("https://localhost").unwrap(),
|
||||
[
|
||||
(
|
||||
LanguageTag::parse("fr").unwrap(),
|
||||
Url::parse("https://localhost/fr").unwrap(),
|
||||
),
|
||||
(
|
||||
LanguageTag::parse("de").unwrap(),
|
||||
Url::parse("https://localhost/de").unwrap(),
|
||||
),
|
||||
],
|
||||
);
|
||||
let metadata = ClientMetadata {
|
||||
redirect_uris: Some(vec![Url::parse("http://localhost/oidc").unwrap()]),
|
||||
client_name: Some(client_name),
|
||||
client_uri: Some(client_uri),
|
||||
..Default::default()
|
||||
}
|
||||
.validate()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(metadata).unwrap(),
|
||||
serde_json::json!({
|
||||
"redirect_uris": ["http://localhost/oidc"],
|
||||
"client_name": "Postbox",
|
||||
"client_name#fr": "Boîte à lettres",
|
||||
"client_uri": "https://localhost/",
|
||||
"client_uri#fr": "https://localhost/fr",
|
||||
"client_uri#de": "https://localhost/de",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
1334
crates/oauth2-types/src/registration/mod.rs
Normal file
1334
crates/oauth2-types/src/registration/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -79,6 +79,18 @@ violation[{"msg": "logo_uri not on the same host as the client_uri"}] {
|
||||
not host_matches_client_uri(input.client_metadata.logo_uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "missing contacts"}] {
|
||||
not input.client_metadata.contacts
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid contacts"}] {
|
||||
not is_array(input.client_metadata.contacts)
|
||||
}
|
||||
|
||||
violation[{"msg": "empty contacts"}] {
|
||||
count(input.client_metadata.contacts) == 0
|
||||
}
|
||||
|
||||
violation[{"msg": "missing redirect_uris"}] {
|
||||
not input.client_metadata.redirect_uris
|
||||
}
|
||||
|
@ -4,17 +4,22 @@ test_valid {
|
||||
allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
}
|
||||
|
||||
test_missing_client_uri {
|
||||
not allow with input.client_metadata as {"redirect_uris": ["https://example.com/callback"]}
|
||||
not allow with input.client_metadata as {
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
}
|
||||
|
||||
test_insecure_client_uri {
|
||||
not allow with input.client_metadata as {
|
||||
"client_uri": "http://example.com/",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +28,7 @@ test_tos_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "https://example.com/tos",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure
|
||||
@ -30,6 +36,7 @@ test_tos_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "http://example.com/tos",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure, but allowed by the config
|
||||
@ -37,6 +44,7 @@ test_tos_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "http://example.com/tos",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
|
||||
@ -45,6 +53,7 @@ test_tos_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "https://example.org/tos",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Host mistmatch, but allowed by the config
|
||||
@ -52,6 +61,7 @@ test_tos_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "https://example.org/tos",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
}
|
||||
@ -61,6 +71,7 @@ test_logo_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "https://example.com/logo.png",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure
|
||||
@ -68,6 +79,7 @@ test_logo_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "http://example.com/logo.png",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure, but allowed by the config
|
||||
@ -75,6 +87,7 @@ test_logo_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "http://example.com/logo.png",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
|
||||
@ -83,6 +96,7 @@ test_logo_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "https://example.org/logo.png",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Host mistmatch, but allowed by the config
|
||||
@ -90,6 +104,7 @@ test_logo_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "https://example.org/logo.png",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
}
|
||||
@ -99,6 +114,7 @@ test_policy_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "https://example.com/policy",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure
|
||||
@ -106,6 +122,7 @@ test_policy_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "http://example.com/policy",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure, but allowed by the config
|
||||
@ -113,6 +130,7 @@ test_policy_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "http://example.com/policy",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
|
||||
@ -121,6 +139,7 @@ test_policy_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "https://example.org/policy",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Host mistmatch, but allowed by the config
|
||||
@ -128,24 +147,30 @@ test_policy_uri {
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "https://example.org/policy",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
}
|
||||
|
||||
test_redirect_uris {
|
||||
# Missing redirect_uris
|
||||
not allow with input.client_metadata as {"client_uri": "https://example.com/"}
|
||||
not allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# redirect_uris is not an array
|
||||
not allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": "https://example.com/callback",
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Empty redirect_uris
|
||||
not allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": [],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +179,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/second/callback", "https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure URL
|
||||
@ -161,6 +187,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://example.com/callback", "https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Insecure URL, but allowed by the config
|
||||
@ -168,6 +195,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://example.com/callback", "https://example.com/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
|
||||
@ -176,6 +204,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/second/callback", "https://example.org/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Host mismatch, but allowed by the config
|
||||
@ -183,6 +212,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/second/callback", "https://example.org/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
|
||||
@ -191,6 +221,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["com.example.app:/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# localhost not allowed
|
||||
@ -198,6 +229,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://locahost:1234/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# localhost not allowed
|
||||
@ -205,6 +237,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://127.0.0.1:1234/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# localhost not allowed
|
||||
@ -212,6 +245,7 @@ test_web_redirect_uri {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://[::1]:1234/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,6 +263,7 @@ test_native_redirect_uri {
|
||||
"http://[::1]/callback",
|
||||
"http://[::1]:1234/callback",
|
||||
],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# We don't allow HTTP URLs other than localhost
|
||||
@ -236,12 +271,14 @@ test_native_redirect_uri {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
not allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://example.com/"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# We don't allow HTTPS on localhost
|
||||
@ -249,6 +286,7 @@ test_native_redirect_uri {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://localhost:1234/"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# Ensure we're not allowing localhost as a prefix
|
||||
@ -256,6 +294,7 @@ test_native_redirect_uri {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://localhost.com/"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
|
||||
# For custom schemes, it should match the client_uri hostname
|
||||
@ -263,6 +302,7 @@ test_native_redirect_uri {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["org.example.app:/callback"],
|
||||
"contacts": ["contact@example.com"],
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,3 +311,25 @@ test_reverse_dns_match {
|
||||
redirect_uri := parse_uri("io.element.app:/callback")
|
||||
reverse_dns_match(client_uri.host, redirect_uri.scheme)
|
||||
}
|
||||
|
||||
test_contacts {
|
||||
# Missing contacts
|
||||
not allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
}
|
||||
|
||||
# contacts is not an array
|
||||
not allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": "contact@example.com",
|
||||
}
|
||||
|
||||
# Empty contacts
|
||||
not allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
"contacts": [],
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ use std::io::Cursor;
|
||||
|
||||
use anyhow::bail;
|
||||
use mas_data_model::{AuthorizationGrant, StorageBackend, User};
|
||||
use oauth2_types::registration::ClientMetadata;
|
||||
use oauth2_types::registration::VerifiedClientMetadata;
|
||||
use opa_wasm::Runtime;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
@ -205,7 +205,7 @@ impl Policy {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn evaluate_client_registration(
|
||||
&mut self,
|
||||
client_metadata: &ClientMetadata,
|
||||
client_metadata: &VerifiedClientMetadata,
|
||||
) -> Result<EvaluationResult, anyhow::Error> {
|
||||
let client_metadata = serde_json::to_value(client_metadata)?;
|
||||
let input = serde_json::json!({
|
||||
|
Reference in New Issue
Block a user