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>
404 lines
9.2 KiB
Go
404 lines
9.2 KiB
Go
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)
|
|
}
|