oxide_auth_async/endpoint/
access_token.rs

1use std::str::from_utf8;
2use std::{borrow::Cow, marker::PhantomData};
3
4use base64::Engine;
5use base64::engine::general_purpose::STANDARD;
6use oxide_auth::{
7    endpoint::{QueryParameter, WebRequest, OAuthError, WebResponse, Template, NormalizedParameter},
8    code_grant::{
9        accesstoken::{
10            Error as TokenError, Request as TokenRequest, Authorization as TokenAuthorization,
11        },
12    },
13};
14
15use super::Endpoint;
16use crate::{
17    code_grant::access_token::{Extension, Endpoint as TokenEndpoint, access_token},
18    primitives::{Issuer, Registrar, Authorizer},
19};
20
21/// Offers access tokens to authenticated third parties.
22///
23/// After having received an authorization code from the resource owner, a client must
24/// directly contact the OAuth endpoint–authenticating itself–to receive the access
25/// token. The token is then used as authorization in requests to the resource. This
26/// request MUST be protected by TLS.
27///
28/// Client credentials can be allowed to appear in the request body instead of being
29/// required to be passed as HTTP Basic authorization. This is not recommended and must be
30/// enabled explicitely. See [`allow_credentials_in_body`] for details.
31///
32/// [`allow_credentials_in_body`]: #method.allow_credentials_in_body
33pub struct AccessTokenFlow<E, R>
34where
35    E: Endpoint<R>,
36    R: WebRequest,
37{
38    endpoint: WrappedToken<E, R>,
39    allow_credentials_in_body: bool,
40}
41
42struct WrappedToken<E, R>
43where
44    E: Endpoint<R>,
45    R: WebRequest,
46{
47    inner: E,
48    extension_fallback: (),
49    r_type: PhantomData<R>,
50}
51
52#[derive(Clone)]
53pub struct WrappedRequest<R: WebRequest> {
54    /// The query in the url.
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
67#[derive(Debug)]
68struct Invalid;
69
70#[derive(Clone)]
71enum FailParse<E> {
72    Invalid,
73    Err(E),
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
77struct Authorization(String, Option<Vec<u8>>);
78
79impl<E, R> AccessTokenFlow<E, R>
80where
81    E: Endpoint<R> + Send + Sync,
82    R: WebRequest + Send + Sync,
83    <R as WebRequest>::Error: Send + Sync,
84{
85    /// Check that the endpoint supports the necessary operations for handling requests.
86    ///
87    /// Binds the endpoint to a particular type of request that it supports, for many
88    /// implementations this is probably single type anyways.
89    ///
90    /// ## Panics
91    ///
92    /// Indirectly `execute` may panic when this flow is instantiated with an inconsistent
93    /// endpoint, for details see the documentation of `Endpoint` and `execute`. For
94    /// consistent endpoints, the panic is instead caught as an error here.
95    pub fn prepare(mut endpoint: E) -> Result<Self, E::Error> {
96        if endpoint.registrar().is_none() {
97            return Err(endpoint.error(OAuthError::PrimitiveError));
98        }
99
100        if endpoint.authorizer_mut().is_none() {
101            return Err(endpoint.error(OAuthError::PrimitiveError));
102        }
103
104        if endpoint.issuer_mut().is_none() {
105            return Err(endpoint.error(OAuthError::PrimitiveError));
106        }
107
108        Ok(AccessTokenFlow {
109            endpoint: WrappedToken {
110                inner: endpoint,
111                extension_fallback: (),
112                r_type: PhantomData,
113            },
114            allow_credentials_in_body: false,
115        })
116    }
117
118    /// Credentials in body should only be enabled if use of HTTP Basic is not possible.
119    ///
120    /// Allows the request body to contain the `client_secret` as a form parameter. This is NOT
121    /// RECOMMENDED and need not be supported. The parameters MUST NOT appear in the request URI
122    /// itself.
123    ///
124    /// Thus support is disabled by default and must be explicitely enabled.
125    pub fn allow_credentials_in_body(&mut self, allow: bool) {
126        self.allow_credentials_in_body = allow;
127    }
128
129    /// Use the checked endpoint to check for authorization for a resource.
130    ///
131    /// ## Panics
132    ///
133    /// When the registrar, authorizer, or issuer returned by the endpoint is suddenly
134    /// `None` when previously it was `Some(_)`.
135    pub async fn execute(&mut self, mut request: R) -> Result<R::Response, E::Error> {
136        let issued = access_token(
137            &mut self.endpoint,
138            &WrappedRequest::new(&mut request, self.allow_credentials_in_body),
139        )
140        .await;
141
142        let token = match issued {
143            Err(error) => return token_error(&mut self.endpoint.inner, &mut request, error),
144            Ok(token) => token,
145        };
146
147        let mut response = self.endpoint.inner.response(&mut request, Template::new_ok())?;
148        response
149            .body_json(&token.to_json())
150            .map_err(|err| self.endpoint.inner.web_error(err))?;
151        Ok(response)
152    }
153}
154
155fn token_error<E, R>(
156    endpoint: &mut E, request: &mut R, error: TokenError,
157) -> Result<R::Response, E::Error>
158where
159    E: Endpoint<R>,
160    R: WebRequest,
161{
162    Ok(match error {
163        TokenError::Invalid(mut json) => {
164            let mut response =
165                endpoint.response(request, Template::new_bad(Some(json.description())))?;
166            response.client_error().map_err(|err| endpoint.web_error(err))?;
167            response
168                .body_json(&json.to_json())
169                .map_err(|err| endpoint.web_error(err))?;
170            response
171        }
172        TokenError::Unauthorized(mut json, scheme) => {
173            let mut response = endpoint.response(
174                request,
175                Template::new_unauthorized(None, Some(json.description())),
176            )?;
177            response
178                .unauthorized(&scheme)
179                .map_err(|err| endpoint.web_error(err))?;
180            response
181                .body_json(&json.to_json())
182                .map_err(|err| endpoint.web_error(err))?;
183            response
184        }
185        TokenError::Primitive(_) => {
186            // FIXME: give the context for restoration.
187            return Err(endpoint.error(OAuthError::PrimitiveError));
188        }
189    })
190}
191
192impl<E, R> TokenEndpoint for WrappedToken<E, R>
193where
194    E: Endpoint<R>,
195    R: WebRequest,
196{
197    fn registrar(&self) -> &(dyn Registrar + Sync) {
198        self.inner.registrar().unwrap()
199    }
200
201    fn authorizer(&mut self) -> &mut (dyn Authorizer + Send) {
202        self.inner.authorizer_mut().unwrap()
203    }
204
205    fn issuer(&mut self) -> &mut (dyn Issuer + Send) {
206        self.inner.issuer_mut().unwrap()
207    }
208
209    fn extension(&mut self) -> &mut (dyn Extension + Send) {
210        self.inner
211            .extension()
212            .and_then(super::Extension::access_token)
213            .unwrap_or(&mut self.extension_fallback)
214    }
215}
216
217impl<R: WebRequest> WrappedRequest<R> {
218    pub fn new(request: &mut R, credentials: bool) -> Self {
219        Self::new_or_fail(request, credentials).unwrap_or_else(Self::from_err)
220    }
221
222    fn new_or_fail(request: &mut R, credentials: bool) -> Result<Self, FailParse<R::Error>> {
223        // If there is a header, it must parse correctly.
224        let authorization = match request.authheader() {
225            Err(err) => return Err(FailParse::Err(err)),
226            Ok(Some(header)) => Self::parse_header(header).map(Some)?,
227            Ok(None) => None,
228        };
229
230        Ok(WrappedRequest {
231            body: request.urlbody().map_err(FailParse::Err)?.into_owned(),
232            authorization,
233            error: None,
234            allow_credentials_in_body: credentials,
235        })
236    }
237
238    fn from_err(err: FailParse<R::Error>) -> Self {
239        WrappedRequest {
240            body: Default::default(),
241            authorization: None,
242            error: Some(err),
243            allow_credentials_in_body: false,
244        }
245    }
246
247    fn parse_header(header: Cow<str>) -> Result<Authorization, Invalid> {
248        let authorization = {
249            if !header.starts_with("Basic ") {
250                return Err(Invalid);
251            }
252
253            let combined = match STANDARD.decode(&header[6..]) {
254                Err(_) => return Err(Invalid),
255                Ok(vec) => vec,
256            };
257
258            let mut split = combined.splitn(2, |&c| c == b':');
259            let client_bin = match split.next() {
260                None => return Err(Invalid),
261                Some(client) => client,
262            };
263            let passwd = match split.next() {
264                None => return Err(Invalid),
265                Some([]) => None,
266                Some(passwd64) => Some(passwd64),
267            };
268
269            let client = match from_utf8(client_bin) {
270                Err(_) => return Err(Invalid),
271                Ok(client) => client,
272            };
273
274            Authorization(client.to_string(), passwd.map(|passwd| passwd.to_vec()))
275        };
276
277        Ok(authorization)
278    }
279}
280
281impl<R: WebRequest> TokenRequest for WrappedRequest<R> {
282    fn valid(&self) -> bool {
283        self.error.is_none()
284    }
285
286    fn code(&self) -> Option<Cow<str>> {
287        self.body.unique_value("code")
288    }
289
290    fn authorization(&self) -> TokenAuthorization {
291        match &self.authorization {
292            None => TokenAuthorization::None,
293            Some(Authorization(username, None)) => TokenAuthorization::Username(username.into()),
294            Some(Authorization(username, Some(password))) => {
295                TokenAuthorization::UsernamePassword(username.into(), password.into())
296            }
297        }
298    }
299
300    fn client_id(&self) -> Option<Cow<str>> {
301        self.body.unique_value("client_id")
302    }
303
304    fn redirect_uri(&self) -> Option<Cow<str>> {
305        self.body.unique_value("redirect_uri")
306    }
307
308    fn grant_type(&self) -> Option<Cow<str>> {
309        self.body.unique_value("grant_type")
310    }
311
312    fn extension(&self, key: &str) -> Option<Cow<str>> {
313        self.body.unique_value(key)
314    }
315
316    fn allow_credentials_in_body(&self) -> bool {
317        self.allow_credentials_in_body
318    }
319}
320
321impl<E> From<Invalid> for FailParse<E> {
322    fn from(_: Invalid) -> Self {
323        FailParse::Invalid
324    }
325}
326
327#[cfg(test)]
328mod test {
329    use oxide_auth::frontends::simple::request::Request;
330    use super::*;
331
332    #[test]
333    fn test_client_id_only() {
334        let result = WrappedRequest::<Request>::parse_header("Basic Zm9vOg==".into());
335        assert!(result.is_ok());
336        let result = result.unwrap();
337        assert_eq!(result, Authorization("foo".into(), None));
338    }
339
340    #[test]
341    fn test_client_id_and_secret() {
342        let result = WrappedRequest::<Request>::parse_header("Basic Zm9vOmJhcg==".into());
343        assert!(result.is_ok());
344        let result = result.unwrap();
345        assert_eq!(result, Authorization("foo".into(), Some("bar".into())));
346    }
347}