Holy moley... all the Optional Json fields needed #[serde(default)] on them. Talk about boilerplate...

This commit is contained in:
Matthew Scheirer 2017-09-17 22:40:28 -04:00
parent 50de2dacb3
commit 3e71047f42
5 changed files with 120 additions and 107 deletions

View File

@ -4,7 +4,6 @@ use inth_oauth2::provider::Provider;
use inth_oauth2::token::Expiring;
use reqwest::{Client, Url};
use url_serde;
use validator::Validate;
use error::Error;
use token::Token;
@ -17,104 +16,63 @@ pub(crate) fn secure(url: &Url) -> Result<(), Error> {
}
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
#[serde(with = "url_serde")]
pub issuer: Url,
#[serde(with = "url_serde")]
pub authorization_endpoint: Url,
#[serde(with = "url_serde")]
#[serde(with = "url_serde")] pub issuer: Url,
#[serde(with = "url_serde")] pub authorization_endpoint: Url,
// Only optional in the implicit flow
// TODO For now, we only support code flows.
pub token_endpoint: Url,
#[serde(with = "url_serde")]
pub userinfo_endpoint: Option<Url>,
#[serde(with = "url_serde")]
pub jwks_uri: Url,
#[serde(with = "url_serde")]
pub registration_endpoint: Option<Url>,
pub scopes_supported: Option<Vec<String>>,
#[serde(with = "url_serde")] pub token_endpoint: Url,
#[serde(default)] #[serde(with = "url_serde")] pub userinfo_endpoint: Option<Url>,
#[serde(with = "url_serde")] pub jwks_uri: Url,
#[serde(default)] #[serde(with = "url_serde")] pub registration_endpoint: Option<Url>,
#[serde(default)] pub scopes_supported: Option<Vec<String>>,
// There are only three valid response types, plus combinations of them, and none
// If we want to make these user friendly we want a struct to represent all 7 types
pub response_types_supported: Vec<String>,
// There are only two possible values here, query and fragment. Default is both.
pub response_modes_supported: Option<Vec<String>>,
#[serde(default)] pub response_modes_supported: Option<Vec<String>>,
// Must support at least authorization_code and implicit.
pub grant_types_supported: Option<Vec<String>>,
pub acr_values_supported: Option<Vec<String>>,
#[serde(default)] pub grant_types_supported: Option<Vec<String>>,
#[serde(default)] pub acr_values_supported: Option<Vec<String>>,
// pairwise and public are valid by spec, but servers can add more
pub subject_types_supported: Vec<String>,
// Must include at least RS256, none is only allowed with response types without id tokens
pub id_token_signing_alg_values_supported: Vec<String>,
pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
pub request_object_signing_alg_values_supported: Option<Vec<String>>,
pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
#[serde(default)] pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
#[serde(default)] pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
#[serde(default)] pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
#[serde(default)] pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
#[serde(default)] pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
#[serde(default)] pub request_object_signing_alg_values_supported: Option<Vec<String>>,
#[serde(default)] pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
#[serde(default)] pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
// Spec options are client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt
// If omitted, client_secret_basic is used
pub token_endpoint_auth_methods_supported: Option<Vec<String>>,
#[serde(default)] pub token_endpoint_auth_methods_supported: Option<Vec<String>>,
// Only wanted with jwt auth methods, should have RS256, none not allowed
pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
pub display_values_supported: Option<Vec<String>>,
#[serde(default)] pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
#[serde(default)] pub display_values_supported: Option<Vec<String>>,
// Valid options are normal, aggregated, and distributed. If omitted, only use normal
pub claim_types_supported: Option<Vec<String>>,
pub claims_supported: Option<Vec<Claim>>,
#[serde(with = "url_serde")]
pub service_documentation: Option<Url>,
pub claims_locales_supported: Option<Vec<String>>,
pub ui_locales_supported: Option<Vec<String>>,
// default false
pub claims_parameter_supported: Option<bool>,
// default false
pub request_parameter_supported: Option<bool>,
// default true
pub request_uri_parameter_supported: Option<bool>,
// default false
pub require_request_uri_registration: Option<bool>,
#[serde(with = "url_serde")]
pub op_policy_uri: Option<Url>,
#[serde(with = "url_serde")]
pub op_tos_uri: Option<Url>,
#[serde(default)] pub claim_types_supported: Option<Vec<String>>,
#[serde(default)] pub claims_supported: Option<Vec<String>>,
#[serde(default)] #[serde(with = "url_serde")] pub service_documentation: Option<Url>,
#[serde(default)] pub claims_locales_supported: Option<Vec<String>>,
#[serde(default)] pub ui_locales_supported: Option<Vec<String>>,
#[serde(default)] pub claims_parameter_supported: bool,
#[serde(default)] pub request_parameter_supported: bool,
#[serde(default = "tru")] pub request_uri_parameter_supported: bool,
#[serde(default)] pub require_request_uri_registration: bool,
#[serde(default)] #[serde(with = "url_serde")] pub op_policy_uri: Option<Url>,
#[serde(default)] #[serde(with = "url_serde")] pub op_tos_uri: Option<Url>,
// This is a NONSTANDARD extension Google uses that is a part of the Oauth discovery draft
pub code_challenge_methods_supported: Option<Vec<String>>,
#[serde(default)] pub code_challenge_methods_supported: Option<Vec<String>>,
}
#[derive(Deserialize, Serialize)]
pub enum Claim {
Name(String),
FamilyName(String),
GivenName(String),
MiddleName(String),
Nickname(String),
PreferredUsername(String),
Profile(
#[serde(with = "url_serde")]
Url
),
Picture(
#[serde(with = "url_serde")]
Url
),
Website(
#[serde(with = "url_serde")]
Url
),
Gender(String),
Birthdate(String),
Zoneinfo(String),
Locale(String),
UpdatedAt(u64),
Email(Email),
}
#[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Email {
#[validate(email)]
pub address: String,
// This seems really dumb...
fn tru() -> bool {
true
}
pub struct Discovered {
@ -137,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 mut resp = client.get(issuer)?.send()?;
let url = issuer.join("/.well-known/openid-configuration")?;
let mut resp = client.get(url)?.send()?;
resp.json().map_err(Error::from)
}
@ -148,3 +107,40 @@ pub fn jwks(client: &Client, url: Url) -> Result<JWKSet<Empty>, Error> {
let mut resp = client.get(url)?.send()?;
resp.json().map_err(Error::from)
}
#[test]
fn config_google() {
let cfg = r#"{ "issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://www.googleapis.com/oauth2/v4/token",
"userinfo_endpoint": "https://www.googleapis.com/oauth2/v3/userinfo",
"revocation_endpoint": "https://accounts.google.com/o/oauth2/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [ "code", "token", "id_token", "code token",
"code id_token", "token id_token",
"code token id_token", "none" ],
"subject_types_supported": [ "public" ],
"id_token_signing_alg_values_supported": [ "RS256" ],
"scopes_supported": [ "openid", "email", "profile" ],
"token_endpoint_auth_methods_supported": [ "client_secret_post",
"client_secret_basic" ],
"claims_supported": [ "aud", "email", "email_verified", "exp", "family_name",
"given_name", "iat", "iss", "locale", "name", "picture",
"sub" ],
"code_challenge_methods_supported": [ "plain", "S256" ]
}"#;
println!("{:?}", ::serde_json::from_str::<Config>(cfg).unwrap());
}
#[test]
fn config_minimum() {
let cfg = r#"{ "issuer": "https://example.com",
"authorization_endpoint": "https://example.com/auth",
"token_endpoint": "https://example.com/token",
"jwks_uri": "https://example.com/certs",
"response_types_supported": [ "code" ],
"subject_types_supported": [ "public" ],
"id_token_signing_alg_values_supported": [ "RS256" ]
}"#;
println!("{:?}", ::serde_json::from_str::<Config>(cfg).unwrap());
}

