So for a rocket project, I decided to setup a security system that will handle grants based of Security voters (a naive version of Symfony ones).
I decided to implement as Security
struct to contain a HashMap
with HashMap<String, T>
where the string is the subject of the voter (like "books", "authors", etc. Some domain stuff) and the T would be a struct implementing the trait SecurityVoter
.
The goal is to store all the security voters in an instance of the Security
struct, then store the security in a rocket State.
Here is my code :
pub trait SecurityVoter {
/// Gets the "subject" supported by the voter
fn supports(&self) -> String;
/// Takes a given right and a user, and checks if the user has_access to the action represented by the right.
fn has_access(&self, right: &str, user: User) -> bool;
}
#[derive(Default)]
pub struct Security<T>
where
T: SecurityVoter,
{
voters: HashMap<String, T>,
}
impl<T> Security<T>
where
T: SecurityVoter,
{
pub fn add_handler(&mut self, handler: T) -> &mut Self {
self.voters.insert(handler.supports(), handler);
self
}
pub fn has_access(&self, subject: &str, right: &str, user: &User) -> bool {
if self.voters.contains_key(subject) {
return self.voters.get(subject).unwrap().has_access(right, user);
}
false
}
}
But when I try to test it with a little unit test, I cannot build because of errors out of my comprehension.
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default)]
struct TestSecurityHandler {}
impl SecurityVoter for TestSecurityHandler {
fn supports(&self) -> String {
"test".to_string()
}
fn has_access(&self, right: &str, _user: &User) -> bool {
if right == "ACCEPT_ACCESS" {
return true;
}
false
}
}
#[test]
fn test_custom_security_handler() {
let mut security = Security::<dyn SecurityVoter>::default(); // <- line with the error
security.add_handler(TestSecurityHandler::default());
let mut user = User::default();
assert!(security.has_access("test", "ACCEPT_ACCESS", &user));
}
}
Here is the error :
error[E0599]: the function or associated item `default` exists for struct `core::security::Security<dyn core::security::SecurityVoter>`, but its trait bounds were not satisfied
--> src/core/security.rs:68:59
|
6 | / pub struct Security<T>
7 | | where
8 | | T: SecurityVoter,
9 | | {
10 | | voters: HashMap<String, T>,
11 | | }
| | -
| | |
| |_function or associated item `default` not found for this
| doesn't satisfy `_: std::default::Default`
...
38 | pub trait SecurityVoter {
| -----------------------
| |
| doesn't satisfy `_: std::default::Default`
| doesn't satisfy `_: std::marker::Sized`
...
68 | let mut security = Security::<dyn SecurityVoter>::default(); // <- line with the error
| ^^^^^^^ function or associated item cannot be called on `core::security::Security<dyn core::security::SecurityVoter>` due to unsatisfied trait bounds
|
note: the following trait bounds were not satisfied:
`dyn core::security::SecurityVoter: std::default::Default`
`dyn core::security::SecurityVoter: std::marker::Sized`
--> src/core/security.rs:5:10
|
5 | #[derive(Default)]
| ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
= note: the following trait bounds were not satisfied:
`dyn core::security::SecurityVoter: std::marker::Sized`
which is required by `core::security::Security<dyn core::security::SecurityVoter>: std::default::Default`
`dyn core::security::SecurityVoter: std::default::Default`
which is required by `core::security::Security<dyn core::security::SecurityVoter>: std::default::Default`
I tried some modification using Sized and Default traits, and tried to use Box<> in my Hashmap but nothing helped.
Can you provide some help to solve this issue ? Thanks :)
Update : I think I fixed my issue modifying my code as this :
- Added a
?Sized
requirement for generics ofSecurity
- Used
Box<>
to store my voters inside the hashmap - Removed any mention of the
Default
trait
pub struct Security<T: ?Sized> {
voters: HashMap<String, Box<T>>,
}
impl<T> Security<T>
where
T: SecurityVoter ?Sized,
{
pub fn new() -> Self {
Self {
voters: HashMap::new(),
}
}
pub fn add_handler(&mut self, handler: Box<T>) -> &mut Self {
self.voters.insert(handler.supports(), handler);
self
}
pub fn is_granted(user: &User, roles: Vec<&str>) -> bool {
roles
.iter()
.all(|item| user.roles.contains(&item.to_string()))
}
pub fn has_access(&self, subject: &str, right: &str, user: &User) -> bool {
if self.voters.contains_key(subject) {
return self.voters.get(subject).unwrap().has_access(right, user);
}
false
}
}
// ...
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default)]
struct TestSecurityHandler {}
impl SecurityVoter for TestSecurityHandler {
fn supports(&self) -> String {
"test".to_string()
}
fn has_access(&self, right: &str, _user: &User) -> bool {
if right == "ACCEPT_ACCESS" {
return true;
}
false
}
}
#[derive(Default)]
struct STestSecurityHandler {}
impl SecurityVoter for STestSecurityHandler {
fn supports(&self) -> String {
"test".to_string()
}
fn has_access(&self, right: &str, _user: &User) -> bool {
if right == "ACCEPT_ACCESS" {
return true;
}
false
}
}
#[test]
fn test_custom_security_handler() {
let mut security = Security::<dyn SecurityVoter>::new(); // <- line with the error
security.add_handler(Box::new(TestSecurityHandler::default()));
security.add_handler(Box::new(STestSecurityHandler::default()));
let user = User::default();
assert!(security.has_access("test", "ACCEPT_ACCESS", &user));
}
}
But I don't really know if this is a good solution.
If you have any advice on how to properly implement this, feel free to comment.
CodePudding user response:
You almost got this correctly. The only thing I would change is to implement Default
. The problem with #[derive(Default)]
is that it generates an incorrect T: Default
bound (the wish for a #[derive]
that won't generate incorrect bounds is called perfect derive, but it is not implemented). Instead, you need to implement Default
manually:
impl<T: ?Sized> Default for Security<T> {
fn default() -> Self {
Self { voters: HashMap::default() }
}
}
Another way to implement this, and better if you also use the struct with concrete, not dyn
SecurityVoter
(because this saves a Box
for this case), is to do require T: Sized
but use Box<dyn SecurityVoter>
instead of bare dyn SecurityVoter
in the test, and implement SecurityVoter
for Box<dyn SecurityVoter>
. You still need to implement Default
manually, though:
pub struct Security<T> {
// I removed the `T: SecurityVoter` bound, see https://stackoverflow.com/q/49229332/7884305.
voters: HashMap<String, T>,
}
impl<T> Default for Security<T> {
fn default() -> Self {
Self { voters: HashMap::default() }
}
}
impl<T: ?Sized SecurityVoter> SecurityVoter for Box<T> {
fn supports(&self) -> String {
T::supports(&**self)
}
fn has_access(&self, right: &str, user: &User) -> bool {
T::has_access(&**self, right, user)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default)]
struct TestSecurityHandler {}
impl SecurityVoter for TestSecurityHandler {
fn supports(&self) -> String {
"test".to_string()
}
fn has_access(&self, right: &str, _user: &User) -> bool {
if right == "ACCEPT_ACCESS" {
return true;
}
false
}
}
#[test]
fn test_custom_security_handler() {
let mut security = Security::<Box<dyn SecurityVoter>>::default();
security.add_handler(Box::<TestSecurityHandler>::default());
let mut user = User::default();
assert!(security.has_access("test", "ACCEPT_ACCESS", &user));
}
}