use super::scope::Scope;
use std::borrow::Cow;
use std::cmp;
use std::collections::HashMap;
use std::fmt;
use std::iter::{Extend, FromIterator};
use std::sync::{Arc, MutexGuard, RwLockWriteGuard};
use std::rc::Rc;
use url::Url;
use ring::{digest, pbkdf2};
use ring::error::Unspecified;
use ring::rand::{SystemRandom, SecureRandom};
pub trait Registrar {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError>;
fn negotiate(&self, client: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError>;
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError>;
}
#[derive(Clone, Debug)]
pub struct ClientUrl<'a> {
pub client_id: Cow<'a, str>,
pub redirect_uri: Option<Cow<'a, Url>>,
}
#[derive(Clone, Debug)]
pub struct BoundClient<'a> {
pub client_id: Cow<'a, str>,
pub redirect_uri: Cow<'a, Url>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreGrant {
pub client_id: String,
pub redirect_uri: Url,
pub scope: Scope,
}
#[derive(Clone, Debug)]
pub enum RegistrarError {
Unspecified,
PrimitiveError,
}
#[derive(Clone, Debug)]
pub struct Client {
client_id: String,
redirect_uri: Url,
default_scope: Scope,
client_type: ClientType,
}
#[derive(Clone, Debug)]
pub struct EncodedClient {
pub client_id: String,
pub redirect_uri: Url,
pub default_scope: Scope,
pub encoded_client: ClientType,
}
pub struct RegisteredClient<'a> {
client: &'a EncodedClient,
policy: &'a dyn PasswordPolicy,
}
#[derive(Clone)]
pub enum ClientType {
Public,
Confidential{
passdata: Vec<u8>,
},
}
#[derive(Default)]
pub struct ClientMap {
clients: HashMap<String, EncodedClient>,
password_policy: Option<Box<dyn PasswordPolicy>>,
}
impl fmt::Debug for ClientType {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
ClientType::Public => write!(f, "<public>"),
ClientType::Confidential { .. } => write!(f, "<confidential>"),
}
}
}
impl From<Unspecified> for RegistrarError {
fn from(err: Unspecified) -> Self {
match err { Unspecified => RegistrarError::Unspecified }
}
}
impl Client {
pub fn public(client_id: &str, redirect_uri: Url, default_scope: Scope) -> Client {
Client { client_id: client_id.to_string(), redirect_uri, default_scope, client_type: ClientType::Public }
}
pub fn confidential(client_id: &str, redirect_uri: Url, default_scope: Scope, passphrase: &[u8]) -> Client {
Client {
client_id: client_id.to_string(),
redirect_uri,
default_scope,
client_type: ClientType::Confidential {
passdata: passphrase.to_owned()
},
}
}
pub fn encode(self, policy: &dyn PasswordPolicy) -> EncodedClient {
let encoded_client = match self.client_type {
ClientType::Public => ClientType::Public,
ClientType::Confidential { passdata: passphrase }
=> ClientType::Confidential {
passdata: policy.store(&self.client_id, &passphrase)
}
};
EncodedClient {
client_id: self.client_id,
redirect_uri: self.redirect_uri,
default_scope: self.default_scope,
encoded_client
}
}
}
impl<'a> RegisteredClient<'a> {
pub fn new(client: &'a EncodedClient, policy: &'a dyn PasswordPolicy) -> Self {
RegisteredClient {
client,
policy,
}
}
pub fn check_authentication(&self, passphrase: Option<&[u8]>) -> Result<(), Unspecified> {
match (passphrase, &self.client.encoded_client) {
(None, &ClientType::Public) => Ok(()),
(Some(provided), &ClientType::Confidential{ passdata: ref stored })
=> self.policy.check(&self.client.client_id, provided, stored),
_ => Err(Unspecified)
}
}
}
impl cmp::PartialOrd<Self> for PreGrant {
fn partial_cmp(&self, rhs: &PreGrant) -> Option<cmp::Ordering> {
if (&self.client_id, &self.redirect_uri) != (&rhs.client_id, &rhs.redirect_uri) {
None
} else {
self.scope.partial_cmp(&rhs.scope)
}
}
}
pub trait PasswordPolicy: Send + Sync {
fn store(&self, client_id: &str, passphrase: &[u8]) -> Vec<u8>;
fn check(&self, client_id: &str, passphrase: &[u8], stored: &[u8]) -> Result<(), Unspecified>;
}
pub struct Pbkdf2 {
random: Option<SystemRandom>,
iterations: u32,
}
impl Default for Pbkdf2 {
fn default() -> Self {
Pbkdf2 {
random: Some(SystemRandom::new()),
.. *PBKDF2_DEFAULTS
}
}
}
impl Clone for Pbkdf2 {
fn clone(&self) -> Self {
Pbkdf2 {
random: Some(SystemRandom::new()),
.. *self
}
}
}
impl fmt::Debug for Pbkdf2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Pbkdf2")
.field("iterations", &self.iterations)
.field("random", &())
.finish()
}
}
impl Pbkdf2 {
pub fn set_relative_strength(&mut self, strength: u8) {
assert!(strength < 32, "Strength value out of range (0-31): {}", strength);
self.iterations = 1u32 << strength;
}
fn salt(&self, user_identifier: &[u8]) -> Vec<u8> {
let mut vec = Vec::with_capacity(user_identifier.len() + 64);
let mut rnd_salt = [0; 16];
match self.random.as_ref() {
Some(random) => random.fill(&mut rnd_salt),
None => SystemRandom::new().fill(&mut rnd_salt),
}.expect("Failed to property initialize password storage salt");
vec.extend_from_slice(user_identifier);
vec.extend_from_slice(&rnd_salt[..]);
vec
}
}
static PBKDF2_DEFAULTS: &Pbkdf2 = &Pbkdf2 {
random: None,
iterations: (1 << 16),
};
impl PasswordPolicy for Pbkdf2 {
fn store(&self, client_id: &str, passphrase: &[u8]) -> Vec<u8> {
let mut output = vec![0; 64];
output.append(&mut self.salt(client_id.as_bytes()));
{
let (output, salt) = output.split_at_mut(64);
pbkdf2::derive(&digest::SHA256, self.iterations, salt, passphrase,
output);
}
output
}
fn check(&self, _client_id: &str , passphrase: &[u8], stored: &[u8])
-> Result<(), Unspecified>
{
if stored.len() < 64 {
return Err(Unspecified)
}
let (verifier, salt) = stored.split_at(64);
pbkdf2::verify(&digest::SHA256, self.iterations, salt, passphrase, verifier)
}
}
impl ClientMap {
pub fn new() -> ClientMap {
ClientMap::default()
}
pub fn register_client(&mut self, client: Client) {
let password_policy = Self::current_policy(&self.password_policy);
self.clients.insert(client.client_id.clone(), client.encode(password_policy));
}
pub fn set_password_policy<P: PasswordPolicy + 'static>(&mut self, new_policy: P) {
self.password_policy = Some(Box::new(new_policy))
}
fn current_policy<'a>(policy: &'a Option<Box<dyn PasswordPolicy>>) -> &'a dyn PasswordPolicy {
policy
.as_ref().map(|boxed| &**boxed)
.unwrap_or(PBKDF2_DEFAULTS)
}
}
impl Extend<Client> for ClientMap {
fn extend<I>(&mut self, iter: I) where I: IntoIterator<Item=Client> {
iter.into_iter().for_each(|client| self.register_client(client))
}
}
impl FromIterator<Client> for ClientMap {
fn from_iter<I>(iter: I) -> Self where I: IntoIterator<Item=Client> {
let mut into = ClientMap::new();
into.extend(iter);
into
}
}
impl<'s, R: Registrar + ?Sized> Registrar for &'s R {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
(**self).bound_redirect(bound)
}
fn negotiate(&self, bound: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
(**self).negotiate(bound, scope)
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
(**self).check(client_id, passphrase)
}
}
impl<'s, R: Registrar + ?Sized> Registrar for &'s mut R {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
(**self).bound_redirect(bound)
}
fn negotiate(&self, bound: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
(**self).negotiate(bound, scope)
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
(**self).check(client_id, passphrase)
}
}
impl<R: Registrar + ?Sized> Registrar for Box<R> {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
(**self).bound_redirect(bound)
}
fn negotiate(&self, bound: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
(**self).negotiate(bound, scope)
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
(**self).check(client_id, passphrase)
}
}
impl<R: Registrar + ?Sized> Registrar for Rc<R> {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
(**self).bound_redirect(bound)
}
fn negotiate(&self, bound: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
(**self).negotiate(bound, scope)
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
(**self).check(client_id, passphrase)
}
}
impl<R: Registrar + ?Sized> Registrar for Arc<R> {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
(**self).bound_redirect(bound)
}
fn negotiate(&self, bound: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
(**self).negotiate(bound, scope)
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
(**self).check(client_id, passphrase)
}
}
impl<'s, R: Registrar + ?Sized + 's> Registrar for MutexGuard<'s, R> {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
(**self).bound_redirect(bound)
}
fn negotiate(&self, bound: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
(**self).negotiate(bound, scope)
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
(**self).check(client_id, passphrase)
}
}
impl<'s, R: Registrar + ?Sized + 's> Registrar for RwLockWriteGuard<'s, R> {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
(**self).bound_redirect(bound)
}
fn negotiate(&self, bound: BoundClient, scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
(**self).negotiate(bound, scope)
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
(**self).check(client_id, passphrase)
}
}
impl Registrar for ClientMap {
fn bound_redirect<'a>(&self, bound: ClientUrl<'a>) -> Result<BoundClient<'a>, RegistrarError> {
let client = match self.clients.get(bound.client_id.as_ref()) {
None => return Err(RegistrarError::Unspecified),
Some(stored) => stored
};
match bound.redirect_uri {
None => (),
Some(ref url) if url.as_ref().as_str() == client.redirect_uri.as_str() => (),
_ => return Err(RegistrarError::Unspecified),
}
Ok(BoundClient {
client_id: bound.client_id,
redirect_uri: bound.redirect_uri.unwrap_or_else(
|| Cow::Owned(client.redirect_uri.clone())),
})
}
fn negotiate(&self, bound: BoundClient, _scope: Option<Scope>) -> Result<PreGrant, RegistrarError> {
let client = self.clients.get(bound.client_id.as_ref())
.expect("Bound client appears to not have been constructed with this registrar");
Ok(PreGrant {
client_id: bound.client_id.into_owned(),
redirect_uri: bound.redirect_uri.into_owned(),
scope: client.default_scope.clone(),
})
}
fn check(&self, client_id: &str, passphrase: Option<&[u8]>) -> Result<(), RegistrarError> {
let password_policy = Self::current_policy(&self.password_policy);
self.clients.get(client_id)
.ok_or(Unspecified)
.and_then(|client| RegisteredClient::new(client, password_policy)
.check_authentication(passphrase))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
pub fn simple_test_suite<Reg, RegFn>(registrar: &mut Reg, register: RegFn)
where
Reg: Registrar,
RegFn: Fn(&mut Reg, Client)
{
let public_id = "PrivateClientId";
let client_url = "https://example.com";
let private_id = "PublicClientId";
let private_passphrase = b"WOJJCcS8WyS2aGmJK6ZADg==";
let public_client = Client::public(public_id, client_url.parse().unwrap(),
"default".parse().unwrap());
register(registrar, public_client);
{
registrar.check(public_id, None)
.expect("Authorization of public client has changed");
registrar.check(public_id, Some(b""))
.err().expect("Authorization with password succeeded");
}
let private_client = Client::confidential(private_id, client_url.parse().unwrap(),
"default".parse().unwrap(), private_passphrase);
register(registrar, private_client);
{
registrar.check(private_id, Some(private_passphrase))
.expect("Authorization with right password did not succeed");
registrar.check(private_id, Some(b"Not the private passphrase"))
.err().expect("Authorization succeed with wrong password");
}
}
#[test]
fn public_client() {
let policy = Pbkdf2::default();
let client = Client::public(
"ClientId",
"https://example.com".parse().unwrap(),
"default".parse().unwrap()
).encode(&policy);
let client = RegisteredClient::new(&client, &policy);
assert!(client.check_authentication(None).is_ok());
assert!(client.check_authentication(Some(b"")).is_err());
}
#[test]
fn confidential_client() {
let policy = Pbkdf2::default();
let pass = b"AB3fAj6GJpdxmEVeNCyPoA==";
let client = Client::confidential(
"ClientId",
"https://example.com".parse().unwrap(),
"default".parse().unwrap(),
pass
).encode(&policy);
let client = RegisteredClient::new(&client, &policy);
assert!(client.check_authentication(None).is_err());
assert!(client.check_authentication(Some(pass)).is_ok());
assert!(client.check_authentication(Some(b"not the passphrase")).is_err());
assert!(client.check_authentication(Some(b"")).is_err());
}
#[test]
fn client_map() {
let mut client_map = ClientMap::new();
simple_test_suite(&mut client_map, ClientMap::register_client);
}
}