Skip to content
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

[v15] User traits editor UI and Web API #43068

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions lib/web/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,16 @@ func createUser(r *http.Request, m userAPIGetter, createdBy string) (*ui.User, e

user.SetRoles(req.Roles)

updateUserTraits(req, user)
// checkAndSetDefaults makes sure either TraitsPreset
// or AllTraits field to be populated. Since empty
// AllTraits is also used to delete all user traits,
// we explicitly check if TraitsPreset is empty so
// to prevent traits deletion.
if req.TraitsPreset == nil {
user.SetTraits(req.AllTraits)
} else {
updateUserTraitsPreset(req, user)
}

user.SetCreatedBy(types.CreatedBy{
User: types.UserRef{Name: createdBy},
Expand All @@ -119,30 +128,30 @@ func createUser(r *http.Request, m userAPIGetter, createdBy string) (*ui.User, e
return ui.NewUser(created)
}

// updateUserTraits receives a saveUserRequest and updates the user traits accordingly
// It only updates the traits that have a non-nil value in saveUserRequest
// This allows the partial update of the properties
func updateUserTraits(req *saveUserRequest, user types.User) {
if req.Traits.Logins != nil {
user.SetLogins(*req.Traits.Logins)
// updateUserTraitsPreset receives a saveUserRequest and updates the user traits
// accordingly. It only updates the traits that have a non-nil value in
// saveUserRequest. This allows the partial update of the properties
func updateUserTraitsPreset(req *saveUserRequest, user types.User) {
if req.TraitsPreset.Logins != nil {
user.SetLogins(*req.TraitsPreset.Logins)
}
if req.Traits.DatabaseUsers != nil {
user.SetDatabaseUsers(*req.Traits.DatabaseUsers)
if req.TraitsPreset.DatabaseUsers != nil {
user.SetDatabaseUsers(*req.TraitsPreset.DatabaseUsers)
}
if req.Traits.DatabaseNames != nil {
user.SetDatabaseNames(*req.Traits.DatabaseNames)
if req.TraitsPreset.DatabaseNames != nil {
user.SetDatabaseNames(*req.TraitsPreset.DatabaseNames)
}
if req.Traits.KubeUsers != nil {
user.SetKubeUsers(*req.Traits.KubeUsers)
if req.TraitsPreset.KubeUsers != nil {
user.SetKubeUsers(*req.TraitsPreset.KubeUsers)
}
if req.Traits.KubeGroups != nil {
user.SetKubeGroups(*req.Traits.KubeGroups)
if req.TraitsPreset.KubeGroups != nil {
user.SetKubeGroups(*req.TraitsPreset.KubeGroups)
}
if req.Traits.WindowsLogins != nil {
user.SetWindowsLogins(*req.Traits.WindowsLogins)
if req.TraitsPreset.WindowsLogins != nil {
user.SetWindowsLogins(*req.TraitsPreset.WindowsLogins)
}
if req.Traits.AWSRoleARNs != nil {
user.SetAWSRoleARNs(*req.Traits.AWSRoleARNs)
if req.TraitsPreset.AWSRoleARNs != nil {
user.SetAWSRoleARNs(*req.TraitsPreset.AWSRoleARNs)
}
}

Expand All @@ -169,7 +178,16 @@ func updateUser(r *http.Request, m userAPIGetter) (*ui.User, error) {

user.SetRoles(req.Roles)

updateUserTraits(req, user)
// checkAndSetDefaults makes sure either TraitsPreset
// or AllTraits field to be populated. Since empty
// AllTraits is also used to delete all user traits,
// we explicitly check if TraitsPreset is empty so
// to prevent traits deletion.
if req.TraitsPreset == nil {
user.SetTraits(req.AllTraits)
} else {
updateUserTraitsPreset(req, user)
}

updated, err := m.UpdateUser(r.Context(), user)
if err != nil {
Expand Down Expand Up @@ -287,7 +305,8 @@ type userAPIGetter interface {
DeleteUser(ctx context.Context, user string) error
}

type userTraits struct {
// traitsPreset are user traits that are pre-defined in Teleport
type traitsPreset struct {
Logins *[]string `json:"logins,omitempty"`
DatabaseUsers *[]string `json:"databaseUsers,omitempty"`
DatabaseNames *[]string `json:"databaseNames,omitempty"`
Expand All @@ -303,11 +322,21 @@ type userTraits struct {
// They are optional and respect the following logic:
// - if the value is nil, we ignore it
// - if the value is an empty array we remove every element from the trait
// - otherwise, we replace the list for that trait
// - otherwise, we replace the list for that trait.
// Use TraitsPreset to selectively update traits.
// Use AllTraits to fully replace existing traits.
type saveUserRequest struct {
Name string `json:"name"`
Roles []string `json:"roles"`
Traits userTraits `json:"traits"`
// Name is username.
Name string `json:"name"`
// Roles is slice of user roles assigned to user.
Roles []string `json:"roles"`
// TraitsPreset holds traits that are pre-defined in Teleport.
// Clients may use TraitsPreset to selectively update user traits.
TraitsPreset *traitsPreset `json:"traits"`
// AllTraits may hold all the user traits, including traits key defined
// in TraitsPreset and/or new trait key values defined by Teleport admin.
// AllTraits should be used to fully replace and update user traits.
AllTraits map[string][]string `json:"allTraits"`
}

func (r *saveUserRequest) checkAndSetDefaults() error {
Expand All @@ -317,5 +346,8 @@ func (r *saveUserRequest) checkAndSetDefaults() error {
if len(r.Roles) == 0 {
return trace.BadParameter("missing roles")
}
if len(r.AllTraits) != 0 && r.TraitsPreset != nil {
return trace.BadParameter("either traits or allTraits must be provided")
}
return nil
}
Loading
Loading