init
This commit is contained in:
+533
@@ -0,0 +1,533 @@
|
|||||||
|
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**.
|
||||||
Reference in New Issue
Block a user