#[cfg(feature = "rest-api-actix")]
pub(crate) mod actix;
#[cfg(feature = "authorization")]
pub mod authorization;
pub mod identity;
use std::str::FromStr;
use crate::error::InvalidArgumentError;
#[cfg(feature = "authorization")]
use super::Method;
#[cfg(feature = "authorization")]
use authorization::{AuthorizationHandler, AuthorizationHandlerResult, Permission, PermissionMap};
use identity::{Identity, IdentityProvider};
enum AuthorizationResult {
Authorized(Identity),
#[cfg(any(
feature = "authorization",
feature = "biome-credentials",
feature = "oauth"
))]
NoAuthorizationNecessary,
Unauthorized,
#[cfg(feature = "authorization")]
UnknownEndpoint,
}
fn authorize(
#[cfg(feature = "authorization")] method: &Method,
#[cfg(any(
feature = "authorization",
feature = "biome-credentials",
feature = "oauth"
))]
endpoint: &str,
#[cfg(not(any(
feature = "authorization",
feature = "biome-credentials",
feature = "oauth"
)))]
_endpoint: &str,
auth_header: Option<&str>,
#[cfg(feature = "authorization")] permission_map: &PermissionMap,
identity_providers: &[Box<dyn IdentityProvider>],
#[cfg(feature = "authorization")] authorization_handlers: &[Box<dyn AuthorizationHandler>],
) -> AuthorizationResult {
#[cfg(feature = "authorization")]
{
let permission = match permission_map.get_permission(method, endpoint) {
Some(perm) => perm,
None => return AuthorizationResult::UnknownEndpoint,
};
match *permission {
Permission::AllowUnauthenticated => AuthorizationResult::NoAuthorizationNecessary,
Permission::AllowAuthenticated => match get_identity(auth_header, identity_providers) {
Some(identity) => AuthorizationResult::Authorized(identity),
None => AuthorizationResult::Unauthorized,
},
Permission::Check { permission_id, .. } => {
match get_identity(auth_header, identity_providers) {
Some(identity) => {
for handler in authorization_handlers {
match handler.has_permission(&identity, permission_id) {
Ok(AuthorizationHandlerResult::Allow) => {
return AuthorizationResult::Authorized(identity)
}
Ok(AuthorizationHandlerResult::Deny) => {
return AuthorizationResult::Unauthorized
}
Ok(AuthorizationHandlerResult::Continue) => {}
Err(err) => error!("{}", err),
}
}
AuthorizationResult::Unauthorized
}
None => AuthorizationResult::Unauthorized,
}
}
}
}
#[cfg(not(feature = "authorization"))]
{
#[cfg(any(feature = "biome-credentials", feature = "oauth"))]
{
let mut is_auth_endpoint = false;
#[cfg(feature = "biome-credentials")]
if endpoint == "/biome/register"
|| endpoint == "/biome/login"
|| endpoint == "/biome/token"
{
is_auth_endpoint = true;
}
#[cfg(feature = "oauth")]
if endpoint == "/oauth/login" || endpoint == "/oauth/callback" {
is_auth_endpoint = true;
}
if is_auth_endpoint {
return AuthorizationResult::NoAuthorizationNecessary;
}
}
match get_identity(auth_header, identity_providers) {
Some(identity) => AuthorizationResult::Authorized(identity),
None => AuthorizationResult::Unauthorized,
}
}
}
fn get_identity(
auth_header: Option<&str>,
identity_providers: &[Box<dyn IdentityProvider>],
) -> Option<Identity> {
let authorization = auth_header?.parse().ok()?;
identity_providers.iter().find_map(|provider| {
provider.get_identity(&authorization).unwrap_or_else(|err| {
error!("{}", err);
None
})
})
}
#[derive(PartialEq)]
pub enum AuthorizationHeader {
Bearer(BearerToken),
Custom(String),
}
impl FromStr for AuthorizationHeader {
type Err = InvalidArgumentError;
fn from_str(str: &str) -> Result<Self, Self::Err> {
let mut parts = str.splitn(2, ' ');
match (parts.next(), parts.next()) {
(Some(auth_scheme), Some(value)) => match auth_scheme {
"Bearer" => Ok(AuthorizationHeader::Bearer(value.parse()?)),
_ => Ok(AuthorizationHeader::Custom(str.to_string())),
},
(Some(_), None) => Ok(AuthorizationHeader::Custom(str.to_string())),
_ => unreachable!(), }
}
}
#[derive(PartialEq)]
pub enum BearerToken {
#[cfg(feature = "biome-credentials")]
Biome(String),
Custom(String),
#[cfg(feature = "cylinder-jwt")]
Cylinder(String),
#[cfg(feature = "oauth")]
OAuth2(String),
}
impl FromStr for BearerToken {
type Err = InvalidArgumentError;
fn from_str(str: &str) -> Result<Self, Self::Err> {
let mut parts = str.splitn(2, ':');
match (parts.next(), parts.next()) {
#[allow(unused_variables, clippy::match_single_binding)]
(Some(token_type), Some(token)) => match token_type {
#[cfg(feature = "biome-credentials")]
"Biome" => Ok(BearerToken::Biome(token.to_string())),
#[cfg(feature = "cylinder-jwt")]
"Cylinder" => Ok(BearerToken::Cylinder(token.to_string())),
#[cfg(feature = "oauth")]
"OAuth2" => Ok(BearerToken::OAuth2(token.to_string())),
_ => Ok(BearerToken::Custom(str.to_string())),
},
(Some(_), None) => Ok(BearerToken::Custom(str.to_string())),
_ => unreachable!(), }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::InternalError;
#[test]
fn parse_authorization_header() {
assert!(matches!(
"Bearer token".parse(),
Ok(AuthorizationHeader::Bearer(token)) if token == "token".parse().unwrap()
));
assert!(matches!(
"Unknown token".parse(),
Ok(AuthorizationHeader::Custom(auth_str)) if auth_str == "Unknown token"
));
assert!(matches!(
"test".parse(),
Ok(AuthorizationHeader::Custom(auth_str)) if auth_str == "test"
));
}
#[test]
fn parse_bearer_token() {
#[cfg(feature = "biome-credentials")]
assert!(matches!(
"Biome:test".parse(),
Ok(BearerToken::Biome(token)) if token == "test"
));
#[cfg(feature = "cylinder-jwt")]
assert!(matches!(
"Cylinder:test".parse(),
Ok(BearerToken::Cylinder(token)) if token == "test"
));
#[cfg(feature = "oauth")]
assert!(matches!(
"OAuth2:test".parse(),
Ok(BearerToken::OAuth2(token)) if token == "test"
));
assert!(matches!(
"Unknown:test".parse(),
Ok(BearerToken::Custom(token)) if token == "Unknown:test"
));
assert!(matches!(
"test".parse(),
Ok(BearerToken::Custom(token)) if token == "test"
));
}
#[test]
fn authorize_no_identity_providers() {
#[cfg(feature = "authorization")]
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::AllowAuthenticated,
);
map
};
assert!(matches!(
authorize(
#[cfg(feature = "authorization")]
&Method::Get,
"/test/endpoint",
Some("auth"),
#[cfg(feature = "authorization")]
&permission_map,
&[],
#[cfg(feature = "authorization")]
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::Unauthorized
));
}
#[test]
fn authorize_no_matching_identity_provider() {
#[cfg(feature = "authorization")]
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::AllowAuthenticated,
);
map
};
assert!(matches!(
authorize(
#[cfg(feature = "authorization")]
&Method::Get,
"/test/endpoint",
Some("auth"),
#[cfg(feature = "authorization")]
&permission_map,
&[Box::new(AlwaysRejectIdentityProvider)],
#[cfg(feature = "authorization")]
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::Unauthorized
));
}
#[test]
fn authorize_no_header_provided() {
#[cfg(feature = "authorization")]
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::AllowAuthenticated,
);
map
};
assert!(matches!(
authorize(
#[cfg(feature = "authorization")]
&Method::Get,
"/test/endpoint",
None,
#[cfg(feature = "authorization")]
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
#[cfg(feature = "authorization")]
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::Unauthorized
));
}
#[cfg(feature = "authorization")]
#[test]
fn authorize_unknown_endpoint() {
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
None,
&Default::default(),
&[Box::new(AlwaysAcceptIdentityProvider)],
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::UnknownEndpoint
));
}
#[test]
fn authorize_no_authorization_necessary() {
#[cfg(feature = "authorization")]
{
let mut permission_map = PermissionMap::new();
permission_map.add_permission(
Method::Get,
"/test/endpoint",
Permission::AllowUnauthenticated,
);
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
None,
&permission_map,
&[Box::new(AlwaysRejectIdentityProvider)],
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::NoAuthorizationNecessary
));
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
Some("auth"),
&permission_map,
&[Box::new(AlwaysRejectIdentityProvider)],
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::NoAuthorizationNecessary
));
}
#[cfg(not(feature = "authorization"))]
{
#[cfg(feature = "biome-credentials")]
{
assert!(matches!(
authorize(
"/biome/register",
None,
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
assert!(matches!(
authorize(
"/biome/login",
None,
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
assert!(matches!(
authorize(
"/biome/token",
None,
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
}
#[cfg(feature = "oauth")]
{
assert!(matches!(
authorize(
"/oauth/login",
None,
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
assert!(matches!(
authorize(
"/oauth/callback",
None,
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
}
#[cfg(feature = "biome-credentials")]
{
assert!(matches!(
authorize(
"/biome/register",
Some("auth"),
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
assert!(matches!(
authorize(
"/biome/login",
Some("auth"),
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
assert!(matches!(
authorize(
"/biome/token",
Some("auth"),
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
}
#[cfg(feature = "oauth")]
{
assert!(matches!(
authorize(
"/oauth/login",
Some("auth"),
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
assert!(matches!(
authorize(
"/oauth/callback",
Some("auth"),
&[Box::new(AlwaysRejectIdentityProvider)]
),
AuthorizationResult::NoAuthorizationNecessary
));
}
}
}
#[test]
fn authorize_successful_one_identity_provider() {
let expected_auth = "auth".parse().unwrap();
let expected_identity = AlwaysAcceptIdentityProvider
.get_identity(&expected_auth)
.unwrap()
.unwrap();
#[cfg(feature = "authorization")]
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::AllowAuthenticated,
);
map
};
assert!(matches!(
authorize(
#[cfg(feature = "authorization")]
&Method::Get,
"/test/endpoint",
Some("auth"),
#[cfg(feature = "authorization")]
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
#[cfg(feature = "authorization")]
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::Authorized(identity) if identity == expected_identity
));
}
#[test]
fn authorize_successful_multiple_identity_providers() {
let expected_auth = "auth".parse().unwrap();
let expected_identity = AlwaysAcceptIdentityProvider
.get_identity(&expected_auth)
.unwrap()
.unwrap();
#[cfg(feature = "authorization")]
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::AllowAuthenticated,
);
map
};
assert!(matches!(
authorize(
#[cfg(feature = "authorization")]
&Method::Get,
"/test/endpoint",
Some("auth"),
#[cfg(feature = "authorization")]
&permission_map,
&[
Box::new(AlwaysRejectIdentityProvider),
Box::new(AlwaysAcceptIdentityProvider),
Box::new(AlwaysRejectIdentityProvider),
],
#[cfg(feature = "authorization")]
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::Authorized(identity) if identity == expected_identity
));
}
#[test]
fn authorize_successful_after_identity_provider_error() {
let expected_auth = "auth".parse().unwrap();
let expected_identity = AlwaysAcceptIdentityProvider
.get_identity(&expected_auth)
.unwrap()
.unwrap();
#[cfg(feature = "authorization")]
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::AllowAuthenticated,
);
map
};
assert!(matches!(
authorize(
#[cfg(feature = "authorization")]
&Method::Get,
"/test/endpoint",
Some("auth"),
#[cfg(feature = "authorization")]
&permission_map,
&[
Box::new(AlwaysErrIdentityProvider),
Box::new(AlwaysAcceptIdentityProvider),
],
#[cfg(feature = "authorization")]
&[Box::new(AlwaysAllowAuthorizationHandler)],
),
AuthorizationResult::Authorized(identity) if identity == expected_identity
));
}
#[cfg(feature = "authorization")]
#[test]
fn authorize_no_authorization_handlers() {
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::Check {
permission_id: "permission",
permission_display_name: "",
permission_description: "",
},
);
map
};
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
Some("auth"),
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
&[],
),
AuthorizationResult::Unauthorized
));
}
#[cfg(feature = "authorization")]
#[test]
fn authorize_no_allowing_authorization_handler() {
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::Check {
permission_id: "permission",
permission_display_name: "",
permission_description: "",
},
);
map
};
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
Some("auth"),
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
&[Box::new(AlwaysContinueAuthorizationHandler)],
),
AuthorizationResult::Unauthorized
));
}
#[cfg(feature = "authorization")]
#[test]
fn authorize_deny_before_allowing_authorization_handler() {
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::Check {
permission_id: "permission",
permission_display_name: "",
permission_description: "",
},
);
map
};
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
Some("auth"),
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
&[
Box::new(AlwaysDenyAuthorizationHandler),
Box::new(AlwaysAllowAuthorizationHandler),
],
),
AuthorizationResult::Unauthorized
));
}
#[cfg(feature = "authorization")]
#[test]
fn authorize_allow_before_denying_authorization_handler() {
let expected_auth = "auth".parse().unwrap();
let expected_identity = AlwaysAcceptIdentityProvider
.get_identity(&expected_auth)
.unwrap()
.unwrap();
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::Check {
permission_id: "permission",
permission_display_name: "",
permission_description: "",
},
);
map
};
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
Some("auth"),
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
&[
Box::new(AlwaysAllowAuthorizationHandler),
Box::new(AlwaysDenyAuthorizationHandler),
],
),
AuthorizationResult::Authorized(identity) if identity == expected_identity
));
}
#[cfg(feature = "authorization")]
#[test]
fn authorize_allow_after_continuing_authorization_handler() {
let expected_auth = "auth".parse().unwrap();
let expected_identity = AlwaysAcceptIdentityProvider
.get_identity(&expected_auth)
.unwrap()
.unwrap();
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::Check {
permission_id: "permission",
permission_display_name: "",
permission_description: "",
},
);
map
};
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
Some("auth"),
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
&[
Box::new(AlwaysContinueAuthorizationHandler),
Box::new(AlwaysAllowAuthorizationHandler),
],
),
AuthorizationResult::Authorized(identity) if identity == expected_identity
));
}
#[cfg(feature = "authorization")]
#[test]
fn authorize_allow_after_err_authorization_handler() {
let expected_auth = "auth".parse().unwrap();
let expected_identity = AlwaysAcceptIdentityProvider
.get_identity(&expected_auth)
.unwrap()
.unwrap();
let permission_map = {
let mut map = PermissionMap::new();
map.add_permission(
Method::Get,
"/test/endpoint",
Permission::Check {
permission_id: "permission",
permission_display_name: "",
permission_description: "",
},
);
map
};
assert!(matches!(
authorize(
&Method::Get,
"/test/endpoint",
Some("auth"),
&permission_map,
&[Box::new(AlwaysAcceptIdentityProvider)],
&[
Box::new(AlwaysErrAuthorizationHandler),
Box::new(AlwaysAllowAuthorizationHandler),
],
),
AuthorizationResult::Authorized(identity) if identity == expected_identity
));
}
#[derive(Clone)]
struct AlwaysAcceptIdentityProvider;
impl IdentityProvider for AlwaysAcceptIdentityProvider {
fn get_identity(
&self,
_authorization: &AuthorizationHeader,
) -> Result<Option<Identity>, InternalError> {
Ok(Some(Identity::Custom("identity".into())))
}
fn clone_box(&self) -> Box<dyn IdentityProvider> {
Box::new(self.clone())
}
}
#[derive(Clone)]
struct AlwaysRejectIdentityProvider;
impl IdentityProvider for AlwaysRejectIdentityProvider {
fn get_identity(
&self,
_authorization: &AuthorizationHeader,
) -> Result<Option<Identity>, InternalError> {
Ok(None)
}
fn clone_box(&self) -> Box<dyn IdentityProvider> {
Box::new(self.clone())
}
}
#[derive(Clone)]
struct AlwaysErrIdentityProvider;
impl IdentityProvider for AlwaysErrIdentityProvider {
fn get_identity(
&self,
_authorization: &AuthorizationHeader,
) -> Result<Option<Identity>, InternalError> {
Err(InternalError::with_message("failed".into()))
}
fn clone_box(&self) -> Box<dyn IdentityProvider> {
Box::new(self.clone())
}
}
#[cfg(feature = "authorization")]
#[derive(Clone)]
struct AlwaysAllowAuthorizationHandler;
#[cfg(feature = "authorization")]
impl AuthorizationHandler for AlwaysAllowAuthorizationHandler {
fn has_permission(
&self,
_identity: &Identity,
_permission_id: &str,
) -> Result<AuthorizationHandlerResult, InternalError> {
Ok(AuthorizationHandlerResult::Allow)
}
fn clone_box(&self) -> Box<dyn AuthorizationHandler> {
Box::new(self.clone())
}
}
#[cfg(feature = "authorization")]
#[derive(Clone)]
struct AlwaysDenyAuthorizationHandler;
#[cfg(feature = "authorization")]
impl AuthorizationHandler for AlwaysDenyAuthorizationHandler {
fn has_permission(
&self,
_identity: &Identity,
_permission_id: &str,
) -> Result<AuthorizationHandlerResult, InternalError> {
Ok(AuthorizationHandlerResult::Deny)
}
fn clone_box(&self) -> Box<dyn AuthorizationHandler> {
Box::new(self.clone())
}
}
#[cfg(feature = "authorization")]
#[derive(Clone)]
struct AlwaysContinueAuthorizationHandler;
#[cfg(feature = "authorization")]
impl AuthorizationHandler for AlwaysContinueAuthorizationHandler {
fn has_permission(
&self,
_identity: &Identity,
_permission_id: &str,
) -> Result<AuthorizationHandlerResult, InternalError> {
Ok(AuthorizationHandlerResult::Continue)
}
fn clone_box(&self) -> Box<dyn AuthorizationHandler> {
Box::new(self.clone())
}
}
#[cfg(feature = "authorization")]
#[derive(Clone)]
struct AlwaysErrAuthorizationHandler;
#[cfg(feature = "authorization")]
impl AuthorizationHandler for AlwaysErrAuthorizationHandler {
fn has_permission(
&self,
_identity: &Identity,
_permission_id: &str,
) -> Result<AuthorizationHandlerResult, InternalError> {
Err(InternalError::with_message("failed".into()))
}
fn clone_box(&self) -> Box<dyn AuthorizationHandler> {
Box::new(self.clone())
}
}
}