oxide_auth/code_grant/
resource.rs

1//! Provides the handling for Resource Requests.
2use std::{fmt, mem};
3use std::borrow::Cow;
4
5use chrono::Utc;
6
7use crate::primitives::issuer::Issuer;
8use crate::primitives::grant::Grant;
9use crate::primitives::scope::Scope;
10
11/// Gives additional information about the reason for an access failure.
12///
13/// According to [rfc6750], this should not be returned if the client has not provided any
14/// authentication information.
15///
16/// [rfc6750]: https://tools.ietf.org/html/rfc6750#section-3.1
17#[derive(Clone, Debug)]
18pub struct AccessFailure {
19    /// The standard error code representation.
20    pub code: Option<ErrorCode>,
21}
22
23/// Indicates the reason for access failure.
24#[derive(Debug, Clone, Copy)]
25pub enum ErrorCode {
26    /// The request did not have enough authorization data or was otherwise malformed.
27    InvalidRequest,
28
29    /// The provided authorization did not grant sufficient priviledges.
30    InsufficientScope,
31
32    /// The token is expired, revoked, malformed or otherwise does not meet expectations.
33    InvalidToken,
34}
35
36/// Additional information provided for the WWW-Authenticate header.
37#[derive(Clone, Debug)]
38pub struct Authenticate {
39    /// Information about which realm the credentials correspond to.
40    pub realm: Option<String>,
41
42    /// The required scope to access the resource.
43    pub scope: Option<Scope>,
44}
45
46/// An error signalling the resource access was not permitted.
47#[derive(Clone, Debug)]
48pub enum Error {
49    /// The client tried to access a resource but was not able to.
50    AccessDenied {
51        /// A specific cause for denying access.
52        failure: AccessFailure,
53
54        /// Information for the `Authenticate` header in the error response.
55        authenticate: Authenticate,
56    },
57
58    /// The client did not provide any bearer authentication.
59    NoAuthentication {
60        /// Information for the `Authenticate` header in the error response.
61        authenticate: Authenticate,
62    },
63
64    /// The request itself was malformed.
65    InvalidRequest {
66        /// Information for the `Authenticate` header in the error response.
67        authenticate: Authenticate,
68    },
69
70    /// Some part of the endpoint failed, defer to endpoint for handling.
71    PrimitiveError,
72}
73
74const BEARER_START: &str = "Bearer ";
75
76type Result<T> = std::result::Result<T, Error>;
77
78/// Required request methods for deciding on the rights to access a protected resource.
79pub trait Request {
80    /// Received request might not be encoded correctly. This method gives implementors the chance
81    /// to signal that a request was received but its encoding was generally malformed. If this is
82    /// the case, then no other attribute will be queried. This method exists mainly to make
83    /// frontends straightforward by not having them handle special cases for malformed requests.
84    fn valid(&self) -> bool;
85
86    /// The authorization used in the request.
87    ///
88    /// Expects the complete `Authorization` HTTP-header, including the qualification as `Bearer`.
89    /// In case the client included multiple forms of authorization, this method MUST return None
90    /// and the request SHOULD be marked as invalid.
91    fn token(&self) -> Option<Cow<str>>;
92}
93
94/// Required functionality to respond to resource requests.
95///
96/// Each method will only be invoked exactly once when processing a correct and authorized request,
97/// and potentially less than once when the request is faulty.  These methods should be implemented
98/// by internally using `primitives`, as it is implemented in the `frontend` module.
99pub trait Endpoint {
100    /// The list of possible scopes required by the resource endpoint.
101    fn scopes(&mut self) -> &[Scope];
102
103    /// Issuer which provides the tokens used for authorization by the client.
104    fn issuer(&mut self) -> &dyn Issuer;
105}
106
107/// The result will indicate whether the resource access should be allowed or not.
108pub struct Resource {
109    state: ResourceState,
110}
111
112enum ResourceState {
113    /// The initial state.
114    New,
115    /// State after request has been validated.
116    Internalized { token: String },
117    /// State after scopes have been determined.
118    Recovering { token: String, scopes: Vec<Scope> },
119    /// State after an error occurred.
120    Err(Error),
121}
122
123/// An input injected by the executor into the state machine.
124#[derive(Clone)]
125pub enum Input<'req> {
126    /// Provide the queried (bearer) token.
127    Recovered(Option<Grant>),
128    /// Determine the scopes of requested resource.
129    Scopes(&'req [Scope]),
130    /// Provides simply the original request.
131    Request {
132        /// The request
133        request: &'req dyn Request,
134    },
135    /// Advance without input as far as possible, or just retrieve the output again.
136    None,
137}
138
139/// A request by the statemachine to the executor.
140///
141/// Each variant is fulfilled by certain variants of the next inputs as an argument to
142/// `Refresh::next`. The output of most states is simply repeated if `Input::None` is provided
143/// instead but note that the successful bearer token response is **not** repeated.
144///
145/// This borrows data from the underlying state machine, so you need to drop it before advancing it
146/// with newly provided input.
147#[derive(Clone, Debug)]
148pub enum Output<'machine> {
149    /// The state requires some information from the request to advance.
150    GetRequest,
151    /// The issuer should try to recover the grant of a bearer token.
152    ///
153    /// Fulfilled by `Input::Recovered`.
154    Recover {
155        /// The token supplied by the client.
156        token: &'machine str,
157    },
158    /// The executor must determine the scopes applying to the resource.
159    ///
160    /// Fulfilled by `Input::Scopes`.
161    DetermineScopes,
162    /// The state machine finished and access was allowed.
163    ///
164    /// Returns the grant with which access was granted in case a detailed inspection or logging is
165    /// required.
166    ///
167    /// This output **can not** be requested repeatedly, any future `Input` will yield a primitive
168    /// error instead.
169    Ok(Box<Grant>),
170    /// The state machine finished in an error.
171    ///
172    /// The error will be repeated on *any* following input.
173    Err(Error),
174}
175
176impl Resource {
177    /// Create a Resource state machine at `ResourceState::New` state
178    pub fn new() -> Self {
179        Resource {
180            state: ResourceState::New,
181        }
182    }
183
184    /// Progress the state machine to next step, taking in needed `Input` parameters
185    pub fn advance(&mut self, input: Input) -> Output<'_> {
186        self.state = match (self.take(), input) {
187            (any, Input::None) => any,
188            (ResourceState::New, Input::Request { request }) => {
189                validate(request).unwrap_or_else(ResourceState::Err)
190            }
191            (ResourceState::Internalized { token }, Input::Scopes(scopes)) => get_scopes(token, scopes),
192            (ResourceState::Recovering { token: _, scopes }, Input::Recovered(grant)) => {
193                match recovered(grant, scopes) {
194                    Ok(grant) => return Output::Ok(Box::new(grant)),
195                    Err(err) => ResourceState::Err(err),
196                }
197            }
198            _ => return Output::Err(Error::PrimitiveError),
199        };
200
201        self.output()
202    }
203
204    fn output(&self) -> Output<'_> {
205        match &self.state {
206            ResourceState::New => Output::GetRequest,
207            ResourceState::Internalized { .. } => Output::DetermineScopes,
208            ResourceState::Recovering { token, .. } => Output::Recover { token },
209            ResourceState::Err(error) => Output::Err(error.clone()),
210        }
211    }
212
213    fn take(&mut self) -> ResourceState {
214        mem::replace(&mut self.state, ResourceState::Err(Error::PrimitiveError))
215    }
216}
217
218/// Do needed verification before granting access to the resource
219pub fn protect(handler: &mut dyn Endpoint, req: &dyn Request) -> Result<Grant> {
220    enum Requested {
221        None,
222        Request,
223        Scopes,
224        Grant(String),
225    }
226
227    let mut resource = Resource::new();
228    let mut requested = Requested::None;
229    loop {
230        let input = match requested {
231            Requested::None => Input::None,
232            Requested::Request => Input::Request { request: req },
233            Requested::Scopes => Input::Scopes(handler.scopes()),
234            Requested::Grant(token) => {
235                let grant = handler
236                    .issuer()
237                    .recover_token(&token)
238                    .map_err(|_| Error::PrimitiveError)?;
239                Input::Recovered(grant)
240            }
241        };
242
243        requested = match resource.advance(input) {
244            Output::Err(error) => return Err(error),
245            Output::Ok(grant) => return Ok(*grant),
246            Output::GetRequest => Requested::Request,
247            Output::DetermineScopes => Requested::Scopes,
248            Output::Recover { token } => Requested::Grant(token.to_string()),
249        };
250    }
251}
252
253fn validate(request: &'_ dyn Request) -> Result<ResourceState> {
254    if !request.valid() {
255        return Err(Error::InvalidRequest {
256            authenticate: Authenticate::empty(),
257        });
258    }
259
260    let client_token = match request.token() {
261        Some(token) => token,
262        None => {
263            return Err(Error::NoAuthentication {
264                authenticate: Authenticate::empty(),
265            })
266        }
267    };
268
269    if !client_token
270        .to_uppercase()
271        .starts_with(&BEARER_START.to_uppercase())
272    {
273        return Err(Error::InvalidRequest {
274            authenticate: Authenticate::empty(),
275        });
276    }
277
278    let token = match client_token {
279        Cow::Borrowed(token) => token[BEARER_START.len()..].to_string(),
280        Cow::Owned(mut token) => token.split_off(BEARER_START.len()),
281    };
282
283    Ok(ResourceState::Internalized { token })
284}
285
286fn get_scopes(token: String, scopes: &'_ [Scope]) -> ResourceState {
287    ResourceState::Recovering {
288        token,
289        scopes: scopes.to_owned(),
290    }
291}
292
293fn recovered(grant: Option<Grant>, mut scopes: Vec<Scope>) -> Result<Grant> {
294    let grant = match grant {
295        Some(grant) => grant,
296        None => {
297            return Err(Error::AccessDenied {
298                failure: AccessFailure {
299                    code: Some(ErrorCode::InvalidRequest),
300                },
301                authenticate: Authenticate {
302                    realm: None,
303                    // TODO. Don't drop the other scopes?
304                    scope: scopes.drain(..).next(),
305                },
306            });
307        }
308    };
309
310    if grant.until < Utc::now() {
311        return Err(Error::AccessDenied {
312            failure: AccessFailure {
313                code: Some(ErrorCode::InvalidToken),
314            },
315            authenticate: Authenticate::empty(),
316        });
317    }
318
319    let allowing = scopes
320        .iter()
321        .find(|resource_scope| resource_scope.allow_access(&grant.scope));
322
323    if allowing.is_none() {
324        return Err(Error::AccessDenied {
325            failure: AccessFailure {
326                code: Some(ErrorCode::InsufficientScope),
327            },
328            authenticate: Authenticate {
329                realm: None,
330                scope: scopes.drain(..).next(),
331            },
332        });
333    }
334
335    // TODO: should we return the allowing scope?
336    Ok(grant)
337}
338
339impl ErrorCode {
340    fn description(self) -> &'static str {
341        match self {
342            ErrorCode::InvalidRequest => "invalid_request",
343            ErrorCode::InsufficientScope => "insufficient_scope",
344            ErrorCode::InvalidToken => "invalid_token",
345        }
346    }
347}
348
349struct BearerHeader {
350    content: String,
351    first_option: bool,
352}
353
354impl BearerHeader {
355    fn new() -> Self {
356        BearerHeader {
357            content: "Bearer".to_string(),
358            first_option: true,
359        }
360    }
361
362    fn add_option(&mut self, args: fmt::Arguments) {
363        if self.first_option {
364            self.content.push(' ');
365        } else {
366            self.content.push(',');
367        }
368        fmt::write(&mut self.content, args).unwrap();
369    }
370
371    fn add_kvp(&mut self, key: &'static str, value: Option<impl fmt::Display>) {
372        if let Some(value) = value {
373            self.add_option(format_args!("{}=\"{}\"", key, value));
374        }
375    }
376
377    fn finalize(self) -> String {
378        self.content
379    }
380}
381
382impl Authenticate {
383    fn empty() -> Self {
384        Authenticate {
385            realm: None,
386            scope: None,
387        }
388    }
389
390    fn extend_header(self, header: &mut BearerHeader) {
391        header.add_kvp("realm", self.realm);
392        header.add_kvp("scope", self.scope);
393    }
394}
395
396impl AccessFailure {
397    fn extend_header(self, header: &mut BearerHeader) {
398        header.add_kvp("error", self.code.map(ErrorCode::description));
399    }
400}
401
402impl Error {
403    /// Convert the guard error into the content used in an WWW-Authenticate header.
404    pub fn www_authenticate(self) -> String {
405        let mut header = BearerHeader::new();
406        match self {
407            Error::AccessDenied {
408                failure,
409                authenticate,
410            } => {
411                failure.extend_header(&mut header);
412                authenticate.extend_header(&mut header);
413            }
414            Error::NoAuthentication { authenticate } => {
415                authenticate.extend_header(&mut header);
416            }
417            Error::InvalidRequest { authenticate } => {
418                authenticate.extend_header(&mut header);
419            }
420            Error::PrimitiveError => (),
421        }
422        header.finalize()
423    }
424}