use chrono::{DateTime, FixedOffset};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize, Serializer};
use super::common::IdAndName;
#[derive(Clone, Debug, Serialize, PartialEq, Eq, Hash)]
pub enum IdOrName {
#[serde(rename = "id")]
Id(String),
#[serde(rename = "name")]
Name(String),
}
#[derive(Clone, Debug, Serialize)]
pub struct UserAndPassword {
#[serde(flatten)]
pub user: IdOrName,
pub password: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub domain: Option<IdOrName>,
}
#[derive(Clone, Debug)]
pub enum Identity {
Password(UserAndPassword),
Token(String),
}
#[derive(Clone, Debug, Serialize)]
pub struct Project {
#[serde(flatten)]
pub project: IdOrName,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub domain: Option<IdOrName>,
}
#[derive(Clone, Debug, Serialize)]
pub enum Scope {
#[serde(rename = "project")]
Project(Project),
#[serde(rename = "domain")]
Domain(IdOrName),
#[serde(rename = "system", serialize_with = "ser_system_scope")]
System,
}
#[derive(Clone, Debug, Serialize)]
pub struct Auth {
pub identity: Identity,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<Scope>,
}
#[derive(Clone, Debug, Serialize)]
pub struct AuthRoot {
pub auth: Auth,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Endpoint {
pub interface: String,
pub region: String,
pub url: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct CatalogRecord {
#[serde(rename = "type")]
pub service_type: String,
pub endpoints: Vec<Endpoint>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct CatalogRoot {
pub catalog: Vec<CatalogRecord>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Token {
pub roles: Vec<IdAndName>,
pub expires_at: DateTime<FixedOffset>,
pub catalog: Vec<CatalogRecord>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct TokenRoot {
pub token: Token,
}
#[derive(Debug, Serialize)]
struct PasswordAuth<'a> {
user: &'a UserAndPassword,
}
#[derive(Debug, Serialize)]
struct TokenAuth<'a> {
id: &'a str,
}
impl IdOrName {
#[inline]
pub fn from_id<T: Into<String>>(id: T) -> IdOrName {
IdOrName::Id(id.into())
}
#[inline]
pub fn from_name<T: Into<String>>(name: T) -> IdOrName {
IdOrName::Name(name.into())
}
}
impl Serialize for Identity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut inner = serializer.serialize_struct("Identity", 2)?;
match self {
Identity::Password(ref user) => {
inner.serialize_field("methods", &["password"])?;
inner.serialize_field("password", &PasswordAuth { user })?;
}
Identity::Token(ref token) => {
inner.serialize_field("methods", &["token"])?;
inner.serialize_field("token", &TokenAuth { id: token })?;
}
}
inner.end()
}
}
fn ser_system_scope<S>(serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut inner = serializer.serialize_struct("System", 1)?;
inner.serialize_field("all", &true)?;
inner.end()
}
#[cfg(test)]
mod test {
use super::super::common::test;
use super::*;
const PASSWORD_NAME_UNSCOPED: &str = r#"
{
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"name": "admin",
"domain": {
"name": "Default"
},
"password": "devstacker"
}
}
}
}
}"#;
const PASSWORD_ID_SCOPED_WITH_ID: &str = r#"
{
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"id": "ee4dfb6e5540447cb3741905149d9b6e",
"password": "devstacker"
}
}
},
"scope": {
"domain": {
"id": "default"
}
}
}
}"#;
const PASSWORD_ID_SYSTEM_SCOPE: &str = r#"
{
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"id": "ee4dfb6e5540447cb3741905149d9b6e",
"password": "devstacker"
}
}
},
"scope": {
"system": {
"all": true
}
}
}
}"#;
const TOKEN_SCOPED_WITH_NAME: &str = r#"
{
"auth": {
"identity": {
"methods": [
"token"
],
"token": {
"id": "abcdef"
}
},
"scope": {
"domain": {
"name": "Default"
}
}
}
}"#;
#[test]
fn test_password_name_unscoped() {
let value = AuthRoot {
auth: Auth {
identity: Identity::Password(UserAndPassword {
user: IdOrName::Name("admin".to_string()),
password: "devstacker".to_string(),
domain: Some(IdOrName::from_name("Default")),
}),
scope: None,
},
};
test::compare(PASSWORD_NAME_UNSCOPED, value);
}
#[test]
fn test_password_id_scoped_with_id() {
let value = AuthRoot {
auth: Auth {
identity: Identity::Password(UserAndPassword {
user: IdOrName::Id("ee4dfb6e5540447cb3741905149d9b6e".to_string()),
password: "devstacker".to_string(),
domain: None,
}),
scope: Some(Scope::Domain(IdOrName::from_id("default"))),
},
};
test::compare(PASSWORD_ID_SCOPED_WITH_ID, value);
}
#[test]
fn test_password_id_system_scope() {
let value = AuthRoot {
auth: Auth {
identity: Identity::Password(UserAndPassword {
user: IdOrName::Id("ee4dfb6e5540447cb3741905149d9b6e".to_string()),
password: "devstacker".to_string(),
domain: None,
}),
scope: Some(Scope::System),
},
};
test::compare(PASSWORD_ID_SYSTEM_SCOPE, value);
}
#[test]
fn test_token_scoped_with_name() {
let value = AuthRoot {
auth: Auth {
identity: Identity::Token("abcdef".to_string()),
scope: Some(Scope::Domain(IdOrName::Name("Default".to_string()))),
},
};
test::compare(TOKEN_SCOPED_WITH_NAME, value);
}
}