Skip to content

Commit

Permalink
chore: static custom role assignment (#13297)
Browse files Browse the repository at this point in the history
For now, only owners can assign custom roles
  • Loading branch information
Emyrk committed May 16, 2024
1 parent 2eba21e commit c58a6fd
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 27 deletions.
74 changes: 72 additions & 2 deletions coderd/database/dbmem/dbmem.go
Original file line number Diff line number Diff line change
Expand Up @@ -3168,6 +3168,30 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
GROUP BY
start_time, user_id, slug, display_name, icon
),
-- Analyze the users unique app usage across all templates. Count
-- usage across consecutive intervals as continuous usage.
times_used AS (
SELECT DISTINCT ON (user_id, slug, display_name, icon, uniq)
slug,
display_name,
icon,
-- Turn start_time into a unique identifier that identifies a users
-- continuous app usage. The value of uniq is otherwise garbage.
--
-- Since we're aggregating per user app usage across templates,
-- there can be duplicate start_times. To handle this, we use the
-- dense_rank() function, otherwise row_number() would suffice.
start_time - (
dense_rank() OVER (
PARTITION BY
user_id, slug, display_name, icon
ORDER BY
start_time
) * '30 minutes'::interval
) AS uniq
FROM
template_usage_stats_with_apps
),
*/

// Due to query optimizations, this logic is somewhat inverted from
Expand All @@ -3179,12 +3203,19 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
DisplayName string
Icon string
}
type appTimesUsedGroupBy struct {
UserID uuid.UUID
Slug string
DisplayName string
Icon string
}
type appInsightsRow struct {
appInsightsGroupBy
TemplateIDs []uuid.UUID
AppUsageMins int64
}
appInsightRows := make(map[appInsightsGroupBy]appInsightsRow)
appTimesUsedRows := make(map[appTimesUsedGroupBy]map[time.Time]struct{})
// FROM
for _, stat := range q.templateUsageStats {
// WHERE
Expand Down Expand Up @@ -3220,9 +3251,42 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
row.TemplateIDs = append(row.TemplateIDs, stat.TemplateID)
row.AppUsageMins = least(row.AppUsageMins+appUsage, 30)
appInsightRows[key] = row

// Prepare to do times_used calculation, distinct start times.
timesUsedKey := appTimesUsedGroupBy{
UserID: stat.UserID,
Slug: slug,
DisplayName: app.DisplayName,
Icon: app.Icon,
}
if appTimesUsedRows[timesUsedKey] == nil {
appTimesUsedRows[timesUsedKey] = make(map[time.Time]struct{})
}
// This assigns a distinct time, so we don't need to
// dense_rank() later on, we can simply do row_number().
appTimesUsedRows[timesUsedKey][stat.StartTime] = struct{}{}
}
}

appTimesUsedTempRows := make(map[appTimesUsedGroupBy][]time.Time)
for key, times := range appTimesUsedRows {
for t := range times {
appTimesUsedTempRows[key] = append(appTimesUsedTempRows[key], t)
}
}
for _, times := range appTimesUsedTempRows {
slices.SortFunc(times, func(a, b time.Time) int {
return int(a.Sub(b))
})
}
for key, times := range appTimesUsedTempRows {
uniq := make(map[time.Time]struct{})
for i, t := range times {
uniq[t.Add(-(30 * time.Minute * time.Duration(i)))] = struct{}{}
}
appTimesUsedRows[key] = uniq
}

/*
-- Even though we allow identical apps to be aggregated across
-- templates, we still want to be able to report which templates
Expand Down Expand Up @@ -3307,14 +3371,20 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G

var rows []database.GetTemplateAppInsightsRow
for key, gr := range groupedRows {
rows = append(rows, database.GetTemplateAppInsightsRow{
row := database.GetTemplateAppInsightsRow{
TemplateIDs: templateRows[key].TemplateIDs,
ActiveUsers: int64(len(uniqueSortedUUIDs(gr.ActiveUserIDs))),
Slug: key.Slug,
DisplayName: key.DisplayName,
Icon: key.Icon,
UsageSeconds: gr.UsageSeconds,
})
}
for tuk, uniq := range appTimesUsedRows {
if key.Slug == tuk.Slug && key.DisplayName == tuk.DisplayName && key.Icon == tuk.Icon {
row.TimesUsed += int64(len(uniq))
}
}
rows = append(rows, row)
}

// NOTE(mafredri): Add sorting if we decide on how to handle PostgreSQL collations.
Expand Down
50 changes: 42 additions & 8 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion coderd/rbac/object_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion coderd/rbac/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ var RBACPermissions = map[string]PermissionDefinition{
Actions: map[Action]ActionDefinition{
ActionAssign: actDef("ability to assign roles"),
ActionRead: actDef("view what roles are assignable"),
ActionDelete: actDef("ability to delete roles"),
ActionDelete: actDef("ability to unassign roles"),
ActionCreate: actDef("ability to create/delete/edit custom roles"),
},
},
"assign_org_role": {
Expand Down
36 changes: 22 additions & 14 deletions coderd/rbac/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const (
templateAdmin string = "template-admin"
userAdmin string = "user-admin"
auditor string = "auditor"
// customSiteRole is a placeholder for all custom site roles.
// This is used for what roles can assign other roles.
// TODO: Make this more dynamic to allow other roles to grant.
customSiteRole string = "custom-site-role"

orgAdmin string = "organization-admin"
orgMember string = "organization-member"
Expand Down Expand Up @@ -52,6 +56,8 @@ func RoleOwner() string {
return roleName(owner, "")
}

func CustomSiteRole() string { return roleName(customSiteRole, "") }

func RoleTemplateAdmin() string {
return roleName(templateAdmin, "")
}
Expand Down Expand Up @@ -320,22 +326,24 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
// map[actor_role][assign_role]<can_assign>
var assignRoles = map[string]map[string]bool{
"system": {
owner: true,
auditor: true,
member: true,
orgAdmin: true,
orgMember: true,
templateAdmin: true,
userAdmin: true,
owner: true,
auditor: true,
member: true,
orgAdmin: true,
orgMember: true,
templateAdmin: true,
userAdmin: true,
customSiteRole: true,
},
owner: {
owner: true,
auditor: true,
member: true,
orgAdmin: true,
orgMember: true,
templateAdmin: true,
userAdmin: true,
owner: true,
auditor: true,
member: true,
orgAdmin: true,
orgMember: true,
templateAdmin: true,
userAdmin: true,
customSiteRole: true,
},
userAdmin: {
member: true,
Expand Down
11 changes: 10 additions & 1 deletion coderd/rbac/roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,15 @@ func TestRolePermissions(t *testing.T) {
false: {otherOrgAdmin, otherOrgMember, memberMe, userAdmin},
},
},
{
Name: "CreateCustomRole",
Actions: []policy.Action{policy.ActionCreate},
Resource: rbac.ResourceAssignRole,
AuthorizeMap: map[bool][]authSubject{
true: {owner},
false: {userAdmin, orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe, templateAdmin},
},
},
{
Name: "RoleAssignment",
Actions: []policy.Action{policy.ActionAssign, policy.ActionDelete},
Expand Down Expand Up @@ -380,7 +389,7 @@ func TestRolePermissions(t *testing.T) {
},
// Some admin style resources
{
Name: "Licences",
Name: "Licenses",
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
Resource: rbac.ResourceLicense,
AuthorizeMap: map[bool][]authSubject{
Expand Down

0 comments on commit c58a6fd

Please sign in to comment.