mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
186 lines
6.3 KiB
Go
186 lines
6.3 KiB
Go
package codersdk
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// SlimRole omits permission information from a role.
|
|
// At present, this is because our apis do not return permission information,
|
|
// and it would require extra db calls to fetch this information. The UI does
|
|
// not need it, so most api calls will use this structure that omits information.
|
|
type SlimRole struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
OrganizationID string `json:"organization_id,omitempty"`
|
|
}
|
|
|
|
func (s SlimRole) String() string {
|
|
if s.DisplayName != "" {
|
|
return s.DisplayName
|
|
}
|
|
return s.Name
|
|
}
|
|
|
|
// UniqueName concatenates the organization ID to create a globally unique
|
|
// string name for the role.
|
|
func (s SlimRole) UniqueName() string {
|
|
if s.OrganizationID != "" {
|
|
return s.Name + ":" + s.OrganizationID
|
|
}
|
|
return s.Name
|
|
}
|
|
|
|
type AssignableRoles struct {
|
|
Role `table:"r,recursive_inline"`
|
|
Assignable bool `json:"assignable" table:"assignable"`
|
|
// BuiltIn roles are immutable
|
|
BuiltIn bool `json:"built_in" table:"built_in"`
|
|
}
|
|
|
|
// Permission is the format passed into the rego.
|
|
type Permission struct {
|
|
// Negate makes this a negative permission
|
|
Negate bool `json:"negate"`
|
|
ResourceType RBACResource `json:"resource_type"`
|
|
Action RBACAction `json:"action"`
|
|
}
|
|
|
|
// Role is a longer form of SlimRole that includes permissions details.
|
|
type Role struct {
|
|
Name string `json:"name" table:"name,default_sort" validate:"username"`
|
|
OrganizationID string `json:"organization_id,omitempty" table:"organization id" format:"uuid"`
|
|
DisplayName string `json:"display_name" table:"display name"`
|
|
SitePermissions []Permission `json:"site_permissions" table:"site permissions"`
|
|
// OrganizationPermissions are specific for the organization in the field 'OrganizationID' above.
|
|
OrganizationPermissions []Permission `json:"organization_permissions" table:"organization permissions"`
|
|
UserPermissions []Permission `json:"user_permissions" table:"user permissions"`
|
|
}
|
|
|
|
// CustomRoleRequest is used to edit custom roles.
|
|
type CustomRoleRequest struct {
|
|
Name string `json:"name" table:"name,default_sort" validate:"username"`
|
|
DisplayName string `json:"display_name" table:"display name"`
|
|
SitePermissions []Permission `json:"site_permissions" table:"site permissions"`
|
|
// OrganizationPermissions are specific to the organization the role belongs to.
|
|
OrganizationPermissions []Permission `json:"organization_permissions" table:"organization permissions"`
|
|
UserPermissions []Permission `json:"user_permissions" table:"user permissions"`
|
|
}
|
|
|
|
// FullName returns the role name scoped to the organization ID. This is useful if
|
|
// printing a set of roles from different scopes, as duplicated names across multiple
|
|
// scopes will become unique.
|
|
// In practice, this is primarily used in testing.
|
|
func (r Role) FullName() string {
|
|
if r.OrganizationID == "" {
|
|
return r.Name
|
|
}
|
|
return r.Name + ":" + r.OrganizationID
|
|
}
|
|
|
|
// CreateOrganizationRole will create a custom organization role
|
|
func (c *Client) CreateOrganizationRole(ctx context.Context, role Role) (Role, error) {
|
|
req := CustomRoleRequest{
|
|
Name: role.Name,
|
|
DisplayName: role.DisplayName,
|
|
SitePermissions: role.SitePermissions,
|
|
OrganizationPermissions: role.OrganizationPermissions,
|
|
UserPermissions: role.UserPermissions,
|
|
}
|
|
|
|
res, err := c.Request(ctx, http.MethodPost,
|
|
fmt.Sprintf("/api/v2/organizations/%s/members/roles", role.OrganizationID), req)
|
|
if err != nil {
|
|
return Role{}, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return Role{}, ReadBodyAsError(res)
|
|
}
|
|
var r Role
|
|
return r, json.NewDecoder(res.Body).Decode(&r)
|
|
}
|
|
|
|
// UpdateOrganizationRole will update an existing custom organization role
|
|
func (c *Client) UpdateOrganizationRole(ctx context.Context, role Role) (Role, error) {
|
|
req := CustomRoleRequest{
|
|
Name: role.Name,
|
|
DisplayName: role.DisplayName,
|
|
SitePermissions: role.SitePermissions,
|
|
OrganizationPermissions: role.OrganizationPermissions,
|
|
UserPermissions: role.UserPermissions,
|
|
}
|
|
|
|
res, err := c.Request(ctx, http.MethodPut,
|
|
fmt.Sprintf("/api/v2/organizations/%s/members/roles", role.OrganizationID), req)
|
|
if err != nil {
|
|
return Role{}, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return Role{}, ReadBodyAsError(res)
|
|
}
|
|
var r Role
|
|
return r, json.NewDecoder(res.Body).Decode(&r)
|
|
}
|
|
|
|
// DeleteOrganizationRole will delete a custom organization role
|
|
func (c *Client) DeleteOrganizationRole(ctx context.Context, organizationID uuid.UUID, roleName string) error {
|
|
res, err := c.Request(ctx, http.MethodDelete,
|
|
fmt.Sprintf("/api/v2/organizations/%s/members/roles/%s", organizationID.String(), roleName), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusNoContent {
|
|
return ReadBodyAsError(res)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListSiteRoles lists all assignable site wide roles.
|
|
func (c *Client) ListSiteRoles(ctx context.Context) ([]AssignableRoles, error) {
|
|
res, err := c.Request(ctx, http.MethodGet, "/api/v2/users/roles", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return nil, ReadBodyAsError(res)
|
|
}
|
|
var roles []AssignableRoles
|
|
return roles, json.NewDecoder(res.Body).Decode(&roles)
|
|
}
|
|
|
|
// ListOrganizationRoles lists all assignable roles for a given organization.
|
|
func (c *Client) ListOrganizationRoles(ctx context.Context, org uuid.UUID) ([]AssignableRoles, error) {
|
|
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/members/roles", org.String()), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return nil, ReadBodyAsError(res)
|
|
}
|
|
var roles []AssignableRoles
|
|
return roles, json.NewDecoder(res.Body).Decode(&roles)
|
|
}
|
|
|
|
// CreatePermissions is a helper function to quickly build permissions.
|
|
func CreatePermissions(mapping map[RBACResource][]RBACAction) []Permission {
|
|
perms := make([]Permission, 0)
|
|
for t, actions := range mapping {
|
|
for _, action := range actions {
|
|
perms = append(perms, Permission{
|
|
ResourceType: t,
|
|
Action: action,
|
|
})
|
|
}
|
|
}
|
|
return perms
|
|
}
|