Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita233040

Browse files
committed
chore: implement patching custom organization roles
1 parent47f8f5d commita233040

File tree

9 files changed

+205
-12
lines changed

9 files changed

+205
-12
lines changed

‎cli/cliui/parameter.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
4343
return"",err
4444
}
4545

46-
values,err:=MultiSelect(inv,options)
46+
values,err:=MultiSelect(inv,MultiSelectOptions{
47+
Options:options,
48+
Defaults:options,
49+
})
4750
iferr==nil {
4851
v,err:=json.Marshal(&values)
4952
iferr!=nil {

‎cli/cliui/select.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func init() {
2121
{{- .CurrentOpt.Value}}
2222
{{- color "reset"}}
2323
{{end}}
24-
24+
{{- if .Message }}{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}{{ end }}
2525
{{- if not .ShowAnswer }}
2626
{{- if .Config.Icons.Help.Text }}
2727
{{- if .FilterMessage }}{{ "Search:" }}{{ .FilterMessage }}
@@ -44,18 +44,20 @@ func init() {
4444
{{- " "}}{{- .CurrentOpt.Value}}
4545
{{end}}
4646
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
47+
{{- if .Message }}{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}{{ end }}
4748
{{- if not .ShowAnswer }}
4849
{{- "\n"}}
4950
{{- range $ix, $option := .PageEntries}}
5051
{{- template "option" $.IterateOption $ix $option}}
5152
{{- end}}
52-
{{- end}}`
53+
{{- end}}`
5354
}
5455

5556
typeSelectOptionsstruct {
5657
Options []string
5758
// Default will be highlighted first if it's a valid option.
5859
Defaultstring
60+
Messagestring
5961
Sizeint
6062
HideSearchbool
6163
}
@@ -122,6 +124,7 @@ func Select(inv *serpent.Invocation, opts SelectOptions) (string, error) {
122124
Options:opts.Options,
123125
Default:defaultOption,
124126
PageSize:opts.Size,
127+
Message:opts.Message,
125128
},&value,survey.WithIcons(func(is*survey.IconSet) {
126129
is.Help.Text="Type to search"
127130
ifopts.HideSearch {
@@ -138,15 +141,22 @@ func Select(inv *serpent.Invocation, opts SelectOptions) (string, error) {
138141
returnvalue,err
139142
}
140143

141-
funcMultiSelect(inv*serpent.Invocation,items []string) ([]string,error) {
144+
typeMultiSelectOptionsstruct {
145+
Messagestring
146+
Options []string
147+
Defaults []string
148+
}
149+
150+
funcMultiSelect(inv*serpent.Invocation,optsMultiSelectOptions) ([]string,error) {
142151
// Similar hack is applied to Select()
143152
ifflag.Lookup("test.v")!=nil {
144-
returnitems,nil
153+
returnopts.Defaults,nil
145154
}
146155

147156
prompt:=&survey.MultiSelect{
148-
Options:items,
149-
Default:items,
157+
Message:opts.Message,
158+
Options:opts.Options,
159+
Default:opts.Defaults,
150160
}
151161

152162
varvalues []string

‎cli/cliui/select_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
107107
varvalues []string
108108
cmd:=&serpent.Command{
109109
Handler:func(inv*serpent.Invocation)error {
110-
selectedItems,err:=cliui.MultiSelect(inv,items)
110+
selectedItems,err:=cliui.MultiSelect(inv, cliui.MultiSelectOptions{
111+
Options:items,
112+
Defaults:items,
113+
})
111114
iferr==nil {
112115
values=selectedItems
113116
}

‎coderd/coderd.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -812,8 +812,6 @@ func New(options *Options) *API {
812812
httpmw.ExtractOrganizationParam(options.Database),
813813
)
814814
r.Get("/",api.organization)
815-
r.Patch("/",api.patchOrganization)
816-
r.Delete("/",api.deleteOrganization)
817815
r.Post("/templateversions",api.postTemplateVersionsByOrganization)
818816
r.Route("/templates",func(r chi.Router) {
819817
r.Post("/",api.postTemplateByOrganization)
@@ -829,6 +827,8 @@ func New(options *Options) *API {
829827
})
830828
r.Route("/members",func(r chi.Router) {
831829
r.Get("/roles",api.assignableOrgRoles)
830+
r.With(httpmw.RequireExperiment(api.Experiments,codersdk.ExperimentCustomRoles)).
831+
Patch("/roles",api.patchOrgRoles)
832832
r.Route("/{user}",func(r chi.Router) {
833833
r.Use(
834834
httpmw.ExtractOrganizationMemberParam(options.Database),
@@ -1249,6 +1249,8 @@ type API struct {
12491249
// passed to dbauthz.
12501250
AccessControlStore*atomic.Pointer[dbauthz.AccessControlStore]
12511251
PortSharer atomic.Pointer[portsharing.PortSharer]
1252+
// CustomRoleHandler is the AGPL/Enterprise implementation for custom roles.
1253+
CustomRoleHandler atomic.Pointer[CustomRoleHandler]
12521254

12531255
HTTPAuth*HTTPAuthorizer
12541256

‎coderd/roles.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coderd
22

33
import (
4+
"context"
45
"net/http"
56

67
"github.com/google/uuid"
@@ -16,6 +17,59 @@ import (
1617
"github.com/coder/coder/v2/coderd/rbac"
1718
)
1819

20+
// CustomRoleHandler handles AGPL/Enterprise interface for handling custom
21+
// roles. Ideally only included in the enterprise package, but the routes are
22+
// intermixed with AGPL endpoints.
23+
typeCustomRoleHandlerinterface {
24+
PatchOrganizationRole(ctx context.Context,db database.Store,rw http.ResponseWriter,orgID uuid.UUID,role codersdk.Role) (codersdk.Role,bool)
25+
}
26+
27+
typeagplCustomRoleHandlerstruct{}
28+
29+
func (agplCustomRoleHandler)PatchOrganizationRole(ctx context.Context,db database.Store,rw http.ResponseWriter,orgID uuid.UUID,role codersdk.Role) (codersdk.Role,bool) {
30+
httpapi.Write(ctx,rw,http.StatusForbidden, codersdk.Response{
31+
Message:"Creating and updating custom roles is an Enterprise feature. Contact sales!",
32+
})
33+
return codersdk.Role{},false
34+
}
35+
36+
// patchRole will allow creating a custom organization role
37+
//
38+
// @Summary Upsert a custom organization role
39+
// @ID upsert-a-custom-organization-role
40+
// @Security CoderSessionToken
41+
// @Produce json
42+
// @Tags Members
43+
// @Success 200 {array} codersdk.Role
44+
// @Router /organizations/{organization}/members/roles [patch]
45+
func (api*API)patchOrgRoles(rw http.ResponseWriter,r*http.Request) {
46+
var (
47+
ctx=r.Context()
48+
handler=*api.CustomRoleHandler.Load()
49+
organization=httpmw.OrganizationParam(r)
50+
)
51+
52+
varreq codersdk.Role
53+
if!httpapi.Read(ctx,rw,r,&req) {
54+
return
55+
}
56+
57+
iferr:=httpapi.NameValid(req.Name);err!=nil {
58+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
59+
Message:"Invalid role name",
60+
Detail:err.Error(),
61+
})
62+
return
63+
}
64+
65+
updated,ok:=handler.PatchOrganizationRole(ctx,api.Database,rw,organization.ID,req)
66+
if!ok {
67+
return
68+
}
69+
70+
httpapi.Write(ctx,rw,http.StatusOK,updated)
71+
}
72+
1973
// AssignableSiteRoles returns all site wide roles that can be assigned.
2074
//
2175
// @Summary Get site member roles

‎enterprise/coderd/coderd.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,11 @@ func (api *API) updateEntitlements(ctx context.Context) error {
761761
api.AGPL.PortSharer.Store(&ps)
762762
}
763763

764+
ifinitial,changed,enabled:=featureChanged(codersdk.FeatureCustomRoles);shouldUpdate(initial,changed,enabled) {
765+
varhandler coderd.CustomRoleHandler=&enterpriseCustomRoleHandler{Enabled:enabled}
766+
api.AGPL.CustomRoleHandler.Store(&handler)
767+
}
768+
764769
// External token encryption is soft-enforced
765770
featureExternalTokenEncryption:=entitlements.Features[codersdk.FeatureExternalTokenEncryption]
766771
featureExternalTokenEncryption.Enabled=len(api.ExternalTokenEncryption)>0

‎enterprise/coderd/roles.go

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package coderd
22

33
import (
4+
"context"
5+
"fmt"
46
"net/http"
57

68
"github.com/google/uuid"
@@ -12,6 +14,100 @@ import (
1214
"github.com/coder/coder/v2/codersdk"
1315
)
1416

17+
typeenterpriseCustomRoleHandlerstruct {
18+
Enabledbool
19+
}
20+
21+
func (henterpriseCustomRoleHandler)PatchOrganizationRole(ctx context.Context,db database.Store,rw http.ResponseWriter,orgID uuid.UUID,role codersdk.Role) (codersdk.Role,bool) {
22+
if!h.Enabled {
23+
httpapi.Write(ctx,rw,http.StatusForbidden, codersdk.Response{
24+
Message:"Custom roles is not enabled",
25+
})
26+
return codersdk.Role{},false
27+
}
28+
29+
// Only organization permissions are allowed to be granted
30+
iflen(role.SitePermissions)>0 {
31+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
32+
Message:"Invalid request, not allowed to assign site wide permissions for an organization role.",
33+
Detail:"organization scoped roles may not contain site wide permissions",
34+
})
35+
return codersdk.Role{},false
36+
}
37+
38+
iflen(role.UserPermissions)>0 {
39+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
40+
Message:"Invalid request, not allowed to assign user permissions for an organization role.",
41+
Detail:"organization scoped roles may not contain user permissions",
42+
})
43+
return codersdk.Role{},false
44+
}
45+
46+
iflen(role.OrganizationPermissions)>1 {
47+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
48+
Message:"Invalid request, Only 1 organization can be assigned permissions",
49+
Detail:"roles can only contain 1 organization",
50+
})
51+
return codersdk.Role{},false
52+
}
53+
54+
iflen(role.OrganizationPermissions)==1 {
55+
_,exists:=role.OrganizationPermissions[orgID.String()]
56+
if!exists {
57+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
58+
Message:fmt.Sprintf("Invalid request, expected permissions for only the organization %q",orgID.String()),
59+
Detail:fmt.Sprintf("only org id %s allowed",orgID.String()),
60+
})
61+
return codersdk.Role{},false
62+
}
63+
}
64+
65+
// Make sure all permissions inputted are valid according to our policy.
66+
rbacRole:=db2sdk.RoleToRBAC(role)
67+
args,err:=rolestore.ConvertRoleToDB(rbacRole)
68+
iferr!=nil {
69+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
70+
Message:"Invalid request",
71+
Detail:err.Error(),
72+
})
73+
return codersdk.Role{},false
74+
}
75+
76+
inserted,err:=db.UpsertCustomRole(ctx, database.UpsertCustomRoleParams{
77+
Name:args.Name,
78+
DisplayName:args.DisplayName,
79+
OrganizationID: uuid.NullUUID{
80+
UUID:orgID,
81+
Valid:true,
82+
},
83+
SitePermissions:args.SitePermissions,
84+
OrgPermissions:args.OrgPermissions,
85+
UserPermissions:args.UserPermissions,
86+
})
87+
ifhttpapi.Is404Error(err) {
88+
httpapi.ResourceNotFound(rw)
89+
return codersdk.Role{},false
90+
}
91+
iferr!=nil {
92+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
93+
Message:"Failed to update role permissions",
94+
Detail:err.Error(),
95+
})
96+
return codersdk.Role{},false
97+
}
98+
99+
convertedInsert,err:=rolestore.ConvertDBRole(inserted)
100+
iferr!=nil {
101+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
102+
Message:"Permissions were updated, unable to read them back out of the database.",
103+
Detail:err.Error(),
104+
})
105+
return codersdk.Role{},false
106+
}
107+
108+
returndb2sdk.Role(convertedInsert),true
109+
}
110+
15111
// patchRole will allow creating a custom role
16112
//
17113
// @Summary Upsert a custom site-wide role
@@ -61,7 +157,6 @@ func (api *API) patchRole(rw http.ResponseWriter, r *http.Request) {
61157
inserted,err:=api.Database.UpsertCustomRole(ctx, database.UpsertCustomRoleParams{
62158
Name:args.Name,
63159
DisplayName:args.DisplayName,
64-
OrganizationID: uuid.NullUUID{},
65160
SitePermissions:args.SitePermissions,
66161
OrgPermissions:args.OrgPermissions,
67162
UserPermissions:args.UserPermissions,

‎enterprise/coderd/roles_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,18 @@ func TestCustomRole(t *testing.T) {
6767
allRoles,err:=tmplAdmin.ListSiteRoles(ctx)
6868
require.NoError(t,err)
6969

70+
varfoundRole codersdk.AssignableRoles
7071
require.True(t,slices.ContainsFunc(allRoles,func(selected codersdk.AssignableRoles)bool {
71-
returnselected.Name==role.Name
72+
ifselected.Name==role.Name {
73+
foundRole=selected
74+
returntrue
75+
}
76+
returnfalse
7277
}),"role missing from site role list")
78+
79+
require.Len(t,foundRole.SitePermissions,7)
80+
require.Len(t,foundRole.OrganizationPermissions,0)
81+
require.Len(t,foundRole.UserPermissions,0)
7382
})
7483

7584
// Revoked licenses cannot modify/create custom roles, but they can

‎scripts/rbacgen/codersdk.gotmpl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,15 @@ const (
1616
{{ $element.Enum }} RBACAction = "{{ $element.Value }}"
1717
{{- end }}
1818
)
19+
20+
// RBACResourceActions is the mapping of resources to which actions are valid for
21+
// said resource type.
22+
var RBACResourceActions = map[RBACResource][]RBACAction{
23+
{{- range $element := . }}
24+
Resource{{ pascalCaseName $element.FunctionName }}: []RBACAction{
25+
{{- range $actionValue, $_ := $element.Actions }}
26+
{{- actionEnum $actionValue -}},
27+
{{- end -}}
28+
},
29+
{{- end }}
30+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp