use tracing::error;
use crate::AppEvent;
use crate::auth::{AuthorizedKeys, ClientRole, Role, RoleId};
pub trait RequestDecoder: Send + Sync {
type Event: AppEvent;
type Role: Role;
fn decode(&self, body: &[u8], role: ClientRole<Self::Role>) -> Decoded<Self::Event>;
}
pub enum Decoded<E: AppEvent> {
Filter,
Permitted(E),
PermissionDenied(&'static str),
DecodeError(&'static str),
}
mod sealed {
pub trait Sealed {}
}
impl<D: RequestDecoder> sealed::Sealed for D {}
pub trait ErasedDecoder<E: AppEvent>: sealed::Sealed + Send + Sync {
fn decode_erased(&self, body: &[u8], role: ClientRole<RoleId>) -> Decoded<E>;
fn matches_keys(&self, keys: &AuthorizedKeys) -> bool;
}
impl<D: RequestDecoder> ErasedDecoder<D::Event> for D {
fn decode_erased(&self, body: &[u8], role: ClientRole<RoleId>) -> Decoded<D::Event> {
let role = match role {
ClientRole::Operator => ClientRole::Operator,
ClientRole::App(id) => match D::Role::ROLES.get(id.index()) {
Some(&(_, role)) => ClientRole::App(role),
None => {
error!(
index = id.index(),
roles = D::Role::ROLES.len(),
"role index outside the decoder's role table; request refused"
);
return Decoded::PermissionDenied("role index outside the role table");
}
},
};
self.decode(body, role)
}
fn matches_keys(&self, keys: &AuthorizedKeys) -> bool {
keys.is_for::<D::Role>()
}
}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use super::*;
use crate::CodecError;
use crate::auth::tests::DeskRole;
use crate::auth::{KeyRole, NoRoles};
#[derive(Debug, Clone, Copy)]
struct Unit;
impl AppEvent for Unit {
const MAX_ENCODED_SIZE: usize = 0;
fn encoded_size(&self) -> usize {
0
}
fn encode(&self, _buf: &mut [u8]) -> usize {
0
}
fn decode(_buf: &[u8]) -> Result<Self, CodecError> {
Ok(Unit)
}
fn is_query(&self) -> bool {
false
}
}
struct Recording<R: Role> {
seen: Mutex<Vec<ClientRole<R>>>,
}
impl<R: Role> Recording<R> {
fn new() -> Self {
Recording {
seen: Mutex::new(Vec::new()),
}
}
fn seen(&self) -> Vec<ClientRole<R>> {
self.seen.lock().unwrap().clone()
}
}
impl<R: Role> RequestDecoder for Recording<R> {
type Event = Unit;
type Role = R;
fn decode(&self, _body: &[u8], role: ClientRole<R>) -> Decoded<Unit> {
self.seen.lock().unwrap().push(role);
Decoded::Permitted(Unit)
}
}
fn client_role_of<R: Role>(token: &str) -> ClientRole<RoleId> {
let key = [0x11u8; 32];
let listed = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, key);
let keys = AuthorizedKeys::parse::<R>(&format!("{token} {listed} test\n")).unwrap();
keys.lookup(&key)
.and_then(KeyRole::client)
.expect("a client role")
}
#[test]
fn every_role_reaches_the_decoder_as_itself() {
let decoder = Recording::<DeskRole>::new();
let mut expected = vec![ClientRole::Operator];
decoder.decode_erased(b"", client_role_of::<DeskRole>("operator"));
for &(token, role) in DeskRole::ROLES {
decoder.decode_erased(b"", client_role_of::<DeskRole>(token));
expected.push(ClientRole::App(role));
}
assert_eq!(decoder.seen(), expected);
}
#[test]
fn an_index_outside_the_role_table_is_refused_without_decoding() {
let past_the_end = client_role_of::<DeskRole>("readonly");
let decoder = Recording::<NoRoles>::new();
assert!(matches!(
decoder.decode_erased(b"", past_the_end),
Decoded::PermissionDenied(_)
));
assert!(decoder.seen().is_empty());
}
#[test]
fn a_decoder_matches_only_keys_parsed_for_its_role_type() {
let decoder = Recording::<DeskRole>::new();
assert!(decoder.matches_keys(&AuthorizedKeys::parse::<DeskRole>("").unwrap()));
assert!(!decoder.matches_keys(&AuthorizedKeys::parse::<NoRoles>("").unwrap()));
}
}