This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-xrpc / src / members.rs
4.3 kB 140 lines
1use std::sync::Arc; 2 3use axum::body::Bytes; 4use axum::extract::State; 5use axum::response::Response; 6use http::HeaderMap; 7use serde::Deserialize; 8 9use knot_acl::{KnotAcl, can_admin_knot}; 10use knot_cob::{CobHome, CobStore}; 11use knot_cobs::{Grant, MembersChange, MembersCob, Removal}; 12use knot_events::KnotMemberUpdate; 13use knot_git::Repo; 14use knot_index::Resolved; 15use knot_runtime::{Clock, HttpTransport}; 16use knot_types::AccountDid; 17 18use crate::cob::grant_set_apply; 19use crate::error::XrpcError; 20use crate::{XrpcState, decode, ok_empty, run_blocking}; 21 22pub(crate) const ADD_ROUTE: &str = "/xrpc/sh.tangled.knot.addMember"; 23pub(crate) const REMOVE_ROUTE: &str = "/xrpc/sh.tangled.knot.removeMember"; 24 25#[derive(Deserialize)] 26pub(crate) struct SubjectInput { 27 pub(crate) subject: AccountDid, 28} 29 30pub(crate) async fn add_member<H: HttpTransport, C: Clock>( 31 State(state): State<Arc<XrpcState<H, C>>>, 32 headers: HeaderMap, 33 method: crate::Method, 34 body: Bytes, 35) -> Result<Response, XrpcError> { 36 let actor = state.authenticate(&headers, &method).await?; 37 let acl = KnotAcl::new(&state.admins, state.admission, &state.index); 38 if !can_admin_knot(&acl, &actor).is_allowed() { 39 return Err(XrpcError::forbidden("only knot admin may add members")); 40 } 41 42 let SubjectInput { subject } = decode(&body)?; 43 grant_membership( 44 &state, 45 Grant { 46 subject, 47 added_by: actor, 48 created_at: state.now(), 49 }, 50 ) 51 .await 52} 53 54pub(crate) async fn grant_membership<H: HttpTransport, C: Clock>( 55 state: &Arc<XrpcState<H, C>>, 56 grant: Grant, 57) -> Result<Response, XrpcError> { 58 if state.admins.contains(&grant.subject) 59 || matches!(state.index.is_member(&grant.subject), Resolved::Ready(true)) 60 { 61 return Ok(ok_empty()); 62 } 63 64 let now = grant.created_at; 65 let event_subject = grant.subject.clone(); 66 let signer = state.secrets.signer(&state.knot_did)?; 67 let meta_path = state.meta_path.clone(); 68 let index = Arc::clone(&state.index); 69 let cob_locks = Arc::clone(&state.cob_locks); 70 let events = Arc::clone(&state.events); 71 let home = CobHome::from(&state.knot_did); 72 run_blocking(move || { 73 let _guard = cob_locks.meta(); 74 let meta = Repo::open(&meta_path)?; 75 let changed = grant_set_apply::<MembersCob>( 76 &CobStore::new(&meta), 77 &home, 78 MembersChange::Add(grant), 79 &signer, 80 now, 81 true, 82 )?; 83 index.refresh_members()?; 84 if changed { 85 events.publish(&KnotMemberUpdate::added(event_subject)); 86 } 87 Ok(()) 88 }) 89 .await?; 90 91 Ok(ok_empty()) 92} 93 94pub(crate) async fn remove_member<H: HttpTransport, C: Clock>( 95 State(state): State<Arc<XrpcState<H, C>>>, 96 headers: HeaderMap, 97 method: crate::Method, 98 body: Bytes, 99) -> Result<Response, XrpcError> { 100 let actor = state.authenticate(&headers, &method).await?; 101 let acl = KnotAcl::new(&state.admins, state.admission, &state.index); 102 if !can_admin_knot(&acl, &actor).is_allowed() { 103 return Err(XrpcError::forbidden("only knot admin may remove members")); 104 } 105 106 let SubjectInput { subject } = decode(&body)?; 107 if matches!(state.index.is_member(&subject), Resolved::Ready(false)) { 108 return Ok(ok_empty()); 109 } 110 111 let now = state.now(); 112 let event_subject = subject.clone(); 113 let removal = Removal { subject }; 114 let signer = state.secrets.signer(&state.knot_did)?; 115 let meta_path = state.meta_path.clone(); 116 let index = Arc::clone(&state.index); 117 let cob_locks = Arc::clone(&state.cob_locks); 118 let events = Arc::clone(&state.events); 119 let home = CobHome::from(&state.knot_did); 120 run_blocking(move || { 121 let _guard = cob_locks.meta(); 122 let meta = Repo::open(&meta_path)?; 123 let changed = grant_set_apply::<MembersCob>( 124 &CobStore::new(&meta), 125 &home, 126 MembersChange::Remove(removal), 127 &signer, 128 now, 129 false, 130 )?; 131 index.refresh_members()?; 132 if changed { 133 events.publish(&KnotMemberUpdate::removed(event_subject)); 134 } 135 Ok(()) 136 }) 137 .await?; 138 139 Ok(ok_empty()) 140}