0.2.0 - now on Rust 2018, uses Failure, adds support for Yahoo and Microsoft accounts
This commit is contained in:
parent
6edce8f750
commit
428a00170f
|
@ -95,7 +95,8 @@ impl Provider for Discovered {
|
|||
/// or an Insecure if the Url isn't https.
|
||||
pub fn discover(client: &Client, issuer: Url) -> Result<Config, Error> {
|
||||
secure(&issuer)?;
|
||||
let url = issuer.join("/.well-known/openid-configuration")?;
|
||||
let url = issuer.join(".well-known/openid-configuration")?;
|
||||
println!("Urls: {} {}", issuer, url);
|
||||
let mut resp = client.get(url).send()?;
|
||||
resp.json().map_err(Error::from)
|
||||
}
|
||||
|
|
253
src/error.rs
253
src/error.rs
|
@ -1,11 +1,10 @@
|
|||
pub use biscuit::errors::Error as Jose;
|
||||
pub use serde_json::Error as Json;
|
||||
pub use inth_oauth2::ClientError as Oauth;
|
||||
pub use reqwest::Error as Http;
|
||||
pub use reqwest::UrlError as Url;
|
||||
pub use serde_json::Error as Json;
|
||||
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
use std::error::Error as ErrorTrait;
|
||||
use failure::Fail;
|
||||
|
||||
macro_rules! from {
|
||||
($to:ident, $from:ident) => {
|
||||
|
@ -17,17 +16,27 @@ macro_rules! from {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
Jose(Jose),
|
||||
Json(Json),
|
||||
Oauth(Oauth),
|
||||
Http(Http),
|
||||
Url(Url),
|
||||
Decode(Decode),
|
||||
Validation(Validation),
|
||||
Userinfo(Userinfo),
|
||||
#[fail(display = "{}", _0)]
|
||||
Jose(#[fail(cause)] Jose),
|
||||
#[fail(display = "{}", _0)]
|
||||
Oauth(#[fail(cause)] Oauth),
|
||||
#[fail(display = "{}", _0)]
|
||||
Http(#[fail(cause)] Http),
|
||||
#[fail(display = "{}", _0)]
|
||||
Url(#[fail(cause)] Url),
|
||||
#[fail(display = "{}", _0)]
|
||||
Json(#[fail(cause)] Json),
|
||||
#[fail(display = "{}", _0)]
|
||||
Decode(#[fail(cause)] Decode),
|
||||
#[fail(display = "{}", _0)]
|
||||
Validation(#[fail(cause)] Validation),
|
||||
#[fail(display = "{}", _0)]
|
||||
Userinfo(#[fail(cause)] Userinfo),
|
||||
#[fail(display = "Url must use TLS: '{}'", _0)]
|
||||
Insecure(::reqwest::Url),
|
||||
#[fail(display = "Scope must contain Openid")]
|
||||
MissingOpenidScope,
|
||||
}
|
||||
|
||||
|
@ -40,228 +49,60 @@ from!(Error, Decode);
|
|||
from!(Error, Validation);
|
||||
from!(Error, Userinfo);
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
use self::Error::*;
|
||||
match *self {
|
||||
Jose(ref err) => Display::fmt(err, f),
|
||||
Json(ref err) => Display::fmt(err, f),
|
||||
Oauth(ref err) => Display::fmt(err, f),
|
||||
Http(ref err) => Display::fmt(err, f),
|
||||
Url(ref err) => Display::fmt(err, f),
|
||||
Decode(ref err) => Display::fmt(err, f),
|
||||
Validation(ref err) => Display::fmt(err, f),
|
||||
Userinfo(ref err) => Display::fmt(err, f),
|
||||
Insecure(ref url) => write!(f, "Url must use HTTPS: '{}'", url),
|
||||
MissingOpenidScope => write!(f, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorTrait for Error {
|
||||
fn description(&self) -> &str {
|
||||
use self::Error::*;
|
||||
match *self {
|
||||
Jose(ref err) => err.description(),
|
||||
Json(ref err) => err.description(),
|
||||
Oauth(ref err) => err.description(),
|
||||
Http(ref err) => err.description(),
|
||||
Url(ref err) => err.description(),
|
||||
Decode(ref err) => err.description(),
|
||||
Validation(ref err) => err.description(),
|
||||
Userinfo(ref err) => err.description(),
|
||||
Insecure(_) => "URL must use TLS",
|
||||
MissingOpenidScope => "Scope must contain Openid",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&ErrorTrait> {
|
||||
use self::Error::*;
|
||||
match *self {
|
||||
Jose(ref err) => Some(err),
|
||||
Json(ref err) => Some(err),
|
||||
Oauth(ref err) => Some(err),
|
||||
Http(ref err) => Some(err),
|
||||
Url(ref err) => Some(err),
|
||||
Decode(_) => None,
|
||||
Validation(_) => None,
|
||||
Userinfo(_) => None,
|
||||
Insecure(_) => None,
|
||||
MissingOpenidScope => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Decode {
|
||||
#[fail(display = "Token Missing a Key Id when the key set has multiple keys")]
|
||||
MissingKid,
|
||||
#[fail(display = "Token wants this key id not in the key set: {}", _0)]
|
||||
MissingKey(String),
|
||||
#[fail(display = "JWK Set is empty")]
|
||||
EmptySet,
|
||||
}
|
||||
|
||||
impl ErrorTrait for Decode {
|
||||
fn description(&self) -> &str {
|
||||
use self::Decode::*;
|
||||
match *self {
|
||||
MissingKid => "Missing Key Id",
|
||||
MissingKey(_) => "Token key not in key set",
|
||||
EmptySet => "JWK Set is empty",
|
||||
}
|
||||
}
|
||||
fn cause(&self) -> Option<&ErrorTrait> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Decode {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
use self::Decode::*;
|
||||
match *self {
|
||||
MissingKid => write!(f, "Token Missing a Key Id when the key set has multiple keys"),
|
||||
MissingKey(ref id) =>
|
||||
write!(f, "Token wants this key id not in the key set: {}", id),
|
||||
EmptySet => write!(f, "JWK Set is empty!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Validation {
|
||||
Mismatch(Mismatch),
|
||||
Missing(Missing),
|
||||
Expired(Expiry),
|
||||
#[fail(display = "{}", _0)]
|
||||
Mismatch(#[fail(cause)] Mismatch),
|
||||
#[fail(display = "{}", _0)]
|
||||
Missing(#[fail(cause)] Missing),
|
||||
#[fail(display = "{}", _0)]
|
||||
Expired(#[fail(cause)] Expiry),
|
||||
}
|
||||
|
||||
impl ErrorTrait for Validation {
|
||||
fn description(&self) -> &str {
|
||||
use self::Validation::*;
|
||||
match *self {
|
||||
Mismatch(ref mm) => {
|
||||
use self::Mismatch::*;
|
||||
match *mm {
|
||||
AuthorizedParty {..} => "Client id and token authorized party mismatch",
|
||||
Issuer {..} => "Config issuer and token issuer mismatch",
|
||||
Nonce {..} => "Supplied nonce and token nonce mismatch",
|
||||
}
|
||||
}
|
||||
Missing(ref mi) => {
|
||||
use self::Missing::*;
|
||||
match *mi {
|
||||
Audience => "Token missing Audience",
|
||||
AuthorizedParty => "Token missing AZP",
|
||||
AuthTime => "Token missing Auth Time",
|
||||
Nonce => "Token missing Nonce"
|
||||
}
|
||||
}
|
||||
Expired(ref ex) => {
|
||||
match *ex {
|
||||
Expiry::Expires(_) => "Token expired",
|
||||
Expiry::MaxAge(_) => "Token too old"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&ErrorTrait> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Validation {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
use self::Validation::*;
|
||||
match *self {
|
||||
Mismatch(ref err) => err.fmt(f),
|
||||
Missing(ref err) => err.fmt(f),
|
||||
Expired(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Mismatch {
|
||||
#[fail(display = "Client ID and Token authorized party mismatch: '{}', '{}'", expected, actual)]
|
||||
AuthorizedParty { expected: String, actual: String },
|
||||
#[fail(display = "Configured issuer and token issuer mismatch: '{}' '{}'", expected, actual)]
|
||||
Issuer { expected: String, actual: String },
|
||||
#[fail(display = "Given nonce does not match token nonce: '{}', '{}'", expected, actual)]
|
||||
Nonce { expected: String, actual: String },
|
||||
}
|
||||
|
||||
impl Display for Mismatch {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
use self::Mismatch::*;
|
||||
match *self {
|
||||
AuthorizedParty { ref expected, ref actual } =>
|
||||
write!(f, "Client ID and Token authorized party mismatch: '{}', '{}'", expected, actual),
|
||||
Issuer { ref expected, ref actual } =>
|
||||
write!(f, "Configured issuer and token issuer mismatch: '{}' '{}'", expected, actual),
|
||||
Nonce { ref expected, ref actual } =>
|
||||
write!(f, "Given nonce does not match token nonce: '{}', '{}'", expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Missing {
|
||||
#[fail(display = "Token missing Audience")]
|
||||
Audience,
|
||||
#[fail(display = "Token missing AZP")]
|
||||
AuthorizedParty,
|
||||
#[fail(display = "Token missing Auth Time")]
|
||||
AuthTime,
|
||||
#[fail(display = "Token missing Nonce")]
|
||||
Nonce,
|
||||
}
|
||||
|
||||
impl Display for Missing {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
use self::Missing::*;
|
||||
match *self {
|
||||
Audience => write!(f, "Token missing Audience"),
|
||||
AuthorizedParty => write!(f, "Token missing AZP"),
|
||||
AuthTime => write!(f, "Token missing Auth Time"),
|
||||
Nonce => write!(f, "Token missing Nonce")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Expiry {
|
||||
#[fail(display = "Token expired at: {}", _0)]
|
||||
Expires(::chrono::naive::NaiveDateTime),
|
||||
#[fail(display = "Token is too old: {}", _0)]
|
||||
MaxAge(::chrono::Duration)
|
||||
}
|
||||
|
||||
impl Display for Expiry {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
use self::Expiry::*;
|
||||
match *self {
|
||||
Expires(time) => write!(f, "Token expired at: {}", time),
|
||||
MaxAge(age) => write!(f, "Token is too old: {}", age)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Userinfo {
|
||||
#[fail(display = "Config has no userinfo url")]
|
||||
NoUrl,
|
||||
#[fail(display = "Token and Userinfo Subjects mismatch: '{}', '{}'", expected, actual)]
|
||||
MismatchSubject { expected: String, actual: String },
|
||||
}
|
||||
|
||||
impl ErrorTrait for Userinfo {
|
||||
fn description(&self) -> &str {
|
||||
use self::Userinfo::*;
|
||||
match *self {
|
||||
NoUrl => "No url",
|
||||
MismatchSubject { .. } => "Mismatch subject"
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&ErrorTrait> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Userinfo {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
use self::Userinfo::*;
|
||||
match *self {
|
||||
NoUrl => write!(f, "Config has no userinfo url"),
|
||||
MismatchSubject { ref expected, ref actual } =>
|
||||
write!(f, "Token and Userinfo Subjects mismatch: '{}', '{}'", expected, actual),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,27 @@
|
|||
use reqwest::Url;
|
||||
|
||||
const STATIC_URL_ERR_MSG: &str = "Static urls should always work!";
|
||||
const STATIC_URL_ERR: &str = "Static urls should always work!";
|
||||
|
||||
// TODO these should all be const, or even better, sttic Urls...a
|
||||
|
||||
pub fn google() -> Url {
|
||||
Url::parse("https://accounts.google.com").expect(STATIC_URL_ERR_MSG)
|
||||
Url::parse("https://accounts.google.com").expect(STATIC_URL_ERR)
|
||||
}
|
||||
|
||||
pub fn microsoft() -> Url {
|
||||
Url::parse("https://login.microsoftonline.com/common/v2.0").expect(STATIC_URL_ERR_MSG)
|
||||
Url::parse("https://login.microsoftonline.com/common/v2.0/").expect(STATIC_URL_ERR)
|
||||
}
|
||||
|
||||
pub fn paypal() -> Url {
|
||||
Url::parse("https://www.paypalobjects.com/").expect(STATIC_URL_ERR_MSG)
|
||||
Url::parse("https://www.paypalobjects.com").expect(STATIC_URL_ERR)
|
||||
}
|
||||
|
||||
pub fn salesforce() -> Url {
|
||||
Url::parse("https://login.salesforce.com").expect(STATIC_URL_ERR_MSG)
|
||||
Url::parse("https://login.salesforce.com").expect(STATIC_URL_ERR)
|
||||
}
|
||||
|
||||
pub fn yahoo() -> Url {
|
||||
Url::parse("https://login.yahoo.com").expect(STATIC_URL_ERR_MSG)
|
||||
Url::parse("https://login.yahoo.com").expect(STATIC_URL_ERR)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -29,33 +29,19 @@ mod tests {
|
|||
use reqwest::Client;
|
||||
use crate::discovery::discover;
|
||||
|
||||
#[test]
|
||||
fn google_disco() {
|
||||
let client = Client::new();
|
||||
discover(&client, super::google()).unwrap();
|
||||
macro_rules! test {
|
||||
($issuer:ident) => {
|
||||
#[test]
|
||||
fn $issuer() {
|
||||
let client = Client::new();
|
||||
discover(&client, super::$issuer()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn microsoft_disco() {
|
||||
let client = Client::new();
|
||||
let res = discover(&client, super::microsoft());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paypal_disco() {
|
||||
let client = Client::new();
|
||||
discover(&client, super::paypal()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn salesforce_disco() {
|
||||
let client = Client::new();
|
||||
discover(&client, super::salesforce()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yahoo_disco() {
|
||||
let client = Client::new();
|
||||
discover(&client, super::yahoo()).unwrap();
|
||||
}
|
||||
test!(google);
|
||||
test!(microsoft);
|
||||
test!(paypal);
|
||||
test!(salesforce);
|
||||
test!(yahoo);
|
||||
}
|
||||
|
|
47
src/lib.rs
47
src/lib.rs
|
@ -503,29 +503,28 @@ pub struct Address {
|
|||
#[serde(default)] pub country: Option<String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn google() {
|
||||
let id = "test".to_string();
|
||||
let secret = "a secret to everybody".to_string();
|
||||
let redirect = Url::parse("https://example.com/re").unwrap();
|
||||
let client = Client::discover(id, secret, redirect, issuer::google()).unwrap();
|
||||
client.auth_url(&Default::default());
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use reqwest::Url;
|
||||
use crate::Client;
|
||||
use crate::issuer;
|
||||
|
||||
#[test]
|
||||
fn paypal() {
|
||||
let id = "test".to_string();
|
||||
let secret = "a secret to everybody".to_string();
|
||||
let redirect = Url::parse("https://example.com/re").unwrap();
|
||||
let client = Client::discover(id, secret, redirect, issuer::paypal()).unwrap();
|
||||
client.auth_url(&Default::default());
|
||||
}
|
||||
macro_rules! test {
|
||||
($issuer:ident) => {
|
||||
#[test]
|
||||
fn $issuer() {
|
||||
let id = "test".to_string();
|
||||
let secret = "a secret to everybody".to_string();
|
||||
let redirect = Url::parse("https://example.com/re").unwrap();
|
||||
let client = Client::discover(id, secret, redirect, issuer::$issuer()).unwrap();
|
||||
client.auth_url(&Default::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn salesforce() {
|
||||
let id = "test".to_string();
|
||||
let secret = "a secret to everybody".to_string();
|
||||
let redirect = Url::parse("https://example.com/re").unwrap();
|
||||
let client = Client::discover(id, secret, redirect, issuer::salesforce()).unwrap();
|
||||
client.auth_url(&Default::default());
|
||||
}
|
||||
test!(google);
|
||||
test!(microsoft);
|
||||
test!(paypal);
|
||||
test!(salesforce);
|
||||
test!(yahoo);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue