mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
+111
-2
@@ -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, ¶ms) {
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user