533 lines
9.4 KiB
Markdown
533 lines
9.4 KiB
Markdown
Oui. Si tu gardes les bitflags, je simplifierais tes modèles autour de cette règle :
|
||
|
||
> Les permissions appartiennent aux **groupes** ou sont accordées directement à un **membre**, et chaque attribution
|
||
> possède un périmètre.
|
||
|
||
Je ne mettrais plus les permissions par défaut directement sur `server` et `channel`.
|
||
|
||
## 1. `user`
|
||
|
||
Je garderais quasiment ton modèle actuel :
|
||
|
||
```plain text
|
||
user
|
||
----
|
||
id
|
||
username
|
||
password
|
||
pub_key
|
||
created_at
|
||
updated_at
|
||
is_superuser
|
||
```
|
||
|
||
`is_superuser` reste un bypass global, indépendant des permissions d’un serveur.
|
||
|
||
Je ne mettrais pas de permissions directement dans `user`, car les permissions doivent être contextuelles à un serveur.
|
||
|
||
## 2. `server`
|
||
|
||
Je supprimerais :
|
||
|
||
```plain text
|
||
default_server_permissions
|
||
default_channel_permissions
|
||
default_voice_permissions
|
||
```
|
||
|
||
Le serveur ne devrait pas porter directement les permissions. Il possède plutôt des groupes.
|
||
|
||
```plain text
|
||
server
|
||
------
|
||
id
|
||
name
|
||
password
|
||
created_at
|
||
updated_at
|
||
is_default
|
||
```
|
||
|
||
Les permissions par défaut seraient celles du groupe système `Everyone`.
|
||
|
||
## 3. `server_user`
|
||
|
||
Je garderais cette table pour l’appartenance d’un utilisateur à un serveur, mais je supprimerais probablement :
|
||
|
||
```plain text
|
||
is_admin
|
||
is_owner
|
||
server_permissions
|
||
channel_permissions
|
||
voice_permissions
|
||
```
|
||
|
||
Je remplacerais les statuts par des groupes :
|
||
|
||
```plain text
|
||
server_user
|
||
-----------
|
||
id
|
||
server_id
|
||
user_id
|
||
username
|
||
joined_at
|
||
updated_at
|
||
```
|
||
|
||
Puis :
|
||
|
||
```plain text
|
||
group_member
|
||
------------
|
||
group_id
|
||
user_id
|
||
```
|
||
|
||
Par exemple :
|
||
|
||
```plain text
|
||
Everyone
|
||
Membre
|
||
Modérateur
|
||
Administrateur
|
||
```
|
||
|
||
L’utilisateur propriétaire pourrait rester une exception structurelle :
|
||
|
||
```plain text
|
||
server.owner_id
|
||
```
|
||
|
||
ou être représenté par un groupe système très privilégié. Personnellement, je conserverais `owner_id` pour éviter qu’un
|
||
propriétaire perde accidentellement ses droits.
|
||
|
||
## 4. `group`
|
||
|
||
Ta table actuelle est déjà proche de ce qu’il faut :
|
||
|
||
```plain text
|
||
group
|
||
-----
|
||
id
|
||
server_id
|
||
name
|
||
is_default
|
||
created_at
|
||
updated_at
|
||
```
|
||
|
||
Je garderais les trois bitmasks :
|
||
|
||
```plain text
|
||
server_permissions
|
||
channel_permissions
|
||
voice_permissions
|
||
```
|
||
|
||
Donc :
|
||
|
||
```rust
|
||
pub struct Model {
|
||
pub id: Uuid,
|
||
pub server_id: Uuid,
|
||
pub name: String,
|
||
pub is_default: bool,
|
||
pub server_permissions: i64,
|
||
pub channel_permissions: i64,
|
||
pub voice_permissions: i64,
|
||
pub created_at: DateTimeUtc,
|
||
}
|
||
```
|
||
|
||
Chaque groupe est alors un rôle contenant un ensemble de permissions.
|
||
|
||
Exemples :
|
||
|
||
```plain text
|
||
Everyone :
|
||
READ_CHANNEL
|
||
SEND_MESSAGE
|
||
JOIN_CHANNEL
|
||
SPEAK
|
||
|
||
Modérateur :
|
||
DELETE_OTHERS_MESSAGES
|
||
MANAGE_MESSAGES
|
||
MUTE_OTHERS
|
||
```
|
||
|
||
## 5. `group_member`
|
||
|
||
C’est ici que j’ajouterais le périmètre.
|
||
|
||
Actuellement, un groupe est uniquement attribué à un utilisateur de manière globale au serveur :
|
||
|
||
```plain text
|
||
group_id
|
||
user_id
|
||
```
|
||
|
||
Pour permettre à un modérateur d’agir seulement dans une catégorie ou un canal, il faut ajouter un scope.
|
||
|
||
### Option que je recommande
|
||
|
||
```plain text
|
||
group_member
|
||
------------
|
||
group_id
|
||
user_id
|
||
scope_type
|
||
scope_id
|
||
```
|
||
|
||
Exemples :
|
||
|
||
```plain text
|
||
Modérateur -> Alice -> server -> serveur A
|
||
Modérateur -> Bob -> category -> catégorie Support
|
||
Modérateur -> Claire -> channel -> canal Général
|
||
```
|
||
|
||
Avec un enum :
|
||
|
||
```rust
|
||
pub enum PermissionScopeType {
|
||
Server,
|
||
Category,
|
||
Channel,
|
||
}
|
||
```
|
||
|
||
Le problème est que `scope_id` peut pointer vers plusieurs tables. Il faudra donc valider la cohérence côté application,
|
||
ou utiliser trois colonnes nullable.
|
||
|
||
### Variante plus propre SQL
|
||
|
||
```plain text
|
||
group_member
|
||
------------
|
||
group_id
|
||
user_id
|
||
server_id
|
||
category_id
|
||
channel_id
|
||
```
|
||
|
||
Avec la règle :
|
||
|
||
```plain text
|
||
exactement une portée est définie
|
||
```
|
||
|
||
Mais cette variante est plus lourde à manipuler.
|
||
|
||
Pour un projet avec SeaORM, je choisirais probablement `scope_type + scope_id`, avec validation dans le service de
|
||
permissions.
|
||
|
||
## 6. Permissions individuelles
|
||
|
||
Tu as deux possibilités.
|
||
|
||
### Option simple : les stocker dans `server_user`
|
||
|
||
Pour une permission individuelle valable sur tout le serveur :
|
||
|
||
```plain text
|
||
server_user
|
||
-----------
|
||
server_permissions
|
||
channel_permissions
|
||
voice_permissions
|
||
```
|
||
|
||
Cela permet :
|
||
|
||
```plain text
|
||
Alice possède individuellement SEND_MESSAGE sur le serveur
|
||
```
|
||
|
||
Mais cette solution ne permet pas facilement une permission individuelle limitée à une catégorie ou un canal.
|
||
|
||
### Option plus flexible : créer `user_permission_scope`
|
||
|
||
Je recommande cette table :
|
||
|
||
```plain text
|
||
user_permission
|
||
---------------
|
||
id
|
||
user_id
|
||
server_id
|
||
category_id nullable
|
||
channel_id nullable
|
||
server_permissions
|
||
channel_permissions
|
||
voice_permissions
|
||
created_at
|
||
```
|
||
|
||
Exemples :
|
||
|
||
```plain text
|
||
Alice -> SEND_MESSAGE -> serveur A
|
||
Bob -> DELETE_OTHERS_MESSAGES -> catégorie Support
|
||
Claire -> SEND_MESSAGE -> canal Général
|
||
```
|
||
|
||
La colonne `server_id` est utile pour garantir que toutes les ressources appartiennent au bon serveur.
|
||
|
||
## 7. Que faire de `channel_user` ?
|
||
|
||
Ta table actuelle contient :
|
||
|
||
```plain text
|
||
channel_user
|
||
------------
|
||
channel_id
|
||
user_id
|
||
role
|
||
permissions
|
||
```
|
||
|
||
Je ne garderais pas `role` et `permissions` sous cette forme si tu as déjà `group` et `group_member`.
|
||
|
||
Je l’utiliserais uniquement pour représenter une relation particulière au canal, par exemple :
|
||
|
||
```plain text
|
||
channel_user
|
||
------------
|
||
id
|
||
channel_id
|
||
user_id
|
||
joined_at
|
||
```
|
||
|
||
Ou je la supprimerais complètement si l’accès au canal est déduit des permissions et de l’appartenance au serveur.
|
||
|
||
Pour les permissions, je préfère `user_permission`, qui couvre déjà :
|
||
|
||
- serveur ;
|
||
- catégorie ;
|
||
- canal.
|
||
|
||
Sinon tu risques d’avoir trois systèmes concurrents :
|
||
|
||
```plain text
|
||
server_user.permissions
|
||
channel_user.permissions
|
||
group.permissions
|
||
```
|
||
|
||
et le calcul deviendra difficile à maintenir.
|
||
|
||
## 8. Que faire des permissions de `channel` ?
|
||
|
||
Je supprimerais :
|
||
|
||
```plain text
|
||
default_channel_permissions
|
||
default_voice_permissions
|
||
```
|
||
|
||
Le rôle par défaut `Everyone` du serveur les remplace.
|
||
|
||
Si tu veux qu’un canal accorde des permissions supplémentaires, tu peux utiliser une table dédiée :
|
||
|
||
```plain text
|
||
channel_permission_grant
|
||
------------------------
|
||
channel_id
|
||
group_id nullable
|
||
user_id nullable
|
||
server_permissions
|
||
channel_permissions
|
||
voice_permissions
|
||
```
|
||
|
||
Mais je ne l’ajouterais pas immédiatement. Commence avec :
|
||
|
||
```plain text
|
||
permissions serveur
|
||
permissions de groupe
|
||
permissions individuelles
|
||
```
|
||
|
||
Puis ajoute les overrides de canal uniquement si le besoin apparaît réellement.
|
||
|
||
## Structure que je choisirais maintenant
|
||
|
||
```plain text
|
||
user
|
||
server
|
||
category
|
||
channel
|
||
|
||
server_user
|
||
group
|
||
group_member
|
||
user_permission
|
||
```
|
||
|
||
### `server_user`
|
||
|
||
Appartenance au serveur :
|
||
|
||
```plain text
|
||
user_id
|
||
server_id
|
||
joined_at
|
||
```
|
||
|
||
### `group`
|
||
|
||
Permissions d’un rôle :
|
||
|
||
```plain text
|
||
id
|
||
server_id
|
||
name
|
||
is_default
|
||
server_permissions
|
||
channel_permissions
|
||
voice_permissions
|
||
```
|
||
|
||
### `group_member`
|
||
|
||
Attribution d’un rôle avec périmètre :
|
||
|
||
```plain text
|
||
group_id
|
||
user_id
|
||
scope_type
|
||
scope_id
|
||
```
|
||
|
||
### `user_permission`
|
||
|
||
Permission individuelle avec périmètre :
|
||
|
||
```plain text
|
||
id
|
||
user_id
|
||
server_id
|
||
scope_type
|
||
scope_id
|
||
server_permissions
|
||
channel_permissions
|
||
voice_permissions
|
||
```
|
||
|
||
## Calcul des permissions
|
||
|
||
Pour un utilisateur et un canal donné :
|
||
|
||
```plain text
|
||
permissions effectives =
|
||
permissions des groupes applicables
|
||
| permissions individuelles applicables
|
||
```
|
||
|
||
Les groupes applicables sont ceux dont le scope est :
|
||
|
||
```plain text
|
||
serveur parent
|
||
catégorie parente
|
||
canal courant
|
||
```
|
||
|
||
Exemple :
|
||
|
||
```plain text
|
||
Alice
|
||
Everyone sur le serveur
|
||
Modérateur sur la catégorie Support
|
||
permission individuelle sur le canal Général
|
||
```
|
||
|
||
Dans un canal de `Support`, elle reçoit :
|
||
|
||
```plain text
|
||
permissions Everyone
|
||
| permissions Modérateur
|
||
| permissions individuelles du canal
|
||
```
|
||
|
||
## Problème important avec `is_admin`
|
||
|
||
Je supprimerais `is_admin` de `server_user`.
|
||
|
||
Un administrateur devient simplement un groupe :
|
||
|
||
```plain text
|
||
Administrateur
|
||
```
|
||
|
||
avec les permissions correspondantes.
|
||
|
||
En revanche, je garderais probablement :
|
||
|
||
```plain text
|
||
server.owner_id
|
||
```
|
||
|
||
car le propriétaire est une relation métier particulière, pas seulement un ensemble de permissions.
|
||
|
||
## À propos de `i64`
|
||
|
||
Puisque tu stockes des valeurs `u64` dans des colonnes `i64`, je ferais au minimum une abstraction claire côté Rust :
|
||
|
||
```rust
|
||
fn permissions_from_db(value: i64) -> u64 {
|
||
value as u64
|
||
}
|
||
```
|
||
|
||
Et j’éviterais de disperser les conversions dans les modèles.
|
||
|
||
Si tu ne comptes pas utiliser le bit de poids fort, tu peux aussi décider officiellement que seuls les 63 premiers bits
|
||
sont valides.
|
||
|
||
## Recommandation finale
|
||
|
||
Je modifierais ton système comme ceci :
|
||
|
||
```plain text
|
||
Supprimer :
|
||
permissions de server
|
||
permissions de channel
|
||
is_admin de server_user
|
||
permissions de channel_user
|
||
role texte de channel_user
|
||
|
||
Garder :
|
||
is_superuser sur user
|
||
owner_id sur server
|
||
trois bitflags sur group
|
||
|
||
Ajouter :
|
||
scope aux attributions de groupes
|
||
table user_permission pour les permissions individuelles
|
||
```
|
||
|
||
Le modèle deviendrait :
|
||
|
||
```plain text
|
||
User
|
||
├── appartient à des serveurs
|
||
├── reçoit des groupes
|
||
└── peut recevoir des permissions individuelles
|
||
|
||
Group
|
||
└── possède trois bitflags
|
||
|
||
GroupMember
|
||
└── associe un groupe à un utilisateur dans un scope
|
||
|
||
UserPermission
|
||
└── associe directement des bitflags à un utilisateur dans un scope
|
||
```
|
||
|
||
C’est le compromis que je choisirais : **bitflags conservés, groupes réutilisables, permissions individuelles possibles,
|
||
et modération limitée par scope**. |