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
use crate::Username;
use base64;
use std::str::FromStr;

// Helpers for parsing authorization methods
pub const BEARER_TOKEN_START: usize = 7;
pub const BASIC_TOKEN_START: usize = 6;

#[derive(Eq, PartialEq, Debug, Clone)]
pub struct Auth {
    username: Username,
    password: String,
}

impl FromStr for Auth {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.replace("%3A", ":"); // When the token is received over the wire, it is URL encoded. We must decode the semi-colons.
        if s.starts_with("Basic") {
            Auth::parse_basic(&s[BASIC_TOKEN_START..])
        } else if s.starts_with("Bearer") {
            Auth::parse_bearer(&s[BEARER_TOKEN_START..])
        } else {
            Auth::parse_text(&s)
        }
    }
}

type AuthResult = Result<Auth, String>;

impl Auth {
    pub fn new(username: &str, password: &str) -> AuthResult {
        Ok(Auth {
            username: Username::from_str(username)?,
            password: password.to_owned(),
        })
    }

    pub fn to_bearer(&self) -> String {
        format!("Bearer {}:{}", self.username, self.password)
    }

    pub fn username(&self) -> &Username {
        &self.username
    }

    pub fn password(&self) -> &str {
        &self.password
    }

    fn parse_basic(s: &str) -> AuthResult {
        let decoded =
            base64::decode(&s).map_err(|_| "could not decode token from base64".to_owned())?;
        let text = std::str::from_utf8(&decoded)
            .map_err(|_| "could not decode token from utf8".to_owned())?;
        Auth::parse_text(text)
    }

    // Currently, we use bearer tokens in a non-standard way, where they each have a
    // username and a password in them. In the future, we will deprecate
    // the non-standard Bearer auth for accounts.
    fn parse_bearer(s: &str) -> AuthResult {
        Auth::parse_text(s)
    }

    fn parse_text(text: &str) -> AuthResult {
        let parts = &mut text.split(':');
        let username = match parts.next() {
            Some(part) => Username::from_str(part)?,
            None => return Err("no username found when parsing auth token".to_owned()),
        };
        let password = match parts.next() {
            Some(part) => part.to_owned(),
            None => return Err("no password found when parsing auth token".to_owned()),
        };
        Ok(Auth { username, password })
    }
}

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

    #[test]
    fn fails_correctly() {
        assert_eq!(
            Auth::from_str("Bearer a").unwrap_err(),
            "invalid username format",
        );
        assert_eq!(
            Auth::from_str("Bearer interledger").unwrap_err(),
            "no password found when parsing auth token",
        );
        assert_eq!(
            Auth::from_str("Basic aDFFDJFDJHFDJHF6cnVzdA==").unwrap_err(),
            "could not decode token from utf8"
        );
        assert_eq!(
            Auth::from_str("Basic 0xasdffsaasdf").unwrap_err(),
            "could not decode token from base64"
        );
    }

    #[test]
    fn parses_correctly() {
        assert_eq!(
            Auth::from_str("Basic aW50ZXJsZWRnZXI6cnVzdA==").unwrap(),
            Auth::new("interledger", "rust").unwrap()
        );

        assert_eq!(
            Auth::from_str("Bearer interledger%3Arust").unwrap(),
            Auth::new("interledger", "rust").unwrap()
        );

        assert_eq!(
            Auth::from_str("Bearer interledger:rust").unwrap(),
            Auth::new("interledger", "rust").unwrap()
        );

        assert!(Auth::from_str("SomethingElse asdf").is_err());
        assert!(Auth::from_str("Basic asdf").is_err());
        assert!(Auth::from_str("Bearer asdf").is_err());

        assert_eq!(
            Auth::new("interledger", "rust").unwrap().to_bearer(),
            "Bearer interledger:rust"
        );
    }
}