Files
backroad/api/http/handler/users/user_create_access_token_test.go

158 lines
4.9 KiB
Go

package users
import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/jwt"
"github.com/segmentio/encoding/json"
"github.com/stretchr/testify/assert"
)
func Test_userCreateAccessToken(t *testing.T) {
is := assert.New(t)
_, store := datastore.MustNewTestStore(t, true, true)
// create admin and standard user(s)
adminUser := &portainer.User{ID: 1, Username: "admin", Role: portainer.AdministratorRole}
err := store.User().Create(adminUser)
is.NoError(err, "error creating admin user")
user := &portainer.User{ID: 2, Username: "standard", Role: portainer.StandardUserRole}
err = store.User().Create(user)
is.NoError(err, "error creating user")
// setup services
jwtService, err := jwt.NewService("1h", store)
is.NoError(err, "Error initiating jwt service")
apiKeyService := apikey.NewAPIKeyService(store.APIKeyRepository(), store.User())
requestBouncer := security.NewRequestBouncer(store, jwtService, apiKeyService)
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
passwordChecker := security.NewPasswordStrengthChecker(store.SettingsService)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, nil, passwordChecker)
h.DataStore = store
// generate standard and admin user tokens
adminJWT, _ := jwtService.GenerateToken(&portainer.TokenData{ID: adminUser.ID, Username: adminUser.Username, Role: adminUser.Role})
jwt, _ := jwtService.GenerateToken(&portainer.TokenData{ID: user.ID, Username: user.Username, Role: user.Role})
t.Run("standard user successfully generates API key", func(t *testing.T) {
data := userAccessTokenCreatePayload{Description: "test-token"}
payload, err := json.Marshal(data)
is.NoError(err)
req := httptest.NewRequest(http.MethodPost, "/users/2/tokens", bytes.NewBuffer(payload))
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", jwt))
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
is.Equal(http.StatusCreated, rr.Code)
body, err := io.ReadAll(rr.Body)
is.NoError(err, "ReadAll should not return error")
var resp accessTokenResponse
err = json.Unmarshal(body, &resp)
is.NoError(err, "response should be json")
is.EqualValues(data.Description, resp.APIKey.Description)
is.NotEmpty(resp.RawAPIKey)
})
t.Run("admin cannot generate API key for standard user", func(t *testing.T) {
data := userAccessTokenCreatePayload{Description: "test-token-admin"}
payload, err := json.Marshal(data)
is.NoError(err)
req := httptest.NewRequest(http.MethodPost, "/users/2/tokens", bytes.NewBuffer(payload))
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", adminJWT))
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
is.Equal(http.StatusForbidden, rr.Code)
_, err = io.ReadAll(rr.Body)
is.NoError(err, "ReadAll should not return error")
})
t.Run("endpoint cannot generate api-key using api-key auth", func(t *testing.T) {
rawAPIKey, _, err := apiKeyService.GenerateApiKey(*user, "test-api-key")
is.NoError(err)
data := userAccessTokenCreatePayload{Description: "test-token-fails"}
payload, err := json.Marshal(data)
is.NoError(err)
req := httptest.NewRequest(http.MethodPost, "/users/2/tokens", bytes.NewBuffer(payload))
req.Header.Add("x-api-key", rawAPIKey)
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
is.Equal(http.StatusUnauthorized, rr.Code)
body, err := io.ReadAll(rr.Body)
is.NoError(err, "ReadAll should not return error")
is.Equal(`{"message":"Auth not supported","details":"JWT Authentication required"}`, string(body))
})
}
func Test_userAccessTokenCreatePayload(t *testing.T) {
is := assert.New(t)
tests := []struct {
payload userAccessTokenCreatePayload
shouldFail bool
}{
{
payload: userAccessTokenCreatePayload{Description: "test-token"},
shouldFail: false,
},
{
payload: userAccessTokenCreatePayload{Description: ""},
shouldFail: true,
},
{
payload: userAccessTokenCreatePayload{Description: "test token"},
shouldFail: false,
},
{
payload: userAccessTokenCreatePayload{Description: "test-token "},
shouldFail: false,
},
{
payload: userAccessTokenCreatePayload{Description: `
this string is longer than 128 characters and hence this will fail.
this string is longer than 128 characters and hence this will fail.
this string is longer than 128 characters and hence this will fail.
this string is longer than 128 characters and hence this will fail.
this string is longer than 128 characters and hence this will fail.
this string is longer than 128 characters and hence this will fail.
`},
shouldFail: true,
},
}
for _, test := range tests {
err := test.payload.Validate(nil)
if test.shouldFail {
is.Error(err)
} else {
is.NoError(err)
}
}
}