mas_oidc_client/requests/
introspection.rs

1// Copyright 2022 Kévin Commaille.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Requests for [Token Introspection].
16//!
17//! [Token Introspection]: https://www.rfc-editor.org/rfc/rfc7662
18
19use 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
37/// The method used to authenticate at the introspection endpoint.
38pub enum IntrospectionAuthentication<'a> {
39    /// Using client authentication.
40    Credentials(ClientCredentials),
41
42    /// Using a bearer token.
43    BearerToken(&'a str),
44}
45
46impl<'a> IntrospectionAuthentication<'a> {
47    /// Constructs an `IntrospectionAuthentication` from the given client
48    /// credentials.
49    #[must_use]
50    pub fn with_client_credentials(credentials: ClientCredentials) -> Self {
51        Self::Credentials(credentials)
52    }
53
54    /// Constructs an `IntrospectionAuthentication` from the given bearer token.
55    #[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/// Obtain information about a token.
97///
98/// # Arguments
99///
100/// * `http_service` - The service to use for making HTTP requests.
101///
102/// * `authentication` - The method used to authenticate the request.
103///
104/// * `revocation_endpoint` - The URL of the issuer's Revocation endpoint.
105///
106/// * `token` - The token to introspect.
107///
108/// * `token_type_hint` - Hint about the type of the token.
109///
110/// * `now` - The current time.
111///
112/// * `rng` - A random number generator.
113///
114/// # Errors
115///
116/// Returns an error if the request fails or the response is invalid.
117#[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}