diff --git a/internal/clients/composer/client.go b/internal/clients/composer/client.go index 22a5318fe..e28f976c1 100644 --- a/internal/clients/composer/client.go +++ b/internal/clients/composer/client.go @@ -146,7 +146,3 @@ func (cc *ComposerClient) CloneCompose(id uuid.UUID, clone CloneComposeBody) (*h func (cc *ComposerClient) CloneStatus(id uuid.UUID) (*http.Response, error) { return cc.request("GET", fmt.Sprintf("%s/clones/%s", cc.composerURL, id), nil, nil) } - -func (cu *User) IsRedacted() bool { - return cu.Password == nil || *cu.Password == "" -} diff --git a/internal/v1/api.go b/internal/v1/api.go index 0c17e05ad..37f31c9c7 100644 --- a/internal/v1/api.go +++ b/internal/v1/api.go @@ -945,11 +945,15 @@ type UploadStatusStatus string // UploadTypes defines model for UploadTypes. type UploadTypes string -// User defines model for User. +// User At least one of password, ssh_key must be set, validator takes care of it. +// On update empty string can be used to remove password or ssh_key, +// but at least one of them still must be present. type User struct { Name string `json:"name"` // Password Plaintext passwords are also supported, they will be hashed and stored using the SHA-512 algorithm. + // The password is never returned in the response. + // Empty string can be used to remove the password during update but only with ssh_key set. Password *string `json:"password,omitempty"` SshKey *string `json:"ssh_key,omitempty"` } diff --git a/internal/v1/api.yaml b/internal/v1/api.yaml index b7bb7c2dc..cc5e7fd5c 100644 --- a/internal/v1/api.yaml +++ b/internal/v1/api.yaml @@ -1883,7 +1883,10 @@ components: type: object required: - name - # One of (password, ssh_key) must be set, validator takes care of it + description: | + At least one of password, ssh_key must be set, validator takes care of it. + On update empty string can be used to remove password or ssh_key, + but at least one of them still must be present. properties: name: type: string @@ -1897,6 +1900,8 @@ components: example: "$6$G91SvTj7uVp3xhqj$zVa8nqnJTlewniDII5dmvsBJnj3kloL3CXWdPDu9.e677VoRQd5zB6GKwkDvfGLoRR7NTl5nXLnJywk6IPIvS." description: | Plaintext passwords are also supported, they will be hashed and stored using the SHA-512 algorithm. + The password is never returned in the response. + Empty string can be used to remove the password during update but only with ssh_key set. Filesystem: type: object required: diff --git a/internal/v1/handler.go b/internal/v1/handler.go index 3ef7cb32c..d3763593a 100644 --- a/internal/v1/handler.go +++ b/internal/v1/handler.go @@ -268,8 +268,9 @@ func (h *Handlers) GetComposeStatus(ctx echo.Context, composeId uuid.UUID) error return err } if composeRequest.Customizations != nil && composeRequest.Customizations.Users != nil { - for _, u := range *composeRequest.Customizations.Users { - u.RedactPassword() + users := *composeRequest.Customizations.Users + for i := range users { + users[i].RedactPassword() } } diff --git a/internal/v1/handler_blueprints.go b/internal/v1/handler_blueprints.go index b91128ad9..3eec43369 100644 --- a/internal/v1/handler_blueprints.go +++ b/internal/v1/handler_blueprints.go @@ -25,6 +25,7 @@ import ( var ( blueprintNameRegex = regexp.MustCompile(`\S+`) + customizationUserNameRegex = regexp.MustCompile(`\S+`) blueprintInvalidNameDetail = "The blueprint name must contain at least two characters." ) @@ -36,15 +37,10 @@ type BlueprintBody struct { func (u *User) CryptPassword() error { // Prevent empty and already hashed password from being hashed - if u.Password == nil || (len(*u.Password) == 0 || crypt.PasswordIsCrypted(*u.Password)) { + if u.Password == nil || len(*u.Password) == 0 || crypt.PasswordIsCrypted(*u.Password) { return nil } - if u.IsRedacted() { - return errors.New( - "password is redacted and thus can't be hashed and stored in database, use plaintext password or already hashed password", - ) - } pw, err := crypt.CryptSHA512(*u.Password) if err != nil { return err @@ -53,15 +49,9 @@ func (u *User) CryptPassword() error { return nil } +// Set password to nil if it is not nil func (u *User) RedactPassword() { - redactedPassword := "" - if u.Password != nil { - *u.Password = redactedPassword - } -} - -func (u *User) IsRedacted() bool { - return u.Password == nil || *u.Password == "" + u.Password = nil } func (bb *BlueprintBody) CryptPasswords() error { @@ -84,6 +74,58 @@ func (bb *BlueprintBody) RedactPasswords() { } } +// Merges Password or SshKey from other User struct to this User struct if it is not set +func (u *User) MergeExisting(other User) { + if u.Password == nil { + u.Password = other.Password + } + if u.SshKey == nil { + u.SshKey = other.SshKey + } +} + +// User must have name and non-empty password or ssh key +func (u *User) Valid() error { + validName := customizationUserNameRegex.MatchString(u.Name) + validPassword := u.Password != nil && len(*u.Password) > 0 + validSshKey := u.SshKey != nil && len(*u.SshKey) > 0 + if !validName || !(validPassword || validSshKey) { + return fmt.Errorf("User ('%s') must have a name and either a password or an SSH key set.", u.Name) + } + return nil +} + +func (u *User) MergeForUpdate(userData []User) error { + // If both password and ssh_key in request user we don't need to fetch user from DB + if !(u.Password != nil && len(*u.Password) > 0 && u.SshKey != nil && len(*u.SshKey) > 0) { + eui := slices.IndexFunc(userData, func(eu User) bool { + return eu.Name == u.Name + }) + + if eui == -1 { // User not found in DB + err := u.Valid() + if err != nil { + return err + } + } else { + u.MergeExisting(userData[eui]) + } + } + + // If there is empty string in password or ssh_key, it means that we should remove it (set to nil) + if u.Password != nil && *u.Password == "" { + u.Password = nil + } + if u.SshKey != nil && *u.SshKey == "" { + u.SshKey = nil + } + + if err := u.Valid(); err != nil { + return err + } + return nil +} + // Util function used to create and update Blueprint from API request (WRITE) func BlueprintFromAPI(cbr CreateBlueprintRequest) (BlueprintBody, error) { bb := BlueprintBody{ @@ -105,7 +147,14 @@ func BlueprintFromEntry(be *db.BlueprintEntry) (BlueprintBody, error) { if err != nil { return BlueprintBody{}, err } + return result, nil +} +func BlueprintFromEntryWithRedactedPasswords(be *db.BlueprintEntry) (BlueprintBody, error) { + result, err := BlueprintFromEntry(be) + if err != nil { + return BlueprintBody{}, err + } result.RedactPasswords() return result, nil } @@ -122,16 +171,6 @@ func (h *Handlers) CreateBlueprint(ctx echo.Context) error { return err } - blueprint, err := BlueprintFromAPI(blueprintRequest) - if err != nil { - return err - } - - body, err := json.Marshal(blueprint) - if err != nil { - return err - } - var metadata []byte if blueprintRequest.Metadata != nil { metadata, err = json.Marshal(blueprintRequest.Metadata) @@ -157,20 +196,31 @@ func (h *Handlers) CreateBlueprint(ctx echo.Context) error { desc = *blueprintRequest.Description } - if blueprintRequest.Customizations.Users != nil { - for _, user := range *blueprintRequest.Customizations.Users { + users := blueprintRequest.Customizations.Users + if users != nil { + for _, user := range *users { // Make sure every user has either ssh key or password set - if user.Password == nil && user.SshKey == nil { + if err := user.Valid(); err != nil { return ctx.JSON(http.StatusUnprocessableEntity, HTTPErrorList{ Errors: []HTTPError{{ Title: "Invalid user", - Detail: "User must have either a password or an SSH key set.", + Detail: err.Error(), }}, }) } } } + blueprint, err := BlueprintFromAPI(blueprintRequest) + if err != nil { + return err + } + + body, err := json.Marshal(blueprint) + if err != nil { + return err + } + err = h.server.db.InsertBlueprint(ctx.Request().Context(), id, versionId, userID.OrgID(), userID.AccountNumber(), blueprintRequest.Name, desc, body, metadata) if err != nil { ctx.Logger().Errorf("Error inserting id into db: %s", err.Error()) @@ -214,7 +264,7 @@ func (h *Handlers) GetBlueprint(ctx echo.Context, id openapi_types.UUID, params return err } - blueprint, err := BlueprintFromEntry(blueprintEntry) + blueprint, err := BlueprintFromEntryWithRedactedPasswords(blueprintEntry) if err != nil { return err } @@ -246,7 +296,7 @@ func (h *Handlers) ExportBlueprint(ctx echo.Context, id openapi_types.UUID) erro return err } - blueprint, err := BlueprintFromEntry(blueprintEntry) + blueprint, err := BlueprintFromEntryWithRedactedPasswords(blueprintEntry) if err != nil { return err } @@ -277,6 +327,37 @@ func (h *Handlers) UpdateBlueprint(ctx echo.Context, blueprintId uuid.UUID) erro return err } + if !blueprintNameRegex.MatchString(blueprintRequest.Name) { + return ctx.JSON(http.StatusUnprocessableEntity, HTTPErrorList{ + Errors: []HTTPError{{ + Title: "Invalid blueprint name", + Detail: blueprintInvalidNameDetail, + }}, + }) + } + + if blueprintRequest.Customizations.Users != nil { + be, err := h.server.db.GetBlueprint(ctx.Request().Context(), blueprintId, userID.OrgID(), nil) + if err != nil { + return err + } + eb, err := BlueprintFromEntry(be) + if err != nil { + return err + } + for i := range *blueprintRequest.Customizations.Users { + err := (*blueprintRequest.Customizations.Users)[i].MergeForUpdate(*eb.Customizations.Users) + if err != nil { + return ctx.JSON(http.StatusUnprocessableEntity, HTTPErrorList{ + Errors: []HTTPError{{ + Title: "Invalid user", + Detail: err.Error(), + }}, + }) + } + } + } + blueprint, err := BlueprintFromAPI(blueprintRequest) if err != nil { return ctx.JSON(http.StatusUnprocessableEntity, HTTPErrorList{ @@ -286,20 +367,12 @@ func (h *Handlers) UpdateBlueprint(ctx echo.Context, blueprintId uuid.UUID) erro }}, }) } + body, err := json.Marshal(blueprint) if err != nil { return err } - if !blueprintNameRegex.MatchString(blueprintRequest.Name) { - return ctx.JSON(http.StatusUnprocessableEntity, HTTPErrorList{ - Errors: []HTTPError{{ - Title: "Invalid blueprint name", - Detail: blueprintInvalidNameDetail, - }}, - }) - } - versionId := uuid.New() desc := "" if blueprintRequest.Description != nil { @@ -335,7 +408,7 @@ func (h *Handlers) ComposeBlueprint(ctx echo.Context, id openapi_types.UUID) err if err != nil { return err } - blueprint, err := BlueprintFromEntry(blueprintEntry) + blueprint, err := BlueprintFromEntryWithRedactedPasswords(blueprintEntry) if err != nil { return err } diff --git a/internal/v1/handler_blueprints_test.go b/internal/v1/handler_blueprints_test.go index c6742b6db..faa92cd9e 100644 --- a/internal/v1/handler_blueprints_test.go +++ b/internal/v1/handler_blueprints_test.go @@ -95,6 +95,363 @@ func TestHandlers_CreateBlueprint(t *testing.T) { require.Equal(t, "Invalid user", jsonResp.Errors[0].Title) } +func TestUser_MergeForUpdate(t *testing.T) { + tests := []struct { + name string + newUser User + existingUsers []User + wantPass *string + wantSsh *string + wantErr bool + }{ + { + name: "Both password and ssh_key are provided, no need to fetch user from DB", + newUser: User{ + Name: "test", + Password: common.ToPtr("password"), + SshKey: common.ToPtr("ssh key"), + }, + existingUsers: []User{}, + wantPass: common.ToPtr("password"), + wantSsh: common.ToPtr("ssh key"), + wantErr: false, + }, + { + name: "User found in DB, merge should keep new values", + newUser: User{ + Name: "test", + Password: common.ToPtr("password"), + SshKey: common.ToPtr("ssh key"), + }, + existingUsers: []User{ + { + Name: "test", + Password: common.ToPtr("old password"), + SshKey: common.ToPtr("old ssh key"), + }, + }, + wantPass: common.ToPtr("password"), + wantSsh: common.ToPtr("ssh key"), + wantErr: false, + }, + { + name: "New user, empty password set to nil", + newUser: User{ + Name: "test", + Password: common.ToPtr(""), + SshKey: common.ToPtr("ssh key"), + }, + existingUsers: []User{}, + wantPass: nil, + wantSsh: common.ToPtr("ssh key"), + wantErr: false, + }, + { + name: "Existing user, empty password set to nil = change to only 'ssh key' user", + newUser: User{ + Name: "test", + Password: common.ToPtr(""), + SshKey: common.ToPtr("ssh key"), + }, + existingUsers: []User{ + { + Name: "test", + Password: common.ToPtr("old password"), + SshKey: nil, + }, + }, + wantPass: nil, + wantSsh: common.ToPtr("ssh key"), + wantErr: false, + }, + { + name: "New user, empty ssh_key set to nil", + newUser: User{ + Name: "test", + Password: common.ToPtr("password"), + SshKey: common.ToPtr(""), + }, + existingUsers: []User{}, + wantPass: common.ToPtr("password"), + wantSsh: nil, + wantErr: false, + }, + { + name: "Existing user, empty ssh key set to nil = change to 'password' user", + newUser: User{ + Name: "test", + Password: common.ToPtr("password"), + SshKey: common.ToPtr(""), + }, + existingUsers: []User{ + { + Name: "test", + Password: nil, + SshKey: common.ToPtr("old ssh key"), + }, + }, + wantPass: common.ToPtr("password"), + wantSsh: nil, + wantErr: false, + }, + { + name: "Both password and ssh_key are empty, invalid", + newUser: User{ + Name: "test", + Password: common.ToPtr(""), + SshKey: common.ToPtr(""), + }, + existingUsers: []User{}, + wantPass: nil, + wantSsh: nil, + wantErr: true, + }, + { + name: "Both password and ssh_key are nil, no existing user, invalid", + newUser: User{ + Name: "test", + }, + existingUsers: []User{}, + wantPass: nil, + wantSsh: nil, + wantErr: true, + }, + { + name: "Both password and ssh_key are nil, existing user, keep old values", + newUser: User{ + Name: "test", + }, + existingUsers: []User{ + { + Name: "test", + Password: common.ToPtr("old password"), + SshKey: common.ToPtr("old ssh key"), + }, + }, + wantPass: common.ToPtr("old password"), + wantSsh: common.ToPtr("old ssh key"), + wantErr: false, + }, + { + name: "Empty password, existing user only with password, fail", + newUser: User{ + Name: "test", + Password: common.ToPtr(""), + SshKey: nil, + }, + existingUsers: []User{ + { + Name: "test", + Password: common.ToPtr("old password"), + SshKey: nil, + }, + }, + wantPass: nil, + wantSsh: nil, + wantErr: true, + }, + { + name: "Empty ssh key, existing user only with ssh key, fail", + newUser: User{ + Name: "test", + SshKey: common.ToPtr(""), + Password: nil, + }, + existingUsers: []User{ + { + Name: "test", + Password: nil, + SshKey: common.ToPtr("old ssh key"), + }, + }, + wantPass: nil, + wantSsh: nil, + wantErr: true, + }, + { + name: "Add new user to one already existing user, fail no password or ssh key", + newUser: User{ + Name: "test2", + SshKey: nil, + Password: nil, + }, + existingUsers: []User{ + { + Name: "test", + SshKey: common.ToPtr("old password"), + Password: nil, + }, + }, + wantPass: nil, + wantSsh: nil, + wantErr: true, + }, + { + name: "Add new user to one already existing user", + newUser: User{ + Name: "test2", + SshKey: common.ToPtr("ssh key"), + Password: nil, + }, + existingUsers: []User{ + { + Name: "test", + SshKey: common.ToPtr("old password"), + Password: nil, + }, + }, + wantPass: nil, + wantSsh: common.ToPtr("ssh key"), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.newUser.MergeForUpdate(tt.existingUsers) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantPass, tt.newUser.Password) + require.Equal(t, tt.wantSsh, tt.newUser.SshKey) + } + }) + } +} + +func TestHandlers_UpdateBlueprint_CustomizationUser(t *testing.T) { + var jsonResp HTTPErrorList + ctx := context.Background() + dbase, err := dbc.NewDB() + require.NoError(t, err) + + db_srv, tokenSrv := startServer(t, &testServerClientsConf{}, &ServerConfig{ + DBase: dbase, + DistributionsDir: "../../distributions", + }) + defer func() { + err := db_srv.Shutdown(ctx) + require.NoError(t, err) + }() + defer tokenSrv.Close() + + body := map[string]interface{}{ + "name": "Blueprint", + "description": "desc", + "customizations": map[string]interface{}{ + "users": []map[string]interface{}{}, + }, + "distribution": "centos-9", + "image_requests": []map[string]interface{}{ + { + "architecture": "x86_64", + "image_type": "aws", + "upload_request": map[string]interface{}{"type": "aws", "options": map[string]interface{}{"share_with_accounts": []string{"test-account"}}}, + }, + }, + } + + var result ComposeResponse + + // No users in the blueprint = SUCCESS + statusCode, responseBody := tutils.PostResponseBody(t, "http://localhost:8086/api/image-builder/v1/blueprints", body) + require.Equal(t, http.StatusCreated, statusCode) + err = json.Unmarshal([]byte(responseBody), &result) + require.NoError(t, err) + + // Add new user with password = SUCCESS + body["customizations"] = map[string]interface{}{"users": []map[string]interface{}{{"name": "test", "password": "test"}}} + statusCode, _ = tutils.PutResponseBody(t, fmt.Sprintf("http://localhost:8086/api/image-builder/v1/blueprints/%s", result.Id), body) + require.Equal(t, http.StatusCreated, statusCode) + + blueprintEntry, err := dbase.GetBlueprint(ctx, result.Id, "000000", nil) + require.NoError(t, err) + updatedBlueprint, err := BlueprintFromEntry(blueprintEntry) + require.NoError(t, err) + require.NotEmpty(t, (*updatedBlueprint.Customizations.Users)[0].Password) // hashed, can't compare with plaintext value + require.Nil(t, (*updatedBlueprint.Customizations.Users)[0].SshKey) + + // Update with hashed password = SUCCESS + userHashedPassword := "$6$foo" + body["customizations"] = map[string]interface{}{"users": []map[string]interface{}{{"name": "test", "password": userHashedPassword}}} + statusCode, _ = tutils.PutResponseBody(t, fmt.Sprintf("http://localhost:8086/api/image-builder/v1/blueprints/%s", result.Id), body) + require.Equal(t, http.StatusCreated, statusCode) + + blueprintEntry, err = dbase.GetBlueprint(ctx, result.Id, "000000", nil) + require.NoError(t, err) + updatedBlueprint, err = BlueprintFromEntry(blueprintEntry) + require.NoError(t, err) + + existingPassword := (*updatedBlueprint.Customizations.Users)[0].Password + require.NotNil(t, existingPassword) + require.Equal(t, userHashedPassword, *existingPassword) + require.Nil(t, (*updatedBlueprint.Customizations.Users)[0].SshKey) + + // keep ssh key and remove password = FAIL (previous ssh key still empty) + body["customizations"] = map[string]interface{}{"users": []map[string]interface{}{{"name": "test", "password": ""}}} + statusCode, responseBody = tutils.PutResponseBody(t, fmt.Sprintf("http://localhost:8086/api/image-builder/v1/blueprints/%s", result.Id), body) + require.Equal(t, http.StatusUnprocessableEntity, statusCode) + err = json.Unmarshal([]byte(responseBody), &jsonResp) + require.NoError(t, err) + require.Equal(t, "Invalid user", jsonResp.Errors[0].Title) + + // add ssh key and remove password = SUCCESS + body["customizations"] = map[string]interface{}{"users": []map[string]interface{}{{"name": "test", "password": "", "ssh_key": "ssh key"}}} + statusCode, _ = tutils.PutResponseBody(t, fmt.Sprintf("http://localhost:8086/api/image-builder/v1/blueprints/%s", result.Id), body) + require.Equal(t, http.StatusCreated, statusCode) + + blueprintEntry, err = dbase.GetBlueprint(ctx, result.Id, "000000", nil) + require.NoError(t, err) + + updatedBlueprint, err = BlueprintFromEntryWithRedactedPasswords(blueprintEntry) + require.NoError(t, err) + require.Nil(t, (*updatedBlueprint.Customizations.Users)[0].Password) + + updatedBlueprint, err = BlueprintFromEntry(blueprintEntry) + require.NoError(t, err) + sshKey := (*updatedBlueprint.Customizations.Users)[0].SshKey + require.NotNil(t, sshKey) + require.Equal(t, "ssh key", *sshKey) + require.Nil(t, (*updatedBlueprint.Customizations.Users)[0].Password) + + // add new user without password or ssh_key = FAIL + users := []map[string]interface{}{ + {"name": "test"}, // keep old values + {"name": "test2"}, // FAIL + } + body["customizations"] = map[string]interface{}{"users": users} + statusCode, responseBody = tutils.PutResponseBody(t, fmt.Sprintf("http://localhost:8086/api/image-builder/v1/blueprints/%s", result.Id), body) + require.Equal(t, http.StatusUnprocessableEntity, statusCode) + err = json.Unmarshal([]byte(responseBody), &jsonResp) + require.NoError(t, err) + require.Equal(t, "Invalid user", jsonResp.Errors[0].Title) + + // add new user with password and ssh_key = SUCCESS + users = []map[string]interface{}{ + {"name": "test"}, // keep old values + {"name": "test2", "password": "test", "ssh_key": "ssh key"}, + } + body["customizations"] = map[string]interface{}{"users": users} + statusCode, _ = tutils.PutResponseBody(t, fmt.Sprintf("http://localhost:8086/api/image-builder/v1/blueprints/%s", result.Id), body) + require.Equal(t, http.StatusCreated, statusCode) + + blueprintEntry, err = dbase.GetBlueprint(ctx, result.Id, "000000", nil) + require.NoError(t, err) + updatedBlueprint, err = BlueprintFromEntry(blueprintEntry) + require.NoError(t, err) + require.Len(t, *updatedBlueprint.Customizations.Users, 2) + user1 := (*updatedBlueprint.Customizations.Users)[0] + require.NotNil(t, user1.SshKey) + require.Equal(t, "ssh key", *user1.SshKey) + require.Nil(t, user1.Password) + + user2 := (*updatedBlueprint.Customizations.Users)[1] + require.Equal(t, "test2", user2.Name) + require.NotNil(t, user2.Password) + require.NotNil(t, user2.SshKey) +} + func TestHandlers_UpdateBlueprint(t *testing.T) { if runtime.GOOS == "darwin" { t.Skip("crypt() not supported on darwin") @@ -145,16 +502,6 @@ func TestHandlers_UpdateBlueprint(t *testing.T) { body["name"] = "Changing to correct body" respStatusCodeNotFound, _ := tutils.PutResponseBody(t, db_srv.URL+fmt.Sprintf("/api/image-builder/v1/blueprints/%s", uuid.New()), body) require.Equal(t, http.StatusNotFound, respStatusCodeNotFound) - - // Test update customization users - invalid redacted password - body["customizations"] = map[string]interface{}{"users": []map[string]interface{}{{"name": "test", "password": ""}}} - statusCode, _ = tutils.PutResponseBody(t, db_srv.URL+fmt.Sprintf("/api/image-builder/v1/blueprints/%s", result.Id), body) - require.Equal(t, http.StatusUnprocessableEntity, statusCode) - - // Test customization users, user - valid password - body["customizations"] = map[string]interface{}{"users": []map[string]interface{}{{"name": "test", "password": "test"}}} - statusCode, _ = tutils.PutResponseBody(t, db_srv.URL+fmt.Sprintf("/api/image-builder/v1/blueprints/%s", result.Id), body) - require.Equal(t, http.StatusCreated, statusCode) } func TestHandlers_ComposeBlueprint(t *testing.T) { @@ -368,13 +715,13 @@ func TestHandlers_GetBlueprintComposes(t *testing.T) { require.Equal(t, 0, result.Meta.Count) } -func TestHandlers_BlueprintFromEntry(t *testing.T) { +func TestHandlers_BlueprintFromEntryWithRedactedPasswords(t *testing.T) { t.Run("plain password", func(t *testing.T) { body := []byte(`{"name": "Blueprint", "description": "desc", "customizations": {"users": [{"name": "user", "password": "foo"}]}, "distribution": "centos-9"}`) be := &db.BlueprintEntry{ Body: body, } - result, err := BlueprintFromEntry(be) + result, err := BlueprintFromEntryWithRedactedPasswords(be) require.NoError(t, err) require.NotEqual(t, common.ToPtr("foo"), (*result.Customizations.Users)[0].Password) }) @@ -383,10 +730,10 @@ func TestHandlers_BlueprintFromEntry(t *testing.T) { be := &db.BlueprintEntry{ Body: body, } - result, err := BlueprintFromEntry(be) + result, err := BlueprintFromEntryWithRedactedPasswords(be) require.NoError(t, err) - require.True(t, (*result.Customizations.Users)[0].IsRedacted()) + require.Nil(t, (*result.Customizations.Users)[0].Password) }) } @@ -471,7 +818,7 @@ func TestHandlers_GetBlueprint(t *testing.T) { require.Equal(t, blueprint.Customizations.Packages, result.Customizations.Packages) // Check that the password returned is redacted for _, u := range *result.Customizations.Users { - require.True(t, u.IsRedacted()) + require.Nil(t, u.Password) } respStatusCodeNotFound, _ := tutils.GetResponseBody(t, db_srv.URL+fmt.Sprintf("/api/image-builder/v1/blueprints/%s", uuid.New()), &tutils.AuthString0) @@ -495,7 +842,7 @@ func TestHandlers_GetBlueprint(t *testing.T) { require.NoError(t, err) require.Equal(t, version2Body.Customizations.Packages, result.Customizations.Packages) for _, u := range *result.Customizations.Users { - require.True(t, u.IsRedacted()) + require.Nil(t, u.Password) } respStatusCode, body = tutils.GetResponseBody(t, db_srv.URL+fmt.Sprintf("/api/image-builder/v1/blueprints/%s?version=%d", id.String(), 2), &tutils.AuthString0) @@ -504,7 +851,7 @@ func TestHandlers_GetBlueprint(t *testing.T) { require.NoError(t, err) require.Equal(t, version2Body.Customizations.Packages, result.Customizations.Packages) for _, u := range *result.Customizations.Users { - require.True(t, u.IsRedacted()) + require.Nil(t, u.Password) } respStatusCode, body = tutils.GetResponseBody(t, db_srv.URL+fmt.Sprintf("/api/image-builder/v1/blueprints/%s?version=%d", id.String(), 1), &tutils.AuthString0) @@ -513,7 +860,7 @@ func TestHandlers_GetBlueprint(t *testing.T) { require.NoError(t, err) require.Equal(t, blueprint.Customizations.Packages, result.Customizations.Packages) for _, u := range *result.Customizations.Users { - require.True(t, u.IsRedacted()) + require.Nil(t, u.Password) } } @@ -606,7 +953,7 @@ func TestHandlers_ExportBlueprint(t *testing.T) { require.Equal(t, blueprint.Customizations.Packages, result.Customizations.Packages) // Check that the password returned is redacted for _, u := range *result.Customizations.Users { - require.True(t, u.IsRedacted()) + require.Nil(t, u.Password) } require.Nil(t, result.Customizations.Subscription) require.Equal(t, &id, result.Metadata.ParentId) @@ -804,20 +1151,6 @@ func TestBlueprintBody_CryptPasswords(t *testing.T) { require.Nil(t, (*blueprint.Customizations.Users)[1].Password) } -func TestUser_IsRedacted(t *testing.T) { - u := &User{Password: nil} - isRedacted := u.IsRedacted() - require.True(t, isRedacted) - - u = &User{Password: common.ToPtr("")} - isRedacted = u.IsRedacted() - require.True(t, isRedacted) - - u = &User{Password: common.ToPtr("test123")} - isRedacted = u.IsRedacted() - require.False(t, isRedacted) -} - func TestUser_RedactPassword(t *testing.T) { user := &User{ Name: "test", @@ -825,5 +1158,5 @@ func TestUser_RedactPassword(t *testing.T) { } user.RedactPassword() - require.Equal(t, "", *user.Password) + require.Nil(t, user.Password) } diff --git a/internal/v1/handler_get_compose_status_test.go b/internal/v1/handler_get_compose_status_test.go index da5dc093a..d90e093f2 100644 --- a/internal/v1/handler_get_compose_status_test.go +++ b/internal/v1/handler_get_compose_status_test.go @@ -237,6 +237,7 @@ func TestComposeStatus(t *testing.T) { require.Equal(t, payload.imageStatus, result.ImageStatus) require.Equal(t, cr.Distribution, result.Request.Distribution) require.Equal(t, cr.Distribution, result.Request.Distribution) - require.True(t, (*result.Request.Customizations.Users)[0].IsRedacted()) + user := (*result.Request.Customizations.Users)[0] + require.Nil(t, user.Password) } } diff --git a/internal/v1/handler_post_compose_test.go b/internal/v1/handler_post_compose_test.go index e13a7f1eb..bb3f30bda 100644 --- a/internal/v1/handler_post_compose_test.go +++ b/internal/v1/handler_post_compose_test.go @@ -2553,7 +2553,7 @@ func TestComposeCustomizations(t *testing.T) { // Check that the password returned is redacted for _, u := range *composerRequest.Customizations.Users { - require.True(t, u.IsRedacted()) + require.True(t, u.Password == nil) } } composerRequest = composer.ComposeRequest{}