Files
codit/backend/internal/handlers/subject_permissions.go

227 lines
6.5 KiB
Go

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
}