More documentation

This commit is contained in:
Matthew Scheirer 2017-09-19 13:32:01 -04:00
parent 65ca0b4fd3
commit b3012f1940
3 changed files with 66 additions and 22 deletions

View File

@ -16,6 +16,7 @@ pub(crate) fn secure(url: &Url) -> Result<(), Error> {
}
}
// 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,

View File

@ -1,7 +1,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::Error as Http;
pub use reqwest::UrlError as Url;
use std::fmt::{Display, Formatter, Result};
@ -22,7 +22,7 @@ pub enum Error {
Jose(Jose),
Json(Json),
Oauth(Oauth),
Reqwest(Reqwest),
Http(Http),
Url(Url),
Decode(Decode),
Validation(Validation),
@ -34,7 +34,7 @@ pub enum Error {
from!(Error, Jose);
from!(Error, Json);
from!(Error, Oauth);
from!(Error, Reqwest);
from!(Error, Http);
from!(Error, Url);
from!(Error, Decode);
from!(Error, Validation);
@ -47,7 +47,7 @@ impl Display for Error {
Jose(ref err) => Display::fmt(err, f),
Json(ref err) => Display::fmt(err, f),
Oauth(ref err) => Display::fmt(err, f),
Reqwest(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),
@ -65,7 +65,7 @@ impl ErrorTrait for Error {
Jose(ref err) => err.description(),
Json(ref err) => err.description(),
Oauth(ref err) => err.description(),
Reqwest(ref err) => err.description(),
Http(ref err) => err.description(),
Url(ref err) => err.description(),
Decode(ref err) => err.description(),
Validation(ref err) => err.description(),
@ -81,7 +81,7 @@ impl ErrorTrait for Error {
Jose(ref err) => Some(err),
Json(ref err) => Some(err),
Oauth(ref err) => Some(err),
Reqwest(ref err) => Some(err),
Http(ref err) => Some(err),
Url(ref err) => Some(err),
Decode(_) => None,
Validation(_) => None,
@ -117,7 +117,7 @@ impl Display for Decode {
fn fmt(&self, f: &mut Formatter) -> Result {
use Decode::*;
match *self {
MissingKid => write!(f, "Token Missing Key Id when key set has multiple keys"),
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!")
@ -139,9 +139,9 @@ impl ErrorTrait for Validation {
Mismatch(ref mm) => {
use error::Mismatch::*;
match *mm {
Authorized {..} => "Client id and token authorized party mismatch",
Issuer {..} => "Config issuer and token issuer mismatch",
Nonce {..} => "Supplied nonce and token nonce mismatch",
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) => {
@ -180,7 +180,7 @@ impl Display for Validation {
#[derive(Debug)]
pub enum Mismatch {
Authorized { expected: String, actual: String },
AuthorizedParty { expected: String, actual: String },
Issuer { expected: String, actual: String },
Nonce { expected: String, actual: String },
}
@ -189,7 +189,7 @@ impl Display for Mismatch {
fn fmt(&self, f: &mut Formatter) -> Result {
use error::Mismatch::*;
match *self {
Authorized { ref expected, ref actual } =>
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),

View File

@ -145,6 +145,7 @@ impl Client {
}
}
/// Passthrough to the inth_oauth2::client's request token.
pub fn request_token(&self,
client: &reqwest::Client,
auth_code: &str,
@ -219,9 +220,19 @@ impl Client {
Ok(token)
}
/// Mutates a Compact::encoded Token to Compact::decoded. Errors are:
///
/// - Decode::MissingKid if the keyset has multiple keys but the key id on the token is missing
/// - Decode::MissingKey if the given key id is not in the key set
/// - Decode::EmptySet if the keyset is empty
/// - Jose::WrongKeyType if the alg of the key and the alg in the token header mismatch
/// - Jose::WrongKeyType if the specified key alg isn't a signature algorithm
/// - Jose error if decoding fails
pub fn decode_token(&self, token: &mut IdToken) -> Result<(), Error> {
// This is an early escape if the token is already decoded
token.encoded()?;
// This is an early return if the token is already decoded
if let Compact::Decoded { .. } = *token {
return Ok(())
}
let header = token.unverified_header()?;
// If there is more than one key, the token MUST have a key id
@ -229,13 +240,15 @@ impl Client {
let token_kid = header.registered.key_id.ok_or(Decode::MissingKid)?;
self.jwks.find(&token_kid).ok_or(Decode::MissingKey(token_kid))?
} else {
// TODO We would want to verify the keyset is >1 in the constructor
// rather than every decode call, but we can't return an error in new().
self.jwks.keys.first().as_ref().ok_or(Decode::EmptySet)?
};
if let Some(alg) = key.common.algorithm.as_ref() {
if let &jwa::Algorithm::Signature(alg) = alg {
if header.registered.algorithm != alg {
return wrong_key!(alg, header.registered.algorithm);
if let &jwa::Algorithm::Signature(sig) = alg {
if header.registered.algorithm != sig {
return wrong_key!(sig, header.registered.algorithm);
}
} else {
return wrong_key!(SignatureAlgorithm::default(), alg);
@ -275,6 +288,19 @@ impl Client {
}
}
/// Validate a decoded token. If you don't get an error, its valid! Nonce and max_age come from
/// your auth_uri options. Errors are:
///
/// - Jose Error if the Token isn't decoded
/// - Validation::Mismatch::Issuer if the provider issuer and token issuer mismatch
/// - Validation::Mismatch::Nonce if a given nonce and the token nonce mismatch
/// - Validation::Missing::Nonce if either the token or args has a nonce and the other does not
/// - Validation::Missing::Audience if the token aud doesn't contain the client id
/// - Validation::Missing::AuthorizedParty if there are multiple audiences and azp is missing
/// - Validation::Mismatch::AuthorizedParty if the azp is not the client_id
/// - Validation::Expired::Expires if the current time is past the expiration time
/// - Validation::Expired::MaxAge is the token is older than the provided max_age
/// - Validation::Missing::Authtime if a max_age was given and the token has no auth time
pub fn validate_token(
&self,
token: &IdToken,
@ -283,15 +309,14 @@ impl Client {
) -> Result<(), Error> {
let claims = token.payload()?;
if claims.iss != self.config().issuer {
let expected = self.config().issuer.as_str().to_string();
let actual = claims.iss.as_str().to_string();
return Err(Validation::Mismatch(Mismatch::Issuer { expected, actual }).into());
}
if let Some(expected) = nonce {
match claims.nonce {
match nonce {
Some(expected) => match claims.nonce {
Some(ref actual) => {
if expected != actual {
let expected = expected.to_string();
@ -302,6 +327,9 @@ impl Client {
}
None => return Err(Validation::Missing(Missing::Nonce).into()),
}
None => if claims.nonce.is_some() {
return Err(Validation::Missing(Missing::Nonce).into())
}
}
if !claims.aud.contains(&self.oauth.client_id) {
@ -318,7 +346,9 @@ impl Client {
if actual != &self.oauth.client_id {
let expected = self.oauth.client_id.to_string();
let actual = actual.to_string();
return Err(Validation::Mismatch(Mismatch::Authorized { expected, actual }).into());
return Err(Validation::Mismatch(Mismatch::AuthorizedParty {
expected, actual
}).into());
}
}
@ -348,6 +378,16 @@ impl Client {
Ok(())
}
/// Get a userinfo json document for a given token at the provider's userinfo endpoint.
/// Errors are:
///
/// - Userinfo::NoUrl if this provider doesn't have a userinfo endpoint
/// - Error::Insecure if the userinfo url is not https
/// - Userinfo::MismatchIssuer if the userinfo origin does not match the provider's issuer
/// - Error::Jose if the token is not decoded
/// - Error::Http if something goes wrong getting the document
/// - Error::Json if the response is not a valid Userinfo document
/// - Userinfo::MismatchSubject if the returned userinfo document and tokens subject mismatch
pub fn request_userinfo(&self, client: &reqwest::Client, token: &Token
) -> Result<Userinfo, Error> {
match self.config().userinfo_endpoint {
@ -361,7 +401,8 @@ impl Client {
let claims = token.id_token.payload()?;
let auth_code = token.access_token().to_string();
let mut resp = client.get(url.clone())?
.header(header::Authorization(header::Bearer { token: auth_code })).send()?;
.header(header::Authorization(header::Bearer { token: auth_code }))
.send()?;
let info: Userinfo = resp.json()?;
if claims.sub != info.sub {
let expected = info.sub.clone();
@ -423,6 +464,7 @@ pub struct Userinfo {
#[serde(default)] pub updated_at: Option<i64>,
}
/// The four values for the preferred display parameter in the Options. See spec for details.
pub enum Display {
Page,
Popup,
@ -442,6 +484,7 @@ impl Display {
}
}
/// The four possible values for the prompt parameter set in Options. See spec for details.
#[derive(PartialEq, Eq, Hash)]
pub enum Prompt {
None,