From 7d8468cada88ef91b5e6931b2eb5e42aa68d3288 Mon Sep 17 00:00:00 2001 From: nghialv Date: Sat, 28 Nov 2020 21:53:17 +0900 Subject: [PATCH] Implement api-key's apis for web client --- BUILD.bazel | 5 +- pkg/app/api/grpcapi/web_api.go | 101 +++++++++++++- .../api/service/webservice/service.pb.auth.go | 8 ++ pkg/app/api/service/webservice/service.proto | 2 +- pkg/datastore/BUILD.bazel | 2 + pkg/datastore/apikey.go | 99 ++++++++++++++ pkg/datastore/apikey_test.go | 127 ++++++++++++++++++ pkg/model/BUILD.bazel | 4 + pkg/model/apikey.go | 69 ++++++++++ pkg/model/apikey.proto | 6 +- pkg/model/apikey_test.go | 51 +++++++ pkg/model/model.go | 52 +++++++ pkg/model/model_test.go | 44 ++++++ pkg/model/piped.go | 33 ----- pkg/model/piped_test.go | 23 ---- 15 files changed, 561 insertions(+), 65 deletions(-) create mode 100644 pkg/datastore/apikey.go create mode 100644 pkg/datastore/apikey_test.go create mode 100644 pkg/model/apikey.go create mode 100644 pkg/model/apikey_test.go create mode 100644 pkg/model/model.go create mode 100644 pkg/model/model_test.go diff --git a/BUILD.bazel b/BUILD.bazel index ceb14b2f15..27dfd1215a 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -35,7 +35,7 @@ buildifier( genrule( name = "copy_piped", - srcs = ["//cmd/piped"], #keep + srcs = ["//cmd/piped"], outs = ["piped"], cmd = "cp $< $@", ) @@ -43,6 +43,7 @@ genrule( # gazelle:exclude pkg/app/helloworld/service/service.pb.validate.go # gazelle:exclude pkg/app/api/service/webservice/service.pb.validate.go # gazelle:exclude pkg/app/api/service/pipedservice/service.pb.validate.go +# gazelle:exclude pkg/model/apikey.pb.validate.go # gazelle:exclude pkg/model/application.pb.validate.go # gazelle:exclude pkg/model/application_live_state.pb.validate.go # gazelle:exclude pkg/model/command.pb.validate.go @@ -57,4 +58,4 @@ genrule( # gazelle:exclude pkg/model/role.pb.validate.go # gazelle:exclude pkg/model/user.pb.validate.go # gazelle:exclude pkg/app/ops/handler/templates.embed.go -# gazelle:exclude pkg/app/api/api/deployment_config_templates.embed.go +# gazelle:exclude pkg/app/api/grpcapi/deployment_config_templates.embed.go diff --git a/pkg/app/api/grpcapi/web_api.go b/pkg/app/api/grpcapi/web_api.go index 2456c18646..f0eb8e144b 100644 --- a/pkg/app/api/grpcapi/web_api.go +++ b/pkg/app/api/grpcapi/web_api.go @@ -52,6 +52,7 @@ type WebAPI struct { deploymentStore datastore.DeploymentStore pipedStore datastore.PipedStore projectStore datastore.ProjectStore + apiKeyStore datastore.APIKeyStore stageLogStore stagelogstore.Store applicationLiveStateStore applicationlivestatestore.Store commandStore commandstore.Store @@ -81,6 +82,7 @@ func NewWebAPI( deploymentStore: datastore.NewDeploymentStore(ds), pipedStore: datastore.NewPipedStore(ds), projectStore: datastore.NewProjectStore(ds), + apiKeyStore: datastore.NewAPIKeyStore(ds), stageLogStore: sls, applicationLiveStateStore: alss, commandStore: cmds, @@ -1204,13 +1206,106 @@ L: } func (a *WebAPI) GenerateAPIKey(ctx context.Context, req *webservice.GenerateAPIKeyRequest) (*webservice.GenerateAPIKeyResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + claims, err := rpcauth.ExtractClaims(ctx) + if err != nil { + a.logger.Error("failed to authenticate the current user", zap.Error(err)) + return nil, err + } + + id := uuid.New().String() + key, hash, err := model.GenerateAPIKey(id) + if err != nil { + a.logger.Error("failed to generate API key", zap.Error(err)) + return nil, status.Error(codes.Internal, "Failed to generate API key") + } + + apiKey := model.APIKey{ + Id: id, + Name: req.Name, + KeyHash: hash, + ProjectId: claims.Role.ProjectId, + Role: req.Role, + Creator: claims.Subject, + } + + err = a.apiKeyStore.AddAPIKey(ctx, &apiKey) + if errors.Is(err, datastore.ErrAlreadyExists) { + return nil, status.Error(codes.AlreadyExists, "The API key already exists") + } + if err != nil { + a.logger.Error("failed to create API key", zap.Error(err)) + return nil, status.Error(codes.Internal, "Failed to create API key") + } + + return &webservice.GenerateAPIKeyResponse{ + Key: key, + }, nil } func (a *WebAPI) DisableAPIKey(ctx context.Context, req *webservice.DisableAPIKeyRequest) (*webservice.DisableAPIKeyResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + claims, err := rpcauth.ExtractClaims(ctx) + if err != nil { + a.logger.Error("failed to authenticate the current user", zap.Error(err)) + return nil, err + } + + if err := a.apiKeyStore.DisableAPIKey(ctx, req.Id, claims.Role.ProjectId); err != nil { + switch err { + case datastore.ErrNotFound: + return nil, status.Error(codes.InvalidArgument, "The API key is not found") + case datastore.ErrInvalidArgument: + return nil, status.Error(codes.InvalidArgument, "Invalid value for update") + default: + a.logger.Error("failed to disable the API key", + zap.String("apikey-id", req.Id), + zap.Error(err), + ) + return nil, status.Error(codes.Internal, "Failed to disable the API key") + } + } + + return &webservice.DisableAPIKeyResponse{}, nil } func (a *WebAPI) ListAPIKeys(ctx context.Context, req *webservice.ListAPIKeysRequest) (*webservice.ListAPIKeysResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + claims, err := rpcauth.ExtractClaims(ctx) + if err != nil { + a.logger.Error("failed to authenticate the current user", zap.Error(err)) + return nil, err + } + + opts := datastore.ListOptions{ + Filters: []datastore.ListFilter{ + { + Field: "ProjectId", + Operator: "==", + Value: claims.Role.ProjectId, + }, + }, + } + + if req.Options != nil { + if req.Options.Enabled != nil { + opts.Filters = append(opts.Filters, datastore.ListFilter{ + Field: "Disabled", + Operator: "==", + Value: !req.Options.Enabled.GetValue(), + }) + } + } + + apiKeys, err := a.apiKeyStore.ListAPIKeys(ctx, opts) + if err != nil { + a.logger.Error("failed to list API keys", zap.Error(err)) + return nil, status.Error(codes.Internal, "Failed to list API keys") + } + + // Redact all sensitive data inside API key before sending to the client. + for i := range apiKeys { + apiKeys[i].RedactSensitiveData() + } + + return &webservice.ListAPIKeysResponse{ + Keys: apiKeys, + }, nil } diff --git a/pkg/app/api/service/webservice/service.pb.auth.go b/pkg/app/api/service/webservice/service.pb.auth.go index 485bd9a641..520135f03f 100644 --- a/pkg/app/api/service/webservice/service.pb.auth.go +++ b/pkg/app/api/service/webservice/service.pb.auth.go @@ -70,6 +70,13 @@ func (a *authorizer) Authorize(method string, r model.Role) bool { return isAdmin(r) case "/pipe.api.service.webservice.WebService/UpdateProjectRBACConfig": return isAdmin(r) + case "/pipe.api.service.webservice.WebService/GenerateAPIKey": + return isAdmin(r) + case "/pipe.api.service.webservice.WebService/DisableAPIKey": + return isAdmin(r) + case "/pipe.api.service.webservice.WebService/ListAPIKey": + return isAdmin(r) + case "/pipe.api.service.webservice.WebService/SyncApplication": return isAdmin(r) || isEditor(r) case "/pipe.api.service.webservice.WebService/CancelDeployment": @@ -78,6 +85,7 @@ func (a *authorizer) Authorize(method string, r model.Role) bool { return isAdmin(r) || isEditor(r) case "/pipe.api.service.webservice.WebService/GenerateApplicationSealedSecret": return isAdmin(r) || isEditor(r) + case "/pipe.api.service.webservice.WebService/GetApplicationLiveState": return isAdmin(r) || isEditor(r) || isViewer(r) case "/pipe.api.service.webservice.WebService/GetProject": diff --git a/pkg/app/api/service/webservice/service.proto b/pkg/app/api/service/webservice/service.proto index 25e24820b0..18e560f296 100644 --- a/pkg/app/api/service/webservice/service.proto +++ b/pkg/app/api/service/webservice/service.proto @@ -381,7 +381,7 @@ message GenerateAPIKeyRequest { } message GenerateAPIKeyResponse { - model.APIKey key = 1; + string key = 1; } message DisableAPIKeyRequest { diff --git a/pkg/datastore/BUILD.bazel b/pkg/datastore/BUILD.bazel index fc22504566..e901fd045f 100644 --- a/pkg/datastore/BUILD.bazel +++ b/pkg/datastore/BUILD.bazel @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "apikey.go", "applicationstore.go", "commandstore.go", "datastore.go", @@ -25,6 +26,7 @@ go_test( name = "go_default_test", size = "small", srcs = [ + "apikey_test.go", "applicationstore_test.go", "commandstore_test.go", "deploymentstore_test.go", diff --git a/pkg/datastore/apikey.go b/pkg/datastore/apikey.go new file mode 100644 index 0000000000..0d153cafe5 --- /dev/null +++ b/pkg/datastore/apikey.go @@ -0,0 +1,99 @@ +// Copyright 2020 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "context" + "fmt" + "time" + + "github.com/pipe-cd/pipe/pkg/model" +) + +const apiKeyModelKind = "APIKey" + +var ( + apiKeyFactory = func() interface{} { + return &model.APIKey{} + } +) + +type APIKeyStore interface { + AddAPIKey(ctx context.Context, k *model.APIKey) error + DisableAPIKey(ctx context.Context, id, projectID string) error + ListAPIKeys(ctx context.Context, opts ListOptions) ([]*model.APIKey, error) +} + +type apiKeyStore struct { + backend + nowFunc func() time.Time +} + +func NewAPIKeyStore(ds DataStore) APIKeyStore { + return &apiKeyStore{ + backend: backend{ + ds: ds, + }, + nowFunc: time.Now, + } +} + +func (s *apiKeyStore) AddAPIKey(ctx context.Context, k *model.APIKey) error { + now := s.nowFunc().Unix() + if k.CreatedAt == 0 { + k.CreatedAt = now + } + if k.UpdatedAt == 0 { + k.UpdatedAt = now + } + if err := k.Validate(); err != nil { + return err + } + return s.ds.Create(ctx, apiKeyModelKind, k.Id, k) +} + +func (s *apiKeyStore) ListAPIKeys(ctx context.Context, opts ListOptions) ([]*model.APIKey, error) { + it, err := s.ds.Find(ctx, apiKeyModelKind, opts) + if err != nil { + return nil, err + } + ks := make([]*model.APIKey, 0) + for { + var k model.APIKey + err := it.Next(&k) + if err == ErrIteratorDone { + break + } + if err != nil { + return nil, err + } + ks = append(ks, &k) + } + return ks, nil +} + +func (s *apiKeyStore) DisableAPIKey(ctx context.Context, id, projectID string) error { + now := s.nowFunc().Unix() + return s.ds.Update(ctx, apiKeyModelKind, id, apiKeyFactory, func(e interface{}) error { + k := e.(*model.APIKey) + if k.ProjectId != projectID { + return fmt.Errorf("invalid project id, expected %s, got %s", k.ProjectId, projectID) + } + + k.Disabled = true + k.UpdatedAt = now + return k.Validate() + }) +} diff --git a/pkg/datastore/apikey_test.go b/pkg/datastore/apikey_test.go new file mode 100644 index 0000000000..8c1d18b507 --- /dev/null +++ b/pkg/datastore/apikey_test.go @@ -0,0 +1,127 @@ +// Copyright 2020 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + "github.com/pipe-cd/pipe/pkg/model" +) + +func TestAddAPIKey(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + testcases := []struct { + name string + apiKey *model.APIKey + dsFactory func(*model.APIKey) DataStore + wantErr bool + }{ + { + name: "Invalid apiKey", + apiKey: &model.APIKey{}, + dsFactory: func(d *model.APIKey) DataStore { return nil }, + wantErr: true, + }, + { + name: "Valid apiKey", + apiKey: &model.APIKey{ + Id: "id", + Name: "name", + KeyHash: "keyHash", + ProjectId: "project-id", + Role: model.APIKey_READ_ONLY, + Creator: "user", + CreatedAt: 1, + UpdatedAt: 1, + }, + dsFactory: func(d *model.APIKey) DataStore { + ds := NewMockDataStore(ctrl) + ds.EXPECT().Create(gomock.Any(), "APIKey", d.Id, d) + return ds + }, + wantErr: false, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + s := NewAPIKeyStore(tc.dsFactory(tc.apiKey)) + err := s.AddAPIKey(context.Background(), tc.apiKey) + assert.Equal(t, tc.wantErr, err != nil) + }) + } +} + +func TestListAPIKeys(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + testcases := []struct { + name string + opts ListOptions + ds DataStore + wantErr error + }{ + { + name: "iterator done", + opts: ListOptions{Page: 1}, + ds: func() DataStore { + it := NewMockIterator(ctrl) + it.EXPECT(). + Next(&model.APIKey{}). + Return(ErrIteratorDone) + + ds := NewMockDataStore(ctrl) + ds.EXPECT(). + Find(gomock.Any(), "APIKey", ListOptions{Page: 1}). + Return(it, nil) + return ds + }(), + wantErr: nil, + }, + { + name: "unexpected error occurred", + opts: ListOptions{Page: 1}, + ds: func() DataStore { + it := NewMockIterator(ctrl) + it.EXPECT(). + Next(&model.APIKey{}). + Return(errors.New("test-error")) + + ds := NewMockDataStore(ctrl) + ds.EXPECT(). + Find(gomock.Any(), "APIKey", ListOptions{Page: 1}). + Return(it, nil) + return ds + }(), + wantErr: errors.New("test-error"), + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + s := NewAPIKeyStore(tc.ds) + _, err := s.ListAPIKeys(context.Background(), tc.opts) + assert.Equal(t, tc.wantErr, err) + }) + } +} diff --git a/pkg/model/BUILD.bazel b/pkg/model/BUILD.bazel index 4388a9cb63..2f7dd0e90f 100644 --- a/pkg/model/BUILD.bazel +++ b/pkg/model/BUILD.bazel @@ -41,6 +41,7 @@ go_library( name = "go_default_library", srcs = [ "analysisprovider.go", + "apikey.go", "application.go", "application_live_state.go", "cloudprovider.go", @@ -52,6 +53,7 @@ go_library( "event.go", "filestore.go", "imageprovider.go", + "model.go", "piped.go", "project.go", "stage.go", @@ -71,6 +73,8 @@ go_test( name = "go_default_test", size = "small", srcs = [ + "apikey_test.go", + "model_test.go", "piped_test.go", "project_test.go", ], diff --git a/pkg/model/apikey.go b/pkg/model/apikey.go new file mode 100644 index 0000000000..3326ac1126 --- /dev/null +++ b/pkg/model/apikey.go @@ -0,0 +1,69 @@ +// Copyright 2020 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "errors" + "fmt" + "strings" + + "golang.org/x/crypto/bcrypt" +) + +const ( + apiKeyLength = 50 +) + +func GenerateAPIKey(id string) (key, hash string, err error) { + k := GenerateRandomString(apiKeyLength) + key = fmt.Sprintf("%s.%s", id, k) + + var encoded []byte + encoded, err = bcrypt.GenerateFromPassword([]byte(key), bcrypt.DefaultCost) + if err != nil { + return + } + + hash = string(encoded) + return +} + +func ExtractAPIKeyID(key string) (string, error) { + parts := strings.Split(key, ".") + if len(parts) != 2 { + return "", errors.New("malformed api key") + } + + if parts[0] == "" || parts[1] == "" { + return "", errors.New("malformed api key") + } + + return parts[0], nil +} + +func (k *APIKey) CompareKey(key string) error { + if key == "" { + return errors.New("key was empty") + } + if err := bcrypt.CompareHashAndPassword([]byte(k.KeyHash), []byte(key)); err != nil { + return fmt.Errorf("wrong api key %s: %w", key, err) + } + return nil +} + +// RedactSensitiveData redacts sensitive data. +func (k *APIKey) RedactSensitiveData() { + k.KeyHash = redactedMessage +} diff --git a/pkg/model/apikey.proto b/pkg/model/apikey.proto index 74a7c1aca1..fd4ae71763 100644 --- a/pkg/model/apikey.proto +++ b/pkg/model/apikey.proto @@ -30,10 +30,10 @@ message APIKey { string id = 1 [(validate.rules).string.min_len = 1]; // The name of the key. string name = 2 [(validate.rules).string.min_len = 1]; - // The key string. - string key = 3 [(validate.rules).string.min_len = 1]; + // The hash value of key string. + string key_hash = 3 [(validate.rules).string.min_len = 1]; // The project this key belongs to. - string project = 4 [(validate.rules).string.min_len = 1]; + string project_id = 4 [(validate.rules).string.min_len = 1]; // The role of the key. Role role = 5 [(validate.rules).enum.defined_only = true]; // Who created the key. diff --git a/pkg/model/apikey_test.go b/pkg/model/apikey_test.go new file mode 100644 index 0000000000..eb97eb34dd --- /dev/null +++ b/pkg/model/apikey_test.go @@ -0,0 +1,51 @@ +// Copyright 2020 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateAPIKey(t *testing.T) { + id := "test-id" + key, hash, err := GenerateAPIKey(id) + require.NoError(t, err) + require.True(t, len(key) > 0) + require.True(t, len(hash) > 0) + + parsedID, err := ExtractAPIKeyID(key) + require.NoError(t, err) + assert.Equal(t, id, parsedID) + + apiKey := &APIKey{ + Id: id, + KeyHash: hash, + } + + err = apiKey.CompareKey(key) + assert.NoError(t, err) +} + +func TestAPIKeyRedactSensitiveData(t *testing.T) { + apiKey := &APIKey{ + Id: "id", + KeyHash: "hash", + } + apiKey.RedactSensitiveData() + assert.Equal(t, apiKey.KeyHash, "redacted") +} diff --git a/pkg/model/model.go b/pkg/model/model.go new file mode 100644 index 0000000000..c7709ef49a --- /dev/null +++ b/pkg/model/model.go @@ -0,0 +1,52 @@ +// Copyright 2020 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "math/rand" + "time" + "unsafe" +) + +var randomSrc = rand.NewSource(time.Now().UnixNano()) + +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = randomSrc.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return *(*string)(unsafe.Pointer(&b)) +} diff --git a/pkg/model/model_test.go b/pkg/model/model_test.go new file mode 100644 index 0000000000..6679989e37 --- /dev/null +++ b/pkg/model/model_test.go @@ -0,0 +1,44 @@ +// Copyright 2020 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateRandomString(t *testing.T) { + validator := func(s string) error { + for _, c := range s { + if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') { + continue + } + return fmt.Errorf("invalid character: %#U", c) + } + return nil + } + + s1 := GenerateRandomString(10) + assert.Equal(t, 10, len(s1)) + assert.NoError(t, validator(s1)) + + s2 := GenerateRandomString(10) + assert.Equal(t, 10, len(s2)) + assert.NoError(t, validator(s2)) + + assert.NotEqual(t, s1, s2) +} diff --git a/pkg/model/piped.go b/pkg/model/piped.go index 7fbb9c15d7..93f57b9c0d 100644 --- a/pkg/model/piped.go +++ b/pkg/model/piped.go @@ -15,10 +15,8 @@ package model import ( - "math/rand" "sort" "time" - "unsafe" "golang.org/x/crypto/bcrypt" ) @@ -103,37 +101,6 @@ func (p *Piped) AddKey(hash, creator string, createdAt time.Time) { p.Keys = keys } -var randomSrc = rand.NewSource(time.Now().UnixNano()) - -const ( - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<= 0; { - if remain == 0 { - cache, remain = randomSrc.Int63(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(letterBytes) { - b[i] = letterBytes[idx] - i-- - } - cache >>= letterIdxBits - remain-- - } - - return *(*string)(unsafe.Pointer(&b)) -} - func (p *Piped) RedactSensitiveData() { p.KeyHash = redactedMessage } diff --git a/pkg/model/piped_test.go b/pkg/model/piped_test.go index 7e72991d85..418ca55a21 100644 --- a/pkg/model/piped_test.go +++ b/pkg/model/piped_test.go @@ -15,7 +15,6 @@ package model import ( - "fmt" "testing" "time" @@ -106,25 +105,3 @@ func TestAddKey(t *testing.T) { }, }, p.Keys) } - -func TestGenerateRandomString(t *testing.T) { - validator := func(s string) error { - for _, c := range s { - if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') { - continue - } - return fmt.Errorf("invalid character: %#U", c) - } - return nil - } - - s1 := GenerateRandomString(10) - assert.Equal(t, 10, len(s1)) - assert.NoError(t, validator(s1)) - - s2 := GenerateRandomString(10) - assert.Equal(t, 10, len(s2)) - assert.NoError(t, validator(s2)) - - assert.NotEqual(t, s1, s2) -}