conjure_object/bearer_token/
mod.rs

1// Copyright 2018 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The Conjure `bearertoken` type.
16use serde::de::{self, Deserialize, Deserializer, Unexpected};
17use serde::ser::{Serialize, Serializer};
18use std::borrow::Borrow;
19use std::error::Error;
20use std::fmt;
21use std::str::FromStr;
22
23#[cfg(test)]
24mod test;
25
26// A lookup table mapping valid characters to themselves and invalid characters to 0. We don't actually care what
27// nonzero value valid characters map to, but it's easier to read this way. There's a test making sure that the mapping
28// is consistent.
29#[rustfmt::skip]
30static VALID_CHARS: [u8; 256] = [
31    // 0     1     2     3     4     5     6     7     8     9
32       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, //   x
33       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, //  1x
34       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, //  2x
35       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, //  3x
36       0,    0,    0, b'+',    0, b'-', b'.', b'/', b'0', b'1', //  4x
37    b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9',    0,    0, //  5x
38       0,    0,    0,    0,    0, b'A', b'B', b'C', b'D', b'E', //  6x
39    b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', //  7x
40    b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', //  8x
41    b'Z',    0,    0,    0,    0, b'_',    0, b'a', b'b', b'c', //  9x
42    b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x
43    b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x
44    b'x', b'y', b'z',    0,    0,    0, b'~',    0,    0,    0, // 12x
45       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 13x
46       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 14x
47       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 15x
48       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 16x
49       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 17x
50       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 18x
51       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 19x
52       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 20x
53       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 21x
54       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 22x
55       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 23x
56       0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 24x
57       0,    0,    0,    0,    0,    0,                         // 25x
58];
59
60/// An authentication bearer token.
61///
62/// Bearer tokens are strings which match the regular expression `^[A-Za-z0-9\-\._~\+/]+=*$`.
63#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct BearerToken(String);
65
66impl BearerToken {
67    /// Creates a bearer token from a string, validating that it is in the correct format.
68    ///
69    /// This function behaves identically to `BearerToken`'s `FromStr` implementation.
70    #[inline]
71    pub fn new(s: &str) -> Result<BearerToken, ParseError> {
72        s.parse()
73    }
74
75    /// Returns the string representation of the bearer token.
76    #[inline]
77    pub fn as_str(&self) -> &str {
78        &self.0
79    }
80
81    /// Consumes the bearer token, returning its owned string representation.
82    #[inline]
83    pub fn into_string(self) -> String {
84        self.0
85    }
86}
87
88impl AsRef<str> for BearerToken {
89    #[inline]
90    fn as_ref(&self) -> &str {
91        &self.0
92    }
93}
94
95impl Borrow<str> for BearerToken {
96    #[inline]
97    fn borrow(&self) -> &str {
98        &self.0
99    }
100}
101
102impl fmt::Debug for BearerToken {
103    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
104        fmt.debug_tuple("BearerToken").field(&"REDACTED").finish()
105    }
106}
107
108impl FromStr for BearerToken {
109    type Err = ParseError;
110
111    fn from_str(s: &str) -> Result<BearerToken, ParseError> {
112        if !is_valid(s) {
113            return Err(ParseError(()));
114        }
115
116        Ok(BearerToken(s.to_string()))
117    }
118}
119
120impl Serialize for BearerToken {
121    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
122    where
123        S: Serializer,
124    {
125        self.0.serialize(s)
126    }
127}
128
129impl<'de> Deserialize<'de> for BearerToken {
130    fn deserialize<D>(d: D) -> Result<BearerToken, D::Error>
131    where
132        D: Deserializer<'de>,
133    {
134        let s = String::deserialize(d)?;
135
136        if is_valid(&s) {
137            Ok(BearerToken(s))
138        } else {
139            Err(de::Error::invalid_value(
140                Unexpected::Str(&s),
141                &"a bearer token",
142            ))
143        }
144    }
145}
146
147fn is_valid(s: &str) -> bool {
148    let stripped = s.trim_end_matches('=');
149
150    if stripped.is_empty() || !stripped.as_bytes().iter().cloned().all(valid_char) {
151        return false;
152    }
153
154    true
155}
156
157// implementing this via a lookup table rather than a match is ~25% faster.
158fn valid_char(b: u8) -> bool {
159    VALID_CHARS[b as usize] != 0
160}
161
162/// An error parsing a string into a `BearerToken`.
163#[derive(Debug)]
164pub struct ParseError(());
165
166impl fmt::Display for ParseError {
167    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
168        fmt.write_str("invalid bearer token")
169    }
170}
171
172impl Error for ParseError {}