oxide_auth/endpoint/
client_credentials.rs

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