coven_protocol/membership/
authority.rs1use super::*;
2
3impl MembershipChain {
4 pub fn can_write_now(&self, pubkey: &str) -> bool {
5 if self.conflict().is_some() {
6 return false;
7 }
8 self.active_grants_for(pubkey)
9 .iter()
10 .any(|(_, record)| record.role.can_write())
11 }
12
13 pub fn is_member_now(&self, pubkey: &str) -> bool {
23 if self.conflict().is_some() {
24 return false;
25 }
26 !self.active_grants_for(pubkey).is_empty()
27 }
28
29 pub fn is_owner_now(&self, pubkey: &str) -> bool {
30 if self.conflict().is_some() {
31 return false;
32 }
33 self.active_grants_for(pubkey)
34 .iter()
35 .any(|(_, record)| record.role.is_owner())
36 }
37
38 pub fn authorizes_write_authority(
39 &self,
40 authority: &MembershipGrantCreationAuthority,
41 pubkey: &str,
42 ) -> bool {
43 let MembershipStatus::Resolved(resolved) = self.status() else {
44 return false;
45 };
46 resolved.active_grants().any(|(_, record)| {
47 record.member_pubkey == pubkey
48 && record.role.can_write()
49 && &record.creation_authority == authority
50 })
51 }
52
53 pub fn active_grant(&self, grant_id: &MembershipGrantId) -> Option<&MembershipGrantRecord> {
54 let MembershipStatus::Resolved(resolved) = self.status() else {
55 return None;
56 };
57 resolved.active_grant(grant_id)
58 }
59
60 pub fn current_members(&self) -> Vec<(String, MemberRole)> {
61 let mut members = BTreeMap::new();
62 for state in self.state.grants.values() {
63 let Some(record) = state.active() else {
64 continue;
65 };
66 members.insert(record.member_pubkey.clone(), record.role.role());
67 }
68 members.into_iter().collect()
69 }
70
71 pub fn active_wrapped_keys_for(&self, recipient_pubkey: &str) -> Vec<WrappedStoreKeyRef> {
72 let active_grants = self.active_grant_ids(recipient_pubkey);
73 self.entries_with_coords()
74 .filter(|(coord, _)| self.included.contains(*coord))
75 .flat_map(|(_, entry)| match &entry.change {
76 MembershipChange::SetMember {
77 grant_id,
78 wrapped_key,
79 ..
80 } if active_grants.contains(grant_id) => std::slice::from_ref(wrapped_key),
81 MembershipChange::RemoveMember { wrapped_keys, .. } => wrapped_keys.as_slice(),
82 MembershipChange::Founder { .. }
83 | MembershipChange::SetMember { .. }
84 | MembershipChange::ProviderAdmin
85 | MembershipChange::ResolutionActivation { .. } => &[],
86 })
87 .filter(|reference| reference.recipient_pubkey == recipient_pubkey)
88 .cloned()
89 .collect::<BTreeSet<_>>()
90 .into_iter()
91 .collect()
92 }
93
94 pub fn wrapped_key_authority_for(
95 &self,
96 recipient_pubkey: &str,
97 ) -> Result<Vec<WrappedStoreKeyRef>, MembershipError> {
98 let active_grants = self.active_grants_for(recipient_pubkey);
99 for (index, (rotation_coord, entry)) in self
100 .entries_with_coords()
101 .enumerate()
102 .filter(|(_, (coord, _))| self.included.contains(*coord))
103 {
104 let MembershipChange::RemoveMember { wrapped_keys, .. } = &entry.change else {
105 continue;
106 };
107 if wrapped_keys
108 .iter()
109 .any(|reference| reference.recipient_pubkey == recipient_pubkey)
110 {
111 continue;
112 }
113 let rotation_generation = wrapped_keys
114 .first()
115 .ok_or(MembershipError::InvalidWrappedKeys(index))?
116 .generation;
117 let covered_by_later_grant = !active_grants.is_empty()
118 && active_grants.iter().all(|(active_grant, _)| {
119 let Some((_, creation)) = self.entries_with_coords().find(|(_, entry)| {
120 matches!(
121 &entry.change,
122 MembershipChange::SetMember { grant_id, .. }
123 if grant_id == *active_grant
124 )
125 }) else {
126 return false;
127 };
128 let MembershipChange::SetMember { wrapped_key, .. } = &creation.change else {
129 return false;
130 };
131 wrapped_key.generation >= rotation_generation
132 && causal_grants::history_closure(&self.entries, &creation.dependencies)
133 .contains(rotation_coord)
134 });
135 if !covered_by_later_grant {
136 return Err(MembershipError::MissingWrappedKeyCoverage {
137 recipient_pubkey: recipient_pubkey.to_string(),
138 rotation: Box::new(rotation_coord.clone()),
139 });
140 }
141 }
142 Ok(self.active_wrapped_keys_for(recipient_pubkey))
143 }
144
145 pub fn current_member_provider_email(&self, pubkey: &str) -> Option<&str> {
146 self.active_grants_for(pubkey)
147 .into_iter()
148 .next()
149 .and_then(|(_, record)| record.provider_account_email.as_deref())
150 }
151
152 pub fn write_grant_authority(&self, pubkey: &str) -> Option<MembershipGrantCreationAuthority> {
153 self.active_grants_for(pubkey)
154 .into_iter()
155 .find(|(_, record)| record.role.can_write())
156 .map(|(_, record)| record.creation_authority.clone())
157 }
158
159 pub fn active_grant_ids(&self, pubkey: &str) -> BTreeSet<MembershipGrantId> {
160 self.active_grants_for(pubkey)
161 .into_iter()
162 .map(|(grant, _)| grant.clone())
163 .collect()
164 }
165
166 pub fn active_owner_grant(&self, pubkey: &str) -> Option<MembershipGrantId> {
167 self.active_grants_for(pubkey)
168 .into_iter()
169 .find(|(_, record)| record.role.is_owner())
170 .map(|(grant, _)| grant.clone())
171 }
172
173 pub(super) fn membership_retirement_barriers(
174 &self,
175 grants: &BTreeSet<MembershipGrantId>,
176 device_state: Option<&StoreDeviceStateRef>,
177 ) -> Result<BTreeMap<MembershipGrantId, MergeMembershipGrantRetirementBarrier>, MembershipError>
178 {
179 let retires_owner = grants.iter().any(|grant| {
180 self.state
181 .grants
182 .get(grant)
183 .and_then(GrantState::active)
184 .is_some_and(|record| record.role.is_owner())
185 });
186 if retires_owner && device_state.is_none() {
187 return Err(MembershipError::MissingOwnerRecoveryState);
188 }
189 if !retires_owner && device_state.is_some() {
190 return Err(MembershipError::UnexpectedOwnerRecoveryState);
191 }
192 let recovery = match device_state {
193 Some(state) => state.recovery(),
194 None => &[],
195 };
196 grants
197 .iter()
198 .map(|grant| {
199 let record = self
200 .state
201 .grants
202 .get(grant)
203 .and_then(GrantState::active)
204 .ok_or_else(|| MembershipError::NotAMember(grant.to_string()))?;
205 let author_streams = StoreGrantStreamBarrier {
206 observed_streams: self
207 .effective_frontier()
208 .into_iter()
209 .filter(|coord| coord.author_owner_grant == *grant)
210 .collect(),
211 };
212 let barrier = if record.role.is_owner() {
213 let cursor = recovery
214 .iter()
215 .find(|cursor| cursor.owner_grant == *grant)
216 .cloned()
217 .ok_or(MembershipError::MissingOwnerRecoveryState)?;
218 MergeMembershipGrantRetirementBarrier::Owner {
219 barrier: MergeStoreOwnerGrantBarrier {
220 author_streams,
221 recovery: cursor,
222 },
223 }
224 } else {
225 MergeMembershipGrantRetirementBarrier::NonOwner { author_streams }
226 };
227 Ok((grant.clone(), barrier))
228 })
229 .collect()
230 }
231
232 pub(super) fn active_grants_for(
233 &self,
234 pubkey: &str,
235 ) -> Vec<(&MembershipGrantId, &MembershipGrantRecord)> {
236 self.state
237 .grants
238 .iter()
239 .filter_map(|(grant, state)| {
240 state
241 .active()
242 .filter(|record| record.member_pubkey == pubkey)
243 .map(|record| (grant, record))
244 })
245 .collect()
246 }
247}