1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

fix(api-key): add password requirement to generate api key [EE-6140] (#10617)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run

This commit is contained in:
Matt Hook 2024-01-09 11:14:24 +13:00 committed by GitHub
parent 236e669332
commit dbd2e609d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 305 additions and 251 deletions

View file

@ -15,18 +15,22 @@ import (
)
type userAccessTokenCreatePayload struct {
Password string `validate:"required" example:"password" json:"password"`
Description string `validate:"required" example:"github-api-key" json:"description"`
}
func (payload *userAccessTokenCreatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Password) {
return errors.New("invalid password: cannot be empty")
}
if govalidator.IsNull(payload.Description) {
return errors.New("invalid description. cannot be empty")
return errors.New("invalid description: cannot be empty")
}
if govalidator.HasWhitespaceOnly(payload.Description) {
return errors.New("invalid description. cannot contain only whitespaces")
return errors.New("invalid description: cannot contain only whitespaces")
}
if govalidator.MinStringLength(payload.Description, "128") {
return errors.New("invalid description. cannot be longer than 128 characters")
return errors.New("invalid description: cannot be longer than 128 characters")
}
return nil
}
@ -82,7 +86,12 @@ func (handler *Handler) userCreateAccessToken(w http.ResponseWriter, r *http.Req
user, err := handler.DataStore.User().Read(portainer.UserID(userID))
if err != nil {
return httperror.BadRequest("Unable to find a user", err)
return httperror.InternalServerError("Unable to find a user with the specified identifier inside the database", err)
}
err = handler.CryptoService.CompareHashAndData(user.Password, payload.Password)
if err != nil {
return httperror.Forbidden("Current password doesn't match", errors.New("Current password does not match the password provided. Please try again"))
}
rawAPIKey, apiKey, err := handler.apiKeyService.GenerateApiKey(*user, payload.Description)

View file

@ -25,7 +25,7 @@ func Test_userCreateAccessToken(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, true)
// create admin and standard user(s)
adminUser := &portainer.User{ID: 1, Username: "admin", Role: portainer.AdministratorRole}
adminUser := &portainer.User{ID: 1, Password: "password", Username: "admin", Role: portainer.AdministratorRole}
err := store.User().Create(adminUser)
is.NoError(err, "error creating admin user")
@ -43,13 +43,14 @@ func Test_userCreateAccessToken(t *testing.T) {
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, nil, passwordChecker)
h.DataStore = store
h.CryptoService = testhelpers.NewCryptoService()
// 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"}
data := userAccessTokenCreatePayload{Password: "password", Description: "test-token"}
payload, err := json.Marshal(data)
is.NoError(err)
@ -72,7 +73,7 @@ func Test_userCreateAccessToken(t *testing.T) {
})
t.Run("admin cannot generate API key for standard user", func(t *testing.T) {
data := userAccessTokenCreatePayload{Description: "test-token-admin"}
data := userAccessTokenCreatePayload{Password: "password", Description: "test-token-admin"}
payload, err := json.Marshal(data)
is.NoError(err)
@ -92,7 +93,7 @@ func Test_userCreateAccessToken(t *testing.T) {
rawAPIKey, _, err := apiKeyService.GenerateApiKey(*user, "test-api-key")
is.NoError(err)
data := userAccessTokenCreatePayload{Description: "test-token-fails"}
data := userAccessTokenCreatePayload{Password: "password", Description: "test-token-fails"}
payload, err := json.Marshal(data)
is.NoError(err)
@ -118,23 +119,23 @@ func Test_userAccessTokenCreatePayload(t *testing.T) {
shouldFail bool
}{
{
payload: userAccessTokenCreatePayload{Description: "test-token"},
payload: userAccessTokenCreatePayload{Password: "password", Description: "test-token"},
shouldFail: false,
},
{
payload: userAccessTokenCreatePayload{Description: ""},
payload: userAccessTokenCreatePayload{Password: "password", Description: ""},
shouldFail: true,
},
{
payload: userAccessTokenCreatePayload{Description: "test token"},
payload: userAccessTokenCreatePayload{Password: "password", Description: "test token"},
shouldFail: false,
},
{
payload: userAccessTokenCreatePayload{Description: "test-token "},
payload: userAccessTokenCreatePayload{Password: "password", Description: "test-token "},
shouldFail: false,
},
{
payload: userAccessTokenCreatePayload{Description: `
payload: userAccessTokenCreatePayload{Password: "password", 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.