Skip to main content

stoat/
permissions.rs

1use std::borrow::Cow;
2
3use async_trait::async_trait;
4use stoat_models::v0::{Channel, Member, Server, User};
5use stoat_permissions::{
6    ChannelType, DEFAULT_PERMISSION_DIRECT_MESSAGE, Override, RelationshipStatus,
7};
8
9use crate::{GlobalCache, HttpClient};
10
11pub use stoat_permissions::{
12    ChannelPermission, UserPermission, calculate_channel_permissions, calculate_server_permissions,
13    calculate_user_permissions,
14};
15
16pub struct PermissionQuery<'a> {
17    cache: GlobalCache,
18    http: HttpClient,
19
20    perspective: Cow<'a, User>,
21    user: Option<Cow<'a, User>>,
22    channel: Option<Cow<'a, Channel>>,
23    server: Option<Cow<'a, Server>>,
24    member: Option<Cow<'a, Member>>,
25}
26
27impl<'a> PermissionQuery<'a> {
28    pub fn new(cache: GlobalCache, http: HttpClient, perspective: Cow<'a, User>) -> Self {
29        Self {
30            cache,
31            http,
32            perspective,
33            user: None,
34            channel: None,
35            server: None,
36            member: None,
37        }
38    }
39
40    /// Use user
41    pub fn user(mut self, user: Cow<'a, User>) -> Self {
42        self.user = Some(user);
43
44        self
45    }
46
47    /// Use channel
48    pub fn channel(mut self, channel: Cow<'a, Channel>) -> Self {
49        self.channel = Some(channel);
50
51        self
52    }
53
54    /// Use server
55    pub fn server(mut self, server: Cow<'a, Server>) -> Self {
56        self.server = Some(server);
57
58        self
59    }
60
61    /// Use member
62    pub fn member(mut self, member: Cow<'a, Member>) -> Self {
63        self.member = Some(member);
64
65        self
66    }
67}
68
69#[async_trait]
70impl stoat_permissions::PermissionQuery for PermissionQuery<'_> {
71    async fn are_we_privileged(&mut self) -> bool {
72        self.perspective.privileged
73    }
74
75    /// Is our perspective user a bot?
76    async fn are_we_a_bot(&mut self) -> bool {
77        self.perspective.bot.is_some()
78    }
79
80    /// Is our perspective user and the currently selected user the same?
81    async fn are_the_users_same(&mut self) -> bool {
82        if let Some(other_user) = &self.user {
83            self.perspective.id == other_user.id
84        } else {
85            false
86        }
87    }
88
89    /// Get the relationship with have with the currently selected user
90    async fn user_relationship(&mut self) -> RelationshipStatus {
91        if let Some(other_user) = &self.user {
92            if self.perspective.id == other_user.id {
93                return RelationshipStatus::User;
94            } else if let Some(bot) = &other_user.bot {
95                if self.perspective.id == bot.owner_id {
96                    return RelationshipStatus::User;
97                }
98            }
99
100            for entry in &self.perspective.relations {
101                if entry.user_id == other_user.id {
102                    return match entry.status {
103                        stoat_models::v0::RelationshipStatus::None => RelationshipStatus::None,
104                        stoat_models::v0::RelationshipStatus::User => RelationshipStatus::User,
105                        stoat_models::v0::RelationshipStatus::Friend => RelationshipStatus::Friend,
106                        stoat_models::v0::RelationshipStatus::Outgoing => {
107                            RelationshipStatus::Outgoing
108                        }
109                        stoat_models::v0::RelationshipStatus::Incoming => {
110                            RelationshipStatus::Incoming
111                        }
112                        stoat_models::v0::RelationshipStatus::Blocked => {
113                            RelationshipStatus::Blocked
114                        }
115                        stoat_models::v0::RelationshipStatus::BlockedOther => {
116                            RelationshipStatus::BlockedOther
117                        }
118                    };
119                }
120            }
121        }
122
123        RelationshipStatus::None
124    }
125
126    /// Whether the currently selected user is a bot
127    async fn user_is_bot(&mut self) -> bool {
128        if let Some(other_user) = &self.user {
129            other_user.bot.is_some()
130        } else {
131            false
132        }
133    }
134
135    async fn have_mutual_connection(&mut self) -> bool {
136        true
137    }
138
139    // * For calculating server permission
140
141    /// Is our perspective user the server's owner?
142    async fn are_we_server_owner(&mut self) -> bool {
143        if let Some(server) = &self.server {
144            server.owner == self.perspective.id
145        } else {
146            false
147        }
148    }
149
150    /// Is our perspective user a member of the server?
151    async fn are_we_a_member(&mut self) -> bool {
152        if let Some(server) = &self.server {
153            if self.member.is_some() {
154                true
155            } else if let Some(member) = self.cache.get_member(&server.id, &self.perspective.id) {
156                self.member = Some(Cow::Owned(member.clone()));
157
158                true
159            } else if let Ok(member) = self
160                .http
161                .fetch_member(&server.id, &self.perspective.id)
162                .await
163            {
164                self.member = Some(Cow::Owned(member));
165                true
166            } else {
167                false
168            }
169        } else {
170            false
171        }
172    }
173
174    /// Get default server permission
175    async fn get_default_server_permissions(&mut self) -> u64 {
176        if let Some(server) = &self.server {
177            server.default_permissions as u64
178        } else {
179            0
180        }
181    }
182
183    /// Get the ordered role overrides (from lowest to highest) for this member in this server
184    async fn get_our_server_role_overrides(&mut self) -> Vec<Override> {
185        if let Some(server) = &self.server {
186            let member_roles = self
187                .member
188                .as_ref()
189                .map(|member| member.roles.clone())
190                .unwrap_or_default();
191
192            let mut roles = server
193                .roles
194                .iter()
195                .filter(|(id, _)| member_roles.contains(id))
196                .map(|(_, role)| {
197                    let v: Override = role.permissions.into();
198                    (role.rank, v)
199                })
200                .collect::<Vec<(i64, Override)>>();
201
202            roles.sort_by(|a, b| b.0.cmp(&a.0));
203            roles.into_iter().map(|(_, v)| v).collect()
204        } else {
205            vec![]
206        }
207    }
208
209    /// Is our perspective user timed out on this server?
210    async fn are_we_timed_out(&mut self) -> bool {
211        if let Some(member) = &self.member {
212            member.timeout.is_some()
213        } else {
214            false
215        }
216    }
217
218    /// Is the member muted?
219    async fn do_we_have_publish_overwrites(&mut self) -> bool {
220        self.member.as_ref().is_none_or(|member| member.can_publish)
221    }
222
223    /// Is the member deafend?
224    async fn do_we_have_receive_overwrites(&mut self) -> bool {
225        self.member.as_ref().is_none_or(|member| member.can_receive)
226    }
227
228    // * For calculating channel permission
229
230    /// Get the type of the channel
231    async fn get_channel_type(&mut self) -> ChannelType {
232        if let Some(channel) = &self.channel {
233            match channel {
234                Cow::Borrowed(Channel::DirectMessage { .. })
235                | Cow::Owned(Channel::DirectMessage { .. }) => ChannelType::DirectMessage,
236                Cow::Borrowed(Channel::Group { .. }) | Cow::Owned(Channel::Group { .. }) => {
237                    ChannelType::Group
238                }
239                Cow::Borrowed(Channel::SavedMessages { .. })
240                | Cow::Owned(Channel::SavedMessages { .. }) => ChannelType::SavedMessages,
241                Cow::Borrowed(Channel::TextChannel { .. })
242                | Cow::Owned(Channel::TextChannel { .. }) => ChannelType::ServerChannel,
243            }
244        } else {
245            ChannelType::Unknown
246        }
247    }
248
249    /// Get the default channel permissions
250    /// Group channel defaults should be mapped to an allow-only override
251    async fn get_default_channel_permissions(&mut self) -> Override {
252        if let Some(channel) = &self.channel {
253            match channel {
254                Cow::Borrowed(Channel::Group { permissions, .. })
255                | Cow::Owned(Channel::Group { permissions, .. }) => Override {
256                    allow: permissions.unwrap_or(*DEFAULT_PERMISSION_DIRECT_MESSAGE as i64) as u64,
257                    deny: 0,
258                },
259                Cow::Borrowed(Channel::TextChannel {
260                    default_permissions,
261                    ..
262                })
263                | Cow::Owned(Channel::TextChannel {
264                    default_permissions,
265                    ..
266                }) => default_permissions.unwrap_or_default().into(),
267                _ => Default::default(),
268            }
269        } else {
270            Default::default()
271        }
272    }
273
274    /// Get the ordered role overrides (from lowest to highest) for this member in this channel
275    async fn get_our_channel_role_overrides(&mut self) -> Vec<Override> {
276        if let Some(channel) = &self.channel {
277            match channel {
278                Cow::Borrowed(Channel::TextChannel {
279                    role_permissions, ..
280                })
281                | Cow::Owned(Channel::TextChannel {
282                    role_permissions, ..
283                }) => {
284                    if let Some(server) = &self.server {
285                        let member_roles = self
286                            .member
287                            .as_ref()
288                            .map(|member| member.roles.clone())
289                            .unwrap_or_default();
290
291                        let mut roles = role_permissions
292                            .iter()
293                            .filter(|(id, _)| member_roles.contains(id))
294                            .filter_map(|(id, permission)| {
295                                server.roles.get(id).map(|role| {
296                                    let v: Override = (*permission).into();
297                                    (role.rank, v)
298                                })
299                            })
300                            .collect::<Vec<(i64, Override)>>();
301
302                        roles.sort_by(|a, b| b.0.cmp(&a.0));
303                        roles.into_iter().map(|(_, v)| v).collect()
304                    } else {
305                        vec![]
306                    }
307                }
308                _ => vec![],
309            }
310        } else {
311            vec![]
312        }
313    }
314
315    /// Do we own this group or saved messages channel if it is one of those?
316    async fn do_we_own_the_channel(&mut self) -> bool {
317        if let Some(channel) = &self.channel {
318            match channel {
319                Cow::Borrowed(Channel::Group { owner, .. })
320                | Cow::Owned(Channel::Group { owner, .. }) => owner == &self.perspective.id,
321                Cow::Borrowed(Channel::SavedMessages { user, .. })
322                | Cow::Owned(Channel::SavedMessages { user, .. }) => user == &self.perspective.id,
323                _ => false,
324            }
325        } else {
326            false
327        }
328    }
329
330    /// Are we a recipient of this channel?
331    async fn are_we_part_of_the_channel(&mut self) -> bool {
332        if let Some(
333            Cow::Borrowed(Channel::DirectMessage { recipients, .. })
334            | Cow::Owned(Channel::DirectMessage { recipients, .. })
335            | Cow::Borrowed(Channel::Group { recipients, .. })
336            | Cow::Owned(Channel::Group { recipients, .. }),
337        ) = &self.channel
338        {
339            recipients.contains(&self.perspective.id)
340        } else {
341            false
342        }
343    }
344
345    /// Set the current user as the recipient of this channel
346    /// (this will only ever be called for DirectMessage channels, use unimplemented!() for other code paths)
347    async fn set_recipient_as_user(&mut self) {
348        if let Some(channel) = &self.channel {
349            match channel {
350                Cow::Borrowed(Channel::DirectMessage { recipients, .. })
351                | Cow::Owned(Channel::DirectMessage { recipients, .. }) => {
352                    let recipient_id = recipients
353                        .iter()
354                        .find(|recipient| recipient != &&self.perspective.id)
355                        .expect("Missing recipient for DM");
356
357                    if let Some(user) = self.cache.get_user(recipient_id) {
358                        self.user.replace(Cow::Owned(user.clone()));
359                    } else if let Ok(user) = self.http.fetch_user(recipient_id).await {
360                        self.user.replace(Cow::Owned(user));
361                    }
362                }
363                _ => unimplemented!(),
364            }
365        }
366    }
367
368    /// Set the current server as the server owning this channel
369    /// (this will only ever be called for server channels, use unimplemented!() for other code paths)
370    async fn set_server_from_channel(&mut self) {
371        if let Some(channel) = &self.channel {
372            match channel {
373                Cow::Borrowed(Channel::TextChannel { server, .. })
374                | Cow::Owned(Channel::TextChannel { server, .. }) => {
375                    if let Some(known_server) = self.server.as_ref().map(|server| server.as_ref()) {
376                        if server == &known_server.id {
377                            // Already cached, return early.
378                            return;
379                        }
380                    }
381
382                    if let Some(server) = self.cache.get_server(server) {
383                        self.server.replace(Cow::Owned(server.clone()));
384                    }
385                }
386                _ => unimplemented!(),
387            }
388        }
389    }
390}
391
392pub fn user_permissions_query<'a>(
393    cache: GlobalCache,
394    http: HttpClient,
395    user: Cow<'a, User>,
396) -> PermissionQuery<'a> {
397    let ourself = cache.get_current_user().unwrap();
398
399    PermissionQuery::new(cache, http, Cow::Owned(ourself)).user(user)
400}