Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -190,21 +190,28 @@
"filename": "utils/hashing/hash_test.go",
"hashed_secret": "4028a0e356acc947fcd2bfbf00cef11e128d484a",
"is_verified": false,
"line_number": 27
"line_number": 28
},
{
"type": "Hex High Entropy String",
"filename": "utils/hashing/hash_test.go",
"hashed_secret": "1f35be0c58b01a2fddd3aded671f0f7efed3ff62",
"is_verified": false,
"line_number": 30
"line_number": 31
},
{
"type": "Hex High Entropy String",
"filename": "utils/hashing/hash_test.go",
"hashed_secret": "67a74306b06d0c01624fe0d0249a570f4d093747",
"is_verified": false,
"line_number": 48
},
{
"type": "Hex High Entropy String",
"filename": "utils/hashing/hash_test.go",
"hashed_secret": "30f0cbefb37316806a7024caee994baf8365fa53",
"is_verified": false,
"line_number": 111
"line_number": 132
}
],
"utils/sharedcache/common.go": [
Expand Down Expand Up @@ -265,5 +272,5 @@
}
]
},
"generated_at": "2025-05-30T10:38:21Z"
"generated_at": "2025-06-05T07:39:32Z"
}
1 change: 1 addition & 0 deletions changes/20250605081112.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:sparkles: [hashing] adding some utilities to incorporate context protection
1 change: 1 addition & 0 deletions changes/20250605083650.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:sparkles: [environment] adding utilities to ease the search of multiple environment variables set in an environment
13 changes: 13 additions & 0 deletions utils/environment/current.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/joho/godotenv"

