Skip to content

feat: Add user roles, but do not yet enforce them #1200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Apr 29, 2022

Conversation

Emyrk
Copy link
Member

@Emyrk Emyrk commented Apr 27, 2022

What this does

Adds some builtin roles and assigns them to users and org members. This does not implement enforcing roles at this time.

Granting user's roles was added to test the api. See how to grant roles from the SDK. The "strange" part is that the rbac library is used to find the role name. In practice, there will be an endpoint to list all roles that can be granted, and the user will select from a list. The code for this exists, but I want to add this in future PRs as I work with the FE on endpoint format.

coder/coderd/users_test.go

Lines 326 to 330 in a643670

_, err = member.GrantUserRoles(ctx, codersdk.Me, codersdk.GrantUserRoles{
Roles: []string{
rbac.RoleAdmin(),
rbac.RoleOrgAdmin(first.OrganizationID),
},

Future Work

  • Add ability to remove roles
  • Add proper authorize usage on endpoints
  • List roles that can be granted

@Emyrk Emyrk requested a review from a team as a code owner April 27, 2022 21:01
coderd/coderd.go Outdated
Comment on lines 186 to 189
// TODO: @emyrk Might want to move these to a /roles group instead of /user.
// As we include more roles like org roles, it makes less sense to scope these here.
r.Put("/roles", api.putUserRoles)
r.Get("/roles", api.getUserRoles)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not bikeshed this url placement atm. I'll move the path when I talk with FE. I know the mvp for endpoints, and will change them in another PR.

Really just trying to get some footing for roles so I can start enforcing them without breaking things.

@codecov
Copy link

codecov bot commented Apr 27, 2022

Codecov Report

Merging #1200 (96fb498) into main (ba4c3ce) will increase coverage by 0.14%.
The diff coverage is 75.60%.

@@            Coverage Diff             @@
##             main    #1200      +/-   ##
==========================================
+ Coverage   65.45%   65.59%   +0.14%     
==========================================
  Files         268      269       +1     
  Lines       17029    17337     +308     
  Branches      162      162              
==========================================
+ Hits        11147    11373     +226     
- Misses       4710     4772      +62     
- Partials     1172     1192      +20     
Flag Coverage Δ
unittest-go-macos-latest 52.89% <63.27%> (+0.02%) ⬆️
unittest-go-postgres- 64.73% <75.60%> (+0.14%) ⬆️
unittest-go-ubuntu-latest 55.30% <63.27%> (+0.06%) ⬆️
unittest-go-windows-2022 52.44% <63.27%> (-0.03%) ⬇️
unittest-js 68.85% <ø> (ø)
Impacted Files Coverage Δ
coderd/audit/table.go 77.77% <ø> (ø)
coderd/rbac/authz.go 55.31% <0.00%> (-13.11%) ⬇️
coderd/users.go 61.20% <62.92%> (+0.19%) ⬆️
codersdk/users.go 64.28% <63.63%> (-0.13%) ⬇️
coderd/members.go 66.66% <66.66%> (ø)
coderd/database/queries.sql.go 81.07% <79.31%> (-0.07%) ⬇️
coderd/rbac/builtin.go 96.29% <96.29%> (ø)
coderd/coderd.go 97.66% <100.00%> (+0.12%) ⬆️
coderd/rbac/object.go 100.00% <100.00%> (ø)
peer/channel.go 84.97% <0.00%> (-0.58%) ⬇️
... and 7 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ba4c3ce...96fb498. Read the comment docs.

@Emyrk Emyrk requested review from kylecarbs and johnstcn April 28, 2022 13:27
Copy link
Member

@kylecarbs kylecarbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All minor things. I'll approve once we resolve some of the outstanding q's.

Comment on lines +16 to +17
orgAdmin string = "organization-admin"
orgMember string = "organization-member"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we reuse the admin and member strings? From what I can tell, it'll be scoped to an organization anyways.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We totally can, they are scoped. I was adding the organization prefix because I imagine in the UI they will strip the "scopeID" when assigning roles. I kinda like the fact org roles have an org prefix to prevent any misunderstanding.

Eg: A dropdown list of roles you can add to a org member, will say organization-member, not organization-member:00000000-00000000-00000000-00000000.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably wouldn't have a single dropdown to show organization and site-level roles though, because that'd easily be misinterpreted by users IMO.

// ListSiteRoles lists all roles that can be applied to a user.
// Note: This should be a list in a database, but until then we build
// the list from the builtins.
func ListSiteRoles() []string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem this is used anywhere right now!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, but it will be when I add the endpoints for retrieving roles that can applied. I wrote this to show how to get the roles for that endpoint in this design.

When we go to a database model, these won't be dynamic lists, so all this looping goes away. I'll drop this and bring it back in my next pr

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, thanks for the context!

Comment on lines 7 to 18
UPDATE
users
SET
rbac_roles = ARRAY ['member', 'organization-member:' || (SELECT id FROM organizations LIMIT 1)];

-- Give the first user created the admin role
UPDATE
users
SET
rbac_roles = rbac_roles || ARRAY ['admin']
WHERE
id = (SELECT id FROM users ORDER BY created_at ASC LIMIT 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might not want to update this data in a migration right now. I'm concerned that changing this behavior will be difficult down the road, so I'd prefer if we just straight-up broke the schema right now (eg. add the rbac_roles directly to the base.up migration. I'm not strict on this though, and it's just a hunch. Any thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should try to not break migrations, lest we become comfortable in the habit and it bite us squarely where we sit down the line.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is anyone running a persistent V2? If we are only running in dev mode, I'll drop all this.
I just didn't want to break any current deployments. But if that's cool, I'm more than happy to drop this code to assign roles in the migration 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently we have a dev and QA instance, so this will be required to not totally break those.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough fair enough

Emyrk added 3 commits April 28, 2022 10:39
- Remove string type alias, use string
- Drop unused funcs
- Fix down migration
@Emyrk Emyrk requested a review from kylecarbs April 28, 2022 19:45
Emyrk added 3 commits April 28, 2022 15:40
- UpdateRoles vs Grant/RemoveRoles
- UpdateRoles and UpdateMemberRoles
@@ -0,0 +1,18 @@
ALTER TABLE ONLY users
ADD COLUMN IF NOT EXISTS rbac_roles text[] DEFAULT '{}' NOT NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL postgres supports native text arrays 😄

organization_members
SET
-- Remove all duplicates from the roles.
roles = ARRAY(SELECT DISTINCT UNNEST(@granted_roles :: text[]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat trick! For posterity here, UNNEST turns an array into a one-row temporary table, SELECT DISTINCT removes duplicates, and then it just becomes an array again.

func (api *api) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
// User is the user to modify
// TODO: Until rbac authorize is implemented, only be able to change your
// own roles. This also means you can grant yourself whatever roles you want.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting, also "only be able to change your own roles"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, since authorize is not yet used, I let you change your roles to w/e you want. This is what we are doing for a lot of user endpoints atm

}

// UpdateOrganizationMemberRoles grants the userID the specified roles in an org.
// Include ALL roles the user has.
Copy link
Member

@johnstcn johnstcn Apr 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarification: if a user has roles {A, B, C} and we call UpdateUserRoles with {D}, does this mean the user ends up with just {D} and not {A, B, C, D}? That seems like a sharp edge.

Copy link
Member Author

@Emyrk Emyrk Apr 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. I actually had it as grant D before, but if we match the current UI, it actually makes sense to pass {A, B, C, D}.

Screenshot from 2022-04-29 08-23-24
t

The alternative is to run the diff from the UI as 2 requests.

PUT {D}
DELETE {A, B, C}

I think we can implement this logic better in the BE. I could add a route/query param to "Add" the role vs update for example. I think this is something we can make easier once the UI needs are better understood too. But w/e the end result is, the database call is ok to do an ALL, and let the Golang figure out the diffs.

Copy link
Member

@kylecarbs kylecarbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The separation of user and organization roles in the API feels really nice. Great work 🥳🥳🥳

@Emyrk Emyrk merged commit 35211e2 into main Apr 29, 2022
@Emyrk Emyrk deleted the stevenmasley/user_assign_role_rebase branch April 29, 2022 14:04
@misskniss misskniss added this to the V2 Beta milestone May 15, 2022
kylecarbs pushed a commit that referenced this pull request Jun 10, 2022
* chore: Rework roles to be expandable by name alone
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants