fully implement acl backend and interface
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// @Summary Get all roles
|
||||
// @Tags roles
|
||||
// @Tags acl/roles
|
||||
// @Produce json
|
||||
// @Success 200 {array} getRolesResponse
|
||||
// @Failure 500 {object} ProblemDetails
|
||||
@@ -43,8 +43,46 @@ func (h *aclAdminHandler) getRoles(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// @Summary Get role by ID
|
||||
// @Tags acl/roles
|
||||
// @Produce json
|
||||
// @Param roleId path int true "Role ID" example(1)
|
||||
// @Success 200 {object} getRoleResponse
|
||||
// @Failure 400 {object} ProblemDetails
|
||||
// @Failure 404 {object} ProblemDetails
|
||||
// @Failure 500 {object} ProblemDetails
|
||||
// @Router /api/acl/roles/{roleId} [get]
|
||||
func (h *aclAdminHandler) getRole(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
roleIDStr := chi.URLParam(r, "roleId")
|
||||
roleID, err := strconv.Atoi(roleIDStr)
|
||||
if err != nil || roleID < 0 {
|
||||
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
|
||||
return
|
||||
}
|
||||
|
||||
role, err := h.a.GetRoleByID(uint(roleID))
|
||||
if err != nil {
|
||||
switch err {
|
||||
case acl.ErrNotInitialized:
|
||||
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
|
||||
case acl.ErrRoleNotFound:
|
||||
writeProblem(w, http.StatusNotFound, "/errors/acl/role-not-found", "Role not found", "No role with ID "+roleIDStr, r)
|
||||
default:
|
||||
slog.Error("unexpected server error", "error", err.Error())
|
||||
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(w).Encode(getRoleResponse{
|
||||
ID: role.ID,
|
||||
Name: role.Name,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Get role users
|
||||
// @Tags roles
|
||||
// @Tags acl/roles
|
||||
// @Produce json
|
||||
// @Param roleId path int true "Role ID" example(1)
|
||||
// @Success 200 {array} getRoleUsersResponse
|
||||
@@ -89,16 +127,16 @@ func (h *aclAdminHandler) getRoleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(respUsers)
|
||||
}
|
||||
|
||||
// @Summary Get role by ID
|
||||
// @Tags roles
|
||||
// @Summary Get role resources
|
||||
// @Tags acl/roles
|
||||
// @Produce json
|
||||
// @Param roleId path int true "Role ID" example(1)
|
||||
// @Success 200 {object} getRoleResponse
|
||||
// @Success 200 {array} getRoleResourcesResponse
|
||||
// @Failure 400 {object} ProblemDetails
|
||||
// @Failure 404 {object} ProblemDetails
|
||||
// @Failure 500 {object} ProblemDetails
|
||||
// @Router /api/acl/roles/{roleId} [get]
|
||||
func (h *aclAdminHandler) getRole(w http.ResponseWriter, r *http.Request) {
|
||||
// @Router /api/acl/roles/{roleId}/resources [get]
|
||||
func (h *aclAdminHandler) getRoleResources(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
roleIDStr := chi.URLParam(r, "roleId")
|
||||
roleID, err := strconv.Atoi(roleIDStr)
|
||||
@@ -106,7 +144,6 @@ func (h *aclAdminHandler) getRole(w http.ResponseWriter, r *http.Request) {
|
||||
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
|
||||
return
|
||||
}
|
||||
|
||||
role, err := h.a.GetRoleByID(uint(roleID))
|
||||
if err != nil {
|
||||
switch err {
|
||||
@@ -120,15 +157,22 @@ func (h *aclAdminHandler) getRole(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(w).Encode(getRoleResponse{
|
||||
ID: role.ID,
|
||||
Name: role.Name,
|
||||
})
|
||||
if len(role.Resources) == 0 {
|
||||
writeProblem(w, http.StatusNotFound, "/errors/acl/role-has-no-users", "Role has no users", "Role has no users", r)
|
||||
return
|
||||
}
|
||||
var respResources getRoleResourcesResponse
|
||||
for _, user := range role.Resources {
|
||||
respResources = append(respResources, getRoleResource{
|
||||
ID: user.ID,
|
||||
Name: user.Key,
|
||||
})
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(respResources)
|
||||
}
|
||||
|
||||
// @Summary Create role
|
||||
// @Tags roles
|
||||
// @Tags acl/roles
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body createRoleRequest true "Role"
|
||||
@@ -170,7 +214,7 @@ func (h *aclAdminHandler) createRole(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// @Summary Update role
|
||||
// @Tags roles
|
||||
// @Tags acl/roles
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param roleId path int true "Role ID" example(1)
|
||||
@@ -222,10 +266,10 @@ func (h *aclAdminHandler) updateRole(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// @Summary Delete role
|
||||
// @Tags roles
|
||||
// @Tags acl/roles
|
||||
// @Produce json
|
||||
// @Param roleId path int true "Role ID" example(1)
|
||||
// @Success 200
|
||||
// @Success 204
|
||||
// @Failure 400 {object} ProblemDetails
|
||||
// @Failure 404 {object} ProblemDetails
|
||||
// @Failure 409 {object} ProblemDetails
|
||||
@@ -256,5 +300,91 @@ func (h *aclAdminHandler) deleteRole(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// @Summary Assign resource to role
|
||||
// @Tags acl/roles
|
||||
// @Produce json
|
||||
// @Param roleId path int true "Role ID" example(1)
|
||||
// @Param request body assignResourceToRoleRequest true "Resource"
|
||||
// @Success 201
|
||||
// @Failure 400 {object} ProblemDetails
|
||||
// @Failure 404 {object} ProblemDetails
|
||||
// @Failure 409 {object} ProblemDetails
|
||||
// @Failure 500 {object} ProblemDetails
|
||||
// @Router /api/acl/roles/{roleId}/resources [post]
|
||||
func (h *aclAdminHandler) assignResourceToRole(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
roleIDStr := chi.URLParam(r, "roleId")
|
||||
roleID, err := strconv.Atoi(roleIDStr)
|
||||
if err != nil || roleID < 0 {
|
||||
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
|
||||
return
|
||||
}
|
||||
var req assignResourceToRoleRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-request-body", "Invalid request body", "Invalid JSON body", r)
|
||||
return
|
||||
}
|
||||
if err := h.a.AssignResourceToRole(uint(roleID), req.ResourceID); err != nil {
|
||||
slog.Error("Failed to assign resource to role", "error", err.Error())
|
||||
switch err {
|
||||
case acl.ErrNotInitialized:
|
||||
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
|
||||
case acl.ErrRoleNotFound:
|
||||
writeProblem(w, http.StatusNotFound, "/errors/acl/role-not-found", "Role not found", "No role with ID "+roleIDStr, r)
|
||||
case acl.ErrResourceNotFound:
|
||||
writeProblem(w, http.StatusNotFound, "/errors/acl/resource-not-found", "Resource not found", "No resource with ID "+strconv.Itoa(int(req.ResourceID)), r)
|
||||
case acl.ErrResourceAlreadyAssigned:
|
||||
writeProblem(w, http.StatusConflict, "/errors/acl/resource-already-assigned", "Resource already assigned", "Resource with ID "+strconv.Itoa(int(req.ResourceID))+" is already assigned to role with ID "+roleIDStr, r)
|
||||
default:
|
||||
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
// @Summary Remove resource from role
|
||||
// @Tags acl/roles
|
||||
// @Produce json
|
||||
// @Param roleId path int true "Role ID" example(1)
|
||||
// @Param resId path int true "Resource ID" example(1)
|
||||
// @Success 204
|
||||
// @Failure 400 {object} ProblemDetails
|
||||
// @Failure 404 {object} ProblemDetails
|
||||
// @Failure 500 {object} ProblemDetails
|
||||
// @Router /api/acl/roles/{roleId}/resources/{resId} [delete]
|
||||
func (h *aclAdminHandler) removeResourceFromRole(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
roleIDStr := chi.URLParam(r, "roleId")
|
||||
roleID, err := strconv.Atoi(roleIDStr)
|
||||
if err != nil || roleID < 0 {
|
||||
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-role-id", "Invalid role ID", "Role ID must be positive integer", r)
|
||||
return
|
||||
}
|
||||
resourceIDStr := chi.URLParam(r, "resId")
|
||||
resourceID, err := strconv.Atoi(resourceIDStr)
|
||||
if err != nil || resourceID < 0 {
|
||||
writeProblem(w, http.StatusBadRequest, "/errors/acl/invalid-resource-id", "Invalid resource ID", "Resource ID must be positive integer", r)
|
||||
return
|
||||
}
|
||||
if err := h.a.RemoveResourceFromRole(uint(roleID), uint(resourceID)); err != nil {
|
||||
slog.Error("Failed to remove resource from role", "error", err.Error())
|
||||
switch err {
|
||||
case acl.ErrNotInitialized:
|
||||
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "ACL service is not initialized", r)
|
||||
case acl.ErrRoleNotFound:
|
||||
writeProblem(w, http.StatusNotFound, "/errors/acl/role-not-found", "Role not found", "No role with ID "+roleIDStr, r)
|
||||
case acl.ErrResourceNotFound:
|
||||
writeProblem(w, http.StatusNotFound, "/errors/acl/resource-not-found", "Resource not found", "No resource with ID "+strconv.Itoa(int(resourceID)), r)
|
||||
case acl.ErrRoleResourceNotFound:
|
||||
writeProblem(w, http.StatusNotFound, "/errors/acl/role-resource-not-found", "Role resource not found", "No role-resource pair with role ID "+roleIDStr+" and resource ID "+strconv.Itoa(int(resourceID)), r)
|
||||
default:
|
||||
writeProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "unexpected error", r)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user