Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,335 @@
|
||||
use crate::{
|
||||
api,
|
||||
api::Request,
|
||||
auth::{Context, Identity, Service},
|
||||
authorization::{
|
||||
self as authz, Declaration, Failure,
|
||||
capability::{self, Tenant},
|
||||
},
|
||||
database::{Binding, Context as DatabaseContext, Registry, Sql, Storage},
|
||||
task::Runtime,
|
||||
};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
pub(super) struct Fixture {
|
||||
directory: PathBuf,
|
||||
pub(super) setting: PathBuf,
|
||||
config: Arc<api::Config>,
|
||||
service: Service,
|
||||
request: Request,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
pub(super) fn new() -> Self {
|
||||
let unique = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let name = format!("dever-tenant-auth-{}-{unique}", std::process::id());
|
||||
let executable = std::env::current_exe().unwrap();
|
||||
let directory = executable.parent().unwrap().join(&name);
|
||||
std::fs::create_dir(&directory).unwrap();
|
||||
let setting = directory.join("setting.json");
|
||||
std::fs::write(&setting, serde_json::json!({
|
||||
"http":{"host":"127.0.0.1","port":8181},
|
||||
"sites":{"admin":{"path":"admin","hosts":["unit.test"]}},
|
||||
"database":{"default":{"type":"sqlite","path":format!("{name}/control.db"),"tenant_directory":format!("{name}/tenants"),"max_connections":1}},
|
||||
"tenant":{"database":"default","max_pools":4,"idle_timeout_ms":1000}
|
||||
}).to_string()).unwrap();
|
||||
let config = Arc::new(api::Config::load(&setting, &["admin"]).unwrap());
|
||||
let service = Service::new(config.clone(), &[], &[]).unwrap();
|
||||
Self {
|
||||
directory,
|
||||
setting,
|
||||
config,
|
||||
service,
|
||||
request: hyper::Request::builder()
|
||||
.method("POST")
|
||||
.uri("/news/article/admin/create")
|
||||
.body(Default::default())
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn identity(&self, tenant: i64, user: i64) -> Context<'_> {
|
||||
let (mut context, _) = self
|
||||
.service
|
||||
.prepare("admin", &self.request, true, 100)
|
||||
.unwrap();
|
||||
context.identity_for_authorization_test(Identity {
|
||||
id: "verified-user".into(),
|
||||
user_id: Some(user),
|
||||
tenant_id: Some(tenant),
|
||||
});
|
||||
context
|
||||
}
|
||||
|
||||
async fn registry(&self, scope: &crate::task::Scope<'_>) -> Arc<Registry> {
|
||||
let mut bindings = authz::schema::BINDINGS.to_vec();
|
||||
bindings.push(Binding {
|
||||
storage: Storage::Tenant,
|
||||
component: "news",
|
||||
database: None,
|
||||
table: "article",
|
||||
schema: Sql::same("CREATE TABLE article(id BIGINT PRIMARY KEY)"),
|
||||
plan: None,
|
||||
references: &[],
|
||||
});
|
||||
let registry = Registry::load(&self.setting, &bindings, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
registry.migrate_tenant(scope, 17).await.unwrap();
|
||||
registry.migrate_tenant(scope, 18).await.unwrap();
|
||||
let database = DatabaseContext::new(registry.clone());
|
||||
let mut control = database.control(scope).await.unwrap();
|
||||
authz::synchronize(
|
||||
&mut control,
|
||||
scope,
|
||||
&self.config,
|
||||
&[Declaration {
|
||||
topic: "admin",
|
||||
component: "news",
|
||||
domain: "article",
|
||||
action: "create",
|
||||
method: "POST",
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
registry
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
std::fs::remove_dir_all(&self.directory).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorization_routes_control_catalog_and_tenant_roles_without_cross_database_joins() {
|
||||
let fixture = Fixture::new();
|
||||
Runtime::new(Default::default())
|
||||
.unwrap()
|
||||
.run(async |scope| routing(&fixture, scope).await)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn routing(fixture: &Fixture, scope: &crate::task::Scope<'_>) {
|
||||
let registry = fixture.registry(scope).await;
|
||||
let mut first = DatabaseContext::new(registry.clone());
|
||||
first.select_tenant(scope, 17).await.unwrap();
|
||||
let mut second = DatabaseContext::new(registry.clone());
|
||||
second.select_tenant(scope, 18).await.unwrap();
|
||||
authz::owner(first.on(1, scope).await.unwrap(), scope, "admin", 7)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut actor = fixture.identity(17, 7);
|
||||
authz::authorize_entry(
|
||||
&mut first, 1, scope, &mut actor, "news", "article", "create",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(actor.entry_namespace().unwrap(), ("news", "article"));
|
||||
let tenant_tables = first
|
||||
.on(1, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(
|
||||
scope,
|
||||
Sql {
|
||||
sqlite: "SELECT COUNT(*) FROM sqlite_master WHERE name='_dever_permission'",
|
||||
postgres: "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_name='_dever_permission'",
|
||||
},
|
||||
vec![],
|
||||
1,
|
||||
|row| row.get::<_, i64>(0),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(tenant_tables, [0]);
|
||||
let key = capability::register_capability(
|
||||
&mut Tenant::open(&mut first, 1, scope, &actor).await.unwrap(),
|
||||
scope,
|
||||
&actor,
|
||||
[1; 16],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
authz::save(
|
||||
&mut first,
|
||||
1,
|
||||
scope,
|
||||
&actor,
|
||||
"editor",
|
||||
"Editor",
|
||||
false,
|
||||
std::slice::from_ref(&key),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
authz::grant(
|
||||
first.on(1, scope).await.unwrap(),
|
||||
scope,
|
||||
&actor,
|
||||
8,
|
||||
"editor",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let reader = fixture.identity(17, 8);
|
||||
let permissions = authz::permissions(&mut first, 1, scope, &reader, true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(permissions.len(), 1);
|
||||
assert_eq!(permissions[0].key, key);
|
||||
let other = fixture.identity(18, 8);
|
||||
assert!(
|
||||
authz::permissions(&mut second, 1, scope, &other, true)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert!(matches!(
|
||||
Tenant::open(&mut second, 1, scope, &actor).await,
|
||||
Err(Failure::Access(403))
|
||||
));
|
||||
assert_eq!(
|
||||
authz::permissions(&mut first, 1, scope, &actor, true)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
2
|
||||
);
|
||||
registry
|
||||
.set_component(scope, 17, "news", false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
authz::authorize_key(&mut first, 1, scope, &actor, "news.article.admin.create").await,
|
||||
Err(Failure::Access(403))
|
||||
));
|
||||
assert!(
|
||||
authz::permissions(&mut first, 1, scope, &actor, true)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
registry
|
||||
.set_component(scope, 17, "news", true)
|
||||
.await
|
||||
.unwrap();
|
||||
authz::authorize_key(&mut first, 1, scope, &actor, "news.article.admin.create")
|
||||
.await
|
||||
.unwrap();
|
||||
drop(first);
|
||||
drop(second);
|
||||
registry.close().await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_role_and_model_changes_share_rollback_and_reject_platform_transactions() {
|
||||
let fixture = Fixture::new();
|
||||
Runtime::new(Default::default())
|
||||
.unwrap()
|
||||
.run(async |scope| rollback(&fixture, scope).await)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn rollback(fixture: &Fixture, scope: &crate::task::Scope<'_>) {
|
||||
let registry = fixture.registry(scope).await;
|
||||
let mut database = DatabaseContext::new(registry.clone());
|
||||
database.select_tenant(scope, 17).await.unwrap();
|
||||
authz::owner(database.on(1, scope).await.unwrap(), scope, "admin", 7)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut actor = fixture.identity(17, 7);
|
||||
authz::authorize_entry(
|
||||
&mut database,
|
||||
1,
|
||||
scope,
|
||||
&mut actor,
|
||||
"news",
|
||||
"article",
|
||||
"create",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let transaction = database.enter();
|
||||
database
|
||||
.on(5, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.execute(scope, "INSERT INTO article VALUES (1)", vec![])
|
||||
.await
|
||||
.unwrap();
|
||||
let key = capability::register_capability(
|
||||
&mut Tenant::open(&mut database, 1, scope, &actor).await.unwrap(),
|
||||
scope,
|
||||
&actor,
|
||||
[2; 16],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
authz::save(
|
||||
&mut database,
|
||||
1,
|
||||
scope,
|
||||
&actor,
|
||||
"rolled-back",
|
||||
"Rollback",
|
||||
false,
|
||||
&[key],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
authz::permissions(&mut database, 1, scope, &actor, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
2
|
||||
);
|
||||
assert!(database.on(0, scope).await.is_err());
|
||||
database.finish(scope, transaction, false).await.unwrap();
|
||||
assert_eq!(
|
||||
authz::permissions(&mut database, 1, scope, &actor, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert!(
|
||||
authz::roles(
|
||||
database.on(1, scope).await.unwrap(),
|
||||
scope,
|
||||
&actor,
|
||||
authz::Selector::Id("rolled-back".into())
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert_eq!(
|
||||
database
|
||||
.on(5, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT COUNT(*) FROM article", vec![], 1, |row| row
|
||||
.get::<_, i64>(
|
||||
0
|
||||
))
|
||||
.await
|
||||
.unwrap(),
|
||||
[0]
|
||||
);
|
||||
let mut missing = DatabaseContext::new(registry.clone());
|
||||
assert!(missing.select_tenant(scope, 19).await.is_err());
|
||||
drop(missing);
|
||||
drop(database);
|
||||
registry.close().await;
|
||||
}
|
||||
Reference in new issue
Block a user