use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{Domain, Project, System};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AuthTokenScopeError {
#[error("Auth data is missing")]
MissingAuthData,
#[error("Cannot determine authorization scope from config")]
MissingScope,
#[error(transparent)]
Builder {
#[from]
source: crate::BuilderError,
},
}
#[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize, Debug, Default)]
#[serde(rename_all = "lowercase")]
pub enum AuthTokenScope {
Project(Project),
Domain(Domain),
System(System),
#[default]
Unscoped,
}
impl AuthTokenScope {
pub fn matches(&self, cached: &Self) -> bool {
match (self, cached) {
(AuthTokenScope::Project(req), AuthTokenScope::Project(cached)) => {
let id_match = req
.id
.as_ref()
.is_none_or(|id| cached.id.as_ref() == Some(id));
let name_match = req
.name
.as_ref()
.is_none_or(|name| cached.name.as_ref() == Some(name));
let domain_match = if let Some(req_domain) = &req.domain {
if let Some(cached_domain) = &cached.domain {
let d_id_match = req_domain
.id
.as_ref()
.is_none_or(|id| cached_domain.id.as_ref() == Some(id));
let d_name_match = req_domain
.name
.as_ref()
.is_none_or(|name| cached_domain.name.as_ref() == Some(name));
d_id_match && d_name_match
} else {
true
}
} else {
true
};
id_match && name_match && domain_match
}
(AuthTokenScope::Domain(req), AuthTokenScope::Domain(cached)) => {
let id_match = req
.id
.as_ref()
.is_none_or(|id| cached.id.as_ref() == Some(id));
let name_match = req
.name
.as_ref()
.is_none_or(|name| cached.name.as_ref() == Some(name));
id_match && name_match
}
(AuthTokenScope::Unscoped, AuthTokenScope::Unscoped) => true,
(AuthTokenScope::System(_), AuthTokenScope::System(_)) => true,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_project_scope(id: Option<&str>, name: Option<&str>) -> AuthTokenScope {
AuthTokenScope::Project(Project {
id: id.map(|s| s.to_string()),
name: name.map(|s| s.to_string()),
domain: None,
})
}
fn make_domain_scope(id: Option<&str>, name: Option<&str>) -> AuthTokenScope {
AuthTokenScope::Domain(Domain {
id: id.map(|s| s.to_string()),
name: name.map(|s| s.to_string()),
})
}
#[test]
fn test_matches_project_exact_id() {
let req = make_project_scope(Some("proj-1"), None);
let cached = make_project_scope(Some("proj-1"), None);
assert!(req.matches(&cached));
}
#[test]
fn test_matches_project_id_mismatch() {
let req = make_project_scope(Some("proj-1"), None);
let cached = make_project_scope(Some("proj-2"), None);
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_project_req_id_none_matches_cached_id_some() {
let req = make_project_scope(None, Some("myproj"));
let cached = make_project_scope(Some("proj-1"), Some("myproj"));
assert!(req.matches(&cached));
}
#[test]
fn test_matches_project_req_name_none_matches_cached_name() {
let req = make_project_scope(None, None);
let cached = make_project_scope(Some("proj-1"), Some("myproj"));
assert!(req.matches(&cached));
}
#[test]
fn test_matches_project_name_mismatch_when_req_name_some() {
let req = make_project_scope(None, Some("req-name"));
let cached = make_project_scope(Some("proj-1"), Some("cached-name"));
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_project_both_none() {
let req = make_project_scope(None, None);
let cached = make_project_scope(None, None);
assert!(req.matches(&cached));
}
#[test]
fn test_matches_domain_exact_id() {
let req = make_domain_scope(Some("d1"), None);
let cached = make_domain_scope(Some("d1"), None);
assert!(req.matches(&cached));
}
#[test]
fn test_matches_domain_id_mismatch() {
let req = make_domain_scope(Some("d1"), None);
let cached = make_domain_scope(Some("d2"), None);
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_domain_req_id_none_matches_cached() {
let req = make_domain_scope(None, Some("D"));
let cached = make_domain_scope(Some("d1"), Some("D"));
assert!(req.matches(&cached));
}
#[test]
fn test_matches_domain_name_mismatch_when_req_name_some() {
let req = make_domain_scope(None, Some("req-D"));
let cached = make_domain_scope(Some("d1"), Some("cached-D"));
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_project_does_not_match_domain() {
let req = make_project_scope(Some("p1"), None);
let cached = make_domain_scope(Some("p1"), None);
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_domain_does_not_match_project() {
let req = make_domain_scope(Some("d1"), None);
let cached = make_project_scope(Some("d1"), None);
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_unscoped_matches_unscoped() {
let req = AuthTokenScope::Unscoped;
let cached = AuthTokenScope::Unscoped;
assert!(req.matches(&cached));
}
#[test]
fn test_matches_unscoped_does_not_match_project() {
let req = AuthTokenScope::Unscoped;
let cached = make_project_scope(Some("p1"), None);
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_project_with_domain_exact() {
let req = AuthTokenScope::Project(Project {
id: Some("project-id".to_string()),
name: None,
domain: Some(Domain {
id: Some("domain-id".to_string()),
name: None,
}),
});
let cached = AuthTokenScope::Project(Project {
id: Some("project-id".to_string()),
name: None,
domain: Some(Domain {
id: Some("domain-id".to_string()),
name: Some("D".to_string()),
}),
});
assert!(req.matches(&cached));
}
#[test]
fn test_matches_project_with_domain_mismatch() {
let req = AuthTokenScope::Project(Project {
id: Some("project-id".to_string()),
name: None,
domain: Some(Domain {
id: Some("domain-1".to_string()),
name: None,
}),
});
let cached = AuthTokenScope::Project(Project {
id: Some("project-id".to_string()),
name: None,
domain: Some(Domain {
id: Some("domain-2".to_string()),
name: Some("D".to_string()),
}),
});
assert!(!req.matches(&cached));
}
#[test]
fn test_matches_project_cached_has_domain_req_has_no_domain() {
let req = AuthTokenScope::Project(Project {
id: Some("project-id".to_string()),
name: None,
domain: None,
});
let cached = AuthTokenScope::Project(Project {
id: Some("project-id".to_string()),
name: None,
domain: Some(Domain {
id: Some("domain-id".to_string()),
name: None,
}),
});
assert!(req.matches(&cached));
}
#[test]
fn test_matches_project_req_domain_by_name() {
let req = AuthTokenScope::Project(Project {
id: None,
name: Some("myproj".to_string()),
domain: Some(Domain {
id: None,
name: Some("Default".to_string()),
}),
});
let cached = AuthTokenScope::Project(Project {
id: Some("proj-123".to_string()),
name: Some("myproj".to_string()),
domain: Some(Domain {
id: Some("d1".to_string()),
name: Some("Default".to_string()),
}),
});
assert!(req.matches(&cached));
}
}