View File

@ -2,6 +2,7 @@ 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 Reqwest;
pub use reqwest::UrlError as Url;
use std::fmt::{Display, Formatter, Result};
use std::error::Error as ErrorTrait;
@ -22,6 +23,7 @@ pub enum Error {
Json(Json),
Oauth(Oauth),
Reqwest(Reqwest),
Url(Url),
Decode(Decode),
Validation(Validation),
Userinfo(Userinfo),
@ -33,6 +35,7 @@ from!(Error, Jose);
from!(Error, Json);
from!(Error, Oauth);
from!(Error, Reqwest);
from!(Error, Url);
from!(Error, Decode);
from!(Error, Validation);
from!(Error, Userinfo);
@ -45,6 +48,7 @@ impl Display for Error {
Json(ref err) => Display::fmt(err, f),
Oauth(ref err) => Display::fmt(err, f),
Reqwest(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),
@ -62,6 +66,7 @@ impl ErrorTrait for Error {
Json(ref err) => err.description(),
Oauth(ref err) => err.description(),
Reqwest(ref err) => err.description(),
Url(ref err) => err.description(),
Decode(ref err) => err.description(),
Validation(ref err) => err.description(),
Userinfo(ref err) => err.description(),
@ -77,9 +82,10 @@ impl ErrorTrait for Error {
Json(ref err) => Some(err),
Oauth(ref err) => Some(err),
Reqwest(ref err) => Some(err),
Decode(ref err) => None,
Validation(ref err) => None,
Userinfo(ref err) => None,
Url(ref err) => Some(err),
Decode(_) => None,
Validation(_) => None,
Userinfo(_) => None,
Insecure(_) => None,
MissingOpenidScope => None,
}
@ -95,9 +101,10 @@ pub enum Decode {
impl ErrorTrait for Decode {
fn description(&self) -> &str {
match self {
use Decode::*;
match *self {
MissingKid => "Missing Key Id",
&Decode::MissingKey(_) => "Token key not in key set",
MissingKey(_) => "Token key not in key set",
EmptySet => "JWK Set is empty",
}
}
@ -108,9 +115,10 @@ impl ErrorTrait for Decode {
impl Display for Decode {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
use Decode::*;
match *self {
MissingKid => write!(f, "Token Missing Key Id when key set has multiple keys"),
&Decode::MissingKey(ref id) =>
MissingKey(ref id) =>
write!(f, "Token wants this key id not in the key set: {}", id),
EmptySet => write!(f, "JWK Set is empty!")
}
@ -137,7 +145,8 @@ impl ErrorTrait for Validation {
}
}
Missing(ref mi) => {
match mi {
use Missing::*;
match *mi {
Audience => "Token missing Audience",
AuthorizedParty => "Token missing AZP",
AuthTime => "Token missing Auth Time",
@ -200,7 +209,8 @@ pub enum Missing {
impl Display for Missing {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
use Missing::*;
match *self {
Audience => write!(f, "Token missing Audience"),
AuthorizedParty => write!(f, "Token missing AZP"),
AuthTime => write!(f, "Token missing Auth Time"),

View File

@ -13,3 +13,9 @@ pub fn paypal() -> Url {
pub fn salesforce() -> Url {
Url::parse("http://login.salesforce.com").unwrap()
}
#[test]
fn google_disco() {
let client = ::reqwest::Client::new().unwrap();
let config = ::discovery::discover(&client, google()).unwrap();
}

View File

@ -425,7 +425,8 @@ pub enum Display {
impl Display {
fn as_str(&self) -> &'static str {
match self {
use Display::*;
match *self {
Page => "page",
Popup => "popup",
Touch => "touch",
@ -444,8 +445,9 @@ pub enum Prompt {
impl Prompt {
fn as_str(&self) -> &'static str {
match self {
&Prompt::None => "none",
use Prompt::*;
match *self {
None => "none",
Login => "login",
Consent => "consent",
SelectAccount => "select_account",
@ -456,11 +458,11 @@ impl Prompt {
/// Address Claim struct. Can be only formatted, only the rest, or both.
#[derive(Deserialize)]
pub struct Address {
pub formatted: Option<String>,
pub street_address: Option<String>,
pub locality: Option<String>,
pub region: Option<String>,
#[serde(default)] pub formatted: Option<String>,
#[serde(default)] pub street_address: Option<String>,
#[serde(default)] pub locality: Option<String>,
#[serde(default)] pub region: Option<String>,
// Countries like the UK use alphanumeric postal codes, so you can't just use a number here
pub postal_code: Option<String>,
pub country: Option<String>,
#[serde(default)] pub postal_code: Option<String>,
#[serde(default)] pub country: Option<String>,
}

View File

@ -11,8 +11,7 @@ type IdToken = Compact<Claims, Empty>;
#[derive(Serialize, Deserialize)]
pub struct Claims {
#[serde(with = "url_serde")]
pub iss: Url,
#[serde(with = "url_serde")] pub iss: Url,
// Max 255 ASCII chars
// Can't deserialize a [u8; 255]
pub sub: String,
@ -27,14 +26,14 @@ pub struct Claims {
pub exp: i64,
pub iat: i64,
// required for max_age request
pub auth_time: Option<i64>,
pub nonce: Option<String>,
#[serde(default)] pub auth_time: Option<i64>,
#[serde(default)] pub nonce: Option<String>,
// base64 encoded, need to decode it!
at_hash: Option<String>,
pub acr: Option<String>,
pub amr: Option<Vec<String>>,
#[serde(default)] at_hash: Option<String>,
#[serde(default)] pub acr: Option<String>,
#[serde(default)] pub amr: Option<Vec<String>>,
// If exists, must be client_id
pub azp: Option<String>,
#[serde(default)] pub azp: Option<String>,
}
impl Claims {