Skip to content

Commit 96d1527

Browse files
committed
chore: static custom role assignment (#13297)
For now, only owners can assign custom roles
1 parent 9305177 commit 96d1527

File tree

7 files changed

+149
-26
lines changed

7 files changed

+149
-26
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ func (q *querier) customRoleEscalationCheck(ctx context.Context, actor rbac.Subj
750750

751751
if perm.Action == policy.WildcardSymbol || perm.ResourceType == policy.WildcardSymbol {
752752
// It is possible to check for supersets with wildcards, but wildcards can also
753-
// include resources and actions that do not exist. Custom roles should only be allowed
753+
// include resources and actions that do not exist today. Custom roles should only be allowed
754754
// to include permissions for existing resources.
755755
return xerrors.Errorf("invalid permission for action=%q type=%q, no wildcard symbols", perm.Action, perm.ResourceType)
756756
}

coderd/database/dbmem/dbmem.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3168,6 +3168,30 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
31683168
GROUP BY
31693169
start_time, user_id, slug, display_name, icon
31703170
),
3171+
-- Analyze the users unique app usage across all templates. Count
3172+
-- usage across consecutive intervals as continuous usage.
3173+
times_used AS (
3174+
SELECT DISTINCT ON (user_id, slug, display_name, icon, uniq)
3175+
slug,
3176+
display_name,
3177+
icon,
3178+
-- Turn start_time into a unique identifier that identifies a users
3179+
-- continuous app usage. The value of uniq is otherwise garbage.
3180+
--
3181+
-- Since we're aggregating per user app usage across templates,
3182+
-- there can be duplicate start_times. To handle this, we use the
3183+
-- dense_rank() function, otherwise row_number() would suffice.
3184+
start_time - (
3185+
dense_rank() OVER (
3186+
PARTITION BY
3187+
user_id, slug, display_name, icon
3188+
ORDER BY
3189+
start_time
3190+
) * '30 minutes'::interval
3191+
) AS uniq
3192+
FROM
3193+
template_usage_stats_with_apps
3194+
),
31713195
*/
31723196

31733197
// Due to query optimizations, this logic is somewhat inverted from
@@ -3179,12 +3203,19 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
31793203
DisplayName string
31803204
Icon string
31813205
}
3206+
type appTimesUsedGroupBy struct {
3207+
UserID uuid.UUID
3208+
Slug string
3209+
DisplayName string
3210+
Icon string
3211+
}
31823212
type appInsightsRow struct {
31833213
appInsightsGroupBy
31843214
TemplateIDs []uuid.UUID
31853215
AppUsageMins int64
31863216
}
31873217
appInsightRows := make(map[appInsightsGroupBy]appInsightsRow)
3218+
appTimesUsedRows := make(map[appTimesUsedGroupBy]map[time.Time]struct{})
31883219
// FROM
31893220
for _, stat := range q.templateUsageStats {
31903221
// WHERE
@@ -3220,9 +3251,42 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
32203251
row.TemplateIDs = append(row.TemplateIDs, stat.TemplateID)
32213252
row.AppUsageMins = least(row.AppUsageMins+appUsage, 30)
32223253
appInsightRows[key] = row
3254+
3255+
// Prepare to do times_used calculation, distinct start times.
3256+
timesUsedKey := appTimesUsedGroupBy{
3257+
UserID: stat.UserID,
3258+
Slug: slug,
3259+
DisplayName: app.DisplayName,
3260+
Icon: app.Icon,
3261+
}
3262+
if appTimesUsedRows[timesUsedKey] == nil {
3263+
appTimesUsedRows[timesUsedKey] = make(map[time.Time]struct{})
3264+
}
3265+
// This assigns a distinct time, so we don't need to
3266+
// dense_rank() later on, we can simply do row_number().
3267+
appTimesUsedRows[timesUsedKey][stat.StartTime] = struct{}{}
32233268
}
32243269
}
32253270

