Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,272 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
api,
|
||||
api::Request,
|
||||
auth::{Identity, Service},
|
||||
database::{Database, Options},
|
||||
task::Runtime,
|
||||
};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
mod mutations;
|
||||
|
||||
static NEXT: AtomicU64 = AtomicU64::new(0);
|
||||
const ID: [u8; 16] = [1, 144, 0, 0, 0, 0, 112, 0, 128, 0, 0, 0, 0, 0, 0, 1];
|
||||
const KEY: &str = "capability:news.article.admin.01900000-0000-7000-8000-000000000001";
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
impl<'database> Tenant<'database> {
|
||||
pub(crate) fn for_test(executor: &'database mut Executor, id: i64) -> Self {
|
||||
Self { executor, id }
|
||||
}
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
directory: PathBuf,
|
||||
service: Service,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let directory = std::env::temp_dir().join(format!(
|
||||
"dever-capability-{}-{}",
|
||||
std::process::id(),
|
||||
NEXT.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
std::fs::create_dir(&directory).unwrap();
|
||||
let setting = directory.join("setting.json");
|
||||
std::fs::write(&setting, r#"{"http":{"host":"127.0.0.1","port":8181},"sites":{"admin":{"path":"admin","hosts":["unit.test"]},"front":{"path":"front","hosts":["unit.test"]}}}"#).unwrap();
|
||||
let config = api::Config::load(&setting, &["admin", "front"]).unwrap();
|
||||
let service = Service::new(Arc::new(config), &[], &[]).unwrap();
|
||||
Self { directory, service }
|
||||
}
|
||||
|
||||
fn context<'request>(
|
||||
&'request self,
|
||||
request: &'request Request,
|
||||
site: &str,
|
||||
tenant: Option<i64>,
|
||||
entry: Option<(&'static str, &'static str)>,
|
||||
) -> Context<'request> {
|
||||
let (mut context, _) = self.service.prepare(site, request, true, 100).unwrap();
|
||||
context.identity_for_authorization_test(Identity {
|
||||
id: "test-only".into(),
|
||||
user_id: Some(7),
|
||||
tenant_id: tenant,
|
||||
});
|
||||
if let Some((component, domain)) = entry {
|
||||
context.bind_entry(component, domain);
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
async fn database(&self, scope: &Scope<'_>, name: &str, initialize: bool) -> Database {
|
||||
let database = Database::open(
|
||||
&self.directory,
|
||||
Options {
|
||||
driver: "sqlite".into(),
|
||||
path: format!("{name}.db"),
|
||||
url: None,
|
||||
tls: None,
|
||||
tenant_directory: None,
|
||||
tenant_database_prefix: None,
|
||||
min_connections: 1,
|
||||
max_connections: 1,
|
||||
max_page_size: 100,
|
||||
wait_timeout_ms: 1000,
|
||||
io_timeout_ms: 1000,
|
||||
},
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
if initialize {
|
||||
let mut executor = database.executor().await.unwrap();
|
||||
for binding in &crate::authorization::schema::BINDINGS[1..] {
|
||||
executor.initialize(scope, binding.schema).await.unwrap();
|
||||
}
|
||||
}
|
||||
database
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
std::fs::remove_dir_all(&self.directory).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn request(method: &str) -> Request {
|
||||
hyper::Request::builder()
|
||||
.method(method)
|
||||
.uri("/test")
|
||||
.body(Default::default())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn denied<Value>(result: Result<Value, Failure>) {
|
||||
assert!(matches!(result, Err(Failure::Access(403))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tenant_and_entry_guards_run_before_database_access() {
|
||||
let fixture = Fixture::new();
|
||||
let post = request("POST");
|
||||
let get = request("GET");
|
||||
Runtime::new(Default::default())
|
||||
.unwrap()
|
||||
.run(async |scope| {
|
||||
let database = fixture.database(scope, "empty", false).await;
|
||||
let mut executor = database.executor().await.unwrap();
|
||||
let mut tenant = Tenant {
|
||||
executor: &mut executor,
|
||||
id: 1,
|
||||
};
|
||||
for tenant_id in [None, Some(0), Some(-1), Some(2)] {
|
||||
let context = fixture.context(&post, "admin", tenant_id, Some(("news", "article")));
|
||||
denied(register_capability(&mut tenant, scope, &context, ID).await);
|
||||
denied(remove_capability(&mut tenant, scope, &context, ID).await);
|
||||
denied(has_protected_roles(&mut tenant, scope, &context, 7).await);
|
||||
denied(capabilities(&mut tenant, scope, &context, false).await);
|
||||
}
|
||||
for (request, entry) in [(&post, None), (&get, Some(("news", "article")))] {
|
||||
let context = fixture.context(request, "admin", Some(1), entry);
|
||||
denied(register_capability(&mut tenant, scope, &context, ID).await);
|
||||
denied(remove_capability(&mut tenant, scope, &context, ID).await);
|
||||
}
|
||||
let (mut anonymous, _) = fixture.service.prepare("admin", &post, true, 100).unwrap();
|
||||
assert!(
|
||||
anonymous
|
||||
.establish(Identity {
|
||||
id: "u".into(),
|
||||
user_id: Some(7),
|
||||
tenant_id: Some(1)
|
||||
})
|
||||
.is_err()
|
||||
);
|
||||
assert!(matches!(
|
||||
has_protected_roles(&mut tenant, scope, &anonymous, 7).await,
|
||||
Err(Failure::Access(401))
|
||||
));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capabilities_use_entry_namespace_and_distinct_physical_tenant_stores() {
|
||||
let fixture = Fixture::new();
|
||||
let post = request("POST");
|
||||
Runtime::new(Default::default())
|
||||
.unwrap()
|
||||
.run(async |scope| {
|
||||
let database = fixture.database(scope, "first", true).await;
|
||||
let mut executor = database.executor().await.unwrap();
|
||||
let mut tenant = Tenant {
|
||||
executor: &mut executor,
|
||||
id: 1,
|
||||
};
|
||||
let context = fixture.context(&post, "admin", Some(1), Some(("news", "article")));
|
||||
for _ in 0..2 {
|
||||
assert_eq!(
|
||||
register_capability(&mut tenant, scope, &context, ID)
|
||||
.await
|
||||
.unwrap(),
|
||||
KEY
|
||||
);
|
||||
}
|
||||
let other = fixture.context(&post, "admin", Some(1), Some(("other", "article")));
|
||||
let other_key = register_capability(&mut tenant, scope, &other, ID)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(other_key, KEY);
|
||||
let front = fixture.context(&post, "front", Some(1), Some(("news", "article")));
|
||||
let front_key = register_capability(&mut tenant, scope, &front, ID)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(front_key, KEY);
|
||||
let listed = capabilities(&mut tenant, scope, &context, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(listed.len(), 2);
|
||||
assert!(listed.iter().all(|value| value.site == "admin"
|
||||
&& value.kind == "capability"
|
||||
&& value.method.is_empty()));
|
||||
assert_eq!(listed[0].action, "01900000-0000-7000-8000-000000000001");
|
||||
assert_eq!(
|
||||
capabilities(&mut tenant, scope, &front, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
let second = fixture.database(scope, "second", true).await;
|
||||
let mut second_executor = second.executor().await.unwrap();
|
||||
let mut second_tenant = Tenant {
|
||||
executor: &mut second_executor,
|
||||
id: 2,
|
||||
};
|
||||
let second_context =
|
||||
fixture.context(&post, "admin", Some(2), Some(("news", "article")));
|
||||
assert!(
|
||||
capabilities(&mut second_tenant, scope, &second_context, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert_eq!(
|
||||
register_capability(&mut second_tenant, scope, &second_context, ID)
|
||||
.await
|
||||
.unwrap(),
|
||||
KEY
|
||||
);
|
||||
remove_capability(&mut tenant, scope, &context, ID)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
capabilities(&mut second_tenant, scope, &second_context, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
capabilities(&mut tenant, scope, &other, false)
|
||||
.await
|
||||
.unwrap()[0]
|
||||
.key,
|
||||
other_key
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protected_roles_span_sites_and_all_permission_roles_follow_the_active_catalog() {
|
||||
let fixture = Fixture::new();
|
||||
let post = request("POST");
|
||||
let get = request("GET");
|
||||
Runtime::new(Default::default()).unwrap().run(async |scope| {
|
||||
let database = fixture.database(scope, "roles", true).await;
|
||||
let mut executor = database.executor().await.unwrap();
|
||||
executor.initialize(scope, "INSERT INTO _dever_role VALUES ('front','owner','Owner',1,0,1),('admin','all','All',1,1,0); INSERT INTO _dever_user_role VALUES ('front',7,'owner'),('admin',7,'all');").await.unwrap();
|
||||
let mut tenant = Tenant { executor: &mut executor, id: 1 };
|
||||
let context = fixture.context(&get, "admin", Some(1), None);
|
||||
assert!(has_protected_roles(&mut tenant, scope, &context, 7).await.unwrap());
|
||||
assert!(!has_protected_roles(&mut tenant, scope, &context, 8).await.unwrap());
|
||||
assert!(matches!(has_protected_roles(&mut tenant, scope, &context, 0).await, Err(Failure::Access(400))));
|
||||
let writer = fixture.context(&post, "admin", Some(1), Some(("news", "article")));
|
||||
register_capability(&mut tenant, scope, &writer, ID).await.unwrap();
|
||||
assert_eq!(capabilities(&mut tenant, scope, &context, true).await.unwrap().len(), 1);
|
||||
remove_capability(&mut tenant, scope, &writer, ID).await.unwrap();
|
||||
assert!(capabilities(&mut tenant, scope, &context, true).await.unwrap().is_empty());
|
||||
register_capability(&mut tenant, scope, &writer, ID).await.unwrap();
|
||||
tenant.executor.execute(scope, "UPDATE _dever_role SET enabled=0 WHERE site='admin'", vec![]).await.unwrap();
|
||||
assert!(capabilities(&mut tenant, scope, &context, true).await.unwrap().is_empty());
|
||||
}).unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user