Skip to content

Commit 88ca559

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

File tree

6 files changed

+150
-27
lines changed

6 files changed

+150
-27
lines changed

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: 42 additions & 8 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)