mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-24 01:43:55 +02:00
Added webfinger implementation
This commit is contained in:
parent
b96fe49b99
commit
d88005a1c6
|
@ -443,6 +443,21 @@ fn require_request_uri_parameter_supported_default() -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OidcWebfingerRel {
|
||||
pub rel: String,
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
/// The response to an Webfinger request. Only a subset of the body is defined here.
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc7033>
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OidcWebfingerResponse {
|
||||
pub subject: String,
|
||||
pub links: Vec<OidcWebfingerRel>,
|
||||
}
|
||||
|
||||
/// The response to an OpenID connect discovery request
|
||||
/// <https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata>
|
||||
#[skip_serializing_none]
|
||||
|
|
|
@ -9,6 +9,7 @@ use kanidm_proto::internal::{
|
|||
IdentifyUserRequest, IdentifyUserResponse, ImageValue, OperationError, RadiusAuthToken,
|
||||
SearchRequest, SearchResponse, UserAuthToken,
|
||||
};
|
||||
use kanidm_proto::oauth2::OidcWebfingerResponse;
|
||||
use kanidm_proto::v1::{
|
||||
AuthIssueSession, AuthRequest, Entry as ProtoEntry, UatStatus, UnixGroupToken, UnixUserToken,
|
||||
WhoamiResponse,
|
||||
|
@ -1509,6 +1510,21 @@ impl QueryServerReadV1 {
|
|||
idms_prox_read.oauth2_openid_discovery(&client_id)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_oauth2_webfinger_discovery(
|
||||
&self,
|
||||
client_id: String,
|
||||
resource_id: String,
|
||||
eventid: Uuid,
|
||||
) -> Result<OidcWebfingerResponse, OperationError> {
|
||||
let idms_prox_read = self.idms.proxy_read().await?;
|
||||
idms_prox_read.oauth2_openid_webfinger_discovery(&client_id, &resource_id)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
|
|
|
@ -513,7 +513,7 @@ pub async fn oauth2_token_post(
|
|||
}
|
||||
}
|
||||
|
||||
// // For future openid integration
|
||||
// For future openid integration
|
||||
pub async fn oauth2_openid_discovery_get(
|
||||
State(state): State<ServerState>,
|
||||
Path(client_id): Path<String>,
|
||||
|
@ -538,6 +538,45 @@ pub async fn oauth2_openid_discovery_get(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn oauth2_openid_webfinger_get(
|
||||
State(state): State<ServerState>,
|
||||
Path(client_id): Path<String>,
|
||||
Query(resource): Query<String>,
|
||||
|
||||
// For the moment this is implemented ignoring the rel's
|
||||
// Query(rel): Query<Vec<String>>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
) -> impl IntoResponse {
|
||||
let cleaned_resource = match resource {
|
||||
s if s.starts_with("acct:") => s[5..].to_string(),
|
||||
s => s,
|
||||
};
|
||||
|
||||
let res = state
|
||||
.qe_r_ref
|
||||
.handle_oauth2_webfinger_discovery(client_id, cleaned_resource, kopid.eventid)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(dsc) => (
|
||||
StatusCode::OK,
|
||||
[
|
||||
(ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
|
||||
(CONTENT_TYPE, "application/jrd+json"),
|
||||
],
|
||||
Json({
|
||||
dsc.subject = resource;
|
||||
dsc
|
||||
}),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => {
|
||||
error!(err = ?e, "Unable to access discovery info");
|
||||
WebError::from(e).response_with_access_control_origin_header()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn oauth2_rfc8414_metadata_get(
|
||||
State(state): State<ServerState>,
|
||||
Path(client_id): Path<String>,
|
||||
|
@ -770,6 +809,10 @@ pub fn route_setup(state: ServerState) -> Router<ServerState> {
|
|||
"/oauth2/openid/:client_id/.well-known/openid-configuration",
|
||||
get(oauth2_openid_discovery_get).options(oauth2_preflight_options),
|
||||
)
|
||||
.route(
|
||||
"/oauth2/openid/:client_id/.well-known/webfinger",
|
||||
get(oauth2_openid_webfinger_get).options(oauth2_preflight_options),
|
||||
)
|
||||
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route(
|
||||
|
|
|
@ -32,7 +32,7 @@ pub use kanidm_proto::oauth2::{
|
|||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
||||
AccessTokenResponse, AuthorisationRequest, CodeChallengeMethod, ErrorResponse, GrantTypeReq,
|
||||
OAuth2RFC9068Token, OAuth2RFC9068TokenExtensions, Oauth2Rfc8414MetadataResponse,
|
||||
OidcDiscoveryResponse, PkceAlg, TokenRevokeRequest,
|
||||
OidcDiscoveryResponse, OidcWebfingerRel, OidcWebfingerResponse, PkceAlg, TokenRevokeRequest,
|
||||
};
|
||||
|
||||
use kanidm_proto::oauth2::{
|
||||
|
@ -2742,6 +2742,46 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn oauth2_openid_webfinger_discovery(
|
||||
&self,
|
||||
client_id: &str,
|
||||
resource_id: &str,
|
||||
) -> Result<OidcWebfingerResponse, OperationError> {
|
||||
let o2rs = self.oauth2rs.inner.rs_set.get(client_id).ok_or_else(|| {
|
||||
admin_warn!(
|
||||
"Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)"
|
||||
);
|
||||
OperationError::NoMatchingEntries
|
||||
})?;
|
||||
|
||||
let Some(spn) = PartialValue::new_spn_s(resource_id) else {
|
||||
return Err(OperationError::NoMatchingEntries);
|
||||
};
|
||||
|
||||
// Ensure that the account exists. If we consider account's spn privileged information
|
||||
// We can shortcut this step and always return valid. This means it will seem like every user exits
|
||||
// However it will fail at logon. I don't think leaking spn's is a problem however
|
||||
if !self
|
||||
.qs_read
|
||||
.internal_exists(Filter::new(f_eq(Attribute::Spn, spn)))
|
||||
{
|
||||
return Err(OperationError::NoMatchingEntries);
|
||||
}
|
||||
|
||||
let issuer = o2rs.iss.clone();
|
||||
|
||||
Ok(OidcWebfingerResponse {
|
||||
// we set the subject to the resource_id to ensure we always send something valid back
|
||||
// but realistically this will be overwritten on at the API layer
|
||||
subject: resource_id.to_string(),
|
||||
links: vec![OidcWebfingerRel {
|
||||
rel: "http://openid.net/specs/connect/1.0/issuer".into(),
|
||||
href: issuer.into(),
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn oauth2_openid_publickey(&self, client_id: &str) -> Result<JwkKeySet, OperationError> {
|
||||
let o2rs = self.oauth2rs.inner.rs_set.get(client_id).ok_or_else(|| {
|
||||
|
|
Loading…
Reference in a new issue