package handlers import "errors" import "net/http" import "strings" import "codit/internal/middleware" import "codit/internal/models" const permissionProjectCreate string = "project.create" type meResponse struct { ID string `json:"id"` Username string `json:"username"` DisplayName string `json:"display_name"` Email string `json:"email"` IsAdmin bool `json:"is_admin"` Disabled bool `json:"disabled"` AuthSource string `json:"auth_source"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` Permissions []string `json:"permissions,omitempty"` } type subjectPermissionTargetRequest struct { SubjectType string `json:"subject_type"` SubjectID string `json:"subject_id"` } type subjectPermissionRequest struct { Permission string `json:"permission"` Targets []subjectPermissionTargetRequest `json:"targets"` } func (api *API) ListSubjectPermissions(w http.ResponseWriter, r *http.Request, _ map[string]string) { var permission string var subjectType string var subjectID string var items []models.SubjectPermission var err error if !api.requireAdmin(w, r) { return } permission = strings.TrimSpace(r.URL.Query().Get("permission")) subjectType = strings.TrimSpace(r.URL.Query().Get("subject_type")) subjectID = strings.TrimSpace(r.URL.Query().Get("subject_id")) items, err = api.store(r).ListSubjectPermissions(permission, subjectType, subjectID) if err != nil { WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } WriteJSON(w, http.StatusOK, items) } func (api *API) CreateSubjectPermissions(w http.ResponseWriter, r *http.Request, _ map[string]string) { var req subjectPermissionRequest var permission string var targets []models.SubjectPermission var items []models.SubjectPermission var item models.SubjectPermission var i int var err error if !api.requireAdmin(w, r) { return } err = DecodeJSON(r, &req) if err != nil { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) return } permission, err = normalizeSubjectPermissionName(req.Permission) if err != nil { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } targets, err = normalizeSubjectPermissionTargets(permission, req.Targets) if err != nil { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } for i = 0; i < len(targets); i++ { targets[i].Permission = permission item, err = api.store(r).UpsertSubjectPermission(targets[i]) if err != nil { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } items = append(items, item) } WriteJSON(w, http.StatusCreated, items) } func (api *API) DeleteSubjectPermission(w http.ResponseWriter, r *http.Request, params map[string]string) { var permission string var subjectType string var subjectID string var err error if !api.requireAdmin(w, r) { return } permission, err = normalizeSubjectPermissionName(params["permission"]) if err != nil { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } subjectType = strings.ToLower(strings.TrimSpace(params["subjectType"])) subjectID = strings.TrimSpace(params["subjectID"]) if subjectType != "user" && subjectType != "group" && subjectType != "principal" { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid subject_type"}) return } if subjectID == "" { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "subject_id is required"}) return } err = api.store(r).DeleteSubjectPermission(permission, subjectType, subjectID) if err != nil { WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } WriteJSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } func (api *API) requirePermission(w http.ResponseWriter, r *http.Request, permission string) bool { var user models.User var principal models.ServicePrincipal var ok bool var allowed bool var err error user, ok = middleware.UserFromContext(r.Context()) if ok { if user.IsAdmin { return true } allowed, err = api.store(r).UserHasPermission(user.ID, permission) if err != nil { w.WriteHeader(http.StatusInternalServerError) return false } if allowed { return true } w.WriteHeader(http.StatusForbidden) return false } principal, ok = middleware.PrincipalFromContext(r.Context()) if !ok || principal.Disabled { w.WriteHeader(http.StatusUnauthorized) return false } if principal.IsAdmin { return true } allowed, err = api.store(r).PrincipalHasPermission(principal.ID, permission) if err != nil { w.WriteHeader(http.StatusInternalServerError) return false } if allowed { return true } w.WriteHeader(http.StatusForbidden) return false } func buildMeResponse(user models.User, permissions []string) meResponse { return meResponse{ ID: user.ID, Username: user.Username, DisplayName: user.DisplayName, Email: user.Email, IsAdmin: user.IsAdmin, Disabled: user.Disabled, AuthSource: user.AuthSource, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, Permissions: permissions, } } func normalizeSubjectPermissionName(raw string) (string, error) { var value string value = strings.TrimSpace(raw) switch value { case permissionProjectCreate: return value, nil default: return "", errors.New("unsupported permission") } } func normalizeSubjectPermissionTargets(permission string, raw []subjectPermissionTargetRequest) ([]models.SubjectPermission, error) { var items []models.SubjectPermission var seen map[string]bool var subjectType string var subjectID string var key string var i int seen = make(map[string]bool) for i = 0; i < len(raw); i++ { subjectType = strings.ToLower(strings.TrimSpace(raw[i].SubjectType)) subjectID = strings.TrimSpace(raw[i].SubjectID) if subjectType != "user" && subjectType != "group" && subjectType != "principal" { return nil, errors.New("invalid subject_type") } if permission == permissionProjectCreate && subjectType == "principal" { return nil, errors.New("project.create can only target users or groups") } if subjectID == "" { return nil, errors.New("subject_id is required") } key = subjectType + ":" + subjectID if seen[key] { continue } seen[key] = true items = append(items, models.SubjectPermission{ SubjectType: subjectType, SubjectID: subjectID, }) } if len(items) == 0 { return nil, errors.New("at least one target is required") } return items, nil }