mas_oidc_client/requests/
introspection.rs1use chrono::{DateTime, Utc};
20use headers::{Authorization, HeaderMapExt};
21use http::Request;
22use mas_http::{CatchHttpCodesLayer, FormUrlencodedRequestLayer, JsonResponseLayer};
23use mas_iana::oauth::OAuthTokenTypeHint;
24use oauth2_types::requests::{IntrospectionRequest, IntrospectionResponse};
25use rand::Rng;
26use serde::Serialize;
27use tower::{Layer, Service, ServiceExt};
28use url::Url;
29
30use crate::{
31 error::IntrospectionError,
32 http_service::HttpService,
33 types::client_credentials::{ClientCredentials, RequestWithClientCredentials},
34 utils::{http_all_error_status_codes, http_error_mapper},
35};
36
37pub enum IntrospectionAuthentication<'a> {
39 Credentials(ClientCredentials),
41
42 BearerToken(&'a str),
44}
45
46impl<'a> IntrospectionAuthentication<'a> {
47 #[must_use]
50 pub fn with_client_credentials(credentials: ClientCredentials) -> Self {
51 Self::Credentials(credentials)
52 }
53
54 #[must_use]
56 pub fn with_bearer_token(token: &'a str) -> Self {
57 Self::BearerToken(token)
58 }
59
60 fn apply_to_request<T: Serialize>(
61 self,
62 request: Request<T>,
63 now: DateTime<Utc>,
64 rng: &mut impl Rng,
65 ) -> Result<Request<RequestWithClientCredentials<T>>, IntrospectionError> {
66 let res = match self {
67 IntrospectionAuthentication::Credentials(client_credentials) => {
68 client_credentials.apply_to_request(request, now, rng)?
69 }
70 IntrospectionAuthentication::BearerToken(access_token) => {
71 let (mut parts, body) = request.into_parts();
72
73 parts
74 .headers
75 .typed_insert(Authorization::bearer(access_token)?);
76
77 let body = RequestWithClientCredentials {
78 body,
79 credentials: None,
80 };
81
82 http::Request::from_parts(parts, body)
83 }
84 };
85
86 Ok(res)
87 }
88}
89
90impl<'a> From<ClientCredentials> for IntrospectionAuthentication<'a> {
91 fn from(credentials: ClientCredentials) -> Self {
92 Self::with_client_credentials(credentials)
93 }
94}
95
96#[tracing::instrument(skip_all, fields(introspection_endpoint))]
118pub async fn introspect_token(
119 http_service: &HttpService,
120 authentication: IntrospectionAuthentication<'_>,
121 introspection_endpoint: &Url,
122 token: String,
123 token_type_hint: Option<OAuthTokenTypeHint>,
124 now: DateTime<Utc>,
125 rng: &mut impl Rng,
126) -> Result<IntrospectionResponse, IntrospectionError> {
127 tracing::debug!("Introspecting token…");
128
129 let introspection_request = IntrospectionRequest {
130 token,
131 token_type_hint,
132 };
133 let introspection_request =
134 http::Request::post(introspection_endpoint.as_str()).body(introspection_request)?;
135
136 let introspection_request = authentication.apply_to_request(introspection_request, now, rng)?;
137
138 let service = (
139 FormUrlencodedRequestLayer::default(),
140 JsonResponseLayer::<IntrospectionResponse>::default(),
141 CatchHttpCodesLayer::new(http_all_error_status_codes(), http_error_mapper),
142 )
143 .layer(http_service.clone());
144
145 let introspection_response = service
146 .ready_oneshot()
147 .await?
148 .call(introspection_request)
149 .await?
150 .into_body();
151
152 Ok(introspection_response)
153}