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

* chore: Rework roles to be expandable by name alone
This commit is contained in:
Steven Masley
2022-04-29 09:04:19 -05:00
committed by GitHub
parent ba4c3ce3b9
commit 35211e2190
26 changed files with 1150 additions and 232 deletions
+111 -2
View File
@@ -19,6 +19,7 @@ import (
"github.com/coder/coder/coderd/gitsshkey"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/coderd/rbac"
"github.com/coder/coder/coderd/userpassword"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/cryptorand"
@@ -82,6 +83,21 @@ func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) {
return
}
// TODO: @emyrk this currently happens outside the database tx used to create
// the user. Maybe I add this ability to grant roles in the createUser api
// and add some rbac bypass when calling api functions this way??
// Add the admin role to this first user
_, err = api.Database.UpdateUserRoles(r.Context(), database.UpdateUserRolesParams{
GrantedRoles: []string{rbac.RoleAdmin(), rbac.RoleMember()},
ID: user.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: err.Error(),
})
return
}
httpapi.Write(rw, http.StatusCreated, codersdk.CreateFirstUserResponse{
UserID: user.ID,
OrganizationID: organizationID,
@@ -344,6 +360,88 @@ func (api *api) putUserSuspend(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations))
}
func (api *api) userRoles(rw http.ResponseWriter, r *http.Request) {
user := httpmw.UserParam(r)
resp := codersdk.UserRoles{
Roles: user.RBACRoles,
OrganizationRoles: make(map[uuid.UUID][]string),
}
memberships, err := api.Database.GetOrganizationMembershipsByUserID(r.Context(), user.ID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get user memberships: %s", err),
})
return
}
for _, mem := range memberships {
resp.OrganizationRoles[mem.OrganizationID] = mem.Roles
}
httpapi.Write(rw, http.StatusOK, resp)
}
func (api *api) putUserRoles(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.
user := httpmw.UserParam(r)
apiKey := httpmw.APIKey(r)
if apiKey.UserID != user.ID {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: fmt.Sprintf("modifying other users is not supported at this time"),
})
return
}
var params codersdk.UpdateRoles
if !httpapi.Read(rw, r, &params) {
return
}
updatedUser, err := api.updateSiteUserRoles(r.Context(), database.UpdateUserRolesParams{
GrantedRoles: params.Roles,
ID: user.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: err.Error(),
})
return
}
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get organization IDs: %s", err.Error()),
})
return
}
httpapi.Write(rw, http.StatusOK, convertUser(updatedUser, organizationIDs))
}
func (api *api) updateSiteUserRoles(ctx context.Context, args database.UpdateUserRolesParams) (database.User, error) {
// Enforce only site wide roles
for _, r := range args.GrantedRoles {
if _, ok := rbac.IsOrgRole(r); ok {
return database.User{}, xerrors.Errorf("must only update site wide roles")
}
if _, err := rbac.RoleByName(r); err != nil {
return database.User{}, xerrors.Errorf("%q is not a supported role", r)
}
}
updatedUser, err := api.Database.UpdateUserRoles(ctx, args)
if err != nil {
return database.User{}, xerrors.Errorf("update site roles: %w", err)
}
return updatedUser, nil
}
// Returns organizations the parameterized user has access to.
func (api *api) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
user := httpmw.UserParam(r)
@@ -440,7 +538,11 @@ func (api *api) postOrganizationsByUser(rw http.ResponseWriter, r *http.Request)
UserID: user.ID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Roles: []string{"organization-admin"},
Roles: []string{
// Also assign member role incase they get demoted from admin
rbac.RoleOrgMember(organization.ID),
rbac.RoleOrgAdmin(organization.ID),
},
})
if err != nil {
return xerrors.Errorf("create organization member: %w", err)
@@ -604,6 +706,7 @@ func (api *api) createAPIKey(rw http.ResponseWriter, r *http.Request, params dat
func (api *api) createUser(ctx context.Context, req codersdk.CreateUserRequest) (database.User, uuid.UUID, error) {
var user database.User
return user, req.OrganizationID, api.Database.InTx(func(db database.Store) error {
var orgRoles []string
// If no organization is provided, create a new one for the user.
if req.OrganizationID == uuid.Nil {
organization, err := db.InsertOrganization(ctx, database.InsertOrganizationParams{
@@ -616,7 +719,10 @@ func (api *api) createUser(ctx context.Context, req codersdk.CreateUserRequest)
return xerrors.Errorf("create organization: %w", err)
}
req.OrganizationID = organization.ID
orgRoles = append(orgRoles, rbac.RoleOrgAdmin(req.OrganizationID))
}
// Always also be a member
orgRoles = append(orgRoles, rbac.RoleOrgMember(req.OrganizationID))
params := database.InsertUserParams{
ID: uuid.New(),
@@ -624,6 +730,8 @@ func (api *api) createUser(ctx context.Context, req codersdk.CreateUserRequest)
Username: req.Username,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
// All new users are defaulted to members of the site.
RBACRoles: []string{rbac.RoleMember()},
}
// If a user signs up with OAuth, they can have no password!
if req.Password != "" {
@@ -659,7 +767,8 @@ func (api *api) createUser(ctx context.Context, req codersdk.CreateUserRequest)
UserID: user.ID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Roles: []string{},
// By default give them membership to the organization
Roles: orgRoles,
})
if err != nil {
return xerrors.Errorf("create organization member: %w", err)