oxide_auth_async/endpoint/
client_credentials.rs

1use std::borrow::Cow;
2use std::str::from_utf8;
3use std::marker::PhantomData;
4
5use base64::{engine::general_purpose::STANDARD, Engine};
6use oxide_auth::{
7    endpoint::{
8        NormalizedParameter, QueryParameter, WebResponse, WebRequest, Template, is_authorization_method,
9    },
10    code_grant::{
11        accesstoken::ErrorDescription,
12        client_credentials::{Error as ClientCredentialsError, Request as ClientCredentialsRequest},
13        error::{AccessTokenError, AccessTokenErrorType},
14    },
15};
16
17use super::{Endpoint, OAuthError, OwnerConsent};
18use crate::{
19    primitives::{Issuer, Registrar, Authorizer},
20    code_grant::client_credentials::{
21        Extension, client_credentials, Endpoint as ClientCredentialsEndpoint,
22    },
23};
24
25/// Offers access tokens to authenticated third parties.
26///
27/// A client may request a token that provides access to their own resources.
28///
29/// Client credentials can be allowed to appear in the request body instead of being
30/// required to be passed as HTTP Basic authorization. This is not recommended and must be
31/// enabled explicitely. See [`allow_credentials_in_body`] for details.
32///
33/// [`allow_credentials_in_body`]: #method.allow_credentials_in_body
34pub struct ClientCredentialsFlow<E, R>
35where
36    E: Endpoint<R> + Send,
37    R: WebRequest,
38{
39    endpoint: WrappedToken<E, R>,
40    allow_credentials_in_body: bool,
41    allow_refresh_token: bool,
42}
43
44struct WrappedToken<E: Endpoint<R>, R: WebRequest> {
45    inner: E,
46    extension_fallback: (),
47    r_type: PhantomData<R>,
48}
49
50struct WrappedRequest<R: WebRequest> {
51    /// Original request.
52    request: PhantomData<R>,
53
54    /// The request body
55    body: NormalizedParameter,
56
57    /// The authorization tuple
58    authorization: Option<Authorization>,
59
60    /// An error if one occurred.
61    error: Option<FailParse<R::Error>>,
62
63    /// The credentials-in-body flag from the flow.
64    allow_credentials_in_body: bool,
65}
66
67struct Invalid;
68
69enum FailParse<E> {
70    Invalid,
71    Err(E),
72}
73
74struct Authorization(String, Vec<u8>);
75
76impl<E, R> ClientCredentialsFlow<E, R>
77where
78    E: Endpoint<R> + Send + Sync,
79    R: WebRequest + Send + Sync,
80    <R as WebRequest>::Error: Send + Sync,
81{
82    /// Check that the endpoint supports the necessary operations for handling requests.
83    ///
84    /// Binds the endpoint to a particular type of request that it supports, for many
85    /// implementations this is probably single type anyways.
86    ///
87    /// ## Panics
88    ///
89    /// Indirectly `execute` may panic when this flow is instantiated with an inconsistent
90    /// endpoint, for details see the documentation of `Endpoint` and `execute`. For
91    /// consistent endpoints, the panic is instead caught as an error here.
92    pub fn prepare(mut endpoint: E) -> Result<Self, E::Error> {
93        if endpoint.registrar().is_none() {
94            return Err(endpoint.error(OAuthError::PrimitiveError));
95        }
96
97        if endpoint.issuer_mut().is_none() {
98            return Err(endpoint.error(OAuthError::PrimitiveError));
99        }
100
101        Ok(ClientCredentialsFlow {
102            endpoint: WrappedToken {
103                inner: endpoint,
104                extension_fallback: (),
105                r_type: PhantomData,
106            },
107            allow_credentials_in_body: false,
108            allow_refresh_token: false,
109        })
110    }
111
112    /// Credentials in body should only be enabled if use of HTTP Basic is not possible.
113    ///
114    /// Allows the request body to contain the `client_secret` as a form parameter. This is NOT
115    /// RECOMMENDED and need not be supported. The parameters MUST NOT appear in the request URI
116    /// itself.
117    ///
118    /// Thus support is disabled by default and must be explicitely enabled.
119    pub fn allow_credentials_in_body(&mut self, allow: bool) {
120        self.allow_credentials_in_body = allow;
121    }
122
123    /// Allow the refresh token to be included in the response.
124    ///
125    /// According to [RFC-6749 Section 4.4.3][4.4.3] "A refresh token SHOULD NOT be included" in
126    /// the response for the client credentials grant. Following that recommendation, the default
127    /// behaviour of this flow is to discard any refresh token that is returned from the issuer.
128    ///
129    /// If this behaviour is not what you want (it is possible that your particular application
130    /// does have a use for a client credentials refresh token), you may enable this feature.
131    ///
132    /// [4.4.3]: https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3
133    pub fn allow_refresh_token(&mut self, allow: bool) {
134        self.allow_refresh_token = allow;
135    }
136
137    /// Use the checked endpoint to check for authorization for a resource.
138    ///
139    /// ## Panics
140    ///
141    /// When the registrar, authorizer, or issuer returned by the endpoint is suddenly
142    /// `None` when previously it was `Some(_)`.
143    pub async fn execute(&mut self, mut request: R) -> Result<R::Response, E::Error> {
144        let pending = client_credentials(
145            &mut self.endpoint,
146            &WrappedRequest::new(&mut request, self.allow_credentials_in_body),
147        )
148        .await;
149
150        let pending = match pending {
151            Err(error) => {
152                return client_credentials_error(&mut self.endpoint.inner, &mut request, error)
153            }
154            Ok(pending) => pending,
155        };
156
157        let consent = self
158            .endpoint
159            .inner
160            .owner_solicitor()
161            .unwrap()
162            .check_consent(&mut request, pending.as_solicitation())
163            .await;
164
165        let owner_id = match consent {
166            OwnerConsent::Authorized(owner_id) => owner_id,
167            OwnerConsent::Error(error) => return Err(self.endpoint.inner.web_error(error)),
168            OwnerConsent::InProgress(..) => {
169                // User interaction is not permitted in the client credentials flow, so
170                // an InProgress response is invalid.
171                return Err(self.endpoint.inner.error(OAuthError::PrimitiveError));
172            }
173            OwnerConsent::Denied => {
174                let mut error = AccessTokenError::default();
175                error.set_type(AccessTokenErrorType::InvalidClient);
176                let mut json = ErrorDescription::new(error);
177                let mut response = self.endpoint.inner.response(
178                    &mut request,
179                    Template::new_unauthorized(None, Some(json.description())).into(),
180                )?;
181
182                response
183                    .client_error()
184                    .map_err(|err| self.endpoint.inner.web_error(err))?;
185                response
186                    .body_json(&json.to_json())
187                    .map_err(|err| self.endpoint.inner.web_error(err))?;
188                return Ok(response);
189            }
190        };
191
192        let token = match pending
193            .issue(&mut self.endpoint, owner_id, self.allow_refresh_token)
194            .await
195        {
196            Err(error) => {
197                return client_credentials_error(&mut self.endpoint.inner, &mut request, error)
198            }
199            Ok(token) => token,
200        };
201
202        let mut response = self
203            .endpoint
204            .inner
205            .response(&mut request, Template::new_ok().into())?;
206        response
207            .body_json(&token.to_json())
208            .map_err(|err| self.endpoint.inner.web_error(err))?;
209        Ok(response)
210    }
211}
212
213fn client_credentials_error<E: Endpoint<R>, R: WebRequest>(
214    endpoint: &mut E, request: &mut R, error: ClientCredentialsError,
215) -> Result<R::Response, E::Error> {
216    Ok(match error {
217        ClientCredentialsError::Ignore => return Err(endpoint.error(OAuthError::DenySilently)),
218        ClientCredentialsError::Invalid(mut json) => {
219            let mut response =
220                endpoint.response(request, Template::new_bad(Some(json.description())).into())?;
221
222            response.client_error().map_err(|err| endpoint.web_error(err))?;
223            response
224                .body_json(&json.to_json())
225                .map_err(|err| endpoint.web_error(err))?;
226            response
227        }
228        ClientCredentialsError::Unauthorized(mut json, scheme) => {
229            let mut response = endpoint.response(
230                request,
231                Template::new_unauthorized(None, Some(json.description())).into(),
232            )?;
233
234            response
235                .unauthorized(&scheme)
236                .map_err(|err| endpoint.web_error(err))?;
237            response
238                .body_json(&json.to_json())
239                .map_err(|err| endpoint.web_error(err))?;
240            response
241        }
242        ClientCredentialsError::Primitive(_) => {
243            // FIXME: give the context for restoration.
244            return Err(endpoint.error(OAuthError::PrimitiveError));
245        }
246    })
247}
248
249impl<E: Endpoint<R>, R: WebRequest> ClientCredentialsEndpoint for WrappedToken<E, R> {
250    fn registrar(&self) -> &(dyn Registrar + Sync) {
251        self.inner.registrar().unwrap()
252    }
253
254    fn authorizer(&mut self) -> &mut (dyn Authorizer + Send) {
255        self.inner.authorizer_mut().unwrap()
256    }
257
258    fn issuer(&mut self) -> &mut (dyn Issuer + Send) {
259        self.inner.issuer_mut().unwrap()
260    }
261
262    fn extension(&mut self) -> &mut (dyn Extension + Send) {
263        self.inner
264            .extension()
265            .and_then(super::Extension::client_credentials)
266            .unwrap_or(&mut self.extension_fallback)
267    }
268}
269
270impl<R: WebRequest> WrappedRequest<R> {
271    pub fn new(request: &mut R, credentials: bool) -> Self {
272        Self::new_or_fail(request, credentials).unwrap_or_else(Self::from_err)
273    }
274
275    fn new_or_fail(request: &mut R, credentials: bool) -> Result<Self, FailParse<R::Error>> {
276        // If there is a header, it must parse correctly.
277        let authorization = match request.authheader() {
278            Err(err) => return Err(FailParse::Err(err)),
279            Ok(Some(header)) => Self::parse_header(header).map(Some)?,
280            Ok(None) => None,
281        };
282
283        Ok(WrappedRequest {
284            request: PhantomData,
285            body: request
286                .urlbody()
287                .map(|body| body.into_owned())
288                .map_err(FailParse::Err)?,
289            authorization,
290            error: None,
291            allow_credentials_in_body: credentials,
292        })
293    }
294
295    fn from_err(err: FailParse<R::Error>) -> Self {
296        WrappedRequest {
297            request: PhantomData,
298            body: Default::default(),
299            authorization: None,
300            error: Some(err),
301            allow_credentials_in_body: false,
302        }
303    }
304
305    fn parse_header(header: Cow<str>) -> Result<Authorization, Invalid> {
306        let authorization = {
307            let auth_data = match is_authorization_method(&header, "Basic ") {
308                None => return Err(Invalid),
309                Some(data) => data,
310            };
311
312            let combined = match STANDARD.decode(auth_data) {
313                Err(_) => return Err(Invalid),
314                Ok(vec) => vec,
315            };
316
317            let mut split = combined.splitn(2, |&c| c == b':');
318            let client_bin = match split.next() {
319                None => return Err(Invalid),
320                Some(client) => client,
321            };
322            let passwd = match split.next() {
323                None => return Err(Invalid),
324                Some(passwd64) => passwd64,
325            };
326
327            let client = match from_utf8(client_bin) {
328                Err(_) => return Err(Invalid),
329                Ok(client) => client,
330            };
331
332            Authorization(client.to_string(), passwd.to_vec())
333        };
334
335        Ok(authorization)
336    }
337}
338
339impl<R: WebRequest> ClientCredentialsRequest for WrappedRequest<R> {
340    fn valid(&self) -> bool {
341        self.error.is_none()
342    }
343
344    fn authorization(&self) -> Option<(Cow<str>, Cow<[u8]>)> {
345        self.authorization
346            .as_ref()
347            .map(|auth| (auth.0.as_str().into(), auth.1.as_slice().into()))
348    }
349
350    fn grant_type(&self) -> Option<Cow<str>> {
351        self.body.unique_value("grant_type")
352    }
353
354    fn scope(&self) -> Option<Cow<str>> {
355        self.body.unique_value("scope")
356    }
357
358    fn extension(&self, key: &str) -> Option<Cow<str>> {
359        self.body.unique_value(key)
360    }
361
362    fn allow_credentials_in_body(&self) -> bool {
363        self.allow_credentials_in_body
364    }
365}
366
367impl<E> From<Invalid> for FailParse<E> {
368    fn from(_: Invalid) -> Self {
369        FailParse::Invalid
370    }
371}