Skip to content

Commit a6996f6

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

File tree

12 files changed

+171
-48
lines changed

12 files changed

+171
-48
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, r
597597
}
598598

599599
grantedRoles := append(added, removed...)
600-
notBuiltInRoles := make([]string, 0)
600+
customRoles := make([]string, 0)
601601
// Validate that the roles being assigned are valid.
602602
for _, r := range grantedRoles {
603603
_, isOrgRole := rbac.IsOrgRole(r)
@@ -610,25 +610,25 @@ func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, r
610610

611611
// All roles should be valid roles
612612
if _, err := rbac.RoleByName(r); err != nil {
613-
notBuiltInRoles = append(notBuiltInRoles, r)
613+
customRoles = append(customRoles, r)
614614
}
615615
}
616616

617-
notBuiltInRolesMap := make(map[string]struct{}, len(notBuiltInRoles))
618-
for _, r := range notBuiltInRoles {
619-
notBuiltInRolesMap[r] = struct{}{}
617+
customRolesMap := make(map[string]struct{}, len(customRoles))
618+
for _, r := range customRoles {
619+
customRolesMap[r] = struct{}{}
620620
}
621621

622-
if len(notBuiltInRoles) > 0 {
623-
customRoles, err := q.CustomRolesByName(ctx, notBuiltInRoles)
622+
if len(customRoles) > 0 {
623+
customRoles, err := q.CustomRolesByName(ctx, customRoles)
624624
if err != nil {
625625
return xerrors.Errorf("fetching custom roles: %w", err)
626626
}
627627

628628
// If the lists are not identical, then have a problem, as some roles
629629
// provided do no exist.
630-
if len(customRoles) != len(notBuiltInRoles) {
631-
for _, role := range notBuiltInRoles {
630+
if len(customRoles) != len(customRoles) {
631+
for _, role := range customRoles {
632632
// Stop at the first one found. We could make a better error that
633633
// returns them all, but then someone could pass in a large list to make us do
634634
// a lot of loop iterations.
@@ -654,7 +654,7 @@ func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, r
654654
}
655655

656656
for _, roleName := range grantedRoles {
657-
if _, isCustom := notBuiltInRolesMap[roleName]; isCustom {
657+
if _, isCustom := customRolesMap[roleName]; isCustom {
658658
// For now, use a constant name so our static assign map still works.
659659
roleName = rbac.CustomSiteRole()
660660
}
@@ -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/dump.sql

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

coderd/database/migrations/000209_custom_roles.up.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ CREATE TABLE custom_roles (
1818

1919
-- extra convenience meta data.
2020
created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
21-
last_updated timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
21+
updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
2222
);
2323

2424
-- Ensure no case variants of the same roles

coderd/database/migrations/testdata/fixtures/000209_custom_roles.up.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ INSERT INTO
66
org_permissions,
77
user_permissions,
88
created_at,
9-
last_updated
9+
updated_at
1010
)
1111
VALUES
1212
(

coderd/database/models.go

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

coderd/database/queries.sql.go

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

coderd/database/queries/roles.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ INSERT INTO
1818
org_permissions,
1919
user_permissions,
2020
created_at,
21-
last_updated
21+
updated_at
2222
)
2323
VALUES (
2424
-- Always force lowercase names
@@ -36,6 +36,6 @@ ON CONFLICT (name)
3636
site_permissions = @site_permissions,
3737
org_permissions = @org_permissions,
3838
user_permissions = @user_permissions,
39-
last_updated = now()
39+
updated_at = now()
4040
RETURNING *
4141
;

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": {

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