fleet/server/service/http_auth_test.go
Victor Lyuboslavsky 913a5904c8
Move NewActivity to activity bounded context (#39521)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #38536 

This PR moves all logic to create new activities to activity bounded
context.
The old service and ActivityModule methods are not facades that route to
the new activity bounded context. The facades will be removed in a
subsequent PR.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added webhook support for activity events with configurable endpoint
and enable/disable settings.
* Enhanced automation-initiated activity creation without requiring a
user context.
* Improved activity service architecture with centralized creation and
management.

* **Improvements**
* Refactored activity creation to use a dedicated service layer for
better separation of concerns.
* Added support for host-specific and automation-originated activities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-25 14:11:03 -06:00

252 lines
7.2 KiB
Go

package service
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/fleetdm/fleet/v4/server/service/contract"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLogin(t *testing.T) {
ds, users, server := setupAuthTest(t)
loginTests := []struct {
email string
status int
password string
}{
{
email: "admin1@example.com",
password: testUsers["admin1"].PlaintextPassword,
status: http.StatusOK,
},
{
email: "user1@example.com",
password: testUsers["user1"].PlaintextPassword,
status: http.StatusOK,
},
{
email: "nosuchuser@example.com",
password: "nosuchuser",
status: http.StatusUnauthorized,
},
{
email: "admin1@example.com",
password: "badpassword",
status: http.StatusUnauthorized,
},
}
for _, tt := range loginTests {
// test sessions
testUser := users[tt.email]
params := contract.LoginRequest{
Email: tt.email,
Password: tt.password,
}
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, tt.status, 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)
if tt.status != http.StatusOK {
assert.NotEqual(t, "", jsn.Err)
continue // skip remaining tests
}
require.NotNil(t, jsn.User)
assert.Equal(t, tt.email, jsn.User.Email)
// 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))
_, err = io.ReadAll(resp.Body)
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)
}
}
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, sessionKeySize int) (*fleet.Session, error) {
key := make([]byte, sessionKeySize)
_, err := rand.Read(key)
if err != nil {
return nil, err
}
sessionKey := base64.StdEncoding.EncodeToString(key)
session := &fleet.Session{
UserID: userID,
Key: sessionKey,
AccessedAt: time.Now(),
}
sessions[sessionKey] = session
return session, nil
}
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, 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 := contract.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)
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)
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
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)
}