Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/v3/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl TryFrom<&BackendToken> for Token {
token.user_id(data.user_id.clone());
token.methods(data.methods.clone());
token.audit_ids(data.audit_ids.clone());
token.expires_at(data.expires_at.clone());
token.expires_at(data.expires_at);
}
Ok(token.build()?)
}
Expand Down
80 changes: 77 additions & 3 deletions src/token/application_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
// SPDX-License-Identifier: Apache-2.0

use chrono::{DateTime, Utc};
use derive_builder::Builder;
use rmp::{decode::read_pfix, encode::write_pfix};
use std::collections::BTreeMap;

use rmp::decode::*;
use std::io::Write;

use crate::token::{
error::TokenProviderError,
Expand All @@ -24,16 +25,42 @@ use crate::token::{
types::Token,
};

#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Builder, Clone, Debug, Default, PartialEq)]
pub struct ApplicationCredentialToken {
pub user_id: String,
#[builder(default, setter(name = _methods))]
pub methods: Vec<String>,
#[builder(default, setter(name = _audit_ids))]
pub audit_ids: Vec<String>,
pub expires_at: DateTime<Utc>,
pub project_id: String,
pub application_credential_id: String,
}

impl ApplicationCredentialTokenBuilder {
pub fn methods<I, V>(&mut self, iter: I) -> &mut Self
where
I: Iterator<Item = V>,
V: Into<String>,
{
self.methods
.get_or_insert_with(Vec::new)
.extend(iter.map(Into::into));
self
}

pub fn audit_ids<I, V>(&mut self, iter: I) -> &mut Self
where
I: Iterator<Item = V>,
V: Into<String>,
{
self.audit_ids
.get_or_insert_with(Vec::new)
.extend(iter.map(Into::into));
self
}
}

impl From<ApplicationCredentialToken> for Token {
fn from(value: ApplicationCredentialToken) -> Self {
Token::ApplicationCredential(value)
Expand All @@ -43,6 +70,25 @@ impl From<ApplicationCredentialToken> for Token {
impl MsgPackToken for ApplicationCredentialToken {
type Token = ApplicationCredentialToken;

fn assemble<W: Write>(
&self,
wd: &mut W,
auth_map: &BTreeMap<usize, String>,
) -> Result<(), TokenProviderError> {
fernet_utils::write_uuid(wd, &self.user_id)?;
write_pfix(
wd,
fernet::encode_auth_methods(self.methods.clone(), auth_map)? as u8,
)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
fernet_utils::write_uuid(wd, &self.project_id)?;
fernet_utils::write_time(wd, self.expires_at)?;
fernet_utils::write_audit_ids(wd, self.audit_ids.clone())?;
fernet_utils::write_uuid(wd, &self.application_credential_id)?;

Ok(())
}

fn disassemble(
rd: &mut &[u8],
auth_map: &BTreeMap<usize, String>,
Expand All @@ -67,3 +113,31 @@ impl MsgPackToken for ApplicationCredentialToken {
})
}
}

#[cfg(test)]
mod tests {
use chrono::{Local, SubsecRound};
use uuid::Uuid;

use super::*;

#[test]
fn test_roundtrip() {
let token = ApplicationCredentialToken {
user_id: Uuid::new_v4().simple().to_string(),
methods: vec!["password".into()],
project_id: Uuid::new_v4().simple().to_string(),
application_credential_id: Uuid::new_v4().simple().to_string(),
audit_ids: vec!["Zm9vCg".into()],
expires_at: Local::now().trunc_subsecs(0).into(),
};
let auth_map = BTreeMap::from([(1, "password".into())]);
let mut buf = vec![];
token.assemble(&mut buf, &auth_map).unwrap();
let encoded_buf = buf.clone();
let decoded =
ApplicationCredentialToken::disassemble(&mut encoded_buf.as_slice(), &auth_map)
.unwrap();
assert_eq!(token, decoded);
}
}
78 changes: 75 additions & 3 deletions src/token/domain_scoped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
// SPDX-License-Identifier: Apache-2.0

use chrono::{DateTime, Utc};
use derive_builder::Builder;
use rmp::{decode::read_pfix, encode::write_pfix};
use std::collections::BTreeMap;

use rmp::decode::*;
use std::io::Write;

use crate::token::{
error::TokenProviderError,
Expand All @@ -24,15 +25,41 @@ use crate::token::{
types::Token,
};

#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Builder, Clone, Debug, Default, PartialEq)]
pub struct DomainScopeToken {
pub user_id: String,
#[builder(default, setter(name = _methods))]
pub methods: Vec<String>,
#[builder(default, setter(name = _audit_ids))]
pub audit_ids: Vec<String>,
pub expires_at: DateTime<Utc>,
pub domain_id: String,
}

impl DomainScopeTokenBuilder {
pub fn methods<I, V>(&mut self, iter: I) -> &mut Self
where
I: Iterator<Item = V>,
V: Into<String>,
{
self.methods
.get_or_insert_with(Vec::new)
.extend(iter.map(Into::into));
self
}

pub fn audit_ids<I, V>(&mut self, iter: I) -> &mut Self
where
I: Iterator<Item = V>,
V: Into<String>,
{
self.audit_ids
.get_or_insert_with(Vec::new)
.extend(iter.map(Into::into));
self
}
}

impl From<DomainScopeToken> for Token {
fn from(value: DomainScopeToken) -> Self {
Token::DomainScope(value)
Expand All @@ -41,6 +68,25 @@ impl From<DomainScopeToken> for Token {

impl MsgPackToken for DomainScopeToken {
type Token = DomainScopeToken;

fn assemble<W: Write>(
&self,
wd: &mut W,
auth_map: &BTreeMap<usize, String>,
) -> Result<(), TokenProviderError> {
fernet_utils::write_uuid(wd, &self.user_id)?;
write_pfix(
wd,
fernet::encode_auth_methods(self.methods.clone(), auth_map)? as u8,
)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
fernet_utils::write_uuid(wd, &self.domain_id)?;
fernet_utils::write_time(wd, self.expires_at)?;
fernet_utils::write_audit_ids(wd, self.audit_ids.clone())?;

Ok(())
}

fn disassemble(
rd: &mut &[u8],
auth_map: &BTreeMap<usize, String>,
Expand All @@ -62,3 +108,29 @@ impl MsgPackToken for DomainScopeToken {
})
}
}

#[cfg(test)]
mod tests {
use chrono::{Local, SubsecRound};
use uuid::Uuid;

use super::*;

#[test]
fn test_roundtrip() {
let token = DomainScopeToken {
user_id: Uuid::new_v4().simple().to_string(),
methods: vec!["password".into()],
domain_id: Uuid::new_v4().simple().to_string(),
audit_ids: vec!["Zm9vCg".into()],
expires_at: Local::now().trunc_subsecs(0).into(),
};
let auth_map = BTreeMap::from([(1, "password".into())]);
let mut buf = vec![];
token.assemble(&mut buf, &auth_map).unwrap();
let encoded_buf = buf.clone();
let decoded =
DomainScopeToken::disassemble(&mut encoded_buf.as_slice(), &auth_map).unwrap();
assert_eq!(token, decoded);
}
}
90 changes: 85 additions & 5 deletions src/token/fernet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ pub(crate) fn encode_auth_methods<I: IntoIterator<Item = String>>(

impl FernetTokenProvider {
/// Parse binary blob as MessagePack after encrypting it with Fernet
fn parse(&self, rd: &mut &[u8]) -> Result<Token, TokenProviderError> {
fn decode(&self, rd: &mut &[u8]) -> Result<Token, TokenProviderError> {
if let Marker::FixArray(_) = read_marker(rd).map_err(ValueReadError::from)? {
match read_payload_token_type(rd)? {
0 => Ok(UnscopedToken::disassemble(rd, &self.auth_map)?.into()),
Expand All @@ -138,8 +138,26 @@ impl FernetTokenProvider {
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
data.assemble(&mut buf, &self.auth_map)?;
}
_ => {
todo!()
Token::DomainScope(data) => {
write_array_len(&mut buf, 6)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
write_pfix(&mut buf, 1)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
data.assemble(&mut buf, &self.auth_map)?;
}
Token::ProjectScope(data) => {
write_array_len(&mut buf, 6)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
write_pfix(&mut buf, 2)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
data.assemble(&mut buf, &self.auth_map)?;
}
Token::ApplicationCredential(data) => {
write_array_len(&mut buf, 7)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
write_pfix(&mut buf, 9)
.map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
data.assemble(&mut buf, &self.auth_map)?;
}
}
Ok(buf.into())
Expand Down Expand Up @@ -173,7 +191,7 @@ impl FernetTokenProvider {
Some(fernet) => fernet.decrypt(credential)?,
_ => self.get_fernet()?.decrypt(credential)?,
};
self.parse(&mut payload.as_slice())
self.decode(&mut payload.as_slice())
}

/// Encrypt the token
Expand Down Expand Up @@ -224,6 +242,7 @@ pub(super) mod tests {
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;
use uuid::Uuid;

fn setup_config() -> Config {
let keys_dir = tempdir().unwrap();
Expand Down Expand Up @@ -270,7 +289,7 @@ pub(super) mod tests {
#[tokio::test]
async fn test_unscoped_roundtrip() {
let token = Token::Unscoped(UnscopedToken {
user_id: "abc".into(),
user_id: Uuid::new_v4().simple().to_string(),
methods: vec!["password".into()],
audit_ids: vec!["Zm9vCg".into()],
expires_at: Local::now().trunc_subsecs(0).into(),
Expand Down Expand Up @@ -309,6 +328,26 @@ pub(super) mod tests {
}
}

#[tokio::test]
async fn test_domain_roundtrip() {
let token = Token::DomainScope(DomainScopeToken {
user_id: Uuid::new_v4().simple().to_string(),
methods: vec!["password".into()],
domain_id: Uuid::new_v4().simple().to_string(),
audit_ids: vec!["Zm9vCg".into()],
expires_at: Local::now().trunc_subsecs(0).into(),
});

let mut backend = FernetTokenProvider::default();
let config = crate::tests::token::setup_config();
backend.set_config(config);
backend.load_keys().unwrap();

let encrypted = backend.encrypt(&token).unwrap();
let dec_token = backend.decrypt(&encrypted).unwrap();
assert_eq!(token, dec_token);
}

#[tokio::test]
async fn test_decrypt_project() {
let token = "gAAAAABns2ixy75K_KfoosWLrNNqG6KW8nm3Xzv0_2dOx8ODWH7B8i2g8CncGLO6XBEH_TYLg83P6XoKQ5bU8An8Kqgw9WX3bvmEQXphnwPM6aRAOQUSdVhTlUm_8otDG9BS2rc70Q7pfy57S3_yBgimy-174aKdP8LPusvdHZsQPEJO9pfeXWw";
Expand All @@ -332,6 +371,26 @@ pub(super) mod tests {
}
}

#[tokio::test]
async fn test_project_roundtrip() {
let token = Token::ProjectScope(ProjectScopeToken {
user_id: Uuid::new_v4().simple().to_string(),
methods: vec!["password".into()],
project_id: Uuid::new_v4().simple().to_string(),
audit_ids: vec!["Zm9vCg".into()],
expires_at: Local::now().trunc_subsecs(0).into(),
});

let mut backend = FernetTokenProvider::default();
let config = crate::tests::token::setup_config();
backend.set_config(config);
backend.load_keys().unwrap();

let encrypted = backend.encrypt(&token).unwrap();
let dec_token = backend.decrypt(&encrypted).unwrap();
assert_eq!(token, dec_token);
}

#[tokio::test]
async fn test_decrypt_application_credential() {
let token = "gAAAAABnt11m57ZlI9JU0g2BKJw2EN-InbAIijcIG7SxvPATntgTlcTMwha-Fh7isNNIwDq2WaWglV1nYgftfoUK245ZnEJ0_gXaIhl6COhNommYv2Bs9PnJqfgrrxrIrB8rh4pfeyCtMkv5ePYgFFPyRFE37l3k7qL5p7qVhYT37yT1-K5lYAV0f6Vy70h3KX1HO0m6Rl90";
Expand All @@ -358,4 +417,25 @@ pub(super) mod tests {
panic!()
}
}

#[tokio::test]
async fn test_application_credential_roundtrip() {
let token = Token::ApplicationCredential(ApplicationCredentialToken {
user_id: Uuid::new_v4().simple().to_string(),
methods: vec!["application_credential".into()],
project_id: Uuid::new_v4().simple().to_string(),
application_credential_id: Uuid::new_v4().simple().to_string(),
audit_ids: vec!["Zm9vCg".into()],
expires_at: Local::now().trunc_subsecs(0).into(),
});

let mut backend = FernetTokenProvider::default();
let config = crate::tests::token::setup_config();
backend.set_config(config);
backend.load_keys().unwrap();

let encrypted = backend.encrypt(&token).unwrap();
let dec_token = backend.decrypt(&encrypted).unwrap();
assert_eq!(token, dec_token);
}
}
Loading