3271+
appTimesUsedTempRows := make(map[appTimesUsedGroupBy][]time.Time)
3272+
for key, times := range appTimesUsedRows {
3273+
for t := range times {
3274+
appTimesUsedTempRows[key] = append(appTimesUsedTempRows[key], t)
3275+
}
3276+
}
3277+
for _, times := range appTimesUsedTempRows {
3278+
slices.SortFunc(times, func(a, b time.Time) int {
3279+
return int(a.Sub(b))
3280+
})
3281+
}
3282+
for key, times := range appTimesUsedTempRows {
3283+
uniq := make(map[time.Time]struct{})
3284+
for i, t := range times {
3285+
uniq[t.Add(-(30 * time.Minute * time.Duration(i)))] = struct{}{}
3286+
}
3287+
appTimesUsedRows[key] = uniq
3288+
}
3289+
32263290
/*
32273291
-- Even though we allow identical apps to be aggregated across
32283292
-- templates, we still want to be able to report which templates
@@ -3307,14 +3371,20 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
33073371

33083372
var rows []database.GetTemplateAppInsightsRow
33093373
for key, gr := range groupedRows {
3310-
rows = append(rows, database.GetTemplateAppInsightsRow{
3374+
row := database.GetTemplateAppInsightsRow{
33113375
TemplateIDs: templateRows[key].TemplateIDs,
33123376
ActiveUsers: int64(len(uniqueSortedUUIDs(gr.ActiveUserIDs))),
33133377
Slug: key.Slug,
33143378
DisplayName: key.DisplayName,
33153379
Icon: key.Icon,
33163380
UsageSeconds: gr.UsageSeconds,
3317-
})
3381+
}
3382+
for tuk, uniq := range appTimesUsedRows {
3383+
if key.Slug == tuk.Slug && key.DisplayName == tuk.DisplayName && key.Icon == tuk.Icon {
3384+
row.TimesUsed += int64(len(uniq))
3385+
}
3386+
}
3387+
rows = append(rows, row)
33183388
}
33193389

33203390
// NOTE(mafredri): Add sorting if we decide on how to handle PostgreSQL collations.

coderd/database/queries.sql.go

Lines changed: 40 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/rbac/object_gen.go

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/rbac/policy/policy.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ var RBACPermissions = map[string]PermissionDefinition{
209209
Actions: map[Action]ActionDefinition{
210210
ActionAssign: actDef("ability to assign roles"),
211211
ActionRead: actDef("view what roles are assignable"),
212-
ActionDelete: actDef("ability to delete roles"),
212+
ActionDelete: actDef("ability to unassign roles"),
213+
ActionCreate: actDef("ability to create/delete/edit custom roles"),
213214
},
214215
},
215216
"assign_org_role": {

coderd/rbac/roles.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ const (
2020
templateAdmin string = "template-admin"
2121
userAdmin string = "user-admin"
2222
auditor string = "auditor"
23+
// customSiteRole is a placeholder for all custom site roles.
24+
// This is used for what roles can assign other roles.
25+
// TODO: Make this more dynamic to allow other roles to grant.
26+
customSiteRole string = "custom-site-role"
2327

2428
orgAdmin string = "organization-admin"
2529
orgMember string = "organization-member"
@@ -52,6 +56,8 @@ func RoleOwner() string {
5256
return roleName(owner, "")
5357
}
5458

59+
func CustomSiteRole() string { return roleName(customSiteRole, "") }
60+
5561
func RoleTemplateAdmin() string {
5662
return roleName(templateAdmin, "")
5763
}
@@ -320,22 +326,24 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
320326
// map[actor_role][assign_role]<can_assign>
321327
var assignRoles = map[string]map[string]bool{
322328
"system": {
323-
owner: true,
324-
auditor: true,
325-
member: true,
326-
orgAdmin: true,
327-
orgMember: true,
328-
templateAdmin: true,
329-
userAdmin: true,
329+
owner: true,
330+
auditor: true,
331+
member: true,
332+
orgAdmin: true,
333+
orgMember: true,
334+
templateAdmin: true,
335+
userAdmin: true,
336+
customSiteRole: true,
330337
},
331338
owner: {
332-
owner: true,
333-
auditor: true,
334-
member: true,
335-
orgAdmin: true,
336-
orgMember: true,
337-
templateAdmin: true,
338-
userAdmin: true,
339+
owner: true,
340+
auditor: true,
341+
member: true,
342+
orgAdmin: true,
343+
orgMember: true,
344+
templateAdmin: true,
345+
userAdmin: true,
346+
customSiteRole: true,
339347
},
340348
userAdmin: {
341349
member: true,

coderd/rbac/roles_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,15 @@ func TestRolePermissions(t *testing.T) {
248248
false: {otherOrgAdmin, otherOrgMember, memberMe, userAdmin},
249249
},
250250
},
251+
{
252+
Name: "CreateCustomRole",
253+
Actions: []policy.Action{policy.ActionCreate},
254+
Resource: rbac.ResourceAssignRole,
255+
AuthorizeMap: map[bool][]authSubject{
256+
true: {owner},
257+
false: {userAdmin, orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe, templateAdmin},
258+
},
259+
},
251260
{
252261
Name: "RoleAssignment",
253262
Actions: []policy.Action{policy.ActionAssign, policy.ActionDelete},
@@ -380,7 +389,7 @@ func TestRolePermissions(t *testing.T) {
380389
},
381390
// Some admin style resources
382391
{
383-
Name: "Licences",
392+
Name: "Licenses",
384393
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
385394
Resource: rbac.ResourceLicense,
386395
AuthorizeMap: map[bool][]authSubject{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy