Initial commit: breakpilot-core - Shared Infrastructure
Docker Compose with 24+ services: - PostgreSQL (PostGIS), Valkey, MinIO, Qdrant - Vault (PKI/TLS), Nginx (Reverse Proxy) - Backend Core API, Consent Service, Billing Service - RAG Service, Embedding Service - Gitea, Woodpecker CI/CD - Night Scheduler, Health Aggregator - Jitsi (Web/XMPP/JVB/Jicofo), Mailpit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
403
consent-service/internal/session/rbac_middleware.go
Normal file
403
consent-service/internal/session/rbac_middleware.go
Normal file
@@ -0,0 +1,403 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Employee permissions
|
||||
var EmployeePermissions = []string{
|
||||
"grades:read", "grades:write",
|
||||
"attendance:read", "attendance:write",
|
||||
"students:read", "students:write",
|
||||
"reports:generate", "consent:admin",
|
||||
"corrections:read", "corrections:write",
|
||||
"classes:read", "classes:write",
|
||||
"timetable:read", "timetable:write",
|
||||
"substitutions:read", "substitutions:write",
|
||||
"parent_communication:read", "parent_communication:write",
|
||||
}
|
||||
|
||||
// Customer permissions
|
||||
var CustomerPermissions = []string{
|
||||
"own_data:read", "own_grades:read", "own_attendance:read",
|
||||
"consent:manage",
|
||||
"meetings:join",
|
||||
"messages:read", "messages:write",
|
||||
"children:read", "children:grades:read", "children:attendance:read",
|
||||
}
|
||||
|
||||
// Admin permissions
|
||||
var AdminPermissions = []string{
|
||||
"users:read", "users:write", "users:manage",
|
||||
"rbac:read", "rbac:write",
|
||||
"audit:read",
|
||||
"settings:read", "settings:write",
|
||||
"dsr:read", "dsr:process",
|
||||
}
|
||||
|
||||
// Employee roles
|
||||
var EmployeeRoles = map[string]bool{
|
||||
"admin": true,
|
||||
"schul_admin": true,
|
||||
"schulleitung": true,
|
||||
"pruefungsvorsitz": true,
|
||||
"klassenlehrer": true,
|
||||
"fachlehrer": true,
|
||||
"sekretariat": true,
|
||||
"erstkorrektor": true,
|
||||
"zweitkorrektor": true,
|
||||
"drittkorrektor": true,
|
||||
"teacher_assistant": true,
|
||||
"teacher": true,
|
||||
"lehrer": true,
|
||||
"data_protection_officer": true,
|
||||
}
|
||||
|
||||
// Customer roles
|
||||
var CustomerRoles = map[string]bool{
|
||||
"parent": true,
|
||||
"student": true,
|
||||
"user": true,
|
||||
"guardian": true,
|
||||
}
|
||||
|
||||
// RequireEmployee requires the user to be an employee
|
||||
func RequireEmployee() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !session.IsEmployee() {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "Employee access required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireCustomer requires the user to be a customer
|
||||
func RequireCustomer() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !session.IsCustomer() {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "Customer access required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireUserType requires a specific user type
|
||||
func RequireUserType(userType UserType) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if session.UserType != userType {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "User type '" + string(userType) + "' required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequirePermission requires a specific permission
|
||||
func RequirePermission(permission string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !session.HasPermission(permission) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "Permission '" + permission + "' required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAnyPermission requires at least one of the permissions
|
||||
func RequireAnyPermission(permissions ...string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !session.HasAnyPermission(permissions) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "One of the required permissions is missing",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAllPermissions requires all specified permissions
|
||||
func RequireAllPermissions(permissions ...string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !session.HasAllPermissions(permissions) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "Missing required permissions",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireRole requires a specific role
|
||||
func RequireRole(role string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !session.HasRole(role) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "Role '" + role + "' required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAnyRole requires at least one of the roles
|
||||
func RequireAnyRole(roles ...string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if session.HasRole(role) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "One of the required roles is missing",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// RequireSameTenant ensures user can only access their tenant's data
|
||||
func RequireSameTenant(tenantIDParam string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
requestTenantID := c.Param(tenantIDParam)
|
||||
if requestTenantID == "" {
|
||||
requestTenantID = c.Query(tenantIDParam)
|
||||
}
|
||||
|
||||
if requestTenantID != "" && session.TenantID != nil && *session.TenantID != requestTenantID {
|
||||
// Check if user is super admin
|
||||
if !session.HasRole("super_admin") {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "forbidden",
|
||||
"message": "Access denied to this tenant",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// DetermineUserType determines user type based on roles
|
||||
func DetermineUserType(roles []string) UserType {
|
||||
for _, role := range roles {
|
||||
if EmployeeRoles[role] {
|
||||
return UserTypeEmployee
|
||||
}
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if CustomerRoles[role] {
|
||||
return UserTypeCustomer
|
||||
}
|
||||
}
|
||||
|
||||
return UserTypeCustomer
|
||||
}
|
||||
|
||||
// GetPermissionsForRoles returns permissions based on roles and user type
|
||||
func GetPermissionsForRoles(roles []string, userType UserType) []string {
|
||||
permSet := make(map[string]bool)
|
||||
|
||||
// Base permissions by user type
|
||||
if userType == UserTypeEmployee {
|
||||
for _, p := range EmployeePermissions {
|
||||
permSet[p] = true
|
||||
}
|
||||
} else {
|
||||
for _, p := range CustomerPermissions {
|
||||
permSet[p] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Admin permissions
|
||||
adminRoles := map[string]bool{
|
||||
"admin": true,
|
||||
"schul_admin": true,
|
||||
"super_admin": true,
|
||||
"data_protection_officer": true,
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if adminRoles[role] {
|
||||
for _, p := range AdminPermissions {
|
||||
permSet[p] = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to slice
|
||||
permissions := make([]string, 0, len(permSet))
|
||||
for p := range permSet {
|
||||
permissions = append(permissions, p)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
// CheckResourceOwnership checks if user owns a resource or is admin
|
||||
func CheckResourceOwnership(session *Session, resourceUserID string, allowAdmin bool) bool {
|
||||
if session == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// User owns the resource
|
||||
if session.UserID == resourceUserID {
|
||||
return true
|
||||
}
|
||||
|
||||
// Admin can access all
|
||||
if allowAdmin && (session.HasRole("admin") || session.HasRole("super_admin")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSessionEmployee checks if current session belongs to an employee
|
||||
func IsSessionEmployee(c *gin.Context) bool {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
return false
|
||||
}
|
||||
return session.IsEmployee()
|
||||
}
|
||||
|
||||
// IsSessionCustomer checks if current session belongs to a customer
|
||||
func IsSessionCustomer(c *gin.Context) bool {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
return false
|
||||
}
|
||||
return session.IsCustomer()
|
||||
}
|
||||
|
||||
// HasSessionPermission checks if session has a permission
|
||||
func HasSessionPermission(c *gin.Context, permission string) bool {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
return false
|
||||
}
|
||||
return session.HasPermission(permission)
|
||||
}
|
||||
|
||||
// HasSessionRole checks if session has a role
|
||||
func HasSessionRole(c *gin.Context, role string) bool {
|
||||
session := GetSession(c)
|
||||
if session == nil {
|
||||
return false
|
||||
}
|
||||
return session.HasRole(role)
|
||||
}
|
||||
Reference in New Issue
Block a user