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