158 lines
4.2 KiB
Go
158 lines
4.2 KiB
Go
package permissions
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
// Checker verifies user permissions against the database.
|
|
type Checker struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewChecker creates a new Checker backed by the given database connection.
|
|
func NewChecker(db *sql.DB) *Checker {
|
|
return &Checker{db: db}
|
|
}
|
|
|
|
// GetUserPermissions returns the effective (OR'd) permissions for a user in a server.
|
|
// It aggregates all permissions from the roles assigned to the user, plus the
|
|
// @everyone role for that server.
|
|
func (c *Checker) GetUserPermissions(ctx context.Context, serverID string, userID string) (int64, error) {
|
|
rows, err := c.db.QueryContext(ctx, `
|
|
SELECT r.permissions
|
|
FROM roles r
|
|
INNER JOIN member_roles mr ON mr.role_id = r.id
|
|
WHERE mr.user_id = $1 AND mr.server_id = $2
|
|
`, userID, serverID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var effective int64
|
|
found := false
|
|
for rows.Next() {
|
|
var perm int64
|
|
if err := rows.Scan(&perm); err != nil {
|
|
return 0, err
|
|
}
|
|
effective |= perm
|
|
found = true
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Also include the @everyone role permissions for this server.
|
|
var everyonePerm int64
|
|
err = c.db.QueryRowContext(ctx, `
|
|
SELECT permissions FROM roles
|
|
WHERE server_id = $1 AND is_default = TRUE
|
|
LIMIT 1
|
|
`, serverID).Scan(&everyonePerm)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return 0, err
|
|
}
|
|
effective |= everyonePerm
|
|
if everyonePerm != 0 {
|
|
found = true
|
|
}
|
|
|
|
if !found {
|
|
return 0, nil
|
|
}
|
|
return effective, nil
|
|
}
|
|
|
|
// CheckPermission reports whether the user has all the required permission bits
|
|
// in the given server. ADMINISTRATOR bypasses all checks.
|
|
func (c *Checker) CheckPermission(ctx context.Context, serverID string, userID string, required int64) (bool, error) {
|
|
// Check if the user is the server owner.
|
|
var ownerID string
|
|
err := c.db.QueryRowContext(ctx, `SELECT owner_id FROM servers WHERE id = $1`, serverID).Scan(&ownerID)
|
|
if err == nil && ownerID == userID {
|
|
return true, nil
|
|
}
|
|
|
|
perms, err := c.GetUserPermissions(ctx, serverID, userID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Administrator bypasses everything.
|
|
if Has(perms, ADMINISTRATOR) {
|
|
return true, nil
|
|
}
|
|
|
|
return Has(perms, required), nil
|
|
}
|
|
|
|
// CheckChannelPermission reports whether the user has the required permission bits
|
|
// for a specific channel. It layers channel-level overrides on top of server role
|
|
// permissions. ADMINISTRATOR bypasses all checks.
|
|
func (c *Checker) CheckChannelPermission(ctx context.Context, serverID, userID, channelID string, required int64) (bool, error) {
|
|
// Check if the user is the server owner.
|
|
var ownerID string
|
|
err := c.db.QueryRowContext(ctx, `SELECT owner_id FROM servers WHERE id = $1`, serverID).Scan(&ownerID)
|
|
if err == nil && ownerID == userID {
|
|
return true, nil
|
|
}
|
|
|
|
perms, err := c.GetUserPermissions(ctx, serverID, userID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Administrator bypasses everything.
|
|
if Has(perms, ADMINISTRATOR) {
|
|
return true, nil
|
|
}
|
|
|
|
// Layer channel overrides: apply deny first (subtract), then allow (add).
|
|
// Role-based overrides from all user roles, plus the @everyone role, then user-specific override.
|
|
rows, err := c.db.QueryContext(ctx, `
|
|
SELECT co.allow_bitflags, co.deny_bitflags
|
|
FROM channel_overrides co
|
|
WHERE co.channel_id = $1 AND co.target_type = 'role'
|
|
AND (
|
|
co.target_id IN (SELECT mr.role_id FROM member_roles mr WHERE mr.user_id = $2 AND mr.server_id = $3)
|
|
OR co.target_id = (SELECT id FROM roles WHERE server_id = $3 AND is_default = TRUE LIMIT 1)
|
|
)
|
|
`, channelID, userID, serverID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
var allow, deny int64
|
|
if rows != nil {
|
|
for rows.Next() {
|
|
var a, d int64
|
|
if err := rows.Scan(&a, &d); err != nil {
|
|
rows.Close()
|
|
return false, err
|
|
}
|
|
allow |= a
|
|
deny |= d
|
|
}
|
|
rows.Close()
|
|
}
|
|
|
|
// User-specific override.
|
|
var ua, ud int64
|
|
err = c.db.QueryRowContext(ctx, `
|
|
SELECT allow_bitflags, deny_bitflags FROM channel_overrides
|
|
WHERE channel_id = $1 AND target_type = 'user' AND target_id = $2
|
|
`, channelID, userID).Scan(&ua, &ud)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return false, err
|
|
}
|
|
allow |= ua
|
|
deny |= ud
|
|
|
|
// Apply deny first, then allow.
|
|
perms = (perms &^ deny) | allow
|
|
|
|
return Has(perms, required), nil
|
|
}
|