Files
backroad/api/http/handler/users/user_create_access_token_test.go
itsconquest bca1c6b9cf feat(internal-auth): ability to set minimum password length [EE-3175] (#6942)
* feat(internal-auth): ability to set minimum password length [EE-3175]

* pass props to react component

* fixes + WIP slider

* fix slider updating + add styles

* remove nested ternary

* fix slider updating + add remind me later button

* add length to settings + value & onchange method

* finish my account view

* fix slider updating

* slider styles

* update style

* move slider in

* update size of slider

* allow admin to browse to authentication view

* use feather icons instead of font awesome

* feat(settings): add colors to password rules

* clean up tooltip styles

* more style changes

* styles

* fixes + use requiredLength in password field for icon logic

* simplify logic

* simplify slider logic and remove debug code

* use required length for logic to display pwd length warning

* fix slider styles

* use requiredPasswordLength to determine if password is valid

* style tooltip based on theme

* reset skips when password is changed

* misc cleanup

* reset skips when required length is changed

* fix formatting

* fix issues

* implement some suggestions

* simplify logic

* update broken test

* pick min password length from DB

* fix suggestions

* set up min password length in the DB

* fix test after migration

* fix formatting issue

* fix bug with icon

* refactored migration

* fix typo

* fixes

* fix logic

* set skips per user

* reset skips for all users on length change

Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com>
Co-authored-by: Dmitry Salakhov <to@dimasalakhov.com>
2022-06-03 16:00:13 +12:00

158 lines
4.9 KiB
Go

package users
import (
"bytes"
"encoding/json"
"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/stretchr/testify/assert"
)
func Test_userCreateAccessToken(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
// 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\"}\n", 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)
}
}
}