Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

@@ -0,0 +1,87 @@
//! 行锁测试固定使用已借用连接;目录路由的真实 Context 验证由租户集成用例覆盖。
pub(super) use crate::authorization::{
disable, grant, owner, remove, revoke, roles, schema, synchronize,
};
use crate::{
auth::Context,
authorization::{Failure, Permission, access, capability, catalog, roles as storage},
database::Executor,
task::Scope,
};
pub(super) async fn permissions(
executor: &mut Executor,
scope: &Scope<'_>,
context: &Context<'_>,
current: bool,
) -> Result<Vec<Permission>, Failure> {
context.id()?;
let mut entries = catalog::read(executor, scope, context.site(), None).await?;
if let Some(id) = context.verified_tenant() {
let mut tenant = capability::Tenant::for_test(executor, id);
entries.extend(capability::capabilities(&mut tenant, scope, context, false).await?);
}
if current {
let Some(user) = context.user_id(None)? else {
return Ok(Vec::new());
};
access::filter(executor, scope, context, user, &mut entries).await?;
}
entries.sort_unstable_by(|left, right| left.key.cmp(&right.key));
Ok(entries)
}
pub(super) async fn authorize_key(
executor: &mut Executor,
scope: &Scope<'_>,
context: &Context<'_>,
key: &str,
) -> Result<(), Failure> {
context.id()?;
if !catalog::read(executor, scope, context.site(), None)
.await?
.iter()
.any(|entry| entry.key == key)
{
return Err(Failure::Access(403));
}
storage::granted(executor, scope, context, key).await
}
pub(super) async fn authorize_entry(
executor: &mut Executor,
scope: &Scope<'_>,
context: &mut Context<'_>,
component: &'static str,
domain: &'static str,
action: &str,
) -> Result<(), Failure> {
authorize_key(
executor,
scope,
context,
&format!("{component}.{domain}.{}.{action}", context.site()),
)
.await?;
context.bind_entry(component, domain);
Ok(())
}
pub(super) async fn save(
executor: &mut Executor,
scope: &Scope<'_>,
context: &Context<'_>,
id: &str,
name: &str,
all: bool,
keys: &[String],
) -> Result<(), Failure> {
context.id()?;
let catalog = catalog::read(executor, scope, context.site(), None)
.await?
.into_iter()
.map(|entry| entry.key)
.collect();
storage::save(executor, scope, context, catalog, id, name, all, keys).await
}
@@ -0,0 +1,222 @@
use super::*;
use crate::authorization::capability::{self, Tenant};
const ID: [u8; 16] = [1, 144, 0, 0, 0, 0, 112, 0, 128, 0, 0, 0, 0, 0, 0, 1];
fn context(case: &Case) -> Context<'_> {
let mut context = case.context("admin", Some(USER));
context.identity_for_authorization_test(Identity {
id: "verified-user".into(),
user_id: Some(USER),
tenant_id: Some(17),
});
context.bind_entry("news", "article");
context
}
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_capabilities_share_role_transactions_and_reregistration_drops_old_grants() {
fixture(async |case, scope| {
let context = context(case);
let mut executor = case.executor(scope).await;
let key = capability::register_capability(
&mut Tenant::for_test(&mut executor, 17),
scope,
&context,
ID,
)
.await
.unwrap();
assert_eq!(
key,
"capability:news.article.admin.01900000-0000-7000-8000-000000000001"
);
authz::save(
&mut executor,
scope,
&context,
"editor",
"Editor",
false,
std::slice::from_ref(&key),
)
.await
.unwrap();
authz::grant(&mut executor, scope, &context, USER, "editor")
.await
.unwrap();
assert_eq!(
authz::permissions(&mut executor, scope, &context, true)
.await
.unwrap()
.len(),
1
);
let transaction = executor.begin(scope).await.unwrap();
capability::remove_capability(
&mut Tenant::for_test(&mut executor, 17),
scope,
&context,
ID,
)
.await
.unwrap();
assert!(
authz::permissions(&mut executor, scope, &context, true)
.await
.unwrap()
.is_empty()
);
executor.finish(scope, transaction, false).await.unwrap();
assert_eq!(
authz::permissions(&mut executor, scope, &context, true)
.await
.unwrap()
.len(),
1
);
capability::remove_capability(
&mut Tenant::for_test(&mut executor, 17),
scope,
&context,
ID,
)
.await
.unwrap();
capability::register_capability(
&mut Tenant::for_test(&mut executor, 17),
scope,
&context,
ID,
)
.await
.unwrap();
assert!(
authz::permissions(&mut executor, scope, &context, true)
.await
.unwrap()
.is_empty()
);
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_role_permission")
.await,
0
);
authz::owner(&mut executor, scope, "front", USER)
.await
.unwrap();
assert!(
capability::has_protected_roles(
&mut Tenant::for_test(&mut executor, 17),
scope,
&context,
USER
)
.await
.unwrap()
);
assert!(
!capability::has_protected_roles(
&mut Tenant::for_test(&mut executor, 17),
scope,
&context,
USER + 1
)
.await
.unwrap()
);
assert_eq!(
authz::permissions(&mut executor, scope, &context, false)
.await
.unwrap()[0]
.kind,
"capability"
);
});
}
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_capability_removal_serializes_with_grants_in_both_lock_orders() {
fixture(async |case, scope| {
let context = context(case);
let mut first = case.executor(scope).await;
let mut second = case.executor(scope).await;
let blocker = case.pid(scope, &mut first).await;
let waiter = case.pid(scope, &mut second).await;
let key = capability::register_capability(
&mut Tenant::for_test(&mut first, 17),
scope,
&context,
ID,
)
.await
.unwrap();
let transaction = first.begin(scope).await.unwrap();
authz::save(
&mut first,
scope,
&context,
"editor",
"Editor",
false,
std::slice::from_ref(&key),
)
.await
.unwrap();
let remove = async {
capability::remove_capability(
&mut Tenant::for_test(&mut second, 17),
scope,
&context,
ID,
)
.await
};
let commit = async {
case.blocked(scope, waiter, blocker).await;
first.finish(scope, transaction, true).await.unwrap();
};
let (removed, ()) = tokio::join!(remove, commit);
removed.unwrap();
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_role_permission")
.await,
0
);
capability::register_capability(&mut Tenant::for_test(&mut first, 17), scope, &context, ID)
.await
.unwrap();
let transaction = first.begin(scope).await.unwrap();
capability::remove_capability(&mut Tenant::for_test(&mut first, 17), scope, &context, ID)
.await
.unwrap();
let grant = authz::save(
&mut second,
scope,
&context,
"late",
"Late",
false,
std::slice::from_ref(&key),
);
let commit = async {
case.blocked(scope, waiter, blocker).await;
first.finish(scope, transaction, true).await.unwrap();
};
let (granted, ()) = tokio::join!(grant, commit);
denied(granted, 400);
assert!(
authz::roles(&mut first, scope, &context, Selector::Id("late".into()))
.await
.unwrap()
.is_empty()
);
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_role_permission")
.await,
0
);
});
}
@@ -0,0 +1,140 @@
use super::*;
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_directory_sync_is_atomic_and_never_broadens_owner_permissions() {
fixture(async |case, scope| {
let mut first = case.executor(scope).await;
let mut second = case.executor(scope).await;
let context = case.context("admin", Some(USER));
authz::synchronize(&mut first, scope, &case.config, &DECLARATIONS)
.await
.unwrap();
authz::owner(&mut first, scope, "admin", USER)
.await
.unwrap();
assert_eq!(
authz::permissions(&mut first, scope, &context, false)
.await
.unwrap()
.len(),
2
);
assert_eq!(
authz::permissions(&mut first, scope, &context, true)
.await
.unwrap()
.len(),
2
);
let owner = first.begin(scope).await.unwrap();
authz::synchronize(&mut first, scope, &case.config, &[])
.await
.unwrap();
denied(
authz::authorize_key(&mut first, scope, &context, CREATE).await,
403,
);
authz::authorize_key(&mut second, scope, &context, CREATE)
.await
.unwrap();
first.finish(scope, owner, false).await.unwrap();
authz::authorize_key(&mut first, scope, &context, CREATE)
.await
.unwrap();
let duplicate = [
Declaration {
topic: "admin",
component: "news",
domain: "article",
action: "create",
method: "POST",
},
Declaration {
topic: "admin",
component: "news",
domain: "article",
action: "create",
method: "DELETE",
},
];
denied(
authz::synchronize(&mut first, scope, &case.config, &duplicate).await,
500,
);
authz::authorize_key(&mut first, scope, &context, REMOVE)
.await
.unwrap();
authz::synchronize(&mut first, scope, &case.config, &DECLARATIONS[1..])
.await
.unwrap();
denied(
authz::authorize_key(&mut first, scope, &context, CREATE).await,
403,
);
authz::authorize_key(&mut first, scope, &context, REMOVE)
.await
.unwrap();
assert_eq!(
authz::permissions(&mut first, scope, &context, true)
.await
.unwrap()
.len(),
1
);
});
}
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_startup_sync_serializes_new_permissions_before_replacing_the_catalog() {
fixture(async |case, scope| {
let mut first = case.executor(scope).await;
let mut second = case.executor(scope).await;
let context = case.context("admin", Some(USER));
let blocker = case.pid(scope, &mut first).await;
let pid = case.pid(scope, &mut second).await;
let owner = first.begin(scope).await.unwrap();
authz::synchronize(&mut first, scope, &case.config, &DECLARATIONS[..1])
.await
.unwrap();
let replace = authz::synchronize(&mut second, scope, &case.config, &DECLARATIONS[1..]);
let commit = async {
case.blocked(scope, pid, blocker).await;
first.finish(scope, owner, true).await.unwrap();
};
let (result, ()) = tokio::join!(replace, commit);
result.unwrap();
authz::owner(&mut first, scope, "admin", USER)
.await
.unwrap();
let active = authz::permissions(&mut first, scope, &context, true)
.await
.unwrap();
assert_eq!(active.len(), 1);
assert_eq!(active[0].key, REMOVE);
denied(
authz::authorize_key(&mut first, scope, &context, CREATE).await,
403,
);
denied(
authz::save(
&mut first,
scope,
&context,
"stale",
"Stale",
false,
&[CREATE.into()],
)
.await,
400,
);
assert!(
authz::roles(&mut first, scope, &context, Selector::Id("stale".into()))
.await
.unwrap()
.is_empty()
);
});
}
@@ -0,0 +1,149 @@
use super::*;
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_role_mutations_check_protection_after_acquiring_the_row_lock() {
fixture(async |case, scope| {
let context = case.context("admin", Some(USER));
for operation in 0..5 {
let id = format!("role{operation}");
let mut locker = case.executor(scope).await;
authz::save(&mut locker, scope, &context, &id, "Original", false, &[])
.await
.unwrap();
authz::grant(&mut locker, scope, &context, USER, &id)
.await
.unwrap();
let blocker = case.pid(scope, &mut locker).await;
let owner = locker.begin(scope).await.unwrap();
locker
.query(
scope,
"SELECT id FROM _dever_role WHERE site='admin' AND id=$1 FOR UPDATE",
vec![Parameter::Text(id.clone())],
1,
|row| row.get::<_, String>(0),
)
.await
.unwrap();
let mut worker = case.executor(scope).await;
let pid = case.pid(scope, &mut worker).await;
let mutation = async {
match operation {
0 => authz::save(&mut worker, scope, &context, &id, "Changed", true, &[]).await,
1 => authz::grant(&mut worker, scope, &context, USER + 1, &id).await,
2 => authz::revoke(&mut worker, scope, &context, USER, &id).await,
3 => authz::disable(&mut worker, scope, &context, &id).await,
_ => authz::remove(&mut worker, scope, &context, &id).await,
}
};
let protect = async {
case.blocked(scope, pid, blocker).await;
locker
.execute(
scope,
"UPDATE _dever_role SET protected=true WHERE site='admin' AND id=$1",
vec![Parameter::Text(id.clone())],
)
.await
.unwrap();
locker.finish(scope, owner, true).await.unwrap();
};
let (result, ()) = tokio::join!(mutation, protect);
denied(result, 403);
let role = authz::roles(&mut worker, scope, &context, Selector::Id(id))
.await
.unwrap();
assert_eq!(role[0].name, "Original");
assert!(role[0].protected && role[0].enabled && !role[0].all_permissions);
}
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_user_role")
.await,
5
);
});
}
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_concurrent_save_keeps_one_complete_role_and_owner_creation_cannot_be_overwritten() {
fixture(async |case, scope| {
let mut first = case.executor(scope).await;
let mut second = case.executor(scope).await;
authz::synchronize(&mut first, scope, &case.config, &DECLARATIONS)
.await
.unwrap();
let context = case.context("admin", Some(USER));
for index in 0..8 {
let id = format!("race{index}");
let first_keys = [CREATE.into()];
let second_keys = [REMOVE.into()];
let (left, right) = tokio::join!(
authz::save(
&mut first,
scope,
&context,
&id,
"Create",
false,
&first_keys
),
authz::save(
&mut second,
scope,
&context,
&id,
"Remove",
true,
&second_keys
),
);
left.unwrap();
right.unwrap();
let role = authz::roles(&mut first, scope, &context, Selector::Id(id))
.await
.unwrap();
assert!(
(role[0].name == "Create"
&& !role[0].all_permissions
&& role[0].permission_keys == [CREATE])
|| (role[0].name == "Remove"
&& role[0].all_permissions
&& role[0].permission_keys == [REMOVE])
);
}
let (owner, saved) = tokio::join!(
authz::owner(&mut first, scope, "admin", USER),
authz::save(
&mut second,
scope,
&context,
"owner",
"Ordinary",
false,
&[]
),
);
let role = authz::roles(&mut first, scope, &context, Selector::Id("owner".into()))
.await
.unwrap();
if owner.is_ok() {
denied(saved, 403);
assert!(role[0].protected && role[0].all_permissions && role[0].enabled);
assert_eq!(role[0].name, "Owner");
} else {
denied(owner, 409);
saved.unwrap();
assert!(!role[0].protected && !role[0].all_permissions);
assert_eq!(
case.count(
scope,
"SELECT COUNT(*) FROM _dever_user_role WHERE role_id='owner'"
)
.await,
0
);
}
});
}
@@ -0,0 +1,212 @@
use crate::{
adapter::Document,
api,
api::Request,
auth::{Context, Identity, Service},
authorization::{Declaration, Failure, Selector},
database::{Database, Error, Executor, Options, Parameter},
task::{Runtime, Scope},
};
use futures_util::FutureExt;
use std::{
panic::AssertUnwindSafe,
path::Path,
sync::Arc,
time::{Duration, SystemTime, UNIX_EPOCH},
};
#[path = "access.rs"]
mod authz;
mod capability;
mod catalog;
mod concurrent;
mod roles;
const CREATE: &str = "news.article.admin.create";
const REMOVE: &str = "news.article.admin.remove";
const USER: i64 = 4_294_967_300;
const DECLARATIONS: [Declaration; 3] = [
Declaration {
topic: "admin",
component: "news",
domain: "article",
action: "create",
method: "POST",
},
Declaration {
topic: "admin",
component: "news",
domain: "article",
action: "remove",
method: "DELETE",
},
Declaration {
topic: "front",
component: "news",
domain: "article",
action: "read",
method: "GET",
},
];
struct Case {
database: Database,
schema: String,
config: Arc<api::Config>,
service: Service,
request: Request,
}
impl Case {
async fn executor(&self, scope: &Scope<'_>) -> Executor {
let mut executor = self.database.executor().await.unwrap();
executor
.query(
scope,
"SELECT set_config('search_path',$1,false)",
vec![Parameter::Text(self.schema.clone())],
1,
|row| row.get::<_, String>(0),
)
.await
.unwrap();
executor
}
fn context(&self, site: &str, user: Option<i64>) -> Context<'_> {
let (mut context, _) = self
.service
.prepare(site, &self.request, true, 100)
.unwrap();
if let Some(user) = user {
context.identity_for_authorization_test(Identity {
id: "verified-fixture-user".into(),
user_id: Some(user),
tenant_id: None,
});
}
context
}
async fn count(&self, scope: &Scope<'_>, sql: &'static str) -> i64 {
self.executor(scope)
.await
.query(scope, sql, vec![], 1, |row| row.get::<_, i64>(0))
.await
.unwrap()[0]
}
async fn pid(&self, scope: &Scope<'_>, executor: &mut Executor) -> i64 {
executor
.query(
scope,
"SELECT CAST(pg_backend_pid() AS BIGINT)",
vec![],
1,
|row| row.get::<_, i64>(0),
)
.await
.unwrap()[0]
}
async fn blocked(&self, scope: &Scope<'_>, pid: i64, blocker: i64) {
let mut observer = self.executor(scope).await;
tokio::time::timeout(Duration::from_secs(2), async {
loop {
let waiting = observer.query(scope, "SELECT CAST(CAST($2 AS BIGINT) AS INTEGER) = ANY(pg_blocking_pids(CAST(CAST($1 AS BIGINT) AS INTEGER)))", vec![Parameter::Integer(pid), Parameter::Integer(blocker)], 1, |row| row.get::<_, bool>(0)).await.unwrap()[0];
if waiting { break; }
tokio::task::yield_now().await;
}
}).await.expect("authorization operation did not wait for its row lock");
}
}
fn denied<Value>(result: Result<Value, Failure>, expected: u16) {
assert!(matches!(result, Err(Failure::Access(status)) if status == expected));
}
fn fixture(check: impl AsyncFnOnce(&Case, &Scope<'_>)) {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../config/setting.json");
let document = Document::load(&path).expect("cannot read explicit PostgreSQL test settings");
let root: serde_json::Value = document.decode().expect("invalid PostgreSQL test settings");
let value = root
.get("database")
.and_then(|database| database.get("postgres_test"))
.expect("database.postgres_test must explicitly select the test database");
let mut options: Options = serde_json::from_value(value.clone())
.unwrap_or_else(|_| panic!("invalid PostgreSQL test options"));
assert_eq!(options.driver, "postgres");
options.max_connections = 8;
options.wait_timeout_ms = 1000;
options.io_timeout_ms = 5000;
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let schema = format!("dever_auth_test_{}_{unique}", std::process::id());
let setting = std::env::temp_dir().join(format!("{schema}.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"]);
std::fs::remove_file(setting).unwrap();
let config = Arc::new(config.unwrap());
let service = Service::new(config.clone(), &[], &[]).unwrap();
Runtime::new(Default::default())
.unwrap()
.run(async |scope| {
let database = Database::open(Path::new("."), options, scope)
.await
.unwrap();
let case = Case {
database,
schema,
config,
service,
request: hyper::Request::builder()
.method("POST")
.uri("/entry")
.body(Default::default())
.unwrap(),
};
let mut owner = case.database.executor().await.unwrap();
owner
.postgres_operation(async |client| {
client
.batch_execute(&format!("CREATE SCHEMA {}", case.schema))
.await
.map_err(Error::from)
})
.await
.unwrap();
let outcome = AssertUnwindSafe(async {
let mut executor = case.executor(scope).await;
for _ in 0..2 {
for binding in authz::schema::BINDINGS {
executor
.schema(scope, binding.table, binding.schema)
.await
.unwrap();
}
}
drop(executor);
tokio::time::timeout(Duration::from_secs(30), check(&case, scope))
.await
.expect("PostgreSQL authorization test deadline exceeded");
})
.catch_unwind()
.await;
let cleanup = owner
.postgres_operation(async |client| {
client
.batch_execute(&format!("DROP SCHEMA {} CASCADE", case.schema))
.await
.map_err(Error::from)
})
.await;
case.database.close();
cleanup.expect("PostgreSQL authorization schema cleanup failed");
if let Err(panic) = outcome {
std::panic::resume_unwind(panic);
}
})
.unwrap();
}
@@ -0,0 +1,347 @@
use super::*;
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_role_grant_revoke_disable_remove_and_entry_authorization() {
fixture(async |case, scope| {
let mut executor = case.executor(scope).await;
authz::synchronize(&mut executor, scope, &case.config, &DECLARATIONS)
.await
.unwrap();
let mut context = case.context("admin", Some(USER));
let front = case.context("front", Some(USER));
denied(
authz::authorize_entry(
&mut executor,
scope,
&mut context,
"news",
"article",
"create",
)
.await,
403,
);
assert!(context.entry_namespace().is_err());
authz::save(
&mut executor,
scope,
&context,
"editor",
"Editor",
false,
&[CREATE.into(), CREATE.into()],
)
.await
.unwrap();
for _ in 0..2 {
authz::grant(&mut executor, scope, &context, USER, "editor")
.await
.unwrap();
}
authz::authorize_entry(
&mut executor,
scope,
&mut context,
"news",
"article",
"create",
)
.await
.unwrap();
assert_eq!(context.entry_namespace().unwrap(), ("news", "article"));
denied(
authz::authorize_key(&mut executor, scope, &context, REMOVE).await,
403,
);
denied(
authz::authorize_key(&mut executor, scope, &front, CREATE).await,
403,
);
let roles = authz::roles(&mut executor, scope, &context, Selector::User(USER))
.await
.unwrap();
assert_eq!(roles.len(), 1);
assert_eq!(roles[0].permission_keys, [CREATE]);
assert!(!roles[0].all_permissions && roles[0].enabled && !roles[0].protected);
assert_eq!(
authz::permissions(&mut executor, scope, &context, true)
.await
.unwrap()[0]
.key,
CREATE
);
authz::revoke(&mut executor, scope, &context, USER, "editor")
.await
.unwrap();
denied(
authz::authorize_key(&mut executor, scope, &context, CREATE).await,
403,
);
authz::grant(&mut executor, scope, &context, USER, "editor")
.await
.unwrap();
authz::disable(&mut executor, scope, &context, "editor")
.await
.unwrap();
denied(
authz::authorize_key(&mut executor, scope, &context, CREATE).await,
403,
);
authz::save(&mut executor, scope, &context, "editor", "All", true, &[])
.await
.unwrap();
authz::authorize_key(&mut executor, scope, &context, REMOVE)
.await
.unwrap();
authz::remove(&mut executor, scope, &context, "editor")
.await
.unwrap();
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_user_role")
.await,
0
);
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_role_permission")
.await,
0
);
denied(
authz::grant(&mut executor, scope, &context, USER, "missing").await,
404,
);
denied(
authz::grant(&mut executor, scope, &context, 0, "missing").await,
400,
);
});
}
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_owner_is_explicit_protected_and_never_authorizes_unknown_keys() {
fixture(async |case, scope| {
let mut executor = case.executor(scope).await;
authz::synchronize(&mut executor, scope, &case.config, &DECLARATIONS)
.await
.unwrap();
let context = case.context("admin", Some(USER));
denied(
authz::authorize_key(&mut executor, scope, &context, CREATE).await,
403,
);
for _ in 0..2 {
authz::owner(&mut executor, scope, "admin", USER)
.await
.unwrap();
}
authz::authorize_key(&mut executor, scope, &context, CREATE)
.await
.unwrap();
denied(
authz::authorize_key(&mut executor, scope, &context, "news.article.admin.unknown")
.await,
403,
);
denied(
authz::save(
&mut executor,
scope,
&context,
"owner",
"Changed",
false,
&[],
)
.await,
403,
);
denied(
authz::disable(&mut executor, scope, &context, "owner").await,
403,
);
denied(
authz::remove(&mut executor, scope, &context, "owner").await,
403,
);
denied(
authz::grant(&mut executor, scope, &context, 8, "owner").await,
403,
);
denied(
authz::revoke(&mut executor, scope, &context, USER, "owner").await,
403,
);
let owner = authz::roles(&mut executor, scope, &context, Selector::Id("owner".into()))
.await
.unwrap();
assert_eq!(owner.len(), 1);
assert!(owner[0].protected && owner[0].all_permissions && owner[0].enabled);
let front = case.context("front", Some(USER));
authz::save(
&mut executor,
scope,
&front,
"owner",
"Ordinary",
false,
&[],
)
.await
.unwrap();
denied(authz::owner(&mut executor, scope, "front", USER).await, 409);
assert!(
authz::roles(&mut executor, scope, &front, Selector::User(USER))
.await
.unwrap()
.is_empty()
);
});
}
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_role_writes_reuse_ambient_transaction_and_roll_back_atomically() {
fixture(async |case, scope| {
let mut executor = case.executor(scope).await;
authz::synchronize(&mut executor, scope, &case.config, &DECLARATIONS)
.await
.unwrap();
let context = case.context("admin", Some(USER));
let owner = executor.begin(scope).await.unwrap();
authz::save(
&mut executor,
scope,
&context,
"editor",
"Editor",
false,
&[CREATE.into()],
)
.await
.unwrap();
authz::grant(&mut executor, scope, &context, USER, "editor")
.await
.unwrap();
authz::authorize_key(&mut executor, scope, &context, CREATE)
.await
.unwrap();
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_role").await,
0
);
denied(
authz::save(
&mut executor,
scope,
&context,
"editor",
"Invalid",
true,
&["unknown".into()],
)
.await,
400,
);
assert!(executor.finish(scope, owner, true).await.is_err());
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_role").await,
0
);
assert_eq!(
case.count(scope, "SELECT COUNT(*) FROM _dever_user_role")
.await,
0
);
authz::save(
&mut executor,
scope,
&context,
"editor",
"Editor",
false,
&[CREATE.into()],
)
.await
.unwrap();
denied(
authz::save(
&mut executor,
scope,
&context,
"editor",
"Invalid",
true,
&["news.article.front.read".into()],
)
.await,
400,
);
let role = authz::roles(&mut executor, scope, &context, Selector::All)
.await
.unwrap();
assert_eq!(role[0].name, "Editor");
assert_eq!(role[0].permission_keys, [CREATE]);
assert!(!role[0].all_permissions);
});
}
#[test]
#[ignore = "requires explicit config/setting.json database.postgres_test"]
fn postgres_missing_identity_is_rejected_before_any_database_query() {
fixture(async |case, scope| {
let mut executor = case.executor(scope).await;
executor
.postgres_operation(async |client| {
client
.batch_execute("SET search_path TO pg_catalog")
.await
.map_err(Error::from)
})
.await
.unwrap();
let mut anonymous = case.context("admin", None);
denied(
authz::roles(&mut executor, scope, &anonymous, Selector::All).await,
401,
);
denied(
authz::permissions(&mut executor, scope, &anonymous, false).await,
401,
);
denied(
authz::save(&mut executor, scope, &anonymous, "x", "X", true, &[]).await,
401,
);
denied(
authz::grant(&mut executor, scope, &anonymous, USER, "x").await,
401,
);
denied(
authz::revoke(&mut executor, scope, &anonymous, USER, "x").await,
401,
);
denied(
authz::disable(&mut executor, scope, &anonymous, "x").await,
401,
);
denied(
authz::remove(&mut executor, scope, &anonymous, "x").await,
401,
);
denied(
authz::authorize_entry(
&mut executor,
scope,
&mut anonymous,
"news",
"article",
"create",
)
.await,
401,
);
assert!(anonymous.entry_namespace().is_err());
});
}