Files
teleport/lib/web/users_test.go
T

383 lines
10 KiB
Go

/**
* Copyright 2021 Gravitational, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package web
import (
"bytes"
"context"
"encoding/json"
"net/http"
"testing"
"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
)
func TestRequestParameters(t *testing.T) {
r := saveUserRequest{
Name: "",
Roles: nil,
Traits: userTraits{},
}
require.True(t, trace.IsBadParameter(r.checkAndSetDefaults()))
r = saveUserRequest{
Name: "",
Roles: []string{"testrole"},
Traits: userTraits{},
}
require.True(t, trace.IsBadParameter(r.checkAndSetDefaults()))
r = saveUserRequest{
Name: "username",
Roles: nil,
Traits: userTraits{},
}
require.True(t, trace.IsBadParameter(r.checkAndSetDefaults()))
r = saveUserRequest{
Name: "username",
Roles: []string{"testrole"},
Traits: userTraits{},
}
require.Nil(t, r.checkAndSetDefaults())
}
func TestCRUDs(t *testing.T) {
u := saveUserRequest{
Name: "testname",
Roles: []string{"testrole"},
Traits: userTraits{},
}
m := &mockedUserAPIGetter{}
m.mockCreateUser = func(ctx context.Context, user types.User) error {
return nil
}
m.mockGetUser = func(name string, withSecrets bool) (types.User, error) {
return types.NewUser(name)
}
m.mockUpdateUser = func(ctx context.Context, user types.User) error {
return nil
}
m.mockGetUsers = func(withSecrets bool) ([]types.User, error) {
u, err := types.NewUser("testname")
return []types.User{u}, err
}
m.mockDeleteUser = func(ctx context.Context, user string) error {
return nil
}
// test create
user, err := createUser(newRequest(t, u), m, "")
require.Nil(t, err)
require.Equal(t, "testname", user.Name)
require.Equal(t, "local", user.AuthType)
require.Contains(t, user.Roles, "testrole")
// test update
u.Roles = []string{"newrole"}
user, err = updateUser(newRequest(t, u), m, "")
require.Nil(t, err)
require.Contains(t, user.Roles, "newrole")
// test list
users, err := getUsers(m)
require.Nil(t, err)
require.Len(t, users, 1)
require.Equal(t, "testname", users[0].Name)
// test delete
param := httprouter.Params{httprouter.Param{Key: "username", Value: "testname"}}
req, err := http.NewRequest("", "/:username", nil)
require.Nil(t, err)
err = deleteUser(req, param, m, "self")
require.Nil(t, err)
}
func TestUpdateUser_setTraits(t *testing.T) {
defaultRoles := []string{"role1"}
defaultLogins := []string{"login1"}
tests := []struct {
name string
updateReq saveUserRequest
expectedTraits map[string][]string
}{
{
name: "Logins",
updateReq: saveUserRequest{
Name: "setlogins",
Roles: defaultRoles,
Traits: userTraits{Logins: &[]string{"login1", "login2"}},
},
expectedTraits: map[string][]string{
constants.TraitLogins: {"login1", "login2"},
},
},
{
name: "DB",
updateReq: saveUserRequest{
Name: "setdb",
Roles: defaultRoles,
Traits: userTraits{
Logins: &defaultLogins,
DatabaseUsers: &[]string{"dbuser1", "dbuser2"},
DatabaseNames: &[]string{"dbname1", "dbname2"},
},
},
expectedTraits: map[string][]string{
constants.TraitDBUsers: {"dbuser1", "dbuser2"},
constants.TraitDBNames: {"dbname1", "dbname2"},
constants.TraitLogins: defaultLogins,
},
},
{
name: "Kube",
updateReq: saveUserRequest{
Name: "setkube",
Roles: defaultRoles,
Traits: userTraits{
Logins: &defaultLogins,
KubeUsers: &[]string{"kubeuser1", "kubeuser2"},
KubeGroups: &[]string{"kubegroup1", "kubegroup2"},
},
},
expectedTraits: map[string][]string{
constants.TraitKubeUsers: {"kubeuser1", "kubeuser2"},
constants.TraitKubeGroups: {"kubegroup1", "kubegroup2"},
constants.TraitLogins: defaultLogins,
},
},
{
name: "WindowsLogins",
updateReq: saveUserRequest{
Name: "setwindowslogins",
Roles: defaultRoles,
Traits: userTraits{
Logins: &defaultLogins,
WindowsLogins: &[]string{"login1", "login2"},
},
},
expectedTraits: map[string][]string{
constants.TraitWindowsLogins: {"login1", "login2"},
constants.TraitLogins: defaultLogins,
},
},
{
name: "AWSRoleARNs",
updateReq: saveUserRequest{
Name: "setawsrolearns",
Roles: defaultRoles,
Traits: userTraits{
Logins: &defaultLogins,
AWSRoleARNs: &[]string{"arn1", "arn2"},
},
},
expectedTraits: map[string][]string{
constants.TraitAWSRoleARNs: {"arn1", "arn2"},
constants.TraitLogins: defaultLogins,
},
},
{
name: "Deduplicates",
updateReq: saveUserRequest{
Name: "deduplicates",
Roles: defaultRoles,
Traits: userTraits{Logins: &[]string{"login1", "login2", "login1"}},
},
expectedTraits: map[string][]string{
constants.TraitLogins: {"login1", "login2"},
},
},
{
name: "RemovesAll",
updateReq: saveUserRequest{
Name: "removesall",
Roles: defaultRoles,
Traits: userTraits{Logins: &[]string{}},
},
expectedTraits: map[string][]string{
constants.TraitLogins: {},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
user, err := types.NewUser(tt.name)
require.NoError(t, err)
user.SetRoles(defaultRoles)
user.SetLogins(defaultLogins)
m := &mockedUserAPIGetter{}
m.mockGetUser = func(name string, withSecrets bool) (types.User, error) {
return user, nil
}
m.mockUpdateUser = func(ctx context.Context, user types.User) error {
return nil
}
_, err = updateUser(newRequest(t, tt.updateReq), m, "")
require.NoError(t, err)
// The traits match
require.Equal(t, tt.expectedTraits, user.GetTraits())
// Other fields dont't change
require.ElementsMatch(t, user.GetRoles(), defaultRoles)
// We can read back the user traits
uiUser, err := getUser(tt.name, m)
require.NoError(t, err)
require.ElementsMatch(t, uiUser.Traits.Logins, tt.expectedTraits[constants.TraitLogins])
require.ElementsMatch(t, uiUser.Traits.DatabaseUsers, tt.expectedTraits[constants.TraitDBUsers])
require.ElementsMatch(t, uiUser.Traits.DatabaseNames, tt.expectedTraits[constants.TraitDBNames])
require.ElementsMatch(t, uiUser.Traits.KubeUsers, tt.expectedTraits[constants.TraitKubeUsers])
require.ElementsMatch(t, uiUser.Traits.KubeGroups, tt.expectedTraits[constants.TraitKubeGroups])
require.ElementsMatch(t, uiUser.Traits.WindowsLogins, tt.expectedTraits[constants.TraitWindowsLogins])
require.ElementsMatch(t, uiUser.Traits.AWSRoleARNs, tt.expectedTraits[constants.TraitAWSRoleARNs])
})
}
}
func TestCRUDErrors(t *testing.T) {
m := &mockedUserAPIGetter{}
m.mockCreateUser = func(ctx context.Context, user types.User) error {
return trace.AlreadyExists("")
}
m.mockGetUser = func(name string, withSecrets bool) (types.User, error) {
return nil, trace.NotFound("")
}
m.mockUpdateUser = func(ctx context.Context, user types.User) error {
return trace.NotFound("")
}
m.mockGetUsers = func(withSecrets bool) ([]types.User, error) {
return nil, trace.AccessDenied("")
}
m.mockDeleteUser = func(ctx context.Context, user string) error {
return trace.NotFound("")
}
u := saveUserRequest{
Name: "testname",
Roles: []string{"testrole"},
Traits: userTraits{Logins: nil},
}
// update errors
user, err := updateUser(newRequest(t, u), m, "")
require.True(t, trace.IsNotFound(err))
require.Nil(t, user)
// create errors
user, err = createUser(newRequest(t, u), m, "")
require.True(t, trace.IsAlreadyExists(err))
require.Nil(t, user)
users, err := getUsers(m)
require.True(t, trace.IsAccessDenied(err))
require.Nil(t, users)
// delete errors
param := httprouter.Params{httprouter.Param{Key: "username", Value: "testname"}}
req, err := http.NewRequest("", "/:username", nil)
require.Nil(t, err)
err = deleteUser(req, param, m, "self")
require.True(t, trace.IsNotFound(err))
// deleting self error
param = httprouter.Params{httprouter.Param{Key: "username", Value: "self"}}
req, err = http.NewRequest("", "/:username", nil)
require.Nil(t, err)
err = deleteUser(req, param, m, "self")
require.True(t, trace.IsBadParameter(err))
}
// newRequest creates http request with given body
func newRequest(t *testing.T, body interface{}) *http.Request {
reqBody, err := json.Marshal(body)
require.Nil(t, err)
req, err := http.NewRequest("", "", bytes.NewBuffer(reqBody))
require.Nil(t, err)
req.Header.Add("Content-Type", "application/json")
return req
}
type mockedUserAPIGetter struct {
mockGetUser func(name string, withSecrets bool) (types.User, error)
mockCreateUser func(ctx context.Context, user types.User) error
mockUpdateUser func(ctx context.Context, user types.User) error
mockGetUsers func(withSecrets bool) ([]types.User, error)
mockDeleteUser func(ctx context.Context, user string) error
}
func (m *mockedUserAPIGetter) GetUser(name string, withSecrets bool) (types.User, error) {
if m.mockGetUser != nil {
return m.mockGetUser(name, withSecrets)
}
return nil, trace.NotImplemented("mockGetUser not implemented")
}
func (m *mockedUserAPIGetter) CreateUser(ctx context.Context, user types.User) error {
if m.mockCreateUser != nil {
return m.mockCreateUser(ctx, user)
}
return trace.NotImplemented("mockCreateUser not implemented")
}
func (m *mockedUserAPIGetter) UpdateUser(ctx context.Context, user types.User) error {
if m.mockUpdateUser != nil {
return m.mockUpdateUser(ctx, user)
}
return trace.NotImplemented("mockUpdateUser not implemented")
}
func (m *mockedUserAPIGetter) GetUsers(withSecrets bool) ([]types.User, error) {
if m.mockGetUsers != nil {
return m.mockGetUsers(withSecrets)
}
return nil, trace.NotImplemented("mockGetUsers not implemented")
}
func (m *mockedUserAPIGetter) DeleteUser(ctx context.Context, name string) error {
if m.mockDeleteUser != nil {
return m.mockDeleteUser(ctx, name)
}
return trace.NotImplemented("mockDeleteUser not implemented")
}