fleet/server/service/http_auth_test.go

245 lines
7.0 KiB
Go
Raw Normal View History

2016-09-26 18:48:55 +00:00
package service
2016-08-28 03:59:17 +00:00
import (
"bytes"
"context"
2016-08-28 03:59:17 +00:00
"encoding/json"
"errors"
2016-08-28 03:59:17 +00:00
"fmt"
"io"
2016-08-28 03:59:17 +00:00
"net/http"
"net/http/httptest"
"strconv"
2016-08-28 03:59:17 +00:00
"testing"
"time"
2016-08-28 03:59:17 +00:00
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
2021-06-26 04:46:51 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2016-08-28 03:59:17 +00:00
)
func TestLogin(t *testing.T) {
ds, users, server := setupAuthTest(t)
loginTests := []struct {
email string
2016-08-28 03:59:17 +00:00
status int
password string
}{
{
email: "admin1@example.com",
password: testUsers["admin1"].PlaintextPassword,
2016-08-28 03:59:17 +00:00
status: http.StatusOK,
},
{
email: "user1@example.com",
password: testUsers["user1"].PlaintextPassword,
status: http.StatusOK,
},
2016-08-28 03:59:17 +00:00
{
email: "nosuchuser@example.com",
2016-08-28 03:59:17 +00:00
password: "nosuchuser",
status: http.StatusUnauthorized,
},
{
email: "admin1@example.com",
2016-08-28 03:59:17 +00:00
password: "badpassword",
status: http.StatusUnauthorized,
},
}
for _, tt := range loginTests {
// test sessions
testUser := users[tt.email]
params := loginRequest{
Email: tt.email,
Password: tt.password,
2016-08-28 03:59:17 +00:00
}
j, err := json.Marshal(&params)
2016-09-14 18:40:51 +00:00
assert.Nil(t, err)
requestBody := io.NopCloser(bytes.NewBuffer(j))
resp, err := http.Post(server.URL+"/api/latest/fleet/login", "application/json", requestBody)
require.Nil(t, err)
2016-09-14 18:40:51 +00:00
assert.Equal(t, tt.status, resp.StatusCode)
2016-08-28 03:59:17 +00:00
jsn := struct {
User *fleet.User `json:"user"`
Token string `json:"token"`
Err []map[string]string `json:"errors,omitempty"`
2016-08-28 03:59:17 +00:00
}{}
2016-09-14 18:40:51 +00:00
err = json.NewDecoder(resp.Body).Decode(&jsn)
require.Nil(t, err)
2016-08-28 03:59:17 +00:00
if tt.status != http.StatusOK {
2016-09-14 18:40:51 +00:00
assert.NotEqual(t, "", jsn.Err)
2016-08-28 03:59:17 +00:00
continue // skip remaining tests
}
require.NotNil(t, jsn.User)
assert.Equal(t, tt.email, jsn.User.Email)
2016-08-28 03:59:17 +00:00
// ensure that a session was created for our test user and stored
sessions, err := ds.ListSessionsForUser(context.Background(), testUser.ID)
assert.Nil(t, err)
assert.Len(t, sessions, 1)
// ensure the session key is not blank
assert.NotEqual(t, "", sessions[0].Key)
// test logout
req, _ := http.NewRequest("POST", server.URL+"/api/latest/fleet/logout", nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", jsn.Token))
client := fleethttp.NewClient()
resp, err = client.Do(req)
require.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, strconv.Itoa(tt.status))
2016-09-14 18:40:51 +00:00
_, err = io.ReadAll(resp.Body)
2016-09-14 18:40:51 +00:00
assert.Nil(t, err)
// ensure that our user's session was deleted from the store
sessions, err = ds.ListSessionsForUser(context.Background(), testUser.ID)
assert.Nil(t, err)
assert.Len(t, sessions, 0)
2016-08-28 03:59:17 +00:00
}
}
func setupAuthTest(t *testing.T) (fleet.Datastore, map[string]fleet.User, *httptest.Server) {
ds := new(mock.Store)
var users []*fleet.User
sessions := make(map[string]*fleet.Session)
ds.NewUserFunc = func(ctx context.Context, user *fleet.User) (*fleet.User, error) {
users = append(users, user)
return user, nil
}
ds.SessionByKeyFunc = func(ctx context.Context, key string) (*fleet.Session, error) {
return sessions[key], nil
}
ds.MarkSessionAccessedFunc = func(ctx context.Context, session *fleet.Session) error {
s := sessions[session.Key]
s.AccessedAt = time.Now()
sessions[session.Key] = s
return nil
}
ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) {
for _, user := range users {
if user.ID == id {
return user, nil
}
}
return nil, errors.New("user not found")
}
ds.ListUsersFunc = func(ctx context.Context, opt fleet.UserListOptions) ([]*fleet.User, error) {
return users, nil
}
ds.ListSessionsForUserFunc = func(ctx context.Context, id uint) ([]*fleet.Session, error) {
var userSessions []*fleet.Session
for _, session := range sessions {
if session.UserID == id {
userSessions = append(userSessions, session)
}
}
return userSessions, nil
}
ds.SessionByIDFunc = func(ctx context.Context, id uint) (*fleet.Session, error) {
for _, session := range sessions {
if session.ID == id {
return session, nil
}
}
return nil, errors.New("session not found")
}
ds.DestroySessionFunc = func(ctx context.Context, session *fleet.Session) error {
delete(sessions, session.Key)
return nil
}
usersMap, server := RunServerForTestsWithDS(t, ds)
ds.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) {
user := usersMap[email]
return &user, nil
}
ds.NewSessionFunc = func(ctx context.Context, userID uint, sessionKey string) (*fleet.Session, error) {
session := &fleet.Session{
UserID: userID,
Key: sessionKey,
AccessedAt: time.Now(),
}
sessions[sessionKey] = session
return session, nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
return nil
}
return ds, usersMap, server
}
func getTestAdminToken(t *testing.T, server *httptest.Server) string {
return getTestUserToken(t, server, "admin1")
}
func getTestUserToken(t *testing.T, server *httptest.Server, testUserId string) string {
testUser := testUsers[testUserId]
params := loginRequest{
Email: testUser.Email,
Password: testUser.PlaintextPassword,
}
j, err := json.Marshal(&params)
assert.Nil(t, err)
requestBody := io.NopCloser(bytes.NewBuffer(j))
resp, err := http.Post(server.URL+"/api/latest/fleet/login", "application/json", requestBody)
require.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
jsn := struct {
User *fleet.User `json:"user"`
Token string `json:"token"`
Err []map[string]string `json:"errors,omitempty"`
}{}
err = json.NewDecoder(resp.Body).Decode(&jsn)
require.Nil(t, err)
return jsn.Token
}
func TestNoHeaderErrorsDifferently(t *testing.T) {
_, _, server := setupAuthTest(t)
req, _ := http.NewRequest("GET", server.URL+"/api/latest/fleet/users", nil)
client := fleethttp.NewClient()
resp, err := client.Do(req)
require.Nil(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
Add UUID to Fleet errors and clean up error msgs (#10411) #8129 Apart from fixing the issue in #8129, this change also introduces UUIDs to Fleet errors. To be able to match a returned error from the API to a error in the Fleet logs. See https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for more context. Samples with the changes in this PR: ``` curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d '' { "message": "Bad request", "errors": [ { "name": "base", "reason": "Expected JSON Body" } ], "uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0" } ``` ``` curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd' { "message": "Bad request", "errors": [ { "name": "base", "reason": "json decoder error" } ], "uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d" } ``` ``` curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams" { "message": "Authentication required", "errors": [ { "name": "base", "reason": "Authentication required" } ], "uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf" } ``` ``` curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}' { "message": "Authorization header required", "errors": [ { "name": "base", "reason": "Authorization header required" } ], "uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3" } ``` ``` curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}' { "message": "Permission Denied", "uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06" } ``` - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md) - ~[ ] Documented any permissions changes~ - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [X] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
jsn := struct {
Message string `json:"message"`
Errs []map[string]string `json:"errors,omitempty"`
UUID string `json:"uuid"`
}{}
err = json.NewDecoder(resp.Body).Decode(&jsn)
require.NoError(t, err)
assert.Equal(t, "Authorization header required", jsn.Message)
require.Len(t, jsn.Errs, 1)
assert.Equal(t, "base", jsn.Errs[0]["name"])
assert.Equal(t, "Authorization header required", jsn.Errs[0]["reason"])
assert.NotEmpty(t, jsn.UUID)
req, _ = http.NewRequest("GET", server.URL+"/api/latest/fleet/users", nil)
req.Header.Add("Authorization", "Bearer AAAA")
resp, err = client.Do(req)
Add UUID to Fleet errors and clean up error msgs (#10411) #8129 Apart from fixing the issue in #8129, this change also introduces UUIDs to Fleet errors. To be able to match a returned error from the API to a error in the Fleet logs. See https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for more context. Samples with the changes in this PR: ``` curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d '' { "message": "Bad request", "errors": [ { "name": "base", "reason": "Expected JSON Body" } ], "uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0" } ``` ``` curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd' { "message": "Bad request", "errors": [ { "name": "base", "reason": "json decoder error" } ], "uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d" } ``` ``` curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams" { "message": "Authentication required", "errors": [ { "name": "base", "reason": "Authentication required" } ], "uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf" } ``` ``` curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}' { "message": "Authorization header required", "errors": [ { "name": "base", "reason": "Authorization header required" } ], "uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3" } ``` ``` curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}' { "message": "Permission Denied", "uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06" } ``` - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md) - ~[ ] Documented any permissions changes~ - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [X] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
Add UUID to Fleet errors and clean up error msgs (#10411) #8129 Apart from fixing the issue in #8129, this change also introduces UUIDs to Fleet errors. To be able to match a returned error from the API to a error in the Fleet logs. See https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for more context. Samples with the changes in this PR: ``` curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d '' { "message": "Bad request", "errors": [ { "name": "base", "reason": "Expected JSON Body" } ], "uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0" } ``` ``` curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd' { "message": "Bad request", "errors": [ { "name": "base", "reason": "json decoder error" } ], "uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d" } ``` ``` curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams" { "message": "Authentication required", "errors": [ { "name": "base", "reason": "Authentication required" } ], "uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf" } ``` ``` curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}' { "message": "Authorization header required", "errors": [ { "name": "base", "reason": "Authorization header required" } ], "uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3" } ``` ``` curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}' { "message": "Permission Denied", "uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06" } ``` - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md) - ~[ ] Documented any permissions changes~ - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [X] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
err = json.NewDecoder(resp.Body).Decode(&jsn)
require.NoError(t, err)
assert.Equal(t, "Authentication required", jsn.Message)
require.Len(t, jsn.Errs, 1)
assert.Equal(t, "base", jsn.Errs[0]["name"])
assert.Equal(t, "Authentication required", jsn.Errs[0]["reason"])
assert.NotEmpty(t, jsn.UUID)
}