blob: 35864892b4c3ba1ea3c656657f824d2da6cae567 [file] [log] [blame]
package v4
/*
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.
*/
import (
"fmt"
"net/http"
"net/mail"
"sort"
"strconv"
"strings"
"testing"
"time"
"github.com/apache/trafficcontrol/lib/go-rfc"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
)
func TestUsers(t *testing.T) {
WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() {
GetTestUsersIMS(t)
currentTime := time.Now().UTC().Add(-5 * time.Second)
time := currentTime.Format(time.RFC1123)
var header http.Header
header = make(map[string][]string)
header.Set(rfc.IfModifiedSince, time)
SortTestUsers(t)
UpdateTestUsers(t)
GetTestUsersIMSAfterChange(t, header)
OpsUpdateAdminTest(t)
UserSelfUpdateTest(t)
UserUpdateOwnRoleTest(t)
GetTestUsers(t)
GetTestUserCurrent(t)
UserTenancyTest(t)
if includeSystemTests {
// UserRegistrationTest deletes test users before registering new users, so it must come after the other user tests.
UserRegistrationTest(t)
}
})
}
func GetTestUsersIMSAfterChange(t *testing.T, header http.Header) {
opts := client.NewRequestOptions()
opts.Header = header
resp, reqInf, err := TOSession.GetUsers(opts)
if err != nil {
t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, resp.Alerts)
}
if reqInf.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 status code, got %v", reqInf.StatusCode)
}
currentTime := time.Now().UTC()
currentTime = currentTime.Add(1 * time.Second)
timeStr := currentTime.Format(time.RFC1123)
opts.Header.Set(rfc.IfModifiedSince, timeStr)
resp, reqInf, err = TOSession.GetUsers(opts)
if err != nil {
t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, resp.Alerts)
}
if reqInf.StatusCode != http.StatusNotModified {
t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
}
}
const SessionUserName = "admin" // TODO make dynamic?
func GetTestUsersIMS(t *testing.T) {
futureTime := time.Now().AddDate(0, 0, 1)
time := futureTime.Format(time.RFC1123)
opts := client.NewRequestOptions()
opts.Header.Set(rfc.IfModifiedSince, time)
resp, reqInf, err := TOSession.GetUsers(opts)
if err != nil {
t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, resp.Alerts)
}
if reqInf.StatusCode != http.StatusNotModified {
t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
}
}
func CreateTestUsers(t *testing.T) {
for _, user := range testData.Users {
resp, _, err := TOSession.CreateUser(user, client.RequestOptions{})
if err != nil {
t.Errorf("could not create user: %v - alerts: %+v", err, resp.Alerts)
}
}
}
func OpsUpdateAdminTest(t *testing.T) {
toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs)
opsTOClient, _, err := client.LoginWithAgent(TOSession.URL, "opsuser", "pa$$word", true, "to-api-v3-client-tests/opsuser", true, toReqTimeout)
if err != nil {
t.Fatalf("failed to get log in with opsuser: %v", err.Error())
}
opts := client.NewRequestOptions()
opts.QueryParameters.Set("username", "admin")
resp, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Errorf("cannot get users filtered by username 'admin': %v - alerts: %+v", err, resp.Alerts)
}
if len(resp.Response) != 1 {
t.Fatalf("Expected exactly one user to exist with username 'admin', found: %d", len(resp.Response))
}
user := resp.Response[0]
if user.ID == nil {
t.Fatal("Traffic Ops returned a representation for the 'admin' user with null or undefined ID")
}
fullName := "oops"
email := "oops@ops.net"
user.FullName = &fullName
user.Email = &email
_, _, err = opsTOClient.UpdateUser(*user.ID, user, client.RequestOptions{})
if err == nil {
t.Error("ops user incorrectly updated an admin")
}
}
func SortTestUsers(t *testing.T) {
resp, _, err := TOSession.GetUsers(client.RequestOptions{})
if err != nil {
t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, resp.Alerts)
}
sortedList := make([]string, 0, len(resp.Response))
for _, user := range resp.Response {
sortedList = append(sortedList, user.Username)
}
if !sort.StringsAreSorted(sortedList) {
t.Errorf("list is not sorted by their names: %v", sortedList)
}
}
func UserRegistrationTest(t *testing.T) {
ForceDeleteTestUsers(t)
var emails []string
opts := client.NewRequestOptions()
for _, user := range testData.Users {
if user.Tenant == nil || user.Email == nil {
t.Error("Found User in the testing data with null or undefined Tenant and/or Email address")
continue
}
opts.QueryParameters.Set("name", *user.Tenant)
resp, _, err := TOSession.GetTenants(opts)
if err != nil {
t.Fatalf("could not get Tenants filtered by name '%s': %v - alerts: %+v", *user.Tenant, err, resp.Alerts)
}
if len(resp.Response) != 1 {
t.Fatalf("Expected exactly one Tenant to exist with the name '%s', found: %d", *user.Tenant, len(resp.Response))
}
tenant := resp.Response[0]
regResp, _, err := TOSession.RegisterNewUser(uint(tenant.ID), user.Role, rfc.EmailAddress{Address: mail.Address{Address: *user.Email}}, client.RequestOptions{})
if err != nil {
t.Fatalf("could not register user: %v - alerts: %+v", err, regResp.Alerts)
}
emails = append(emails, fmt.Sprintf(`'%v'`, *user.Email))
}
db, err := OpenConnection()
if err != nil {
t.Error("cannot open db")
}
defer db.Close()
q := `DELETE FROM tm_user WHERE email IN (` + strings.Join(emails, ",") + `)`
if err := execSQL(db, q); err != nil {
t.Errorf("cannot execute SQL to delete registered users: %s; SQL is %s", err.Error(), q)
}
}
func UserSelfUpdateTest(t *testing.T) {
toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs)
opsTOClient, _, err := client.LoginWithAgent(TOSession.URL, "opsuser", "pa$$word", true, "to-api-v3-client-tests/opsuser", true, toReqTimeout)
if err != nil {
t.Fatalf("failed to get log in with opsuser: %v", err.Error())
}
opts := client.NewRequestOptions()
opts.QueryParameters.Set("username", "opsuser")
resp, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Fatalf("cannot get users filtered by username 'opsuser': %v - alerts: %+v", err, resp.Alerts)
}
if len(resp.Response) < 1 {
t.Fatalf("no users returned when requesting user 'opsuser'")
}
user := resp.Response[0]
if user.ID == nil {
t.Fatalf("user 'opsuser' has a null or missing ID - cannot proceed")
}
user.FullName = util.StrPtr("Oops-man")
user.Email = util.StrPtr("operator@not.example.com")
updateResp, _, err := opsTOClient.UpdateUser(*user.ID, user, client.RequestOptions{})
if err != nil {
t.Fatalf("cannot update user: %v - alerts: %+v", err, updateResp.Alerts)
}
// Make sure it got updated
opts.QueryParameters.Del("username")
opts.QueryParameters.Set("id", strconv.Itoa(*user.ID))
resp2, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Fatalf("cannot get users filtered by ID %d: %v - alerts: %+v", *user.ID, err, resp2.Alerts)
}
if len(resp2.Response) < 1 {
t.Fatalf("no results returned when requesting user #%d", *user.ID)
}
updatedUser := resp2.Response[0]
if updatedUser.FullName == nil {
t.Errorf("user was not correctly updated, FullName is null or missing")
} else if *updatedUser.FullName != "Oops-man" {
t.Errorf("results do not match actual: '%s', expected: 'Oops-man'\n", *updatedUser.FullName)
}
if updatedUser.Email == nil {
t.Errorf("user was not correctly updated, Email is null or missing")
} else if *updatedUser.Email != "operator@not.example.com" {
t.Errorf("results do not match actual: '%s', expected: 'operator@not.example.com'\n", *updatedUser.Email)
}
// Same thing using /user/current
user.FullName = util.StrPtr("ops-man")
user.Email = util.StrPtr("operator@example.com")
updateResp, _, err = opsTOClient.UpdateCurrentUser(user, client.RequestOptions{})
if err != nil {
t.Fatalf("error updating current user: %v - alerts: %+v", err, updateResp.Alerts)
}
// Make sure it got updated
resp2, _, err = TOSession.GetUsers(opts)
if err != nil {
t.Fatalf("error getting user #%d: %v - alerts: %+v", *user.ID, err, resp2.Alerts)
}
if len(resp2.Response) < 1 {
t.Fatalf("no user returned when requesting user #%d", *user.ID)
}
if resp2.Response[0].FullName == nil {
t.Errorf("FullName missing or null after update")
} else if *resp2.Response[0].FullName != "ops-man" {
t.Errorf("Expected FullName to be 'ops-man', but it was '%s'", *resp2.Response[0].FullName)
}
if resp2.Response[0].Email == nil {
t.Errorf("Email missing or null after update")
} else if *resp2.Response[0].Email != "operator@example.com" {
t.Errorf("Expected Email to be restored to 'operator@example.com', but it was '%s'", *resp2.Response[0].Email)
}
// now test using an invalid email address
currentEmail := *user.Email
user.Email = new(string)
updateResp, _, err = TOSession.UpdateCurrentUser(user, client.RequestOptions{})
if err == nil {
t.Fatal("error was expected updating user with email: '' - got none")
}
// Ensure it wasn't actually updated
resp2, _, err = TOSession.GetUsers(opts)
if err != nil {
t.Fatalf("error getting user #%d: %v - alerts: %+v", *user.ID, err, resp2.Alerts)
}
if len(resp2.Response) < 1 {
t.Fatalf("no user returned when requesting user #%d", *user.ID)
}
if resp2.Response[0].Email == nil {
t.Errorf("Email missing or null after update")
} else if *resp2.Response[0].Email != currentEmail {
t.Errorf("Expected Email to still be '%s', but it was '%s'", currentEmail, *resp2.Response[0].Email)
}
}
func UserUpdateOwnRoleTest(t *testing.T) {
opts := client.NewRequestOptions()
opts.QueryParameters.Set("username", SessionUserName)
resp, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Errorf("cannot get users filtered by username '%s': %v - alerts: %+v", SessionUserName, err, resp.Alerts)
}
user := resp.Response[0]
if user.ID == nil {
t.Fatalf("Traffic Ops returned a representation for user '%s' with null or undefined ID", SessionUserName)
}
user.Role = user.Role + "_updated"
_, _, err = TOSession.UpdateUser(*user.ID, user, client.RequestOptions{})
if err == nil {
t.Error("user incorrectly updated their role")
}
}
func UpdateTestUsers(t *testing.T) {
if len(testData.Users) < 1 {
t.Fatal("Need at least one User to test updating users")
}
firstUsername := testData.Users[0].Username
opts := client.NewRequestOptions()
opts.QueryParameters.Set("username", firstUsername)
resp, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Errorf("cannot get users filtered by username '%s': %v - alerts: %+v", firstUsername, err, resp.Alerts)
}
if len(resp.Response) != 1 {
t.Fatalf("Expected exactly one user to exist with username '%s', found: %d", firstUsername, len(resp.Response))
}
user := resp.Response[0]
if user.City == nil || user.ID == nil {
t.Fatal("Traffic Ops returned a representation for a user with null or undefined ID and/or City")
}
newCity := "kidz kable kown"
*user.City = newCity
var updateResp tc.UpdateUserResponseV4
updateResp, _, err = TOSession.UpdateUser(*user.ID, user, client.RequestOptions{})
if err != nil {
t.Errorf("cannot update user: %v - alerts: %+v", err, updateResp.Alerts)
}
// Make sure it got updated
opts.QueryParameters.Del("username")
opts.QueryParameters.Set("id", strconv.Itoa(*user.ID))
resp2, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Errorf("cannot get users filtered by id %d: %v - alerts: %+v", *user.ID, err, resp2.Alerts)
}
if len(resp2.Response) != 1 {
t.Fatalf("Expected exactly one user to exist with ID %d, found: %d", *user.ID, len(resp2.Response))
}
updatedUser := resp2.Response[0]
if updatedUser.City == nil {
t.Error("Traffic Ops returned a representation of a user with null or undefined City")
} else if *updatedUser.City != newCity {
t.Errorf("results do not match actual: %s, expected: %s", *updatedUser.City, newCity)
}
if user.RegistrationSent == nil {
if updatedUser.RegistrationSent != nil {
t.Errorf("Updated user has registration sent time when original did not (and no registration was sent): %s", *updatedUser.RegistrationSent)
}
} else if updatedUser.RegistrationSent == nil {
t.Errorf("Updated user was supposed to have registration sent time '%s', but it had null or undefined", *user.RegistrationSent)
} else if *resp.Response[0].RegistrationSent != *resp2.Response[0].RegistrationSent {
t.Errorf("registration_sent value shouldn't have been updated, expectd: %s, got: %s", *resp.Response[0].RegistrationSent, *resp2.Response[0].RegistrationSent)
}
}
func GetTestUsers(t *testing.T) {
resp, _, err := TOSession.GetUsers(client.RequestOptions{})
if err != nil {
t.Errorf("cannot get users: %v - alerts: %+v", err, resp.Alerts)
}
if len(resp.Response) < 1 {
t.Fatalf("expected a users list, got nothing")
}
}
func GetTestUserCurrent(t *testing.T) {
user, _, err := TOSession.GetUserCurrent(client.RequestOptions{})
if err != nil {
t.Errorf("cannot get current user: %v - alerts: %+v", err, user.Alerts)
}
if user.Response.Username != SessionUserName {
t.Errorf("current user expected: '%s' actual: '%s'", SessionUserName, user.Response.Username)
}
}
func UserTenancyTest(t *testing.T) {
users, _, err := TOSession.GetUsers(client.RequestOptions{})
if err != nil {
t.Errorf("cannot get users: %v - alerts: %+v", err, users.Alerts)
}
tenant3Found := false
tenant4Found := false
tenant3Username := "tenant3user"
tenant4Username := "tenant4user"
tenant3User := tc.UserV4{}
// assert admin user can view tenant3user and tenant4user
for _, user := range users.Response {
if user.ID == nil {
t.Error("Traffic Ops returned a representation for a user with null or undefined ID")
continue
}
if user.Username == tenant3Username {
tenant3Found = true
tenant3User = user
} else if user.Username == tenant4Username {
tenant4Found = true
}
if tenant3Found && tenant4Found {
break
}
}
if !tenant3Found || !tenant4Found {
t.Error("expected admin to be able to view tenants: tenant3 and tenant4")
}
toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs)
tenant4TOClient, _, err := client.LoginWithAgent(TOSession.URL, "tenant4user", "pa$$word", true, "to-api-v3-client-tests/tenant4user", true, toReqTimeout)
if err != nil {
t.Fatalf("failed to log in with tenant4user: %v", err.Error())
}
usersReadableByTenant4, _, err := tenant4TOClient.GetUsers(client.RequestOptions{})
if err != nil {
t.Errorf("tenant4user cannot get users: %v - alerts: %+v", err, usersReadableByTenant4.Alerts)
}
tenant4canReadItself := false
for _, user := range usersReadableByTenant4.Response {
// assert that tenant4user cannot read tenant3user
if user.Username == tenant3Username {
t.Error("expected tenant4user to be unable to read tenant3user")
}
// assert that tenant4user can read itself
if user.Username == tenant4Username {
tenant4canReadItself = true
}
}
if !tenant4canReadItself {
t.Error("expected tenant4user to be able to read itself")
}
// assert that tenant4user cannot update tenant3user
if _, _, err = tenant4TOClient.UpdateUser(*tenant3User.ID, tenant3User, client.RequestOptions{}); err == nil {
t.Error("expected tenant4user to be unable to update tenant4user")
}
// assert that tenant4user cannot create a user outside of its tenant
opts := client.NewRequestOptions()
opts.QueryParameters.Set("name", "root")
resp, _, err := TOSession.GetTenants(opts)
if err != nil {
t.Errorf("Unexpected error getting the root Tenant: %v - alerts: %+v", err, resp.Alerts)
}
if len(resp.Response) != 1 {
t.Fatalf("Expected exactly one Tenant to exist with the name 'root', found: %d", len(resp.Response))
}
rootTenant := resp.Response[0]
if len(testData.Users) < 1 {
t.Fatal("Need at least one User to continue testing User Tenancy")
}
newUser := testData.Users[0]
newUser.Email = util.StrPtr("testusertenancy@example.com")
newUser.Username = "testusertenancy"
newUser.TenantID = rootTenant.ID
if _, _, err = tenant4TOClient.CreateUser(newUser, client.RequestOptions{}); err == nil {
t.Error("expected tenant4user to be unable to create a new user in the root tenant")
}
}
// ForceDeleteTestUsers forcibly deletes the users from the db.
func ForceDeleteTestUsers(t *testing.T) {
// NOTE: Special circumstances! This should *NOT* be done without a really good reason!
// Connects directly to the DB to remove users rather than going thru the client.
// This is required here because the DeleteUser action does not really delete users, but disables them.
db, err := OpenConnection()
if err != nil {
t.Error("cannot open db")
}
defer db.Close()
var usernames []string
for _, user := range testData.Users {
usernames = append(usernames, `'`+user.Username+`'`)
}
// there is a constraint that prevents users from being deleted when they have a log
q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE username = 'admin')`
err = execSQL(db, q)
if err != nil {
t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
}
q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, ",") + `)`
err = execSQL(db, q)
if err != nil {
t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
}
}
func ForceDeleteTestUsersByUsernames(t *testing.T, usernames []string) {
// NOTE: Special circumstances! This should *NOT* be done without a really good reason!
// Connects directly to the DB to remove users rather than going thru the client.
// This is required here because the DeleteUser action does not really delete users, but disables them.
db, err := OpenConnection()
if err != nil {
t.Error("cannot open db")
}
defer db.Close()
for i, u := range usernames {
usernames[i] = `'` + u + `'`
}
// there is a constraint that prevents users from being deleted when they have a log
q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE username = 'admin')`
err = execSQL(db, q)
if err != nil {
t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
}
q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, ",") + `)`
err = execSQL(db, q)
if err != nil {
t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
}
}
func DeleteTestUsers(t *testing.T) {
opts := client.NewRequestOptions()
for _, user := range testData.Users {
opts.QueryParameters.Set("username", user.Username)
resp, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Errorf("cannot get users filtered by username '%s': %v - alerts: %+v", user.Username, err, resp.Alerts)
}
if len(resp.Response) > 0 {
respUser := resp.Response[0]
if respUser.ID == nil {
t.Error("Traffic Ops returned a representation for a user with null or undefined ID")
continue
}
delResp, _, err := TOSession.DeleteUser(*respUser.ID, client.RequestOptions{})
if err != nil {
t.Errorf("cannot delete user '%s': %v - alerts: %+v", user.Username, err, delResp.Alerts)
}
// Make sure it got deleted
resp, _, err := TOSession.GetUsers(opts)
if err != nil {
t.Errorf("error getting users filtered by username after supposed deletion: %v - alerts: %+v", err, resp.Alerts)
}
if len(resp.Response) > 0 {
t.Errorf("expected user: %s to be deleted", user.Username)
}
}
}
}