mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
* chore: create type for unique role names Using `string` was confusing when something should be combined with org context, and when not to. Naming this new name, "RoleIdentifier"
155 lines
4.8 KiB
Go
155 lines
4.8 KiB
Go
package coderd
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
|
"github.com/coder/coder/v2/coderd/httpmw"
|
|
"github.com/coder/coder/v2/coderd/rbac/policy"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
|
|
"github.com/coder/coder/v2/coderd/httpapi"
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
)
|
|
|
|
// CustomRoleHandler handles AGPL/Enterprise interface for handling custom
|
|
// roles. Ideally only included in the enterprise package, but the routes are
|
|
// intermixed with AGPL endpoints.
|
|
type CustomRoleHandler interface {
|
|
PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, r *http.Request, orgID uuid.UUID, role codersdk.Role) (codersdk.Role, bool)
|
|
}
|
|
|
|
type agplCustomRoleHandler struct{}
|
|
|
|
func (agplCustomRoleHandler) PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, _ *http.Request, _ uuid.UUID, _ codersdk.Role) (codersdk.Role, bool) {
|
|
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
|
Message: "Creating and updating custom roles is an Enterprise feature. Contact sales!",
|
|
})
|
|
return codersdk.Role{}, false
|
|
}
|
|
|
|
// patchRole will allow creating a custom organization role
|
|
//
|
|
// @Summary Upsert a custom organization role
|
|
// @ID upsert-a-custom-organization-role
|
|
// @Security CoderSessionToken
|
|
// @Produce json
|
|
// @Param organization path string true "Organization ID" format(uuid)
|
|
// @Tags Members
|
|
// @Success 200 {array} codersdk.Role
|
|
// @Router /organizations/{organization}/members/roles [patch]
|
|
func (api *API) patchOrgRoles(rw http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
ctx = r.Context()
|
|
handler = *api.CustomRoleHandler.Load()
|
|
organization = httpmw.OrganizationParam(r)
|
|
)
|
|
|
|
var req codersdk.Role
|
|
if !httpapi.Read(ctx, rw, r, &req) {
|
|
return
|
|
}
|
|
|
|
updated, ok := handler.PatchOrganizationRole(ctx, rw, r, organization.ID, req)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
httpapi.Write(ctx, rw, http.StatusOK, updated)
|
|
}
|
|
|
|
// AssignableSiteRoles returns all site wide roles that can be assigned.
|
|
//
|
|
// @Summary Get site member roles
|
|
// @ID get-site-member-roles
|
|
// @Security CoderSessionToken
|
|
// @Produce json
|
|
// @Tags Members
|
|
// @Success 200 {array} codersdk.AssignableRoles
|
|
// @Router /users/roles [get]
|
|
func (api *API) AssignableSiteRoles(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
actorRoles := httpmw.UserAuthorization(r)
|
|
if !api.Authorize(r, policy.ActionRead, rbac.ResourceAssignRole) {
|
|
httpapi.Forbidden(rw)
|
|
return
|
|
}
|
|
|
|
dbCustomRoles, err := api.Database.CustomRoles(ctx, database.CustomRolesParams{
|
|
LookupRoles: nil,
|
|
// Only site wide custom roles to be included
|
|
ExcludeOrgRoles: true,
|
|
OrganizationID: uuid.Nil,
|
|
})
|
|
if err != nil {
|
|
httpapi.InternalServerError(rw, err)
|
|
return
|
|
}
|
|
|
|
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, rbac.SiteRoles(), dbCustomRoles))
|
|
}
|
|
|
|
// assignableOrgRoles returns all org wide roles that can be assigned.
|
|
//
|
|
// @Summary Get member roles by organization
|
|
// @ID get-member-roles-by-organization
|
|
// @Security CoderSessionToken
|
|
// @Produce json
|
|
// @Tags Members
|
|
// @Param organization path string true "Organization ID" format(uuid)
|
|
// @Success 200 {array} codersdk.AssignableRoles
|
|
// @Router /organizations/{organization}/members/roles [get]
|
|
func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
organization := httpmw.OrganizationParam(r)
|
|
actorRoles := httpmw.UserAuthorization(r)
|
|
|
|
if !api.Authorize(r, policy.ActionRead, rbac.ResourceAssignOrgRole.InOrg(organization.ID)) {
|
|
httpapi.ResourceNotFound(rw)
|
|
return
|
|
}
|
|
|
|
roles := rbac.OrganizationRoles(organization.ID)
|
|
dbCustomRoles, err := api.Database.CustomRoles(ctx, database.CustomRolesParams{
|
|
LookupRoles: nil,
|
|
ExcludeOrgRoles: false,
|
|
OrganizationID: organization.ID,
|
|
})
|
|
if err != nil {
|
|
httpapi.InternalServerError(rw, err)
|
|
return
|
|
}
|
|
|
|
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles, dbCustomRoles))
|
|
}
|
|
|
|
func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role, customRoles []database.CustomRole) []codersdk.AssignableRoles {
|
|
assignable := make([]codersdk.AssignableRoles, 0)
|
|
for _, role := range roles {
|
|
// The member role is implied, and not assignable.
|
|
// If there is no display name, then the role is also unassigned.
|
|
// This is not the ideal logic, but works for now.
|
|
if role.Identifier == rbac.RoleMember() || (role.DisplayName == "") {
|
|
continue
|
|
}
|
|
assignable = append(assignable, codersdk.AssignableRoles{
|
|
Role: db2sdk.RBACRole(role),
|
|
Assignable: rbac.CanAssignRole(actorRoles, role.Identifier),
|
|
BuiltIn: true,
|
|
})
|
|
}
|
|
|
|
for _, role := range customRoles {
|
|
assignable = append(assignable, codersdk.AssignableRoles{
|
|
Role: db2sdk.Role(role),
|
|
Assignable: rbac.CanAssignRole(actorRoles, role.RoleIdentifier()),
|
|
BuiltIn: false,
|
|
})
|
|
}
|
|
return assignable
|
|
}
|