From 4cddb6b5cb102a8dbe126ec3118c16ccfd827fe2 Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Sun, 6 Nov 2022 15:26:34 +0200 Subject: [PATCH] added pseudorandom generator --- apis/settings.go | 2 +- daos/record.go | 4 +- daos/record_expand.go | 2 +- forms/collection_upsert_test.go | 2 +- models/schema/schema.go | 2 +- resolvers/record_field_resolver.go | 4 +- tools/search/filter.go | 6 +-- tools/security/random.go | 42 +++++++++++++---- tools/security/random_test.go | 72 +++++++++++++++++++----------- 9 files changed, 89 insertions(+), 47 deletions(-) diff --git a/apis/settings.go b/apis/settings.go index 56035a1f..2ee1e71d 100644 --- a/apis/settings.go +++ b/apis/settings.go @@ -91,7 +91,7 @@ func (api *settingsApi) testS3(c echo.Context) error { } defer fs.Close() - testFileKey := "pb_test_" + security.RandomString(5) + "/test.txt" + testFileKey := "pb_test_" + security.PseudoRandomString(5) + "/test.txt" if err := fs.Upload([]byte("test"), testFileKey); err != nil { return NewBadRequestError("Failed to upload a test file. Raw error: \n"+err.Error(), nil) diff --git a/daos/record.go b/daos/record.go index caf9e322..5ac6d9cd 100644 --- a/daos/record.go +++ b/daos/record.go @@ -530,7 +530,7 @@ func (dao *Dao) SyncRecordTableSchema(newCollection *models.Collection, oldColle // This way we are always doing 1 more rename operation but it provides better dev experience. if oldField == nil { - tempName := field.Name + security.RandomString(5) + tempName := field.Name + security.PseudoRandomString(5) toRename[tempName] = field.Name // add @@ -539,7 +539,7 @@ func (dao *Dao) SyncRecordTableSchema(newCollection *models.Collection, oldColle return err } } else if oldField.Name != field.Name { - tempName := field.Name + security.RandomString(5) + tempName := field.Name + security.PseudoRandomString(5) toRename[tempName] = field.Name // rename diff --git a/daos/record_expand.go b/daos/record_expand.go index cbff9e2f..0bfc2078 100644 --- a/daos/record_expand.go +++ b/daos/record_expand.go @@ -129,7 +129,7 @@ func (dao *Dao) expandRecords(records []*models.Record, expandPath string, fetch } // indirect relation relField = &schema.SchemaField{ - Id: "indirect_" + security.RandomString(3), + Id: "indirect_" + security.PseudoRandomString(5), Type: schema.FieldTypeRelation, Name: parts[0], Options: relFieldOptions, diff --git a/forms/collection_upsert_test.go b/forms/collection_upsert_test.go index db2bd6bb..784adede 100644 --- a/forms/collection_upsert_test.go +++ b/forms/collection_upsert_test.go @@ -494,7 +494,7 @@ func TestCollectionUpsertWithCustomId(t *testing.T) { newCollection := func() *models.Collection { return &models.Collection{ - Name: "c_" + security.RandomString(4), + Name: "c_" + security.PseudoRandomString(4), Schema: existingCollection.Schema, } } diff --git a/models/schema/schema.go b/models/schema/schema.go index f9f9b31f..278ddff3 100644 --- a/models/schema/schema.go +++ b/models/schema/schema.go @@ -113,7 +113,7 @@ func (s *Schema) RemoveField(id string) { func (s *Schema) AddField(newField *SchemaField) { if newField.Id == "" { // set default id - newField.Id = strings.ToLower(security.RandomString(8)) + newField.Id = strings.ToLower(security.PseudoRandomString(8)) } for i, field := range s.fields { diff --git a/resolvers/record_field_resolver.go b/resolvers/record_field_resolver.go index 10dd5b36..52319de6 100644 --- a/resolvers/record_field_resolver.go +++ b/resolvers/record_field_resolver.go @@ -193,7 +193,7 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac currentCollectionName = collection.Name currentTableAlias = "__auth_" + inflector.Columnify(currentCollectionName) - authIdParamKey := "auth" + security.RandomString(5) + authIdParamKey := "auth" + security.PseudoRandomString(5) authIdParams := dbx.Params{authIdParamKey: authRecordId} // --- @@ -354,7 +354,7 @@ func (r *RecordFieldResolver) resolveStaticRequestField(path ...string) (resultN resultVal = val } - placeholder := "f" + security.RandomString(7) + placeholder := "f" + security.PseudoRandomString(5) name := fmt.Sprintf("{:%s}", placeholder) params := dbx.Params{placeholder: resultVal} diff --git a/tools/search/filter.go b/tools/search/filter.go index 837aa828..8834c9a7 100644 --- a/tools/search/filter.go +++ b/tools/search/filter.go @@ -133,7 +133,7 @@ func (f FilterData) resolveToken(token fexpr.Token, fieldResolver FieldResolver) // current datetime constant // --- if token.Literal == "@now" { - placeholder := "t" + security.RandomString(7) + placeholder := "t" + security.PseudoRandomString(8) name := fmt.Sprintf("{:%s}", placeholder) params := dbx.Params{placeholder: types.NowDateTime().String()} @@ -161,13 +161,13 @@ func (f FilterData) resolveToken(token fexpr.Token, fieldResolver FieldResolver) return name, params, err case fexpr.TokenText: - placeholder := "t" + security.RandomString(7) + placeholder := "t" + security.PseudoRandomString(8) name := fmt.Sprintf("{:%s}", placeholder) params := dbx.Params{placeholder: token.Literal} return name, params, nil case fexpr.TokenNumber: - placeholder := "t" + security.RandomString(7) + placeholder := "t" + security.PseudoRandomString(8) name := fmt.Sprintf("{:%s}", placeholder) params := dbx.Params{placeholder: cast.ToFloat64(token.Literal)} diff --git a/tools/security/random.go b/tools/security/random.go index e461547a..6efb7d1a 100644 --- a/tools/security/random.go +++ b/tools/security/random.go @@ -1,18 +1,18 @@ package security import ( - "crypto/rand" + cryptoRand "crypto/rand" "math/big" + mathRand "math/rand" ) -// RandomString generates a random string with the specified length. -// -// The generated string is cryptographically random and matches -// [A-Za-z0-9]+ (aka. it's transparent to URL-encoding). -func RandomString(length int) string { - const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +const defaultRandomAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - return RandomStringWithAlphabet(length, alphabet) +// RandomString generates a cryptographically random string with the specified length. +// +// The generated string matches [A-Za-z0-9]+ and it's transparent to URL-encoding. +func RandomString(length int) string { + return RandomStringWithAlphabet(length, defaultRandomAlphabet) } // RandomStringWithAlphabet generates a cryptographically random string @@ -24,7 +24,7 @@ func RandomStringWithAlphabet(length int, alphabet string) string { max := big.NewInt(int64(len(alphabet))) for i := range b { - n, err := rand.Int(rand.Reader, max) + n, err := cryptoRand.Int(cryptoRand.Reader, max) if err != nil { panic(err) } @@ -33,3 +33,27 @@ func RandomStringWithAlphabet(length int, alphabet string) string { return string(b) } + +// RandomString generates a pseudorandom string with the specified length. +// +// The generated string matches [A-Za-z0-9]+ and it's transparent to URL-encoding. +// +// For a cryptographically random string (but a little bit slower) use PseudoRandomString instead. +func PseudoRandomString(length int) string { + return RandomStringWithAlphabet(length, defaultRandomAlphabet) +} + +// PseudoRandomStringWithAlphabet generates a pseudorandom string +// with the specified length and characters set. +// +// For a cryptographically random (but a little bit slower) use RandomStringWithAlphabet instead. +func PseudoRandomStringWithAlphabet(length int, alphabet string) string { + b := make([]byte, length) + max := len(alphabet) + + for i := range b { + b[i] = alphabet[mathRand.Intn(max)] + } + + return string(b) +} diff --git a/tools/security/random_test.go b/tools/security/random_test.go index e27b92d6..85eb69ed 100644 --- a/tools/security/random_test.go +++ b/tools/security/random_test.go @@ -8,47 +8,39 @@ import ( ) func TestRandomString(t *testing.T) { - generated := []string{} - reg := regexp.MustCompile(`[a-zA-Z0-9]+`) - length := 10 - - for i := 0; i < 100; i++ { - result := security.RandomString(length) - - if len(result) != length { - t.Fatalf("(%d) Expected the length of the string to be %d, got %d", i, length, len(result)) - } - - if match := reg.MatchString(result); !match { - t.Fatalf("(%d) The generated string should have only [a-zA-Z0-9]+ characters, got %q", i, result) - } - - for _, str := range generated { - if str == result { - t.Fatalf("(%d) Repeating random string - found %q in \n%v", i, result, generated) - } - } - - generated = append(generated, result) - } + testRandomString(t, security.RandomString) } func TestRandomStringWithAlphabet(t *testing.T) { + testRandomStringWithAlphabet(t, security.RandomStringWithAlphabet) +} + +func TestPseudoRandomString(t *testing.T) { + testRandomString(t, security.PseudoRandomString) +} + +func TestPseudoRandomStringWithAlphabet(t *testing.T) { + testRandomStringWithAlphabet(t, security.PseudoRandomStringWithAlphabet) +} + +// ------------------------------------------------------------------- + +func testRandomStringWithAlphabet(t *testing.T, randomFunc func(n int, alphabet string) string) { scenarios := []struct { alphabet string expectPattern string }{ {"0123456789_", `[0-9_]+`}, - {"abcd", `[abcd]+`}, + {"abcdef", `[abcdef]+`}, {"!@#$%^&*()", `[\!\@\#\$\%\^\&\*\(\)]+`}, } for i, s := range scenarios { - generated := make([]string, 0, 100) + generated := make([]string, 0, 1000) length := 10 - for j := 0; j < 100; j++ { - result := security.RandomStringWithAlphabet(length, s.alphabet) + for j := 0; j < 1000; j++ { + result := randomFunc(length, s.alphabet) if len(result) != length { t.Fatalf("(%d:%d) Expected the length of the string to be %d, got %d", i, j, length, len(result)) @@ -69,3 +61,29 @@ func TestRandomStringWithAlphabet(t *testing.T) { } } } + +func testRandomString(t *testing.T, randomFunc func(n int) string) { + generated := make([]string, 0, 1000) + reg := regexp.MustCompile(`[a-zA-Z0-9]+`) + length := 10 + + for i := 0; i < 1000; i++ { + result := randomFunc(length) + + if len(result) != length { + t.Fatalf("(%d) Expected the length of the string to be %d, got %d", i, length, len(result)) + } + + if match := reg.MatchString(result); !match { + t.Fatalf("(%d) The generated string should have only [a-zA-Z0-9]+ characters, got %q", i, result) + } + + for _, str := range generated { + if str == result { + t.Fatalf("(%d) Repeating random string - found %q in \n%v", i, result, generated) + } + } + + generated = append(generated, result) + } +}