"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/filesystem"
"github.com/ARM-software/golang-utils/utils/platform"
)
Expand All @@ -29,6 +30,18 @@ func (c *currentEnv) GetEnvironmentVariables(dotEnvFiles ...string) (variables [
return
}

// FindEnvironmentVariableInEnvironment returns a list of environment variables set in the environment env (and optionally those in the supplied in `dotEnvFiles`)
// `dotEnvFiles` corresponds to `.env` files present on the machine and follows the mechanism described by https://github.com/bkeepers/dotenv
// If no environment variable is found in env, an error is returned.
func FindEnvironmentVariableInEnvironment(env IEnvironment, dotEnvFiles []string, envvars ...string) (variables []IEnvironmentVariable, err error) {
if env == nil {
err = commonerrors.UndefinedVariable("environment")
return
}
variables, err = FindEnvironmentVariables(env.GetEnvironmentVariables(dotEnvFiles...), envvars...)
return
}

func (c *currentEnv) GetExpandedEnvironmentVariables(dotEnvFiles ...string) []IEnvironmentVariable {
return ExpandEnvironmentVariables(true, c.GetEnvironmentVariables(dotEnvFiles...)...)
}
Expand Down
8 changes: 7 additions & 1 deletion utils/environment/current_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ func TestNewCurrentEnvironment(t *testing.T) {
func Test_currentEnv_GetEnvironmentVariables(t *testing.T) {
t.Run("No dotenv files", func(t *testing.T) {
os.Clearenv()
require.NoError(t, os.Setenv("test1", faker.Sentence()))
value := faker.Sentence()
require.NoError(t, os.Setenv("test1", value))
require.NoError(t, os.Setenv("test2", faker.Sentence()))
current := NewCurrentEnvironment()
envVars := current.GetEnvironmentVariables()
assert.Len(t, envVars, 2)
assert.False(t, envVars[0].Equal(envVars[1]))
found, err := FindEnvironmentVariableInEnvironment(current, nil, "test1")
require.NoError(t, err)
assert.Len(t, found, 1)
assert.NotEmpty(t, found[0])
assert.Equal(t, value, found[0].GetValue())
})

t.Run("With dotenv files", func(t *testing.T) {
Expand Down
40 changes: 35 additions & 5 deletions utils/environment/envvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ func (e *EnvVar) String() string {
func (e *EnvVar) Validate() (err error) {
err = validation.Validate(e.GetKey(), validation.Required, IsEnvironmentVariableKey)
if err != nil {
err = fmt.Errorf("%w: environment variable name `%v` is not valid: %v", commonerrors.ErrInvalid, e.GetKey(), err.Error())
err = commonerrors.WrapErrorf(commonerrors.ErrInvalid, err, "environment variable name `%v` is not valid", e.GetKey())
return
}
if len(e.validationRules) > 0 {
err = validation.Validate(e.GetValue(), e.validationRules...)
if err != nil {
err = fmt.Errorf("%w: environment variable `%v` value is not valid: %v", commonerrors.ErrInvalid, e.GetKey(), err.Error())
err = commonerrors.WrapErrorf(commonerrors.ErrInvalid, err, "environment variable `%v` value is not valid", e.GetKey())
}
}
return
Expand All @@ -90,7 +90,7 @@ func isEnvVarKey(value string) bool {
func ParseEnvironmentVariable(variable string) (IEnvironmentVariable, error) {
elements := strings.Split(strings.TrimSpace(variable), "=")
if len(elements) < 2 {
return nil, fmt.Errorf("%w: invalid environment variable entry as not following key=value", commonerrors.ErrInvalid)
return nil, commonerrors.New(commonerrors.ErrInvalid, "invalid environment variable entry as not following key=value")
}
value := elements[1]
if len(elements) > 2 {
Expand Down Expand Up @@ -158,7 +158,22 @@ func FindEnvironmentVariable(envvar string, envvars ...IEnvironmentVariable) (IE
return envvars[i], nil
}
}
return nil, fmt.Errorf("%w: environment variable '%v' not set", commonerrors.ErrNotFound, envvar)
return nil, commonerrors.Newf(commonerrors.ErrNotFound, "environment variable '%v' not set", envvar)
}

// FindEnvironmentVariables looks for environment variables in a list. if no environment variable matches, an error is returned
func FindEnvironmentVariables(environment []IEnvironmentVariable, envvarToSearchFor ...string) ([]IEnvironmentVariable, error) {
envs := make([]IEnvironmentVariable, 0, len(envvarToSearchFor))
for i := range envvarToSearchFor {
found, err := FindEnvironmentVariable(envvarToSearchFor[i], environment...)
if err == nil && found != nil {
envs = append(envs, found)
}
}
if len(envs) == 0 {
return nil, commonerrors.Newf(commonerrors.ErrNotFound, "could not find any environment variables %v set", envvarToSearchFor)
}
return envs, nil
}

// FindFoldEnvironmentVariable looks for an environment variable in a list similarly to FindEnvironmentVariable but without case-sensitivity.
Expand All @@ -168,7 +183,22 @@ func FindFoldEnvironmentVariable(envvar string, envvars ...IEnvironmentVariable)
return envvars[i], nil
}
}
return nil, fmt.Errorf("%w: environment variable '%v' not set", commonerrors.ErrNotFound, envvar)
return nil, commonerrors.Newf(commonerrors.ErrNotFound, "environment variable '%v' not set", envvar)
}

// FindFoldEnvironmentVariables looks for environment variables in a list with no case-sensitivity. if no environment variable matches, an error is returned
func FindFoldEnvironmentVariables(environment []IEnvironmentVariable, envvarToSearchFor ...string) ([]IEnvironmentVariable, error) {
envs := make([]IEnvironmentVariable, 0, len(envvarToSearchFor))
for i := range envvarToSearchFor {
found, err := FindFoldEnvironmentVariable(envvarToSearchFor[i], environment...)
if err == nil && found != nil {
envs = append(envs, found)
}
}
if len(envs) == 0 {
return nil, commonerrors.Newf(commonerrors.ErrNotFound, "could not find any environment variables %v set", envvarToSearchFor)
}
return envs, nil
}

// ExpandEnvironmentVariables returns a list of environment variables with their value being expanded.
Expand Down
43 changes: 43 additions & 0 deletions utils/hashing/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"hash"
"io"
"math"
"slices"
"strings"

"github.com/OneOfOne/xxhash"
Expand Down Expand Up @@ -118,6 +119,30 @@ func CalculateStringHash(hashingAlgo IHash, text string) string {
return hash
}

// CalculateStringHashWithContext returns the hash of some text using a particular hashing algorithm
func CalculateStringHashWithContext(ctx context.Context, hashingAlgo IHash, text string) string {
if hashingAlgo == nil {
return ""
}
hash, err := hashingAlgo.CalculateWithContext(ctx, strings.NewReader(text))
if err != nil {
return ""
}
return hash
}

// CalculateStringHashList returns the hash of a list of strings using a particular hashing algorithm
func CalculateStringHashList(ctx context.Context, hashingAlgo IHash, text ...string) string {
if hashingAlgo == nil {
return ""
}
if len(text) == 0 {
return CalculateStringHashWithContext(ctx, hashingAlgo, "")
}
slices.Sort(text)
return CalculateStringHashWithContext(ctx, hashingAlgo, strings.Join(text, " "))
}

// CalculateHash calculates the hash of some text using the requested htype hashing algorithm.
func CalculateHash(text, htype string) string {
hashing, err := NewHashingAlgorithm(htype)
Expand All @@ -127,6 +152,24 @@ func CalculateHash(text, htype string) string {
return CalculateStringHash(hashing, text)
}

// CalculateHashWithContext calculates the hash of some text using the requested htype hashing algorithm.
func CalculateHashWithContext(ctx context.Context, text, htype string) string {
hashing, err := NewHashingAlgorithm(htype)
if err != nil {
return ""
}
return CalculateStringHashWithContext(ctx, hashing, text)
}

// CalculateHashOfListOfStrings calculates the hash of some text using the requested htype hashing algorithm.
func CalculateHashOfListOfStrings(ctx context.Context, htype string, text ...string) string {
hashing, err := NewHashingAlgorithm(htype)
if err != nil {
return ""
}
return CalculateStringHashList(ctx, hashing, text...)
}

// HasLikelyHexHashStringEntropy states whether a string has an entropy which may entail it is a hexadecimal hash
// This is based on the work done by `detect-secrets` https://github.com/Yelp/detect-secrets/blob/2fc0e31f067af98d97ad0f507dac032c9506f667/detect_secrets/plugins/high_entropy_strings.py#L150
func HasLikelyHexHashStringEntropy(str string) bool {
Expand Down
47 changes: 41 additions & 6 deletions utils/hashing/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package hashing

import (
"context"
"fmt"
"strings"
"testing"
Expand All @@ -30,10 +31,26 @@ func TestHasher(t *testing.T) {
Hash: "c61d595888f85f6d30e99ef6cacfcb7d",
}}
for _, testCase := range testCases {
hash, err := hasher.Calculate(strings.NewReader(testCase.Input))
require.Nil(t, err)
assert.Equal(t, testCase.Hash, hash)
t.Run(testCase.Input, func(t *testing.T) {
hash, err := hasher.Calculate(strings.NewReader(testCase.Input))
require.Nil(t, err)
assert.Equal(t, testCase.Hash, hash)
})
t.Run(testCase.Input+" with context", func(t *testing.T) {
hash, err := hasher.CalculateWithContext(context.Background(), strings.NewReader(testCase.Input))
require.Nil(t, err)
assert.Equal(t, testCase.Hash, hash)
})
}
t.Run("hashing list of strings", func(t *testing.T) {
require.NotEmpty(t, CalculateHashOfListOfStrings(context.Background(), HashMd5, strings.Split(faker.Sentence(), " ")...))
require.NotEmpty(t, CalculateHashOfListOfStrings(context.Background(), HashMd5))
assert.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", CalculateHashOfListOfStrings(context.Background(), HashMd5))
assert.Equal(t, "c61d595888f85f6d30e99ef6cacfcb7d", CalculateHashOfListOfStrings(context.Background(), HashMd5, "CMSIS"))
hash1 := CalculateHashOfListOfStrings(context.Background(), HashMd5, "CMSIS", "test")
hash2 := CalculateHashOfListOfStrings(context.Background(), HashMd5, "test", "CMSIS")
assert.Equal(t, hash1, hash2)
})
}

func TestMd5(t *testing.T) {
Expand Down Expand Up @@ -107,6 +124,10 @@ func TestIsLikelyHexHashString(t *testing.T) {
input: CalculateHash(faker.Paragraph(), HashSha256),
isHash: true,
},
{
input: CalculateHashWithContext(context.Background(), faker.Paragraph(), HashSha256),
isHash: true,
},
{
input: "85817ddeed66c3e3805c73dbc7082de2674e349c",
isHash: true,
Expand All @@ -128,7 +149,21 @@ func TestBespokeHash(t *testing.T) {
require.NoError(t, err)
hashing, err := NewBespokeHashingAlgorithm(algo)
require.NoError(t, err)
hash := CalculateStringHash(hashing, faker.Paragraph())
require.NotEmpty(t, hash)
assert.Equal(t, size*2, len(hash))
t.Run("hash", func(t *testing.T) {
hash := CalculateStringHash(hashing, faker.Paragraph())
require.NotEmpty(t, hash)
assert.Equal(t, size*2, len(hash))
})
t.Run("with context", func(t *testing.T) {
hash := CalculateStringHashWithContext(context.Background(), hashing, faker.Paragraph())
require.NotEmpty(t, hash)
assert.Equal(t, size*2, len(hash))
})
t.Run("with cancelled context", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
hash := CalculateStringHashWithContext(ctx, hashing, faker.Paragraph())
require.Empty(t, hash)
})

}
Loading