1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use std::str;
use std::fmt;
use std::default::Default;

use bytes::{BufMut, Bytes, BytesMut};
use actix_web::http::StatusCode;
use actix_web::http::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes};

use super::Challenge;

/// Bearer authorization error types, described in [RFC 6750](https://tools.ietf.org/html/rfc6750#section-3.1)
#[derive(Debug, Copy, Clone)]
pub enum Error {
    /// The request is missing a required parameter, includes an unsupported parameter
    /// or parameter value, repeats the same parameter, uses more than one method
    /// for including an access token, or is otherwise malformed.
    InvalidRequest,

    /// The access token provided is expired, revoked, malformed, or invalid for other reasons.
    InvalidToken,

    /// The request requires higher privileges than provided by the access token.
    InsufficientScope,
}

impl Error {
    pub fn status_code(&self) -> StatusCode {
        match *self {
            Error::InvalidRequest => StatusCode::BAD_REQUEST,
            Error::InvalidToken => StatusCode::UNAUTHORIZED,
            Error::InsufficientScope => StatusCode::FORBIDDEN,
        }
    }

    fn as_str(&self) -> &'static str {
        match *self {
            Error::InvalidRequest => "invalid_request",
            Error::InvalidToken => "invalid_token",
            Error::InsufficientScope => "insufficient_scope",
        }
    }
}

/// Challenge for `WWW-Authenticate` header with HTTP Bearer auth scheme,
/// described in [RFC 6750](https://tools.ietf.org/html/rfc6750#section-3)
#[derive(Debug, Clone)]
pub struct Bearer {
    pub scope: Option<String>,
    pub realm: Option<String>,
    pub error: Option<Error>,
    pub error_description: Option<String>,
    /// It is up to implementor to provide correct absolute URI
    pub error_uri: Option<String>,
}

impl Challenge for Bearer {
    fn to_bytes(&self) -> Bytes {
        let desc_uri_required =
            self.error_description.as_ref().map_or(0, |desc| desc.len() + 20) +
            self.error_uri.as_ref().map_or(0, |url| url.len() + 12);
        let capacity = 6 +
            self.realm.as_ref().map_or(0, |realm| realm.len() + 9) +
            self.scope.as_ref().map_or(0, |scope| scope.len() + 9) +
            desc_uri_required;
        let mut buffer = BytesMut::with_capacity(capacity);
        buffer.put("Bearer");

        if let Some(ref realm) = self.realm {
            buffer.put(" realm=\"");
            buffer.put(realm);
            buffer.put("\"");
        }

        if let Some(ref scope) = self.scope {
            buffer.put(" scope=\"");
            buffer.put(scope);
            buffer.put("\"");
        }

        if let Some(ref error) = self.error {
            let error_repr = error.as_str();
            let remaining = buffer.remaining_mut();
            let required = desc_uri_required + error_repr.len() + 9; // 9 is for `" error=\"\""`
            if remaining < required {
                buffer.reserve(required);
            }
            buffer.put(" error=\"");
            buffer.put(error_repr);
            buffer.put("\"")
        }

        if let Some(ref error_description) = self.error_description {
            buffer.put(" error_description=\"");
            buffer.put(error_description);
            buffer.put("\"");
        }

        if let Some(ref error_uri) = self.error_uri {
            buffer.put(" error_uri=\"");
            buffer.put(error_uri);
            buffer.put("\"");
        }

        buffer.freeze()
    }
}

impl Default for Bearer {
    fn default() -> Self {
        Bearer {
            scope: None,
            realm: None,
            error: None,
            error_description: None,
            error_uri: None,
        }
    }
}

impl fmt::Display for Bearer {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let bytes = self.to_bytes();
        let repr = str::from_utf8(&bytes)
            // Should not happen since challenges are crafted manually
            // from `&'static str`'s and Strings
            .map_err(|_| fmt::Error)?;

        f.write_str(repr)
    }
}

impl IntoHeaderValue for Bearer {
    type Error = InvalidHeaderValueBytes;

    fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
        HeaderValue::from_shared(self.to_bytes())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn to_bytes() {
        let b = Bearer {
            scope: None,
            realm: None,
            error: Some(Error::InvalidToken),
            error_description: Some(String::from("Subject 8740827c-2e0a-447b-9716-d73042e4039d not found")),
            error_uri: None,
        };
        assert_eq!("Bearer error=\"invalid_token\" error_description=\"Subject 8740827c-2e0a-447b-9716-d73042e4039d not found\"",
            format!("{}", b));
    }
}