Admin API: add endpoint to get an Upstream OAuth Provider by ID
This commit is contained in:
@@ -195,4 +195,11 @@ where
|
||||
self::upstream_oauth_providers::list_doc,
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/upstream-oauth-providers/{id}",
|
||||
get_with(
|
||||
self::upstream_oauth_providers::get,
|
||||
self::upstream_oauth_providers::get_doc,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
196
crates/handlers/src/admin/v1/upstream_oauth_providers/get.rs
Normal file
196
crates/handlers/src/admin/v1/upstream_oauth_providers/get.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
use aide::{OperationIo, transform::TransformOperation};
|
||||
use axum::{Json, response::IntoResponse};
|
||||
use hyper::StatusCode;
|
||||
use mas_axum_utils::record_error;
|
||||
use mas_storage::{RepositoryAccess, upstream_oauth2::UpstreamOAuthProviderRepository};
|
||||
|
||||
use crate::{
|
||||
admin::{
|
||||
call_context::CallContext,
|
||||
model::UpstreamOAuthProvider,
|
||||
params::UlidPathParam,
|
||||
response::{ErrorResponse, SingleResponse},
|
||||
},
|
||||
impl_from_error_for_route,
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error, OperationIo)]
|
||||
#[aide(output_with = "Json<ErrorResponse>")]
|
||||
pub enum RouteError {
|
||||
#[error(transparent)]
|
||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
|
||||
#[error("Provider not found")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl_from_error_for_route!(mas_storage::RepositoryError);
|
||||
|
||||
impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let error = ErrorResponse::from_error(&self);
|
||||
let sentry_event_id = record_error!(self, Self::Internal(_));
|
||||
let status = match self {
|
||||
Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::NotFound => StatusCode::NOT_FOUND,
|
||||
};
|
||||
|
||||
(status, sentry_event_id, Json(error)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn doc(operation: TransformOperation) -> TransformOperation {
|
||||
operation
|
||||
.id("getUpstreamOAuthProvider")
|
||||
.summary("Get upstream OAuth provider")
|
||||
.tag("upstream-oauth-provider")
|
||||
.response_with::<200, Json<SingleResponse<UpstreamOAuthProvider>>, _>(|t| {
|
||||
let [sample, ..] = UpstreamOAuthProvider::samples();
|
||||
t.description("The upstream OAuth provider")
|
||||
.example(SingleResponse::new_canonical(sample))
|
||||
})
|
||||
.response_with::<404, Json<ErrorResponse>, _>(|t| t.description("Provider not found"))
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "handler.admin.v1.upstream_oauth_providers.get", skip_all)]
|
||||
pub async fn handler(
|
||||
CallContext { mut repo, .. }: CallContext,
|
||||
id: UlidPathParam,
|
||||
) -> Result<Json<SingleResponse<UpstreamOAuthProvider>>, RouteError> {
|
||||
let provider = repo
|
||||
.upstream_oauth_provider()
|
||||
.lookup(*id)
|
||||
.await?
|
||||
.ok_or(RouteError::NotFound)?;
|
||||
|
||||
Ok(Json(SingleResponse::new_canonical(
|
||||
UpstreamOAuthProvider::from(provider),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hyper::{Request, StatusCode};
|
||||
use mas_data_model::{
|
||||
UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports,
|
||||
UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderOnBackchannelLogout,
|
||||
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderTokenAuthMethod,
|
||||
};
|
||||
use mas_iana::jose::JsonWebSignatureAlg;
|
||||
use mas_storage::{
|
||||
RepositoryAccess,
|
||||
upstream_oauth2::{UpstreamOAuthProviderParams, UpstreamOAuthProviderRepository},
|
||||
};
|
||||
use oauth2_types::scope::{OPENID, Scope};
|
||||
use sqlx::PgPool;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
|
||||
|
||||
async fn create_test_provider(state: &mut TestState) -> UpstreamOAuthProvider {
|
||||
let mut repo = state.repository().await.unwrap();
|
||||
|
||||
let params = UpstreamOAuthProviderParams {
|
||||
issuer: Some("https://accounts.google.com".to_owned()),
|
||||
human_name: Some("Google".to_owned()),
|
||||
brand_name: Some("google".to_owned()),
|
||||
discovery_mode: UpstreamOAuthProviderDiscoveryMode::Oidc,
|
||||
pkce_mode: UpstreamOAuthProviderPkceMode::Auto,
|
||||
jwks_uri_override: None,
|
||||
authorization_endpoint_override: None,
|
||||
token_endpoint_override: None,
|
||||
userinfo_endpoint_override: None,
|
||||
fetch_userinfo: true,
|
||||
userinfo_signed_response_alg: None,
|
||||
client_id: "google-client-id".to_owned(),
|
||||
encrypted_client_secret: Some("encrypted-secret".to_owned()),
|
||||
token_endpoint_signing_alg: None,
|
||||
token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::ClientSecretPost,
|
||||
id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
|
||||
response_mode: None,
|
||||
scope: Scope::from_iter([OPENID]),
|
||||
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
|
||||
additional_authorization_parameters: vec![],
|
||||
forward_login_hint: false,
|
||||
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
|
||||
ui_order: 0,
|
||||
};
|
||||
|
||||
let provider = repo
|
||||
.upstream_oauth_provider()
|
||||
.add(&mut state.rng(), &state.clock, params)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Box::new(repo).save().await.unwrap();
|
||||
|
||||
provider
|
||||
}
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_get_provider(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let admin_token = state.token_with_scope("urn:mas:admin").await;
|
||||
let provider = create_test_provider(&mut state).await;
|
||||
|
||||
let request = Request::get(format!(
|
||||
"/api/admin/v1/upstream-oauth-providers/{}",
|
||||
provider.id
|
||||
))
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::OK);
|
||||
let body: serde_json::Value = response.json::<serde_json::Value>();
|
||||
|
||||
assert_eq!(body["data"]["type"], "upstream-oauth-provider");
|
||||
assert_eq!(body["data"]["id"], provider.id.to_string());
|
||||
assert_eq!(body["data"]["attributes"]["human_name"], "Google");
|
||||
|
||||
insta::assert_json_snapshot!(body, @r###"
|
||||
{
|
||||
"data": {
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG0MZAA6S4AF7CTV32E",
|
||||
"attributes": {
|
||||
"issuer": "https://accounts.google.com",
|
||||
"human_name": "Google",
|
||||
"brand_name": "google",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG0MZAA6S4AF7CTV32E"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG0MZAA6S4AF7CTV32E"
|
||||
}
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_not_found(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let admin_token = state.token_with_scope("urn:mas:admin").await;
|
||||
|
||||
let provider_id = Ulid::nil();
|
||||
let request = Request::get(format!(
|
||||
"/api/admin/v1/upstream-oauth-providers/{provider_id}"
|
||||
))
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
mod get;
|
||||
mod list;
|
||||
|
||||
pub use self::list::{doc as list_doc, handler as list};
|
||||
pub use self::{
|
||||
get::{doc as get_doc, handler as get},
|
||||
list::{doc as list_doc, handler as list},
|
||||
};
|
||||
|
||||
@@ -3552,6 +3552,68 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/admin/v1/upstream-oauth-providers/{id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"upstream-oauth-provider"
|
||||
],
|
||||
"summary": "Get upstream OAuth provider",
|
||||
"operationId": "getUpstreamOAuthProvider",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"title": "The ID of the resource",
|
||||
"$ref": "#/components/schemas/ULID"
|
||||
},
|
||||
"style": "simple"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The upstream OAuth provider",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SingleResponse_for_UpstreamOAuthProvider"
|
||||
},
|
||||
"example": {
|
||||
"data": {
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01040G2081040G2081040G2081",
|
||||
"attributes": {
|
||||
"issuer": "https://accounts.google.com",
|
||||
"human_name": "Google",
|
||||
"brand_name": "google",
|
||||
"created_at": "1970-01-01T00:00:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01040G2081040G2081040G2081"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01040G2081040G2081040G2081"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Provider not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -5243,6 +5305,22 @@
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"SingleResponse_for_UpstreamOAuthProvider": {
|
||||
"description": "A top-level response with a single resource",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data",
|
||||
"links"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/SingleResource_for_UpstreamOAuthProvider"
|
||||
},
|
||||
"links": {
|
||||
"$ref": "#/components/schemas/SelfLinks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user