From d43a8f1a006d9815fa7238dc10f0788fe7364f8f Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 8 Apr 2022 10:43:38 +0200 Subject: [PATCH] Basic Webfinger support --- crates/handlers/src/lib.rs | 1 + crates/handlers/src/oauth2/mod.rs | 1 + crates/handlers/src/oauth2/webfinger.rs | 53 ++++++++++++++++ crates/oauth2-types/src/lib.rs | 3 +- crates/oauth2-types/src/webfinger.rs | 83 +++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 crates/handlers/src/oauth2/webfinger.rs create mode 100644 crates/oauth2-types/src/webfinger.rs diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index f75006a0..4b39d7da 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -61,6 +61,7 @@ where "/.well-known/openid-configuration", get(self::oauth2::discovery::get), ) + .route("/.well-known/webfinger", get(self::oauth2::webfinger::get)) .route("/oauth2/keys.json", get(self::oauth2::keys::get)) .route( "/oauth2/userinfo", diff --git a/crates/handlers/src/oauth2/mod.rs b/crates/handlers/src/oauth2/mod.rs index 1a519111..d3dac044 100644 --- a/crates/handlers/src/oauth2/mod.rs +++ b/crates/handlers/src/oauth2/mod.rs @@ -18,5 +18,6 @@ pub mod introspection; pub mod keys; pub mod token; pub mod userinfo; +pub mod webfinger; pub(crate) use authorization::ContinueAuthorizationGrant; diff --git a/crates/handlers/src/oauth2/webfinger.rs b/crates/handlers/src/oauth2/webfinger.rs new file mode 100644 index 00000000..497bdde4 --- /dev/null +++ b/crates/handlers/src/oauth2/webfinger.rs @@ -0,0 +1,53 @@ +// 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 axum::{extract::Query, response::IntoResponse, Extension, Json, TypedHeader}; +use headers::ContentType; +use mas_axum_utils::UrlBuilder; +use oauth2_types::webfinger::WebFingerResponse; +use serde::Deserialize; + +#[derive(Deserialize)] +pub(crate) struct Params { + resource: String, + + // TODO: handle multiple rel= + #[serde(default)] + rel: Option, +} + +fn jrd() -> mime::Mime { + "application/jrd+json".parse().unwrap() +} + +pub(crate) async fn get( + Query(params): Query, + Extension(url_builder): Extension, +) -> impl IntoResponse { + // TODO: should we validate the subject? + let subject = params.resource; + + let wants_issuer = params + .rel + .iter() + .any(|i| i == "http://openid.net/specs/connect/1.0/issuer"); + + let res = if wants_issuer { + WebFingerResponse::new(subject).with_issuer(url_builder.oidc_issuer()) + } else { + WebFingerResponse::new(subject) + }; + + (TypedHeader(ContentType::from(jrd())), Json(res)) +} diff --git a/crates/oauth2-types/src/lib.rs b/crates/oauth2-types/src/lib.rs index 2c9f5a5b..db44958d 100644 --- a/crates/oauth2-types/src/lib.rs +++ b/crates/oauth2-types/src/lib.rs @@ -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. @@ -52,6 +52,7 @@ pub mod oidc; pub mod pkce; pub mod requests; pub mod scope; +pub mod webfinger; pub mod prelude { pub use crate::{pkce::CodeChallengeMethodExt, ResponseTypeExt}; diff --git a/crates/oauth2-types/src/webfinger.rs b/crates/oauth2-types/src/webfinger.rs new file mode 100644 index 00000000..20670e95 --- /dev/null +++ b/crates/oauth2-types/src/webfinger.rs @@ -0,0 +1,83 @@ +// 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 serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub struct WebFingerResponse { + subject: String, + links: Vec, +} + +impl WebFingerResponse { + #[must_use] + pub const fn new(subject: String) -> Self { + Self { + subject, + links: Vec::new(), + } + } + + #[must_use] + pub fn with_link(mut self, link: WebFingerLink) -> Self { + self.links.push(link); + self + } + + #[must_use] + pub fn with_issuer(self, issuer: Url) -> Self { + self.with_link(WebFingerLink::issuer(issuer)) + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[serde(tag = "rel")] +pub enum WebFingerLink { + #[serde(rename = "http://openid.net/specs/connect/1.0/issuer")] + OidcIssuer { href: Url }, +} + +impl WebFingerLink { + #[must_use] + pub const fn issuer(href: Url) -> Self { + Self::OidcIssuer { href } + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn serialize_webfinger_response_test() { + let res = WebFingerResponse::new("acct:john@example.com".to_string()) + .with_issuer(Url::parse("https://account.example.com/").unwrap()); + + let res = serde_json::to_value(&res).unwrap(); + + assert_eq!( + res, + json!({ + "subject": "acct:john@example.com", + "links": [{ + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": "https://account.example.com/", + }] + }) + ); + } +}