use super::RoleDefinition;
use crate::SQLError;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, sync::Arc};
pub use uqa_core::catalog_role::RoleIdentity;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoleBinding {
pub name: String,
pub oid: u32,
pub object_id: [u8; 16],
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RoleReference {
Named(String),
Bound(Arc<RoleBinding>),
}
impl From<String> for RoleReference {
fn from(name: String) -> Self {
Self::Named(name)
}
}
impl From<&str> for RoleReference {
fn from(name: &str) -> Self {
Self::Named(name.into())
}
}
impl RoleReference {
pub fn from_identity(
identity: RoleIdentity,
roles: &BTreeMap<String, RoleDefinition>,
) -> Result<Self, SQLError> {
let name = identity.role_name(roles).ok_or_else(|| {
SQLError::Internal("role identity references a missing or replaced incarnation".into())
})?;
Ok(Self::Bound(Arc::new(RoleBinding::from_definition(
&roles[name],
)?)))
}
pub fn require_name<'a>(
&'a self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Result<&'a str, SQLError> {
match self {
Self::Named(name) => {
super::require_role_exists(roles, name)?;
Ok(name)
}
Self::Bound(role) => role.require_name(roles),
}
}
pub fn catalog_name(
&self,
roles: &BTreeMap<String, RoleDefinition>,
) -> Result<String, SQLError> {
match self {
Self::Named(name) => Ok(name.clone()),
Self::Bound(role) => role.require_name(roles).map(str::to_owned),
}
}
pub fn bind(&self, roles: &BTreeMap<String, RoleDefinition>) -> Result<RoleBinding, SQLError> {
let name = self.require_name(roles)?;
RoleBinding::from_definition(&roles[name])
}
}
impl RoleSubject for RoleReference {
fn role_name<'a>(&'a self, roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
match self {
Self::Named(name) => name.role_name(roles),
Self::Bound(role) => role.role_name(roles),
}
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
match self {
Self::Named(name) => name.role_definition(roles),
Self::Bound(role) => role.role_definition(roles),
}
}
}
impl RoleBinding {
pub fn identity(&self) -> RoleIdentity {
RoleIdentity {
oid: i64::from(self.oid),
object_id: self.object_id,
}
}
pub fn from_definition(role: &RoleDefinition) -> Result<Self, SQLError> {
if role.oid <= 0 {
return Err(SQLError::Internal("invalid role OID".into()));
}
if role.object_id == [0; 16] {
return Err(SQLError::Internal("role has no object identity".into()));
}
Ok(Self {
name: role.name.clone(),
oid: u32::try_from(role.oid)
.map_err(|_| SQLError::Internal("invalid role OID".into()))?,
object_id: role.object_id,
})
}
pub fn revalidate(&self, roles: &BTreeMap<String, RoleDefinition>) -> Result<(), SQLError> {
if self.role_definition(roles).is_some() {
Ok(())
} else {
Err(SQLError::Routine {
sqlstate: "42704".into(),
message: format!("role {} was concurrently dropped", self.oid),
})
}
}
fn matches_definition(&self, role: &RoleDefinition) -> bool {
role.oid == i64::from(self.oid) && role.object_id == self.object_id
}
pub fn require_name<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Result<&'a str, SQLError> {
self.role_definition(roles)
.map(|role| role.name.as_str())
.ok_or_else(|| SQLError::Routine {
sqlstate: "42704".into(),
message: format!("invalid role OID: {}", self.oid),
})
}
}
pub trait RoleSubject {
fn role_name<'a>(&'a self, roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str>;
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition>;
}
impl<T: RoleSubject + ?Sized> RoleSubject for Arc<T> {
fn role_name<'a>(&'a self, roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
self.as_ref().role_name(roles)
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
self.as_ref().role_definition(roles)
}
}
impl<T: RoleSubject + ?Sized> RoleSubject for &T {
fn role_name<'a>(&'a self, roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
(*self).role_name(roles)
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
(*self).role_definition(roles)
}
}
impl RoleSubject for str {
fn role_name<'a>(&'a self, _roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
Some(self)
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
roles.get(self)
}
}
impl RoleSubject for String {
fn role_name<'a>(&'a self, roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
self.as_str().role_name(roles)
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
self.as_str().role_definition(roles)
}
}
impl RoleSubject for uqa_core::catalog_acl::AclGrantee {
fn role_name<'a>(&'a self, _roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
self.role_name()
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
self.role_name().and_then(|name| roles.get(name))
}
}
impl RoleSubject for RoleBinding {
fn role_name<'a>(&'a self, roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
self.role_definition(roles).map(|role| role.name.as_str())
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
roles
.get(&self.name)
.filter(|role| self.matches_definition(role))
.or_else(|| roles.values().find(|role| self.matches_definition(role)))
}
}
impl RoleSubject for RoleIdentity {
fn role_name<'a>(&'a self, roles: &'a BTreeMap<String, RoleDefinition>) -> Option<&'a str> {
self.role_definition(roles).map(|role| role.name.as_str())
}
fn role_definition<'a>(
&self,
roles: &'a BTreeMap<String, RoleDefinition>,
) -> Option<&'a RoleDefinition> {
if !self.is_valid() {
return None;
}
roles.values().find(|role| role.identity() == *self)
}
}
#[cfg(test)]
mod tests;