use biscuit::Empty; use biscuit::jwk::JWKSet; use inth_oauth2::provider::Provider; use inth_oauth2::token::Expiring; use reqwest::{Client, Url}; use url_serde; use error::Error; use token::Token; pub(crate) fn secure(url: &Url) -> Result<(), Error> { if url.scheme() != "https" { Err(Error::Insecure(url.clone())) } else { Ok(()) } } // TODO I wish we could impl default for this, but you cannot have a config without issuer etc #[derive(Debug, Deserialize, Serialize)] pub struct Config { #[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. #[serde(with = "url_serde")] pub token_endpoint: Url, #[serde(default)] #[serde(with = "url_serde")] pub userinfo_endpoint: Option, #[serde(with = "url_serde")] pub jwks_uri: Url, #[serde(default)] #[serde(with = "url_serde")] pub registration_endpoint: Option, #[serde(default)] pub scopes_supported: Option>, // 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, // There are only two possible values here, query and fragment. Default is both. #[serde(default)] pub response_modes_supported: Option>, // Must support at least authorization_code and implicit. #[serde(default)] pub grant_types_supported: Option>, #[serde(default)] pub acr_values_supported: Option>, // pairwise and public are valid by spec, but servers can add more pub subject_types_supported: Vec, // Must include at least RS256, none is only allowed with response types without id tokens pub id_token_signing_alg_values_supported: Vec, #[serde(default)] pub id_token_encryption_alg_values_supported: Option>, #[serde(default)] pub id_token_encryption_enc_values_supported: Option>, #[serde(default)] pub userinfo_signing_alg_values_supported: Option>, #[serde(default)] pub userinfo_encryption_alg_values_supported: Option>, #[serde(default)] pub userinfo_encryption_enc_values_supported: Option>, #[serde(default)] pub request_object_signing_alg_values_supported: Option>, #[serde(default)] pub request_object_encryption_alg_values_supported: Option>, #[serde(default)] pub request_object_encryption_enc_values_supported: Option>, // Spec options are client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt // If omitted, client_secret_basic is used #[serde(default)] pub token_endpoint_auth_methods_supported: Option>, // Only wanted with jwt auth methods, should have RS256, none not allowed #[serde(default)] pub token_endpoint_auth_signing_alg_values_supported: Option>, #[serde(default)] pub display_values_supported: Option>, // Valid options are normal, aggregated, and distributed. If omitted, only use normal #[serde(default)] pub claim_types_supported: Option>, #[serde(default)] pub claims_supported: Option>, #[serde(default)] #[serde(with = "url_serde")] pub service_documentation: Option, #[serde(default)] pub claims_locales_supported: Option>, #[serde(default)] pub ui_locales_supported: Option>, #[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, #[serde(default)] #[serde(with = "url_serde")] pub op_tos_uri: Option, // This is a NONSTANDARD extension Google uses that is a part of the Oauth discovery draft #[serde(default)] pub code_challenge_methods_supported: Option>, } // This seems really dumb... fn tru() -> bool { true } pub struct Discovered { pub config: Config, } impl Provider for Discovered { type Lifetime = Expiring; type Token = Token; fn auth_uri(&self) -> &str { self.config.authorization_endpoint.as_ref() } fn token_uri(&self) -> &str { self.config.token_endpoint.as_ref() } } /// Get the discovery config document from the given issuer url. Errors are either a reqwest error /// or an Insecure if the Url isn't https. pub fn discover(client: &Client, issuer: Url) -> Result { secure(&issuer)?; let url = issuer.join("/.well-known/openid-configuration")?; let mut resp = client.get(url)?.send()?; resp.json().map_err(Error::from) } /// Get the JWK set from the given Url. Errors are either a reqwest error or an Insecure error if /// the url isn't https. pub fn jwks(client: &Client, url: Url) -> Result, Error> { secure(&url)?; let mut resp = client.get(url)?.send()?; resp.json().map_err(Error::from) } #[test] fn config_google() { // Formatting this took time off my lifespan... 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" ] }"#; ::serde_json::from_str::(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" ] }"#; ::serde_json::from_str::(cfg).unwrap(); }