Holy moley... all the Optional Json fields needed #[serde(default)] on them. Talk about boilerplate...
This commit is contained in:
parent
50de2dacb3
commit
3e71047f42
158
src/discovery.rs
158
src/discovery.rs
|
@ -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());
|
||||
}
|
||||
|
|
28
src/error.rs
28
src/error.rs
|
@ -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"),
|
||||
|
|
|
@ -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();
|
||||
}
|
20
src/lib.rs
20
src/lib.rs
|
@ -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>,
|
||||
}
|
||||
|
|
15
src/token.rs
15
src/token.rs
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue