aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/access.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/access.rs')
-rw-r--r--src/api/access.rs79
1 files changed, 78 insertions, 1 deletions
diff --git a/src/api/access.rs b/src/api/access.rs
index ade6542..63aa87b 100644
--- a/src/api/access.rs
+++ b/src/api/access.rs
@@ -20,7 +20,7 @@ use crate::state::AppState;
/// What the user of a request may do (a request extension, set by `authorize`).
#[derive(Clone, Debug)]
pub(crate) enum Access {
- /// The swarm has no registry (one from before it, `access.md`): every user the
+ /// The swarm has no registry (`access.md`, A swarm without a registry): every user the
/// node lets in may do what all could before; protocol data only those in
/// `--protocol-writers` (`store::parse_write_path`).
Everything,
@@ -138,6 +138,70 @@ pub(crate) fn prune_shallow(mut value: JsonValue, path: &[String], access: &Acce
value
}
+/// The registry as a write found it (`registry_kept`): whether it existed, and whether
+/// the writing user could manage users (`registry::managers`).
+#[derive(Clone, Copy)]
+pub(crate) struct Before {
+ existed: bool,
+ managed: bool,
+}
+
+impl Before {
+ pub(crate) fn of(doc: &impl crate::document::ReadDoc, user: &str) -> Self {
+ use crate::access::registry;
+ let existed = registry::exists(doc);
+ Self {
+ existed,
+ managed: existed
+ && registry::caps_of(doc, user)
+ .allows(crate::access::Right::Write, &registry::users_dir()),
+ }
+ }
+}
+
+/// `409` for a write to `path` by `user` that would lock that user out of managing
+/// users: one that could and no longer may (deleting, disabling or narrowing its own
+/// entry), and the write that makes the registry, if it doesn't make `user` one who may
+/// (from then on only registered users are served). And for one that would remove
+/// the registry, back to a swarm without one. Checked in the write's transaction,
+/// after it: the write is dropped whole. Others are not kept from removing the last who
+/// may manage users: no node could keep two such writes at once on two nodes from
+/// leaving nobody once they merge; then the users in `--protocol-writers` may do
+/// everything again (`access::authorize`).
+pub(crate) fn registry_kept(
+ doc: &impl crate::document::ReadDoc,
+ path: &[String],
+ before: Before,
+ user: &str,
+) -> Result<(), super::ApiError> {
+ use crate::access::registry;
+ if !crate::document::store::paths_related(path, &registry::users_dir()) {
+ return Ok(());
+ }
+ let refuse = |why: &str| {
+ Err(super::ApiError(
+ StatusCode::CONFLICT,
+ Some(format!("{why} (docs/access.md)")),
+ ))
+ };
+ if !registry::exists(doc) {
+ return if before.existed {
+ refuse("this would remove the user registry")
+ } else {
+ Ok(())
+ };
+ }
+ let manages =
+ registry::caps_of(doc, user).allows(crate::access::Right::Write, &registry::users_dir());
+ if !before.existed && !manages {
+ return refuse("the registry's first users must include you, with write on _continuum");
+ }
+ if before.managed && !manages {
+ return refuse("this would keep you from managing users");
+ }
+ Ok(())
+}
+
/// What an endpoint needs (`needed`).
#[derive(Debug, PartialEq, Eq)]
enum Need {
@@ -164,6 +228,14 @@ fn needed(method: &Method, path: &str) -> Need {
Need::Observe
} else if under("/peers") {
protocol(Right::Write, &[meta::FORGOTTEN])
+ } else if under("/admin/users") {
+ // Changing them is checked by their handlers too (`users::may_manage`): in a
+ // swarm without a registry, `--protocol-writers`.
+ if looking {
+ protocol(Right::Read, &[meta::USERS])
+ } else {
+ protocol(Right::Write, &[meta::USERS])
+ }
} else if under("/admin/remakes") {
if looking {
protocol(Right::Read, &[meta::REMAKES])
@@ -296,6 +368,11 @@ mod tests {
write(&[meta::REMAKES])
);
assert_eq!(needed(&Method::GET, "/admin/config"), read(&[]));
+ assert_eq!(
+ needed(&Method::GET, "/admin/users/alice"),
+ read(&[meta::USERS])
+ );
+ assert_eq!(needed(&Method::POST, "/admin/users"), write(&[meta::USERS]));
assert_eq!(needed(&Method::GET, "/admin/quarantine/q"), read(&[]));
assert_eq!(
needed(&Method::POST, "/admin/quarantine/q/replay"),