diff --git a/Makefile b/Makefile index 9974f982b..f0724de4c 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ coverage-horusec-analytic: deployments/scripts/coverage.sh 98 "./horusec-analytic" coverage-horusec-auth: chmod +x deployments/scripts/coverage.sh - deployments/scripts/coverage.sh 97 "./horusec-auth" + deployments/scripts/coverage.sh 96 "./horusec-auth" coverage-horusec-webhook: chmod +x deployments/scripts/coverage.sh deployments/scripts/coverage.sh 99 "./horusec-webhook" diff --git a/README.md b/README.md index 41434a422..563f88b71 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,10 @@ Currently, performance analysis consists of: * [GitLeaks][Gitleaks] * PHP * [Semgrep][Semgrep] -* C + * [PHPCS][PHPCS] +* C/C++ * [Semgrep][Semgrep] + * [Flawfinder][Flawfinder] * HTML * [Semgrep][Semgrep] * JSON @@ -185,3 +187,5 @@ This project exists thanks to all the [contributors]((https://github.com/ZupIT/h [SecuriyCodeScan]: https://security-code-scan.github.io/ [Semgrep]: https://github.com/returntocorp/semgrep [EsLint]: https://github.com/eslint/eslint +[Flawfinder]: https://github.com/david-a-wheeler/flawfinder +[PHPCS]: https://github.com/FloeDesignTechnologies/phpcs-security-audit diff --git a/deployments/dockerfiles/flawfinder/.semver.yaml b/deployments/dockerfiles/flawfinder/.semver.yaml new file mode 100644 index 000000000..36a2accc3 --- /dev/null +++ b/deployments/dockerfiles/flawfinder/.semver.yaml @@ -0,0 +1,4 @@ +alpha: 0 +beta: 0 +rc: 0 +release: v1.0.0 diff --git a/deployments/dockerfiles/flawfinder/Dockerfile b/deployments/dockerfiles/flawfinder/Dockerfile new file mode 100644 index 000000000..d92cf144f --- /dev/null +++ b/deployments/dockerfiles/flawfinder/Dockerfile @@ -0,0 +1,18 @@ +# Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +# +# 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. + +FROM python:3.7-alpine + +RUN apk add --no-cache git bash +RUN pip install flawfinder \ No newline at end of file diff --git a/deployments/dockerfiles/phpcs/.semver.yaml b/deployments/dockerfiles/phpcs/.semver.yaml new file mode 100644 index 000000000..36a2accc3 --- /dev/null +++ b/deployments/dockerfiles/phpcs/.semver.yaml @@ -0,0 +1,4 @@ +alpha: 0 +beta: 0 +rc: 0 +release: v1.0.0 diff --git a/deployments/dockerfiles/phpcs/Dockerfile b/deployments/dockerfiles/phpcs/Dockerfile new file mode 100644 index 000000000..05cb3c0f7 --- /dev/null +++ b/deployments/dockerfiles/phpcs/Dockerfile @@ -0,0 +1,25 @@ +# Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +# +# 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. + +FROM php:7.4-alpine + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +RUN composer global config bin-dir /usr/local/bin + +RUN composer global require "squizlabs/php_codesniffer=*" + +RUN composer require --dev pheromone/phpcs-security-audit + +RUN phpcs --config-set installed_paths /vendor/pheromone/phpcs-security-audit/Security diff --git a/deployments/scripts/update-image-tool.sh b/deployments/scripts/update-image-tool.sh index 091e8ea19..dcc0b4188 100755 --- a/deployments/scripts/update-image-tool.sh +++ b/deployments/scripts/update-image-tool.sh @@ -101,6 +101,14 @@ getDirectoryAndImageNameByToolName () { IMAGE_NAME="horuszup/eslint" DIRECTORY_CONFIG="$CURRENT_FOLDER/horusec-cli/internal/services/formatters/javascript/eslint/config.go" DIRECTORY_SEMVER="$CURRENT_FOLDER/deployments/dockerfiles/eslint";; + "phpcs") + IMAGE_NAME="horuszup/horusec-phpcs" + DIRECTORY_CONFIG="$CURRENT_FOLDER/horusec-cli/internal/services/formatters/php/phpcs/config.go" + DIRECTORY_SEMVER="$CURRENT_FOLDER/deployments/dockerfiles/phpcs";; + "flawfinder") + IMAGE_NAME="horuszup/horusec-flawfinder" + DIRECTORY_CONFIG="$CURRENT_FOLDER/horusec-cli/internal/services/formatters/c/flawfinder/config.go" + DIRECTORY_SEMVER="$CURRENT_FOLDER/deployments/dockerfiles/flawfinder";; "horusec-nodejs") IMAGE_NAME="horuszup/horusec-nodejs" DIRECTORY_CONFIG="$CURRENT_FOLDER/horusec-cli/internal/services/formatters/javascript/horusecnodejs/config.go" @@ -111,7 +119,7 @@ getDirectoryAndImageNameByToolName () { DIRECTORY_SEMVER="$CURRENT_FOLDER/horusec-kubernetes";; *) echo "Param Tool Name is invalid, please use the examples bellow allowed and try again!" - echo "Params Tool Name allowed: bandit, brakeman, gitleaks, gosec, npmaudit, safety, securitycodescan, hcl, spotbugs, horusec-kotlin, horusec-java, horusec-leaks, horusec-csharp, horusec-nodejs, horusec-kubernetes" + echo "Params Tool Name allowed: bandit, brakeman, gitleaks, gosec, npmaudit, safety, securitycodescan, hcl, spotbugs, horusec-kotlin, horusec-java, horusec-leaks, horusec-csharp, horusec-nodejs, horusec-kubernetes, phpcs, flawfinder" exit 1;; esac } diff --git a/development-kit/pkg/databases/relational/repository/account/account.go b/development-kit/pkg/databases/relational/repository/account/account.go index de076d5c0..7b081dcb6 100644 --- a/development-kit/pkg/databases/relational/repository/account/account.go +++ b/development-kit/pkg/databases/relational/repository/account/account.go @@ -25,6 +25,7 @@ type IAccount interface { GetByAccountID(accountID uuid.UUID) (*authEntities.Account, error) GetByEmail(email string) (*authEntities.Account, error) Update(account *authEntities.Account) error + UpdatePassword(account *authEntities.Account) error GetByUsername(username string) (*authEntities.Account, error) DeleteAccount(accountID uuid.UUID) error } @@ -61,7 +62,13 @@ func (a *Account) GetByEmail(email string) (*authEntities.Account, error) { func (a *Account) Update(account *authEntities.Account) error { account.SetUpdatedAt() - return a.databaseWrite.Update(account.ToMap(), map[string]interface{}{"account_id": account.AccountID}, + return a.databaseWrite.Update(account.ToUpdateMap(), map[string]interface{}{"account_id": account.AccountID}, + account.GetTable()).GetError() +} + +func (a *Account) UpdatePassword(account *authEntities.Account) error { + account.SetUpdatedAt() + return a.databaseWrite.Update(account.ToUpdatePasswordMap(), map[string]interface{}{"account_id": account.AccountID}, account.GetTable()).GetError() } diff --git a/development-kit/pkg/engines/leaks/analysis/analysis_test.go b/development-kit/pkg/engines/leaks/analysis/analysis_test.go index f5e66acd8..0a20af56f 100644 --- a/development-kit/pkg/engines/leaks/analysis/analysis_test.go +++ b/development-kit/pkg/engines/leaks/analysis/analysis_test.go @@ -41,7 +41,7 @@ func TestAnalysis_StartAnalysis(t *testing.T) { data := []engine.Finding{} _ = json.Unmarshal(fileBytes, &data) assert.NoError(t, os.RemoveAll(configs.GetOutputFilePath())) - assert.Equal(t, len(data), 17) + assert.Equal(t, len(data), 19) }) t.Run("Should return success when read analysis and return two vulnerabilities", func(t *testing.T) { configs := config.NewConfig() @@ -117,6 +117,6 @@ func TestAnalysis_StartRegularAnalysis(t *testing.T) { vulnCounter++ } } - assert.Equal(t, vulnCounter, 10) + assert.Equal(t, 12, vulnCounter) }) } diff --git a/development-kit/pkg/entities/analyser/c/result.go b/development-kit/pkg/entities/analyser/c/result.go new file mode 100644 index 000000000..a78d1c40b --- /dev/null +++ b/development-kit/pkg/entities/analyser/c/result.go @@ -0,0 +1,54 @@ +// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +// +// 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 c + +import ( + "fmt" + "github.com/ZupIT/horusec/development-kit/pkg/enums/severity" + "strconv" + "strings" +) + +type Result struct { + File string `json:"file"` + Line string `json:"line"` + Column string `json:"column"` + Level string `json:"level"` + Warning string `json:"warning"` + Suggestion string `json:"suggestion"` + Note string `json:"note"` + Context string `json:"context"` +} + +func (r *Result) GetDetails() string { + return fmt.Sprintf("%s %s %s", r.Warning, r.Suggestion, r.Note) +} + +func (r *Result) GetSeverity() severity.Severity { + level, _ := strconv.Atoi(r.Level) + if level <= 2 { + return severity.Low + } + + if level >= 3 && level <= 4 { + return severity.Medium + } + + return severity.High +} + +func (r *Result) GetFilename() string { + return strings.ReplaceAll(r.File, "./", "") +} diff --git a/development-kit/pkg/entities/analyser/c/result_test.go b/development-kit/pkg/entities/analyser/c/result_test.go new file mode 100644 index 000000000..286f6b9b3 --- /dev/null +++ b/development-kit/pkg/entities/analyser/c/result_test.go @@ -0,0 +1,82 @@ +package c + +import ( + "github.com/ZupIT/horusec/development-kit/pkg/enums/severity" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetDetails(t *testing.T) { + result := &Result{ + Warning: "test", + Suggestion: "test", + Note: "test", + } + + t.Run("should success get details", func(t *testing.T) { + details := result.GetDetails() + + assert.NotEmpty(t, details) + assert.Equal(t, "test test test", details) + }) + +} + +func TestGetSeverity(t *testing.T) { + result := &Result{ + Level: "0", + } + + t.Run("should get severity low", func(t *testing.T) { + assert.Equal(t, severity.Low, result.GetSeverity()) + + result.Level = "1" + assert.Equal(t, severity.Low, result.GetSeverity()) + + result.Level = "2" + assert.Equal(t, severity.Low, result.GetSeverity()) + }) + + t.Run("should get severity medium", func(t *testing.T) { + result.Level = "3" + assert.Equal(t, severity.Medium, result.GetSeverity()) + + result.Level = "4" + assert.Equal(t, severity.Medium, result.GetSeverity()) + + result.Level = "2" + assert.NotEqual(t, severity.Medium, result.GetSeverity()) + + result.Level = "5" + assert.NotEqual(t, severity.Medium, result.GetSeverity()) + }) + + t.Run("should get severity high", func(t *testing.T) { + result.Level = "5" + assert.Equal(t, severity.High, result.GetSeverity()) + + result.Level = "6" + assert.Equal(t, severity.High, result.GetSeverity()) + + result.Level = "1" + assert.NotEqual(t, severity.High, result.GetSeverity()) + + result.Level = "4" + assert.NotEqual(t, severity.High, result.GetSeverity()) + }) +} + +func TestGetFilename(t *testing.T) { + result := &Result{ + File: "./test.c", + } + + t.Run("should success get filename", func(t *testing.T) { + filename := result.GetFilename() + + assert.NotEmpty(t, filename) + assert.NotContains(t, filename, "./") + assert.Equal(t, "test.c", filename) + }) + +} diff --git a/development-kit/pkg/entities/analyser/php/phpcs/message.go b/development-kit/pkg/entities/analyser/php/phpcs/message.go new file mode 100644 index 000000000..aba44416f --- /dev/null +++ b/development-kit/pkg/entities/analyser/php/phpcs/message.go @@ -0,0 +1,26 @@ +package phpcs + +import ( + "strconv" + "strings" +) + +type Message struct { + Message string `json:"message"` + Line int `json:"line"` + Column int `json:"column"` + Type string `json:"type"` +} + +func (m *Message) GetLine() string { + return strconv.Itoa(m.Line) +} + +func (m *Message) GetColumn() string { + return strconv.Itoa(m.Column) +} + +func (m *Message) IsValidMessage() bool { + return m.Type == "ERROR" && + !strings.Contains(m.Message, "This implies that some PHP code is not scanned by PHPCS") +} diff --git a/development-kit/pkg/entities/analyser/php/phpcs/message_test.go b/development-kit/pkg/entities/analyser/php/phpcs/message_test.go new file mode 100644 index 000000000..2936b114f --- /dev/null +++ b/development-kit/pkg/entities/analyser/php/phpcs/message_test.go @@ -0,0 +1,52 @@ +package phpcs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetLine(t *testing.T) { + message := &Message{ + Line: 1, + } + + t.Run("should success get line", func(t *testing.T) { + line := message.GetLine() + + assert.NotEmpty(t, line) + assert.Equal(t, "1", line) + }) +} + +func TestGetColumn(t *testing.T) { + message := &Message{ + Column: 1, + } + + t.Run("should success get column", func(t *testing.T) { + column := message.GetColumn() + + assert.NotEmpty(t, column) + assert.Equal(t, "1", column) + }) +} + +func TestIsValidMessage(t *testing.T) { + t.Run("should return false if invalid message", func(t *testing.T) { + message := &Message{ + Message: "This implies that some PHP code is not scanned by PHPCS", + Type: "ERROR", + } + + assert.False(t, message.IsValidMessage()) + }) + + t.Run("should return true if valid message", func(t *testing.T) { + message := &Message{ + Message: "", + Type: "ERROR", + } + + assert.True(t, message.IsValidMessage()) + }) +} diff --git a/development-kit/pkg/entities/analyser/php/phpcs/result.go b/development-kit/pkg/entities/analyser/php/phpcs/result.go new file mode 100644 index 000000000..5fd5debb2 --- /dev/null +++ b/development-kit/pkg/entities/analyser/php/phpcs/result.go @@ -0,0 +1,5 @@ +package phpcs + +type Result struct { + Messages []Message `json:"messages"` +} diff --git a/development-kit/pkg/entities/auth/account.go b/development-kit/pkg/entities/auth/account.go index 875e83f30..62672a26b 100644 --- a/development-kit/pkg/entities/auth/account.go +++ b/development-kit/pkg/entities/auth/account.go @@ -118,6 +118,21 @@ func (a *Account) ToMap() map[string]interface{} { } } +func (a *Account) ToUpdateMap() map[string]interface{} { + return map[string]interface{}{ + "email": a.Email, + "username": a.Username, + "updated_at": a.UpdatedAt, + "is_confirmed": a.IsConfirmed, + } +} + +func (a *Account) ToUpdatePasswordMap() map[string]interface{} { + return map[string]interface{}{ + "password": a.Password, + } +} + func (a *Account) IsNotApplicationAdminAccount() bool { return !a.IsApplicationAdmin } diff --git a/development-kit/pkg/entities/auth/account_test.go b/development-kit/pkg/entities/auth/account_test.go index 21341d931..818e15f50 100644 --- a/development-kit/pkg/entities/auth/account_test.go +++ b/development-kit/pkg/entities/auth/account_test.go @@ -15,10 +15,11 @@ package auth import ( - "github.com/google/uuid" - "github.com/stretchr/testify/assert" "testing" "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" ) func TestSetPassword(t *testing.T) { @@ -113,6 +114,25 @@ func TestToMap(t *testing.T) { }) } +func TestToUpdateMap(t *testing.T) { + t.Run("should success parse to map", func(t *testing.T) { + account := &Account{ + AccountID: uuid.New(), + Email: "test", + Password: "test", + Username: "test", + IsConfirmed: false, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + accountMap := account.ToUpdateMap() + + assert.NotEmpty(t, account) + assert.Empty(t, accountMap["password"]) + }) +} + func TestIsNotApplicationAdminAccount(t *testing.T) { t.Run("Should return true when get if user is application admin", func(t *testing.T) { account := &Account{ diff --git a/development-kit/pkg/entities/auth/config_auth.go b/development-kit/pkg/entities/auth/config_auth.go index 80b4b2e23..49c6bff3a 100644 --- a/development-kit/pkg/entities/auth/config_auth.go +++ b/development-kit/pkg/entities/auth/config_auth.go @@ -21,6 +21,7 @@ import ( type ConfigAuth struct { ApplicationAdminEnable bool `json:"applicationAdminEnable"` + DisabledBroker bool `json:"disabledBroker"` AuthType auth.AuthorizationType `json:"authType"` } diff --git a/development-kit/pkg/entities/webhook/headers_test.go b/development-kit/pkg/entities/webhook/headers_test.go index fffa940b1..c8c6c11df 100644 --- a/development-kit/pkg/entities/webhook/headers_test.go +++ b/development-kit/pkg/entities/webhook/headers_test.go @@ -16,15 +16,16 @@ package webhook import ( "encoding/json" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestHeaderType_Value(t *testing.T) { var h HeaderType h = []Headers{ { - Key: "Authorization", + Key: "X-Horusec-Authorization", Value: "Bearer Token", }, } @@ -42,7 +43,7 @@ func TestHeaderType_Scan(t *testing.T) { var h HeaderType bytes, err := json.Marshal([]Headers{ { - Key: "Authorization", + Key: "X-Horusec-Authorization", Value: "Bearer Token", }, }) diff --git a/development-kit/pkg/entities/webhook/webhook_test.go b/development-kit/pkg/entities/webhook/webhook_test.go index 13893c136..d2e5c4c44 100644 --- a/development-kit/pkg/entities/webhook/webhook_test.go +++ b/development-kit/pkg/entities/webhook/webhook_test.go @@ -15,11 +15,12 @@ package webhook import ( + "net/http" + "testing" + errorsEnum "github.com/ZupIT/horusec/development-kit/pkg/enums/errors" "github.com/google/uuid" "github.com/stretchr/testify/assert" - "net/http" - "testing" ) func TestWebhook_GetMethod(t *testing.T) { @@ -58,14 +59,14 @@ func TestWebhook_GetHeaders(t *testing.T) { w := &Webhook{ Headers: []Headers{ { - Key: "Authorization", + Key: "X-Horusec-Authorization", Value: "Bearer token", }, }, } headerMap := w.GetHeaders() assert.NotEmpty(t, headerMap) - assert.Equal(t, "Bearer token", headerMap["Authorization"]) + assert.Equal(t, "Bearer token", headerMap["X-Horusec-Authorization"]) } func TestWebhook_Validate(t *testing.T) { diff --git a/development-kit/pkg/enums/engine/advisories/leaks/regular/regular.go b/development-kit/pkg/enums/engine/advisories/leaks/regular/regular.go index a8ddbcd00..8d105e197 100644 --- a/development-kit/pkg/enums/engine/advisories/leaks/regular/regular.go +++ b/development-kit/pkg/enums/engine/advisories/leaks/regular/regular.go @@ -249,7 +249,7 @@ func NewLeaksRegularGoogleGCPServiceAccount() text.TextRule { Expressions: []*regexp.Regexp{ regexp.MustCompile(`"type": "service_account"`), regexp.MustCompile(`(?i)(google|gcp|youtube|drive|yt)(.{0,20})?['\"][AIza[0-9a-z\\-_]{35}]['\"]`), - regexp.MustCompile(`(?i)(google|gcp|auth)(.{0,20})?['\"][0-9]+-[0-9a-z_]{32}\\.apps\\.googleusercontent\\.com['\"]`), + regexp.MustCompile(`(?i)(google|gcp|auth)(.{0,20})?['\"][0-9]+-[0-9a-z_]{32}\.apps\.googleusercontent\.com['\"]`), }, } } @@ -425,7 +425,7 @@ func NewLeaksRegularHardCodedCredentialGeneric() text.TextRule { }, Type: text.Regular, Expressions: []*regexp.Regexp{ - regexp.MustCompile(`(?i)(dbpasswd|dbuser|dbname|dbhost|api_key|apikey|client_secret|clientsecret|access_key|accesskey|secret_key|secretkey)(.{0,20})?['|"]([0-9a-zA-Z-_\/+!{}/=]{4,120})['|"]`), + regexp.MustCompile(`(?i)(dbpasswd|dbuser|dbname|dbhost|api_key|apikey|client_secret|clientsecret|access_key|accesskey|secret_key|secretkey)(.{0,20})?['|"]([0-9a-zA-Z-_\/+!{}/=:@#%\*]{4,120})['|"]`), }, } } @@ -477,7 +477,7 @@ func NewLeaksRegularWPConfig() text.TextRule { }, Type: text.Regular, Expressions: []*regexp.Regexp{ - regexp.MustCompile(`define(.{0,20})?(DB_CHARSET|NONCE_SALT|LOGGED_IN_SALT|AUTH_SALT|NONCE_KEY|DB_HOST|DB_PASSWORD|AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|DB_NAME|DB_USER)(.{0,20})?[''|"].{10,120}[''|"]`), + regexp.MustCompile(`define(.{0,20})?(DB_CHARSET|NONCE_SALT|LOGGED_IN_SALT|AUTH_SALT|NONCE_KEY|DB_HOST|DB_PASSWORD|AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|DB_NAME|DB_USER).*,\s*[''|"].{6,120}[''|"]`), }, } } diff --git a/development-kit/pkg/enums/engine/advisories/leaks/regular/regular_test.go b/development-kit/pkg/enums/engine/advisories/leaks/regular/regular_test.go new file mode 100644 index 000000000..b047e5441 --- /dev/null +++ b/development-kit/pkg/enums/engine/advisories/leaks/regular/regular_test.go @@ -0,0 +1,1365 @@ +package regular + +import ( + engine "github.com/ZupIT/horusec-engine" + "github.com/stretchr/testify/assert" + "testing" + + "github.com/ZupIT/horusec-engine/text" +) + +func parseTextUnitsToUnits(textUnits []text.TextUnit) (units []engine.Unit) { + for index := range textUnits { + units = append(units, textUnits[index]) + } + return units +} + +func TestNewLeaksRegularAWSManagerID(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularAWSManagerID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + ACCESS_KEY: 'AKIAJSIE27KKMHXI3BJQ' +` + rule := NewLeaksRegularAWSManagerID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: "ACCESS_KEY: 'AKIAJSIE27KKMHXI3BJQ'", + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 18, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularAWSManagerID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + ACCESS_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularAWSManagerID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularAWSSecretKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularAWSSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + AWS_SECRET_KEY: 'doc5eRXFpsWllGC5yKJV/Ymm5KwF+IRZo95EudOm' +` + rule := NewLeaksRegularAWSSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `AWS_SECRET_KEY: 'doc5eRXFpsWllGC5yKJV/Ymm5KwF+IRZo95EudOm'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularAWSSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SECRET_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularAWSSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularAWSMWSKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularAWSMWSKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + AWS_WMS_KEY: 'amzn.mws.986478f0-9775-eabc-2af4-e499a8496828' +` + rule := NewLeaksRegularAWSMWSKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `AWS_WMS_KEY: 'amzn.mws.986478f0-9775-eabc-2af4-e499a8496828'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 20, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularAWSMWSKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + WMS_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularAWSMWSKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularFacebookSecretKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularFacebookSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + FB_SECRET_KEY: 'cb6f53505911332d30867f44a1c1b9b5' +` + rule := NewLeaksRegularFacebookSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `FB_SECRET_KEY: 'cb6f53505911332d30867f44a1c1b9b5'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularFacebookSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + FB_SECRET_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularFacebookSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularFacebookClientID(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularFacebookClientID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + FB_CLIENT_ID: '148695999071979' +` + rule := NewLeaksRegularFacebookClientID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `FB_CLIENT_ID: '148695999071979'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularFacebookClientID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + FB_CLIENT_ID: ${SECRET_KEY} +` + rule := NewLeaksRegularFacebookClientID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularTwitterClientID(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularTwitterClientID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + TWITTER_CLIENT_ID: '1h6433fsvygnyre5a40' +` + rule := NewLeaksRegularTwitterClientID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `TWITTER_CLIENT_ID: '1h6433fsvygnyre5a40'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularTwitterClientID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + TWITTER_CLIENT_ID: ${SECRET_KEY} +` + rule := NewLeaksRegularTwitterClientID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularTwitterSecretKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularTwitterSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + TWITTER_SECRET_KEY: 'ej64cqk9k8px9ae3e47ip89l7if58tqhpxi1r' +` + rule := NewLeaksRegularTwitterSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `TWITTER_SECRET_KEY: 'ej64cqk9k8px9ae3e47ip89l7if58tqhpxi1r'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularTwitterSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + TWITTER_SECRET_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularTwitterSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularGithub(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularGithub", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + GITHUB_SECRET_KEY: 'edzvPbU3SYUc7pFc9le20lzIRErTOaxCABQ1' +` + rule := NewLeaksRegularGithub() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `GITHUB_SECRET_KEY: 'edzvPbU3SYUc7pFc9le20lzIRErTOaxCABQ1'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularGithub", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + GITHUB_SECRET_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularGithub() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularLinkedInClientID(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularLinkedInClientID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + LINKEDIN_CLIENT_ID: 'g309xttlaw25' +` + rule := NewLeaksRegularLinkedInClientID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `LINKEDIN_CLIENT_ID: 'g309xttlaw25'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularLinkedInClientID", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + LINKEDIN_CLIENT_ID: ${SECRET_KEY} +` + rule := NewLeaksRegularLinkedInClientID() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularLinkedInSecretKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularLinkedInSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + LINKEDIN_SECRET_KEY: '0d16kcnjyfzmcmjp' +` + rule := NewLeaksRegularLinkedInSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `LINKEDIN_SECRET_KEY: '0d16kcnjyfzmcmjp'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularLinkedInSecretKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + LINKEDIN_SECRET_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularLinkedInSecretKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularSlack(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularSlack", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SLACK_WEBHOOK: 'https://hooksWslackKcom/services/TNeqvYPeO/BncTJ74Hf/NlvFFKKAKPkd6h7FlQCz1Blu' +` + rule := NewLeaksRegularSlack() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `SLACK_WEBHOOK: 'https://hooksWslackKcom/services/TNeqvYPeO/BncTJ74Hf/NlvFFKKAKPkd6h7FlQCz1Blu'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 22, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularSlack", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SLACK_WEBHOOK: ${SECRET_KEY} +` + rule := NewLeaksRegularSlack() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularAsymmetricPrivateKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularAsymmetricPrivateKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SSH_PRIVATE_KEY: '-----BEGIN PRIVATE KEY-----MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBj08sp5++4anGcmQxJjAkBgNVBAoTHVByb2dyZXNzIFNvZnR3YXJlIENvcnBvcmF0aW9uMSAwHgYDVQQDDBcqLmF3cy10ZXN0LnByb2dyZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD...bml6YXRpb252YWxzaGEyZzIuY3JsMIGgBggrBgEFBQcBAQSBkzCBkDBNBggrBgEFBQcwAoZBaHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nvcmdhz3P668YfhUbKdRF6S42Cg6zn-----END PRIVATE KEY-----' +` + rule := NewLeaksRegularAsymmetricPrivateKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `SSH_PRIVATE_KEY: '-----BEGIN PRIVATE KEY-----MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBj08sp5++4anGcmQxJjAkBgNVBAoTHVByb2dyZXNzIFNvZnR3YXJlIENvcnBvcmF0aW9uMSAwHgYDVQQDDBcqLmF3cy10ZXN0LnByb2dyZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD...bml6YXRpb252YWxzaGEyZzIuY3JsMIGgBggrBgEFBQcBAQSBkzCBkDBNBggrBgEFBQcwAoZBaHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nvcmdhz3P668YfhUbKdRF6S42Cg6zn-----END PRIVATE KEY-----'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 24, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularAsymmetricPrivateKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SSH_PRIVATE_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularAsymmetricPrivateKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularGoogleAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularGoogleAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + GCP_API_KEY: 'AIzaMPZHYiu1RdzE1nG2SaVyOoz244TuacQIR6m' +` + rule := NewLeaksRegularGoogleAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `GCP_API_KEY: 'AIzaMPZHYiu1RdzE1nG2SaVyOoz244TuacQIR6m'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 20, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularGoogleAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + GCP_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularGoogleAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularGoogleGCPServiceAccount(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularGoogleGCPServiceAccount", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + GCP_SERVICE_ACCOUNT: '18256698220617903267772185514630273595-oy8_uzouz8tyy46y84ckrwei9_6rq_pb.apps.googleusercontent.com' +` + rule := NewLeaksRegularGoogleGCPServiceAccount() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `GCP_SERVICE_ACCOUNT: '18256698220617903267772185514630273595-oy8_uzouz8tyy46y84ckrwei9_6rq_pb.apps.googleusercontent.com'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularGoogleGCPServiceAccount", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + GCP_SERVICE_ACCOUNT: ${SECRET_KEY} +` + rule := NewLeaksRegularGoogleGCPServiceAccount() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularHerokuAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularHerokuAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + HEROKU_API_KEY: '3623f8e9-2d05-c9bb-2209082d6b5c' +` + rule := NewLeaksRegularHerokuAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `HEROKU_API_KEY: '3623f8e9-2d05-c9bb-2209082d6b5c'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularHerokuAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + HEROKU_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularHerokuAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularMailChimpAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularMailChimpAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + MAILCHIMP_API_KEY: 'f7e9c13c10d0b19c3bb003a9f635d488-us72' +` + rule := NewLeaksRegularMailChimpAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `MAILCHIMP_API_KEY: 'f7e9c13c10d0b19c3bb003a9f635d488-us72'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularMailChimpAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + MAILCHIMP_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularMailChimpAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularMailgunAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularMailgunAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + MAILGUN_API_KEY: 'key-xke9nbc2i5po5cjw3ngyxiz450zxpapu' +` + rule := NewLeaksRegularMailgunAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `MAILGUN_API_KEY: 'key-xke9nbc2i5po5cjw3ngyxiz450zxpapu'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularMailgunAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + MAILGUN_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularMailgunAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularPayPalBraintreeAccessToken(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularPayPalBraintreeAccessToken", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + PAY_PAL_ACCESS_TOKEN: 'access_token$production$mk0sech2v7qqsol3$db651af2221c22b4ca2f0f583798135e' +` + rule := NewLeaksRegularPayPalBraintreeAccessToken() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `PAY_PAL_ACCESS_TOKEN: 'access_token$production$mk0sech2v7qqsol3$db651af2221c22b4ca2f0f583798135e'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 29, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularPayPalBraintreeAccessToken", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + PAY_PAL_ACCESS_TOKEN: ${SECRET_KEY} +` + rule := NewLeaksRegularPayPalBraintreeAccessToken() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularPicaticAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularPicaticAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + PICATIC_API_KEY: 'sk_live_voy1p9k7r9g9j8ezmif488nk2p8310nl' +` + rule := NewLeaksRegularPicaticAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `PICATIC_API_KEY: 'sk_live_voy1p9k7r9g9j8ezmif488nk2p8310nl'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 24, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularPicaticAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + PICATIC_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularPicaticAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularSendGridAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularSendGridAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SEND_GRID_API_KEY: 'SG.44b7kq3FurdH0bSHBGjPSWhE8vJ.1evu4Un0TXFIb1_6zW4YOdjTMeE' +` + rule := NewLeaksRegularSendGridAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `SEND_GRID_API_KEY: 'SG.44b7kq3FurdH0bSHBGjPSWhE8vJ.1evu4Un0TXFIb1_6zW4YOdjTMeE'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 26, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularSendGridAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SEND_GRID_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularSendGridAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularStripeAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularStripeAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + STRIPE_API_KEY: 'rk_live_8qSZpoI9t0BOGkOLVzvesc6K' +` + rule := NewLeaksRegularStripeAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `STRIPE_API_KEY: 'rk_live_8qSZpoI9t0BOGkOLVzvesc6K'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularStripeAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + STRIPE_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularStripeAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularSquareAccessToken(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularSquareAccessToken", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SQUARE_ACCESS_TOKEN: 'sq0atp-clYRBSht6oefa7w_2R56ra' +` + rule := NewLeaksRegularSquareAccessToken() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `SQUARE_ACCESS_TOKEN: 'sq0atp-clYRBSht6oefa7w_2R56ra'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 28, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularSquareAccessToken", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SQUARE_ACCESS_TOKEN: ${SECRET_KEY} +` + rule := NewLeaksRegularSquareAccessToken() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularSquareOAuthSecret(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularSquareOAuthSecret", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SQUARE_SECRET: 'sq0csp-LsEBYQNja]OgT3hRxjJV5cWX^XjpT12n3QkRY_vep2z' +` + rule := NewLeaksRegularSquareOAuthSecret() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `SQUARE_SECRET: 'sq0csp-LsEBYQNja]OgT3hRxjJV5cWX^XjpT12n3QkRY_vep2z'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 22, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularSquareOAuthSecret", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + SQUARE_SECRET: ${SECRET_KEY} +` + rule := NewLeaksRegularSquareOAuthSecret() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularTwilioAPIKey(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularTwilioAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + TWILIO_API_KEY: '^SK9ae6bd84ccd091eb6bfad8e2a474af95' +` + rule := NewLeaksRegularTwilioAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `TWILIO_API_KEY: '^SK9ae6bd84ccd091eb6bfad8e2a474af95'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 6, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularTwilioAPIKey", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + TWILIO_API_KEY: ${SECRET_KEY} +` + rule := NewLeaksRegularTwilioAPIKey() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularHardCodedCredentialGeneric(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularHardCodedCredentialGeneric", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + POSTGRES_DBPASSWD: 'Ch@ng3m3' +` + rule := NewLeaksRegularHardCodedCredentialGeneric() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `POSTGRES_DBPASSWD: 'Ch@ng3m3'`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "deployments/docker-compose.yaml", + Line: 7, + Column: 15, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularHardCodedCredentialGeneric", func(t *testing.T) { + code := ` +version: '3' +services: + backend: + image: image/my-backend:latest + environment: + POSTGRES_DBPASSWD: ${SECRET_KEY} +` + rule := NewLeaksRegularHardCodedCredentialGeneric() + textFile, err := text.NewTextFile("deployments/docker-compose.yaml", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularHardCodedPassword(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularHardCodedPassword", func(t *testing.T) { + code := ` +package main + +import ( + "fmt" + "gorm.io/gorm" + "gorm.io/driver/postgres" +) + +func main() { + DB_USER="gorm" + DB_PASSWORD="gorm" + DB_NAME="gorm" + DB_PORT="9920" + dsn := fmt.Sprintf("user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", DB_USER, DB_PASSWORD, DB_NAME, DB_PORT) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + panic(err) + } + print(db) +} +` + rule := NewLeaksRegularHardCodedPassword() + textFile, err := text.NewTextFile("main.go", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `DB_PASSWORD="gorm"`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "main.go", + Line: 12, + Column: 4, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularHardCodedPassword", func(t *testing.T) { + code := ` +package main + +import ( + "os" + "fmt" + "gorm.io/gorm" + "gorm.io/driver/postgres" +) + +func main() { + DB_USER="gorm" + DB_PASSWORD=os.Getenv("DB_PASSWORD") + DB_NAME="gorm" + DB_PORT="9920" + dsn := fmt.Sprintf("user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", DB_USER, DB_PASSWORD, DB_NAME, DB_PORT) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + panic(err) + } + print(db) +} +` + rule := NewLeaksRegularHardCodedPassword() + textFile, err := text.NewTextFile("main.go", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularPasswordExposedInHardcodedURL(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularPasswordExposedInHardcodedURL", func(t *testing.T) { + code := ` +package main + +import ( + "gorm.io/gorm" + "gorm.io/driver/postgres" +) + +func main() { + dsn := "postgresql://gorm:gorm@127.0.0.1:5432/gorm?sslmode=disable" + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + panic(err) + } + print(db) +} +` + rule := NewLeaksRegularPasswordExposedInHardcodedURL() + textFile, err := text.NewTextFile("main.go", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 1) + assert.Equal(t, engine.Finding{ + ID: rule.ID, + Name: rule.Name, + Severity: rule.Severity, + CodeSample: `dsn := "postgresql://gorm:gorm@127.0.0.1:5432/gorm?sslmode=disable"`, + Confidence: rule.Confidence, + Description: rule.Description, + SourceLocation: engine.Location{ + Filename: "main.go", + Line: 10, + Column: 9, + }, + }, findings[0]) + }) + t.Run("Should not return vulnerable code NewLeaksRegularPasswordExposedInHardcodedURL", func(t *testing.T) { + code := ` +package main + +import ( + "os" + "gorm.io/gorm" + "gorm.io/driver/postgres" +) + +func main() { + dsn := os.Getenv("DB_QUERY_STRING") + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + panic(err) + } + print(db) +} +` + rule := NewLeaksRegularPasswordExposedInHardcodedURL() + textFile, err := text.NewTextFile("main.go", []byte(code)) + assert.NoError(t, err) + findings := engine.Run(parseTextUnitsToUnits([]text.TextUnit{{Files: []text.TextFile{textFile}}}), []engine.Rule{rule}) + assert.Len(t, findings, 0) + }) +} + +func TestNewLeaksRegularWPConfig(t *testing.T) { + t.Run("Should return vulnerable code NewLeaksRegularWPConfig", func(t *testing.T) { + code := ` ./horusec-cli/deployments/version-cli-latest.txt - aws s3 cp ./horusec-cli/deployments/version-cli-latest.txt s3://horusec-cli/version-cli-latest.txt - aws s3 cp "./horusec-cli/bin/horusec/$ACTUAL_RELEASE_FORMATTED" "s3://horusec-cli/latest" --recursive + aws s3 cp ./horusec-cli/deployments/version-cli-latest.txt s3://horusec.io/bin/version-cli-latest.txt + aws s3 cp "./horusec-cli/bin/horusec/$ACTUAL_RELEASE_FORMATTED" "s3://horusec.io/bin/latest" --recursive + docker build -t "horuszup/horusec-cli:latest" -f horusec-cli/deployments/Dockerfile . + docker push "horuszup/horusec-cli:latest" fi echo "$ACTUAL_RELEASE_FORMATTED" >> ./horusec-cli/deployments/all-version-cli.txt - aws s3 cp ./horusec-cli/deployments/all-version-cli.txt s3://horusec-cli/all-version-cli.txt + aws s3 cp ./horusec-cli/deployments/all-version-cli.txt s3://horusec.io/bin/all-version-cli.txt } resetAlphaRcToMaster () { diff --git a/horusec-cli/deployments/version-cli-latest.txt b/horusec-cli/deployments/version-cli-latest.txt index bb2fa5746..b85ad4e79 100644 --- a/horusec-cli/deployments/version-cli-latest.txt +++ b/horusec-cli/deployments/version-cli-latest.txt @@ -1 +1 @@ -v1-5-0 +v1-6-0 diff --git a/horusec-cli/internal/controllers/analyser/analyser.go b/horusec-cli/internal/controllers/analyser/analyser.go index 1dcf0f3ba..e0ab81b64 100644 --- a/horusec-cli/internal/controllers/analyser/analyser.go +++ b/horusec-cli/internal/controllers/analyser/analyser.go @@ -24,6 +24,9 @@ import ( "strings" "time" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/c/flawfinder" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/php/phpcs" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/csharp/horuseccsharp" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/javascript/horusecnodejs" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/yaml/horuseckubernetes" @@ -189,6 +192,8 @@ func (a *Analyser) mapDetectVulnerabilityByLanguage() map[languages.Language]fun languages.HCL: a.detectVulnerabilityHCL, languages.Generic: a.detectVulnerabilityGeneric, languages.Yaml: a.detectVulnerabilityYaml, + languages.C: a.detectVulnerabilityC, + languages.PHP: a.detectVulnerabilityPHP, } } @@ -253,6 +258,16 @@ func (a *Analyser) detectVulnerabilityYaml(projectSubPath string) { go horuseckubernetes.NewFormatter(a.formatterService).StartAnalysis(projectSubPath) } +func (a *Analyser) detectVulnerabilityC(projectSubPath string) { + a.monitor.AddProcess(1) + go flawfinder.NewFormatter(a.formatterService).StartAnalysis(projectSubPath) +} + +func (a *Analyser) detectVulnerabilityPHP(projectSubPath string) { + a.monitor.AddProcess(1) + go phpcs.NewFormatter(a.formatterService).StartAnalysis(projectSubPath) +} + func (a *Analyser) detectVulnerabilityGeneric(projectSubPath string) { a.monitor.AddProcess(1) go semgrep.NewFormatter(a.formatterService).StartAnalysis(projectSubPath) diff --git a/horusec-cli/internal/controllers/language_detect/language_detect.go b/horusec-cli/internal/controllers/language_detect/language_detect.go index 0f8242fc9..eab38d45f 100644 --- a/horusec-cli/internal/controllers/language_detect/language_detect.go +++ b/horusec-cli/internal/controllers/language_detect/language_detect.go @@ -205,18 +205,32 @@ func (ld *LanguageDetect) isSupportedLanguage(langName string) bool { func (ld *LanguageDetect) appendLanguagesFound(existingLanguages, languagesFound []string) []string { for _, lang := range languagesFound { - if ld.isTypescriptOrJavascriptLang(lang) { - existingLanguages = append(existingLanguages, languages.Javascript.ToString()) - } else { - existingLanguages = append(existingLanguages, lang) - } + existingLanguages = ld.updateExistingLanguages(lang, existingLanguages) } + return ld.uniqueLanguages(existingLanguages) } +func (ld *LanguageDetect) updateExistingLanguages(lang string, existingLanguages []string) []string { + if ld.isTypescriptOrJavascriptLang(lang) { + return append(existingLanguages, languages.Javascript.ToString()) + } + + if ld.isCPlusPLusOrCLang(lang) { + return append(existingLanguages, languages.C.ToString()) + } + + return append(existingLanguages, lang) +} + func (ld *LanguageDetect) isTypescriptOrJavascriptLang(lang string) bool { return strings.EqualFold(lang, languages.Javascript.ToString()) || strings.EqualFold(lang, languages.TypeScript.ToString()) || strings.EqualFold(lang, "TSX") || strings.EqualFold(lang, "JSX") } + +func (ld *LanguageDetect) isCPlusPLusOrCLang(lang string) bool { + return strings.EqualFold(lang, "C++") || + strings.EqualFold(lang, "C") +} diff --git a/horusec-cli/internal/controllers/printresults/print_results.go b/horusec-cli/internal/controllers/printresults/print_results.go index 0490fc736..75b75bfa8 100644 --- a/horusec-cli/internal/controllers/printresults/print_results.go +++ b/horusec-cli/internal/controllers/printresults/print_results.go @@ -238,7 +238,7 @@ func (pr *PrintResults) printTextOutputVulnerabilityData(vulnerability *horusecE fmt.Println(fmt.Sprintf("Column: %s", vulnerability.Column)) fmt.Println(fmt.Sprintf("SecurityTool: %s", vulnerability.SecurityTool)) fmt.Println(fmt.Sprintf("Confidence: %s", vulnerability.Confidence)) - fmt.Println(fmt.Sprintf("File: %s", vulnerability.File)) + fmt.Println(fmt.Sprintf("File: %s/%s", pr.configs.GetProjectPath(), vulnerability.File)) fmt.Println(fmt.Sprintf("Code: %s", vulnerability.Code)) fmt.Println(fmt.Sprintf("Details: %s", vulnerability.Details)) fmt.Println(fmt.Sprintf("Type: %s", vulnerability.Type)) diff --git a/horusec-cli/internal/services/formatters/c/flawfinder/config.go b/horusec-cli/internal/services/formatters/c/flawfinder/config.go new file mode 100644 index 000000000..62abbbd72 --- /dev/null +++ b/horusec-cli/internal/services/formatters/c/flawfinder/config.go @@ -0,0 +1,26 @@ +// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +// +// 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 flawfinder + +const ( + ImageName = "horuszup/horusec-flawfinder" + ImageTag = "v1.0.0" + // nolint + ImageCmd = ` + flawfinder --columns --singleline --dataonly --context --csv . > /tmp/result-ANALYSISID.json + cat /tmp/result-ANALYSISID.json + chmod -R 777 . + ` +) diff --git a/horusec-cli/internal/services/formatters/c/flawfinder/formatter.go b/horusec-cli/internal/services/formatters/c/flawfinder/formatter.go new file mode 100644 index 000000000..98ce09f1a --- /dev/null +++ b/horusec-cli/internal/services/formatters/c/flawfinder/formatter.go @@ -0,0 +1,131 @@ +// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +// +// 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 flawfinder + +import ( + "fmt" + "github.com/ZupIT/horusec/development-kit/pkg/entities/analyser/c" + "github.com/ZupIT/horusec/development-kit/pkg/entities/horusec" + "github.com/ZupIT/horusec/development-kit/pkg/enums/languages" + "github.com/ZupIT/horusec/development-kit/pkg/enums/tools" + fileUtil "github.com/ZupIT/horusec/development-kit/pkg/utils/file" + "github.com/ZupIT/horusec/development-kit/pkg/utils/logger" + vulnhash "github.com/ZupIT/horusec/development-kit/pkg/utils/vuln_hash" + dockerEntities "github.com/ZupIT/horusec/horusec-cli/internal/entities/docker" + "github.com/ZupIT/horusec/horusec-cli/internal/helpers/messages" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters" + "github.com/gocarina/gocsv" +) + +type Formatter struct { + formatters.IService +} + +func NewFormatter(service formatters.IService) formatters.IFormatter { + return &Formatter{ + service, + } +} + +func (f *Formatter) StartAnalysis(projectSubPath string) { + if f.ToolIsToIgnore(tools.Flawfinder) { + logger.LogDebugWithLevel(messages.MsgDebugToolIgnored+tools.Flawfinder.ToString(), logger.DebugLevel) + return + } + + err := f.startFlawFinder(projectSubPath) + f.SetLanguageIsFinished() + f.LogAnalysisError(err, tools.Flawfinder, projectSubPath) +} + +func (f *Formatter) startFlawFinder(projectSubPath string) error { + f.LogDebugWithReplace(messages.MsgDebugToolStartAnalysis, tools.Flawfinder) + + output, err := f.ExecuteContainer(f.getConfigData(projectSubPath)) + if err != nil { + f.SetAnalysisError(err) + return err + } + + f.LogDebugWithReplace(messages.MsgDebugToolFinishAnalysis, tools.Flawfinder) + return f.parseOutput(output) +} + +func (f *Formatter) getConfigData(projectSubPath string) *dockerEntities.AnalysisData { + return &dockerEntities.AnalysisData{ + Image: ImageName, + Tag: ImageTag, + CMD: f.AddWorkDirInCmd(ImageCmd, projectSubPath, tools.Flawfinder), + Language: languages.C, + } +} + +func (f *Formatter) parseOutput(output string) error { + var results []c.Result + + if err := gocsv.UnmarshalString(output, &results); err != nil { + f.SetAnalysisError(fmt.Errorf("{HORUSEC_CLI} Error %s", output)) + return err + } + + f.appendResults(results) + return nil +} + +func (f *Formatter) appendResults(results []c.Result) { + for index := range results { + f.GetAnalysis().AnalysisVulnerabilities = append(f.GetAnalysis().AnalysisVulnerabilities, + horusec.AnalysisVulnerabilities{ + Vulnerability: *f.setVulnerabilityData(results, index), + }) + } +} + +func (f *Formatter) setVulnerabilityData(results []c.Result, index int) *horusec.Vulnerability { + vulnerability := f.getDefaultVulnerabilitySeverity() + vulnerability.Severity = results[index].GetSeverity() + vulnerability.Details = results[index].GetDetails() + vulnerability.Line = results[index].Line + vulnerability.Column = results[index].Column + vulnerability.Code = f.GetCodeWithMaxCharacters(results[index].Context, 0) + vulnerability.File = results[index].GetFilename() + vulnerability = vulnhash.Bind(vulnerability) + + return f.setCommitAuthor(vulnerability) +} + +func (f *Formatter) setCommitAuthor(vulnerability *horusec.Vulnerability) *horusec.Vulnerability { + commitAuthor := f.GetCommitAuthor(vulnerability.Line, f.getFilePathFromPackageName(vulnerability.File)) + + vulnerability.CommitAuthor = commitAuthor.Author + vulnerability.CommitHash = commitAuthor.CommitHash + vulnerability.CommitDate = commitAuthor.Date + vulnerability.CommitEmail = commitAuthor.Email + vulnerability.CommitMessage = commitAuthor.Message + + return vulnerability +} + +func (f *Formatter) getDefaultVulnerabilitySeverity() *horusec.Vulnerability { + vulnerabilitySeverity := &horusec.Vulnerability{} + vulnerabilitySeverity.SecurityTool = tools.Flawfinder + vulnerabilitySeverity.Language = languages.C + return vulnerabilitySeverity +} + +func (f *Formatter) getFilePathFromPackageName(filePath string) string { + return fileUtil.GetPathIntoFilename(filePath, + fmt.Sprintf("%s/", f.GetConfigProjectPath())) +} diff --git a/horusec-cli/internal/services/formatters/c/flawfinder/formatter_test.go b/horusec-cli/internal/services/formatters/c/flawfinder/formatter_test.go new file mode 100644 index 000000000..59597644f --- /dev/null +++ b/horusec-cli/internal/services/formatters/c/flawfinder/formatter_test.go @@ -0,0 +1,120 @@ +// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +// +// 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 flawfinder + +import ( + "bytes" + "encoding/csv" + "errors" + "github.com/ZupIT/horusec/development-kit/pkg/entities/horusec" + cliConfig "github.com/ZupIT/horusec/horusec-cli/config" + "github.com/ZupIT/horusec/horusec-cli/internal/entities/workdir" + "github.com/ZupIT/horusec/horusec-cli/internal/services/docker" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters" + "github.com/stretchr/testify/assert" + "testing" +) + +func getCsvString() string { + pairs := [][]string{ + {"File", "./test.c"}, + {"Line", "16"}, + {"Column", "9"}, + {"Level", "5"}, + {"Category", "4"}, + {"Name", "test"}, + {"Warning", "test"}, + {"Suggestion", "test"}, + {"Note", "test"}, + {"CWEs", "test"}, + {"Context", "test"}, + {"Fingerprint", "test"}, + } + + buffer := new(bytes.Buffer) + writer := csv.NewWriter(buffer) + + _ = writer.WriteAll(pairs) + return buffer.String() +} + +func TestStartCFlawfinder(t *testing.T) { + t.Run("should success execute container and process output", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := getCsvString() + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + + assert.NotEmpty(t, analysis) + assert.Len(t, analysis.AnalysisVulnerabilities, 11) + }) + + t.Run("should return error when invalid output", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := "" + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + assert.NotPanics(t, func() { + formatter.StartAnalysis("") + }) + }) + + t.Run("should return error when executing container", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("", errors.New("test")) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + assert.NotPanics(t, func() { + formatter.StartAnalysis("") + }) + }) + t.Run("Should not execute tool because it's ignored", func(t *testing.T) { + analysis := &horusec.Analysis{} + dockerAPIControllerMock := &docker.Mock{} + config := &cliConfig.Config{ + ToolsToIgnore: "gosec,securitycodescan,brakeman,safety,bandit,npmaudit,yarnaudit,spotbugs,horuseckotlin,horusecjava,horusecleaks,gitleaks,tfsec,semgrep,flawfinder", + } + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + }) +} diff --git a/horusec-cli/internal/services/formatters/leaks/horusecleaks/config.go b/horusec-cli/internal/services/formatters/leaks/horusecleaks/config.go index d8aed4f1a..1dcf457b7 100644 --- a/horusec-cli/internal/services/formatters/leaks/horusecleaks/config.go +++ b/horusec-cli/internal/services/formatters/leaks/horusecleaks/config.go @@ -16,7 +16,7 @@ package horusecleaks const ( ImageName = "horuszup/horusec-leaks" - ImageTag = "v0.2.6" + ImageTag = "v0.3.0" ImageCmd = ` {{WORK_DIR}} horusec-leaks run -o="./output-ANALYSISID.json" diff --git a/horusec-cli/internal/services/formatters/php/phpcs/config.go b/horusec-cli/internal/services/formatters/php/phpcs/config.go new file mode 100644 index 000000000..f5ae837fe --- /dev/null +++ b/horusec-cli/internal/services/formatters/php/phpcs/config.go @@ -0,0 +1,26 @@ +// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +// +// 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 phpcs + +const ( + ImageName = "horuszup/horusec-phpcs" + ImageTag = "v1.0.0" + // nolint + ImageCmd = ` + phpcs --report=json --standard=/vendor/pheromone/phpcs-security-audit/example_drupal7_ruleset.xml . > /tmp/result-ANALYSISID.json + cat /tmp/result-ANALYSISID.json + chmod -R 777 . + ` +) diff --git a/horusec-cli/internal/services/formatters/php/phpcs/formatter.go b/horusec-cli/internal/services/formatters/php/phpcs/formatter.go new file mode 100644 index 000000000..508fc7661 --- /dev/null +++ b/horusec-cli/internal/services/formatters/php/phpcs/formatter.go @@ -0,0 +1,149 @@ +// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +// +// 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 phpcs + +import ( + "encoding/json" + "fmt" + phpEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/analyser/php/phpcs" + "github.com/ZupIT/horusec/development-kit/pkg/entities/horusec" + "github.com/ZupIT/horusec/development-kit/pkg/enums/languages" + "github.com/ZupIT/horusec/development-kit/pkg/enums/severity" + "github.com/ZupIT/horusec/development-kit/pkg/enums/tools" + "github.com/ZupIT/horusec/development-kit/pkg/utils/logger" + vulnhash "github.com/ZupIT/horusec/development-kit/pkg/utils/vuln_hash" + dockerEntities "github.com/ZupIT/horusec/horusec-cli/internal/entities/docker" + "github.com/ZupIT/horusec/horusec-cli/internal/helpers/messages" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters" +) + +type Formatter struct { + formatters.IService +} + +func NewFormatter(service formatters.IService) formatters.IFormatter { + return &Formatter{ + service, + } +} + +func (f *Formatter) StartAnalysis(projectSubPath string) { + if f.ToolIsToIgnore(tools.PhpCS) { + logger.LogDebugWithLevel(messages.MsgDebugToolIgnored+tools.PhpCS.ToString(), logger.DebugLevel) + return + } + + err := f.startPhpCs(projectSubPath) + f.SetLanguageIsFinished() + f.LogAnalysisError(err, tools.PhpCS, projectSubPath) +} + +func (f *Formatter) startPhpCs(projectSubPath string) error { + f.LogDebugWithReplace(messages.MsgDebugToolStartAnalysis, tools.PhpCS) + + output, err := f.ExecuteContainer(f.getConfigData(projectSubPath)) + if err != nil { + f.SetAnalysisError(err) + return err + } + + f.LogDebugWithReplace(messages.MsgDebugToolFinishAnalysis, tools.PhpCS) + return f.parseOutput(output) +} + +func (f *Formatter) getConfigData(projectSubPath string) *dockerEntities.AnalysisData { + return &dockerEntities.AnalysisData{ + Image: ImageName, + Tag: ImageTag, + CMD: f.AddWorkDirInCmd(ImageCmd, projectSubPath, tools.PhpCS), + Language: languages.PHP, + } +} + +func (f *Formatter) parseOutput(output string) error { + var results map[string]interface{} + + if err := json.Unmarshal([]byte(output), &results); err != nil { + f.SetAnalysisError(fmt.Errorf("{HORUSEC_CLI} Error %s", output)) + return err + } + + f.parseResults(results) + return nil +} + +func (f *Formatter) parseResults(results map[string]interface{}) { + if results != nil { + files := results["files"] + for filepath, result := range files.(map[string]interface{}) { + f.parseMessages(filepath, result) + } + } +} + +func (f *Formatter) parseMessages(filepath string, result interface{}) { + for _, message := range f.parseToResult(result).Messages { + f.appendResults(filepath, message) + } +} + +func (f *Formatter) appendResults(filepath string, message phpEntities.Message) { + if message.IsValidMessage() { + f.GetAnalysis().AnalysisVulnerabilities = append(f.GetAnalysis().AnalysisVulnerabilities, + horusec.AnalysisVulnerabilities{ + Vulnerability: *f.setVulnerabilityData(filepath, message), + }) + } +} + +func (f *Formatter) setVulnerabilityData(filepath string, result phpEntities.Message) *horusec.Vulnerability { + vulnerability := f.getDefaultVulnerabilitySeverity() + vulnerability.Severity = severity.Info + vulnerability.Details = result.Message + vulnerability.Line = result.GetLine() + vulnerability.Column = result.GetColumn() + vulnerability.File = f.RemoveSrcFolderFromPath(filepath) + vulnerability = vulnhash.Bind(vulnerability) + + return f.setCommitAuthor(vulnerability) +} + +func (f *Formatter) setCommitAuthor(vulnerability *horusec.Vulnerability) *horusec.Vulnerability { + commitAuthor := f.GetCommitAuthor(vulnerability.Line, vulnerability.File) + + vulnerability.CommitAuthor = commitAuthor.Author + vulnerability.CommitHash = commitAuthor.CommitHash + vulnerability.CommitDate = commitAuthor.Date + vulnerability.CommitEmail = commitAuthor.Email + vulnerability.CommitMessage = commitAuthor.Message + + return vulnerability +} + +func (f *Formatter) getDefaultVulnerabilitySeverity() *horusec.Vulnerability { + vulnerabilitySeverity := &horusec.Vulnerability{} + vulnerabilitySeverity.SecurityTool = tools.PhpCS + vulnerabilitySeverity.Language = languages.PHP + return vulnerabilitySeverity +} + +func (f *Formatter) parseToResult(messageInterface interface{}) *phpEntities.Result { + var result *phpEntities.Result + + bytes, _ := json.Marshal(messageInterface) + _ = json.Unmarshal(bytes, &result) + + return result +} diff --git a/horusec-cli/internal/services/formatters/php/phpcs/formatter_test.go b/horusec-cli/internal/services/formatters/php/phpcs/formatter_test.go new file mode 100644 index 000000000..46f14bd30 --- /dev/null +++ b/horusec-cli/internal/services/formatters/php/phpcs/formatter_test.go @@ -0,0 +1,95 @@ +// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA +// +// 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 phpcs + +import ( + "errors" + "github.com/ZupIT/horusec/development-kit/pkg/entities/horusec" + cliConfig "github.com/ZupIT/horusec/horusec-cli/config" + "github.com/ZupIT/horusec/horusec-cli/internal/entities/workdir" + "github.com/ZupIT/horusec/horusec-cli/internal/services/docker" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStartCFlawfinder(t *testing.T) { + t.Run("should success execute container and process output", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := "{ \"files\":{ \"\\/src\\/XSS\\/XSS_level5.php\":{ \"errors\":1, \"warnings\":4, \"messages\":[ { \"message\":\"User input detetected with $_SERVER.\", \"source\":\"PHPCS_SecurityAudit.Drupal7.UserInputWatch.D7UserInWaWarn\", \"severity\":5, \"fixable\":false, \"type\":\"WARNING\", \"line\":14, \"column\":39 }, { \"message\":\"Easy XSS detected because of direct user input with $_SERVER on echo\", \"source\":\"PHPCS_SecurityAudit.BadFunctions.EasyXSS.EasyXSSerr\", \"severity\":5, \"fixable\":false, \"type\":\"ERROR\", \"line\":14, \"column\":39 } ] } } }" + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + + assert.NotEmpty(t, analysis) + assert.Len(t, analysis.AnalysisVulnerabilities, 1) + }) + + t.Run("should return error when invalid output", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := "" + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + assert.NotPanics(t, func() { + formatter.StartAnalysis("") + }) + }) + + t.Run("should return error when executing container", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("", errors.New("test")) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + assert.NotPanics(t, func() { + formatter.StartAnalysis("") + }) + }) + t.Run("Should not execute tool because it's ignored", func(t *testing.T) { + analysis := &horusec.Analysis{} + dockerAPIControllerMock := &docker.Mock{} + config := &cliConfig.Config{ + ToolsToIgnore: "gosec,securitycodescan,brakeman,safety,bandit,npmaudit,yarnaudit,spotbugs,horuseckotlin,horusecjava,horusecleaks,gitleaks,tfsec,semgrep,flawfinder,phpcs", + } + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + }) +} diff --git a/horusec-cli/internal/services/horusapi/horus_api.go b/horusec-cli/internal/services/horusapi/horus_api.go index db13a4372..ce5267e3a 100644 --- a/horusec-cli/internal/services/horusapi/horus_api.go +++ b/horusec-cli/internal/services/horusapi/horus_api.go @@ -19,11 +19,12 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "github.com/ZupIT/horusec/development-kit/pkg/entities/api" - "github.com/google/uuid" "io/ioutil" "net/http" + "github.com/ZupIT/horusec/development-kit/pkg/entities/api" + "github.com/google/uuid" + "github.com/ZupIT/horusec/development-kit/pkg/entities/horusec" "github.com/ZupIT/horusec/development-kit/pkg/utils/http-request/client" httpResponse "github.com/ZupIT/horusec/development-kit/pkg/utils/http-request/response" @@ -90,7 +91,7 @@ func (s *Service) sendFindAnalysisRequest(analysisID uuid.UUID) (httpResponse.In return nil, err } - req.Header.Add("Authorization", s.config.GetRepositoryAuthorization()) + s.addHeaders(req) return s.httpUtil.DoRequest(req, tlsConfig) } @@ -105,7 +106,7 @@ func (s *Service) sendCreateAnalysisRequest(analysis *horusec.Analysis) (httpRes return nil, err } - req.Header.Add("Authorization", s.config.GetRepositoryAuthorization()) + s.addHeaders(req) return s.httpUtil.DoRequest(req, tlsConfig) } @@ -171,3 +172,10 @@ func (s *Service) newRequestData(analysis *horusec.Analysis) []byte { return analysisData.ToBytes() } + +func (s *Service) addHeaders(req *http.Request) { + req.Header.Add("X-Horusec-Authorization", s.config.GetRepositoryAuthorization()) + for key, value := range s.config.GetHeaders() { + req.Header.Add(key, value) + } +} diff --git a/horusec-cli/internal/services/horusapi/horus_api_test.go b/horusec-cli/internal/services/horusapi/horus_api_test.go index 54b65f816..309ed48d8 100644 --- a/horusec-cli/internal/services/horusapi/horus_api_test.go +++ b/horusec-cli/internal/services/horusapi/horus_api_test.go @@ -47,7 +47,7 @@ func TestSendAnalysis(t *testing.T) { service := Service{ httpUtil: httpMock, - config: &cliConfig.Config{RepositoryAuthorization: "test"}, + config: &cliConfig.Config{RepositoryAuthorization: "test", Headers: `{"some-header": "some-value"}`}, } assert.NotPanics(t, func() { diff --git a/horusec-config.json b/horusec-config.json index 67bdff4d5..15b01a905 100644 --- a/horusec-config.json +++ b/horusec-config.json @@ -10,8 +10,8 @@ "horusecCliFilesOrPathsToIgnore": "**/e2e/**, **/examples/**, **/*.toml, **/*_test.go, **/*_mock.go, **/*README.md, **/development-kit/pkg/enums/engine/advisories/**, **/horusec-lp/.cache/**, **/horusec-lp/public/**, **/deployments/docker-compose*, **/horusec-cli/cmd/horusec/start/analysis/*, **/horusec-manager/src/helpers/localStorage/**", "horusecCliReturnErrorIfFoundVulnerability": false, "horusecCliProjectPath": "./", - "horusecCliFalsePositiveHashes": "b17a7ef9ebf374c594700c1bfbf9d3594de68f0fc23af6171269f1f629a8abcf, 0870cfa59cfe7ef087e45762ce1d66cb6fdc85196323fcfae2e90167f242e4b3, 3198f2595d15ba1a01174329c944e3af9f7b3a7af7914e857eb9b82684633236, b85977d0bc430b00f17bc9f431d70b272110afea2549ee41bf03369bf99572d2, 312a4ee6b6b74a8c667e6f907568d9feaa0f0a69091f322abbcdf0562b9a3914, a452ff6d2565d67f118c80866a38a25871606751421347211b8932eb55aed85b, 3a0c53aae9a54d01e97417f2e495ca74c1f8874b0e4805fc622e50f7633838a8, 6a669b83a533c64fccc304fd7dbf3d491e85ec0a852f0d154fe813704765da8e, 4d4cc9d51d9c049b4a7bcedd330445d54bab832a8760543ebf990e86c17fdca0, 4868ef9970205c00a1c48b0e3e0debccf5a31e2e68767e25bd1d6bab36966822, f4f5d429ea2a8cbed3813ee58bac655162c65e50c64f68b65de6cfe408ab91c1, 8fd7c193876b7a7d26936c897e15135693d26f55b3c7c91bbdd5024f8ce12f81, 87956a8852a3d1b4b904b48815904ecd8f8932cd577d3a109e4139b3b5955e2c", - "horusecCliRiskAcceptHashes": "45aa5c46df5ba51d7e59da826544412352c189a6acf5707f941922181c94f989, ba56b6e4ac8f790026b82a488c5624d7e2d6f6dd60584a9375c3c8948b608dbf, 2ce87bddc40e085562618f441750eeefe3cffc79d0b05b2e07a98f644c55b2c5, e2eaa19612eed0124b1fec396f8d41381c618c677c2025fc07c1cd0ccbe92b3c, 0ffc51a6b0187bec02837cb1e8dddfa05519e83d861af3fbd553bc4d0fbe852d, 4294bf00b848d82c4e012f45e0747996eb75109e089a626af930580a7a179ea4, fa41e0534388707279458969d1dcdb58ff932357660e8855d2bb4170fdbcb391, 5114704fb26983f549c5f179a0a90d8e95c8db28f9e68d32d864b2a6743cb499, 068f660ece48b7ef680152a6553d6f231413712b5d228f68f6d70a4c10e2b00b, a438599f015899ab0ce0bc9030915a59629f978127e2eac2a6ea7caec974c8d6, ab8d60c9c0796a0d528faf6d0fcb8693388a7182f903046a4aa54ffd81e080c7, 000467d49617182841ba72f0b63ddd396e350356542342d60b957835e943fcdf, d057a467e98c66fa437c62b11afc3fbecbab63e2331fdfe299bad0a011e46707, 4530caeaffd6d7fa42cf73240710b141a455547d75dd643ecbdbc6adf7c66037", + "horusecCliFalsePositiveHashes": "b17a7ef9ebf374c594700c1bfbf9d3594de68f0fc23af6171269f1f629a8abcf, 0870cfa59cfe7ef087e45762ce1d66cb6fdc85196323fcfae2e90167f242e4b3, 3198f2595d15ba1a01174329c944e3af9f7b3a7af7914e857eb9b82684633236, b85977d0bc430b00f17bc9f431d70b272110afea2549ee41bf03369bf99572d2, 312a4ee6b6b74a8c667e6f907568d9feaa0f0a69091f322abbcdf0562b9a3914, a452ff6d2565d67f118c80866a38a25871606751421347211b8932eb55aed85b, 3a0c53aae9a54d01e97417f2e495ca74c1f8874b0e4805fc622e50f7633838a8, 6a669b83a533c64fccc304fd7dbf3d491e85ec0a852f0d154fe813704765da8e, 4d4cc9d51d9c049b4a7bcedd330445d54bab832a8760543ebf990e86c17fdca0, 4868ef9970205c00a1c48b0e3e0debccf5a31e2e68767e25bd1d6bab36966822, f4f5d429ea2a8cbed3813ee58bac655162c65e50c64f68b65de6cfe408ab91c1, 8fd7c193876b7a7d26936c897e15135693d26f55b3c7c91bbdd5024f8ce12f81, 87956a8852a3d1b4b904b48815904ecd8f8932cd577d3a109e4139b3b5955e2c, 020f321ecd79df4cde2d93a06e4e5ba711a315060d4a9ee167de518606ee853f", + "horusecCliRiskAcceptHashes": "ba56b6e4ac8f790026b82a488c5624d7e2d6f6dd60584a9375c3c8948b608dbf, 2ce87bddc40e085562618f441750eeefe3cffc79d0b05b2e07a98f644c55b2c5, e2eaa19612eed0124b1fec396f8d41381c618c677c2025fc07c1cd0ccbe92b3c, 0ffc51a6b0187bec02837cb1e8dddfa05519e83d861af3fbd553bc4d0fbe852d, 4294bf00b848d82c4e012f45e0747996eb75109e089a626af930580a7a179ea4, fa41e0534388707279458969d1dcdb58ff932357660e8855d2bb4170fdbcb391, 5114704fb26983f549c5f179a0a90d8e95c8db28f9e68d32d864b2a6743cb499, 068f660ece48b7ef680152a6553d6f231413712b5d228f68f6d70a4c10e2b00b, a438599f015899ab0ce0bc9030915a59629f978127e2eac2a6ea7caec974c8d6, ab8d60c9c0796a0d528faf6d0fcb8693388a7182f903046a4aa54ffd81e080c7, 000467d49617182841ba72f0b63ddd396e350356542342d60b957835e943fcdf, d057a467e98c66fa437c62b11afc3fbecbab63e2331fdfe299bad0a011e46707, 4530caeaffd6d7fa42cf73240710b141a455547d75dd643ecbdbc6adf7c66037, 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ, 1e08930833e061f7e2e84d3a6fd51c25dd8528f06cad26828d7b782bf04caad3", "horusecCliWorkDir": { "go": [], "csharp": [], diff --git a/horusec-leaks/.semver.yaml b/horusec-leaks/.semver.yaml index 7a248d181..de73c0435 100644 --- a/horusec-leaks/.semver.yaml +++ b/horusec-leaks/.semver.yaml @@ -1,4 +1,4 @@ alpha: 0 beta: 0 rc: 0 -release: v0.2.6 +release: v0.3.0 diff --git a/horusec-manager/.semver.yaml b/horusec-manager/.semver.yaml index 661ea3c03..09212016b 100644 --- a/horusec-manager/.semver.yaml +++ b/horusec-manager/.semver.yaml @@ -1,4 +1,4 @@ alpha: 0 beta: 0 rc: 0 -release: v1.3.0 +release: v1.4.0 diff --git a/horusec-manager/src/assets/svg/config.svg b/horusec-manager/src/assets/svg/config.svg new file mode 100644 index 000000000..9b6d4eec2 --- /dev/null +++ b/horusec-manager/src/assets/svg/config.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/horusec-manager/src/assets/svg/delete.svg b/horusec-manager/src/assets/svg/delete.svg index 1940055d1..8a2c7e816 100644 --- a/horusec-manager/src/assets/svg/delete.svg +++ b/horusec-manager/src/assets/svg/delete.svg @@ -1,2 +1,2 @@ - + diff --git a/horusec-manager/src/assets/svg/edit.svg b/horusec-manager/src/assets/svg/edit.svg index fe46c1a65..5eb94754f 100644 --- a/horusec-manager/src/assets/svg/edit.svg +++ b/horusec-manager/src/assets/svg/edit.svg @@ -3,8 +3,8 @@ - - + + diff --git a/horusec-manager/src/assets/svg/list.svg b/horusec-manager/src/assets/svg/list.svg index 57d32a093..b4eae0f8c 100644 --- a/horusec-manager/src/assets/svg/list.svg +++ b/horusec-manager/src/assets/svg/list.svg @@ -1,7 +1,7 @@ - + diff --git a/horusec-manager/src/assets/svg/no-view.svg b/horusec-manager/src/assets/svg/no-view.svg index 4537b2df5..17f16b7ec 100644 --- a/horusec-manager/src/assets/svg/no-view.svg +++ b/horusec-manager/src/assets/svg/no-view.svg @@ -1,6 +1,5 @@ - diff --git a/horusec-manager/src/assets/svg/plus.svg b/horusec-manager/src/assets/svg/plus.svg index e2363fa27..c2e6f8df5 100644 --- a/horusec-manager/src/assets/svg/plus.svg +++ b/horusec-manager/src/assets/svg/plus.svg @@ -1,2 +1,2 @@ - + diff --git a/horusec-manager/src/assets/svg/view.svg b/horusec-manager/src/assets/svg/view.svg index cad344c08..777dc3f74 100644 --- a/horusec-manager/src/assets/svg/view.svg +++ b/horusec-manager/src/assets/svg/view.svg @@ -1,6 +1,5 @@ - diff --git a/horusec-manager/src/assets/svg/webhook.svg b/horusec-manager/src/assets/svg/webhook.svg new file mode 100644 index 000000000..2cc7f9b2f --- /dev/null +++ b/horusec-manager/src/assets/svg/webhook.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/horusec-manager/src/components/SideMenu/index.tsx b/horusec-manager/src/components/SideMenu/index.tsx index 68968462e..b7fd7b588 100644 --- a/horusec-manager/src/components/SideMenu/index.tsx +++ b/horusec-manager/src/components/SideMenu/index.tsx @@ -28,10 +28,13 @@ import { clearCurrentCompany, } from 'helpers/localStorage/currentCompany'; import ReactTooltip from 'react-tooltip'; +import { getCurrentConfig } from 'helpers/localStorage/horusecConfig'; +import { authTypes } from 'helpers/enums/authTypes'; const SideMenu: React.FC = () => { const history = useHistory(); const { t } = useTranslation(); + const { authType, disabledBroker } = getCurrentConfig(); const [selectedRoute, setSelectedRoute] = useState(); const [selectedSubRoute, setSelectedSubRoute] = useState(); @@ -83,10 +86,11 @@ const SideMenu: React.FC = () => { }, { name: t('SIDE_MENU.WEBHOOK'), - icon: 'link', + icon: 'webhook', path: '/home/webhooks', type: 'route', roles: ['admin'], + rule: () => !disabledBroker, }, ]; @@ -116,17 +120,19 @@ const SideMenu: React.FC = () => { const renderRoute = (route: InternalRoute, index: number) => { if (route.roles.includes(userRoleInCurrentCompany())) { - return ( - handleSelectedRoute(route)} - > - - - {route.name} - - ); + if (!route?.rule || (route?.rule && route?.rule())) { + return ( + handleSelectedRoute(route)} + > + + + {route.name} + + ); + } } }; @@ -135,6 +141,12 @@ const SideMenu: React.FC = () => { history.replace('/organization'); }; + const goToSettings = () => { + history.replace('/home/settings'); + setSelectedRoute(null); + setSelectedSubRoute(null); + }; + const fetchSubRoutes = () => find(routes, { path: selectedRoute?.path })?.subRoutes || []; @@ -166,9 +178,17 @@ const SideMenu: React.FC = () => { - - - + ) : null} + + { onClick={backToOrganization} /> + + diff --git a/horusec-manager/src/components/SideMenu/styled.ts b/horusec-manager/src/components/SideMenu/styled.ts index 42f1b90c9..a58b142eb 100644 --- a/horusec-manager/src/components/SideMenu/styled.ts +++ b/horusec-manager/src/components/SideMenu/styled.ts @@ -120,10 +120,10 @@ const SubMenu = styled.div` `}; `; -const Back = styled(Icon)` - margin: 20px 0px 0px 17px; +const Option = styled(Icon)` width: 30px; cursor: pointer; + margin: 0 0 20px 15px; `; export default { @@ -137,5 +137,5 @@ export default { SubMenu, SubRoutesList, SubRouteItem, - Back, + Option, }; diff --git a/horusec-manager/src/config/axios/default.ts b/horusec-manager/src/config/axios/default.ts index 8652f3fba..555c56755 100644 --- a/horusec-manager/src/config/axios/default.ts +++ b/horusec-manager/src/config/axios/default.ts @@ -46,7 +46,7 @@ instance.interceptors.request.use(async (config: AxiosRequestConfig) => { const accessToken = getAccessToken(); if (accessToken) { - config.headers.common['Authorization'] = `Bearer ${accessToken}`; + config.headers.common['X-Horusec-Authorization'] = `Bearer ${accessToken}`; } return config; diff --git a/horusec-manager/src/config/axios/forceRenewToken.ts b/horusec-manager/src/config/axios/forceRenewToken.ts index 122de0392..3f4989c8a 100644 --- a/horusec-manager/src/config/axios/forceRenewToken.ts +++ b/horusec-manager/src/config/axios/forceRenewToken.ts @@ -29,7 +29,7 @@ instance.interceptors.request.use(async (config: AxiosRequestConfig) => { const accessToken = getAccessToken(); if (accessToken) { - config.headers.common['Authorization'] = `Bearer ${accessToken}`; + config.headers.common['X-Horusec-Authorization'] = `Bearer ${accessToken}`; } return config; diff --git a/horusec-manager/src/config/i18n/enUS.json b/horusec-manager/src/config/i18n/enUS.json index 3b7ed6e35..d4feb0f25 100644 --- a/horusec-manager/src/config/i18n/enUS.json +++ b/horusec-manager/src/config/i18n/enUS.json @@ -63,7 +63,9 @@ "INVALID_CONFIRM_PASS": "Passwords are not the same.", "SUBMIT": "Register", "CONFIRM": "Ok, I got it.", - "SUCCESS_CREATE_ACCOUNT": "An e-mail has been sent to verify your account." + "SUCCESS_CREATE_ACCOUNT": "Your Horusec account has been successfully created!", + "SUCCESS_CREATE_ACCOUNT_WITH_CONFIRM": "An e-mail has been sent to verify your account.", + "OLD_PASS": "Old Password" }, "COMPANY_SCREEN": { @@ -291,8 +293,10 @@ "WEBHOOK_SCREEN": { "TITLE": "Webhooks", - "ADD": "Create webhook", + "ADD": "Add webhook", + "EDIT": "Edit webhook", "SAVE": "Save", + "SEARCH": "Search by URL", "DESCRIPTION_LABEL": "Enter the description for the webhook:", "DESCRIPTION": "Description", "RESPOSITORY_LABEL": "Select the repository that will trigger the webhook:", @@ -305,22 +309,51 @@ "SUCCESS_DELETE": "Webhook successfully deleted.", "SUCCESS_UPDATE": "Webhook successfully updated.", "CONFIRM_DELETE": "Do you really want to delete this webhook? This action cannot be undone.", - "YES": "Sim", + "YES": "Yes", "HEADERS_LABEL": "Headers", "KEY": "Key", "VALUE": "Value", "TABLE": { - "EMPTY": "There are no registered webhooks", + "EMPTY": "No registered webhooks were found", "METHOD": "Method", "URL": "URL", "REPOSITORY": "Repository", "DESCRIPTION": "Description", "ACTION": "Action", "DELETE": "Delete", - "EDIT": "Edit" + "EDIT": "Edit", + "COPY": "Copy" } }, + "SETTINGS_SCREEN": { + "TITLE": "Account configurations", + "TABLE": { + "USER": "User", + "EMAIL": "Email", + "ACTION": "Action", + "DELETE": "Delete", + "EDIT": "Edit", + "PASSWORD": "Password" + }, + "CONFIRM_DELETE": "Do you really want to delete your account This action cannot be undone.", + "YES": "Yes", + "EDIT_ACCOUNT": "Edit account", + "SAVE": "Save", + "EMAIL": "Email", + "NAME": "Username", + "INVALID_EMAIL":"Invalid e-mail", + "NEW_PASS": "New Password", + "CONFIRM_PASS": "Confirm password", + "CHANGE_PASS": "Change Password", + "INVALID_CONFIRM_PASS": "Passwords are not the same.", + "INVALID_PASS": "Enter password", + "EDIT_SUCCESS": "information successfully changed", + "SUCCESS_UPDATE": "A request to verify the new email has been sent to your email.", + "CONFIRM":"Ok, I got it.", + "SUCCESS_DELETE": "Your account has been successfully deleted!" + }, + "SIDE_MENU": { "DASHBOARD": "Dashboard", "VULNERABILITIES": "Vulnerabilities", @@ -329,6 +362,7 @@ "REPOSITORY": "Repository", "ORGANIZATION_USERS": "Organization users", "BACK_ORGANIZATION": "Back to organizations", + "CONFIG": "Settings", "WEBHOOK": "Webhooks", "LOGOUT": "Logout" }, @@ -343,7 +377,8 @@ "ALREADY_IN_COMPANY": "This user is already part of this organization.", "PRIVILEGES": "You do not have sufficient permissions to perform this action.", "REPO_NAME_IN_USE": "Repository name already in use.", - "USERNAME_IN_USE": "Username already in use." + "USERNAME_IN_USE": "Username already in use.", + "SAME_PASSWORD": "The new password cannot be the same as the current one." }, "NOT_FOUND_SCREEN": { diff --git a/horusec-manager/src/config/i18n/ptBR.json b/horusec-manager/src/config/i18n/ptBR.json index 9565b9cb0..a326c9a4d 100644 --- a/horusec-manager/src/config/i18n/ptBR.json +++ b/horusec-manager/src/config/i18n/ptBR.json @@ -63,7 +63,9 @@ "INVALID_CONFIRM_PASS": "As senhas não são iguais.", "SUBMIT": "Cadastrar", "CONFIRM": "Ok, entendi.", - "SUCCESS_CREATE_ACCOUNT": "Foi enviado um email para verificação da sua conta." + "SUCCESS_CREATE_ACCOUNT": "Sua conta Horusec foi criada com sucesso!", + "SUCCESS_CREATE_ACCOUNT_WITH_CONFIRM": "Foi enviado um email para verificação da sua conta.", + "OLD_PASS": "Antiga senha" }, "COMPANY_SCREEN": { @@ -291,8 +293,10 @@ "WEBHOOK_SCREEN": { "TITLE": "Webhooks", - "ADD": "Criar webhook", + "ADD": "Adicionar webhook", + "EDIT": "Editar webhook", "SAVE": "Salvar", + "SEARCH": "Pesquisar por URL", "DESCRIPTION_LABEL": "Informe a descrição para o webhook:", "DESCRIPTION": "Descrição", "RESPOSITORY_LABEL": "Selecione o repositório que ira acionar o webhook:", @@ -310,17 +314,46 @@ "KEY": "Chave", "VALUE": "Valor", "TABLE": { - "EMPTY": "Não há webhooks cadastrados", + "EMPTY": "Não foram encontrados webhooks cadastrados", "METHOD": "Método", "URL": "URL", "REPOSITORY": "Repositório", "DESCRIPTION": "Descrição", "ACTION": "Ação", "DELETE": "Deletar", - "EDIT": "Editar" + "EDIT": "Editar", + "COPY": "Copiar" } }, + "SETTINGS_SCREEN": { + "TITLE": "Configurações da conta", + "TABLE": { + "USER": "Usuário", + "EMAIL": "Email", + "ACTION": "Ação", + "DELETE": "Deletar", + "EDIT": "Editar", + "PASSWORD": "Senha" + }, + "CONFIRM_DELETE": "Você deseja realmente deletar sua conta? Essa ação não poderá ser desfeita.", + "YES": "Sim", + "EDIT_ACCOUNT": "Editar conta", + "SAVE": "Salvar", + "EMAIL": "Email", + "NAME": "Nome de usuário", + "INVALID_EMAIL": "Email inválido", + "NEW_PASS": "Nova senha", + "CONFIRM_PASS": "Confirmar senha", + "CHANGE_PASS": "Alterar Senha", + "INVALID_CONFIRM_PASS": "As senhas não são iguais.", + "INVALID_PASS":"Informe a senha", + "EDIT_SUCCESS": "Informações alteradas com sucesso!", + "SUCCESS_UPDATE": "Foi enviado para seu e-mail uma solicitação de verificação do novo e-mail.", + "CONFIRM": "Ok, entendi.", + "SUCCESS_DELETE": "Sua conta foi deletada com sucesso!" + }, + "SIDE_MENU": { "DASHBOARD": "Dashboard", "VULNERABILITIES": "Vulnerabilidades", @@ -329,6 +362,7 @@ "REPOSITORY": "Repositório", "ORGANIZATION_USERS": "Usuários da organização", "BACK_ORGANIZATION": "Voltar para organizações", + "CONFIG": "Configurações", "WEBHOOK": "Webhooks", "LOGOUT": "Sair" }, @@ -343,7 +377,8 @@ "ALREADY_IN_COMPANY": "Este usuário já faz parte desta organização.", "PRIVILEGES": "Você não tem permissões suficientes para realizar esta ação.", "REPO_NAME_IN_USE": "Nome do repositório já em uso.", - "USERNAME_IN_USE": "Nome de usuário já em uso" + "USERNAME_IN_USE": "Nome de usuário já em uso", + "SAME_PASSWORD": "A nova senha não pode ser igual a atual." }, "NOT_FOUND_SCREEN": { diff --git a/horusec-manager/src/helpers/hooks/useResponseMessage.ts b/horusec-manager/src/helpers/hooks/useResponseMessage.ts index 8a2bfa348..7f49b9301 100644 --- a/horusec-manager/src/helpers/hooks/useResponseMessage.ts +++ b/horusec-manager/src/helpers/hooks/useResponseMessage.ts @@ -49,6 +49,7 @@ const useResponseMessage = () => { 'already exists webhook to repository selected': t( 'WEBHOOK_SCREEN.ALREADY_TO_REPOSITORY' ), + '{ACCOUNT} password is not valid': t('API_ERRORS.SAME_PASSWORD'), generic: t('API_ERRORS.GENERIC_ERROR'), }; diff --git a/horusec-manager/src/helpers/interfaces/HorusecConfig.ts b/horusec-manager/src/helpers/interfaces/HorusecConfig.ts index d08394083..1f41a9912 100644 --- a/horusec-manager/src/helpers/interfaces/HorusecConfig.ts +++ b/horusec-manager/src/helpers/interfaces/HorusecConfig.ts @@ -16,5 +16,6 @@ export interface HorusecConfig { applicationAdminEnable: boolean; + disabledBroker: boolean; authType: 'horusec' | 'keycloak' | 'ldap'; } diff --git a/horusec-manager/src/helpers/interfaces/InternalRoute.ts b/horusec-manager/src/helpers/interfaces/InternalRoute.ts index bcd9dc871..d80f93c1d 100644 --- a/horusec-manager/src/helpers/interfaces/InternalRoute.ts +++ b/horusec-manager/src/helpers/interfaces/InternalRoute.ts @@ -19,6 +19,7 @@ export interface InternalRoute { icon: string; path?: string; roles?: string[]; + rule?(): boolean; subRoutes?: InternalRoute[]; type: 'route' | 'subRoute'; } diff --git a/horusec-manager/src/helpers/localStorage/horusecConfig.ts b/horusec-manager/src/helpers/localStorage/horusecConfig.ts index b3ae4151a..bc93fd44d 100644 --- a/horusec-manager/src/helpers/localStorage/horusecConfig.ts +++ b/horusec-manager/src/helpers/localStorage/horusecConfig.ts @@ -20,6 +20,7 @@ import { HorusecConfig } from 'helpers/interfaces/HorusecConfig'; const initialValues: HorusecConfig = { authType: null, applicationAdminEnable: false, + disabledBroker: false, }; const getCurrentConfig = (): HorusecConfig => { diff --git a/horusec-manager/src/pages/External/Auth/Horusec/CreateAccount/Password/index.tsx b/horusec-manager/src/pages/External/Auth/Horusec/CreateAccount/Password/index.tsx index e0560585a..d5ba803c3 100644 --- a/horusec-manager/src/pages/External/Auth/Horusec/CreateAccount/Password/index.tsx +++ b/horusec-manager/src/pages/External/Auth/Horusec/CreateAccount/Password/index.tsx @@ -28,9 +28,11 @@ import { hasUpperCase, } from 'helpers/validators'; import { CreateAccountContext } from 'contexts/CreateAccount'; +import { getCurrentConfig } from 'helpers/localStorage/horusecConfig'; function PasswordForm() { const { t } = useTranslation(); + const { disabledBroker } = getCurrentConfig(); const history = useHistory(); const { password, @@ -146,7 +148,11 @@ function PasswordForm() { history.push('/auth')} roundedButton /> diff --git a/horusec-manager/src/pages/External/Auth/Horusec/Login/index.tsx b/horusec-manager/src/pages/External/Auth/Horusec/Login/index.tsx index 89bc0d4df..06a4b89e2 100644 --- a/horusec-manager/src/pages/External/Auth/Horusec/Login/index.tsx +++ b/horusec-manager/src/pages/External/Auth/Horusec/Login/index.tsx @@ -21,12 +21,14 @@ import { useHistory, useRouteMatch } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Field } from 'helpers/interfaces/Field'; import useAuth from 'helpers/hooks/useAuth'; +import { getCurrentConfig } from 'helpers/localStorage/horusecConfig'; function LoginScreen() { const { t } = useTranslation(); const history = useHistory(); const { path } = useRouteMatch(); const { login, loginInProgress } = useAuth(); + const { disabledBroker } = getCurrentConfig(); const [email, setEmail] = useState({ value: '', isValid: false }); const [password, setPassword] = useState({ @@ -64,11 +66,13 @@ function LoginScreen() { invalidMessage={t('LOGIN_SCREEN.INVALID_PASS')} /> - history.push(`${path}/recovery-password`)} - > - {t('LOGIN_SCREEN.FORGOT_PASS')} - + {!disabledBroker ? ( + history.push(`${path}/recovery-password`)} + > + {t('LOGIN_SCREEN.FORGOT_PASS')} + + ) : null} = ({ filters }) => { {t('DASHBOARD_SCREEN.ALL_VULNERABILITIES')} - - - - - + {isLoading ? ( + + + + ) : ( + + )} ); diff --git a/horusec-manager/src/pages/Internal/Home/Dashboard/AllVulnerabilities/styled.ts b/horusec-manager/src/pages/Internal/Home/Dashboard/AllVulnerabilities/styled.ts index 92414e3bb..d6e1992a8 100644 --- a/horusec-manager/src/pages/Internal/Home/Dashboard/AllVulnerabilities/styled.ts +++ b/horusec-manager/src/pages/Internal/Home/Dashboard/AllVulnerabilities/styled.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import styled, { css } from 'styled-components'; - -interface LoadingWrapperProps { - isLoading: boolean; -} +import styled from 'styled-components'; const Wrapper = styled.div` background-color: ${({ theme }) => theme.colors.background.secundary}; @@ -28,7 +24,7 @@ const Wrapper = styled.div` position: relative; `; -const LoadingWrapper = styled.div` +const LoadingWrapper = styled.div` display: flex; justify-content: center; align-items: center; @@ -38,13 +34,7 @@ const LoadingWrapper = styled.div` left: 0; background-color: ${({ theme }) => theme.colors.background.secundary}; z-index: 2; - visibility: hidden; - - ${({ isLoading }) => - isLoading && - css` - visibility: visible; - `}; + visibility: visible; `; const Title = styled.h4` diff --git a/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/index.tsx b/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/index.tsx index 46f5f51cb..113fda127 100644 --- a/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/index.tsx +++ b/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/index.tsx @@ -126,17 +126,19 @@ const VulnerabilitiesByLanguage: React.FC = ({ filters }) => { {t('DASHBOARD_SCREEN.VULNERABILITIES_BY_LANG')} - - - - - + {isLoading ? ( + + + + ) : ( + + )} ); diff --git a/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/styled.ts b/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/styled.ts index 96ea504cd..e04bfce0b 100644 --- a/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/styled.ts +++ b/horusec-manager/src/pages/Internal/Home/Dashboard/VulnerabilitiesByLanguage/styled.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import styled, { css } from 'styled-components'; +import styled from 'styled-components'; interface LoadingWrapperProps { isLoading: boolean; @@ -29,7 +29,7 @@ const Wrapper = styled.div` position: relative; `; -const LoadingWrapper = styled.div` +const LoadingWrapper = styled.div` display: flex; justify-content: center; align-items: center; @@ -39,13 +39,7 @@ const LoadingWrapper = styled.div` left: 0; background-color: ${({ theme }) => theme.colors.background.secundary}; z-index: 2; - visibility: hidden; - - ${({ isLoading }) => - isLoading && - css` - visibility: visible; - `}; + visibility: visible; `; const Title = styled.h4` diff --git a/horusec-manager/src/pages/Internal/Home/Settings/ChangePassword/index.tsx b/horusec-manager/src/pages/Internal/Home/Settings/ChangePassword/index.tsx new file mode 100644 index 000000000..ee9995ff7 --- /dev/null +++ b/horusec-manager/src/pages/Internal/Home/Settings/ChangePassword/index.tsx @@ -0,0 +1,192 @@ +/** + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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. + */ + +import React, { useState } from 'react'; +import { Dialog } from 'components'; +import { useTranslation } from 'react-i18next'; +import Styled from './styled'; +import useResponseMessage from 'helpers/hooks/useResponseMessage'; +import useFlashMessage from 'helpers/hooks/useFlashMessage'; +import { Field } from 'helpers/interfaces/Field'; +import { + isEmptyString, + hasLowerCase, + hasNumber, + hasSpecialCharacter, + hasUpperCase, +} from 'helpers/validators'; +import { useTheme } from 'styled-components'; +import accountService from 'services/account'; + +interface Props { + isVisible: boolean; + onCancel: () => void; + onConfirm: () => void; +} + +const ChangePassword: React.FC = ({ + isVisible, + onCancel, + onConfirm, +}) => { + const { t } = useTranslation(); + const { colors } = useTheme(); + const { dispatchMessage } = useResponseMessage(); + const { showSuccessFlash } = useFlashMessage(); + + const [isLoading, setLoading] = useState(false); + + const [password, setPassword] = useState({ + isValid: false, + value: '', + }); + + const [confirmPass, setConfirmPass] = useState({ + isValid: false, + value: '', + }); + + const [passValidations, setPassValidations] = useState({ + alpha: false, + number: false, + minCharacters: false, + characterSpecial: false, + }); + + const validateEqualsPassword = (value: string) => { + return value === password.value; + }; + + const resetFields = () => { + setConfirmPass({ isValid: false, value: '' }); + setPassword({ isValid: false, value: '' }); + setPassValidations({ + minCharacters: false, + alpha: false, + number: false, + characterSpecial: false, + }); + }; + + const handleCancel = () => { + onCancel(); + resetFields(); + }; + + const handlePasswordValue = (field: Field) => { + setPassValidations({ + minCharacters: field.value.length < 8, + alpha: !hasUpperCase(field.value) || !hasLowerCase(field.value), + number: !hasNumber(field.value), + characterSpecial: !hasSpecialCharacter(field.value), + }); + + setPassword(field); + }; + + const handleConfirmSave = () => { + setLoading(true); + + accountService + .updatePassword(password.value) + .then(() => { + showSuccessFlash(t('USERS_SCREEN.EDIT_SUCCESS')); + onConfirm(); + }) + .catch((err) => { + dispatchMessage(err?.response?.data); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( + + + + {t('CREATE_ACCOUNT_SCREEN.PASSWORD_REQUIREMENTS')} + + + + {t('CREATE_ACCOUNT_SCREEN.MIN_CHARACTERS')} + + + + {t('CREATE_ACCOUNT_SCREEN.ALPHA_REQUIREMENTS')} + + + + {t('CREATE_ACCOUNT_SCREEN.NUMBER_REQUIREMENT')} + + + + {t('CREATE_ACCOUNT_SCREEN.SPECIAL_CHARACTER')} + + + {t('CREATE_ACCOUNT_SCREEN.NO_EQUALS')} + + {t('CREATE_ACCOUNT_SCREEN.USER_NAME')} + + {t('CREATE_ACCOUNT_SCREEN.OLD_PASS')} + + + + handlePasswordValue(field)} + invalidMessage={t('SETTINGS_SCREEN.INVALID_PASS')} + validation={isEmptyString} + /> + + setConfirmPass(field)} + invalidMessage={t('SETTINGS_SCREEN.INVALID_CONFIRM_PASS')} + validation={validateEqualsPassword} + /> + + + ); +}; + +export default ChangePassword; diff --git a/horusec-manager/src/pages/Internal/Home/Settings/ChangePassword/styled.ts b/horusec-manager/src/pages/Internal/Home/Settings/ChangePassword/styled.ts new file mode 100644 index 000000000..4f120a8eb --- /dev/null +++ b/horusec-manager/src/pages/Internal/Home/Settings/ChangePassword/styled.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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. + */ +/** + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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. + */ + +import styled, { css } from 'styled-components'; +import { Input } from 'components'; + +interface ItemProps { + isInvalid?: boolean; +} + +const Form = styled.form` + display: flex; + flex-direction: column; +`; + +const Field = styled(Input)` + margin-top: 25px; + margin-bottom: 10px; +`; + +const PassRequirements = styled.div` + margin-bottom: 10px; + display: block; +`; + +const Info = styled.p` + color: ${({ theme }) => theme.colors.text.opaque}; + font-size: ${({ theme }) => theme.metrics.fontSize.medium}; + align-self: flex-start; + margin: 10px 0; + font-weight: 600; +`; + +const Item = styled.li` + color: ${({ theme }) => theme.colors.text.opaque}; + font-size: ${({ theme }) => theme.metrics.fontSize.small}; + margin: 0 0 5px 10px; + + ${({ isInvalid }) => + isInvalid && + css` + color: ${({ theme }) => theme.colors.input.error}; + `}; +`; + +export default { + Form, + Field, + Item, + Info, + PassRequirements, +}; diff --git a/horusec-manager/src/pages/Internal/Home/Settings/Edit/index.tsx b/horusec-manager/src/pages/Internal/Home/Settings/Edit/index.tsx new file mode 100644 index 000000000..3c1643dc0 --- /dev/null +++ b/horusec-manager/src/pages/Internal/Home/Settings/Edit/index.tsx @@ -0,0 +1,156 @@ +/** + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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. + */ + +import React, { useState } from 'react'; +import { Dialog } from 'components'; +import { useTranslation } from 'react-i18next'; +import Styled from './styled'; +import useResponseMessage from 'helpers/hooks/useResponseMessage'; +import useFlashMessage from 'helpers/hooks/useFlashMessage'; +import { Field } from 'helpers/interfaces/Field'; +import { + getCurrentUser, + setCurrentUser, +} from 'helpers/localStorage/currentUser'; +import { isValidEmail } from 'helpers/validators'; +import { useTheme } from 'styled-components'; +import accountService from 'services/account'; +import useAuth from 'helpers/hooks/useAuth'; +import { useHistory } from 'react-router-dom'; +import { getCurrentConfig } from 'helpers/localStorage/horusecConfig'; + +interface Props { + isVisible: boolean; + onCancel: () => void; + onConfirm: () => void; +} + +const EditAccount: React.FC = ({ isVisible, onCancel, onConfirm }) => { + const { t } = useTranslation(); + const currentUser = getCurrentUser(); + const { colors } = useTheme(); + const { dispatchMessage } = useResponseMessage(); + const { showSuccessFlash } = useFlashMessage(); + const { logout } = useAuth(); + const history = useHistory(); + const { disabledBroker } = getCurrentConfig(); + + const [isLoading, setLoading] = useState(false); + const [successDialogIsOpen, setSuccessDialogIsOpen] = useState(false); + + const [nameOfUser, setNameOfUser] = useState({ + isValid: true, + value: currentUser.username, + }); + + const [emailOfUser, setEmailOfUser] = useState({ + isValid: true, + value: currentUser.email, + }); + + const resetFields = () => { + setEmailOfUser({ isValid: true, value: currentUser.email }); + setNameOfUser({ isValid: true, value: currentUser.username }); + }; + + const handleCancel = () => { + onCancel(); + resetFields(); + }; + + const handleConfirmSave = () => { + if (nameOfUser.isValid && emailOfUser.isValid) { + setLoading(true); + + accountService + .update(nameOfUser.value, emailOfUser.value) + .then(() => { + if (emailOfUser.value !== currentUser.email && !disabledBroker) { + setSuccessDialogIsOpen(true); + } + + setCurrentUser({ + ...currentUser, + email: emailOfUser.value, + username: nameOfUser.value, + }); + + showSuccessFlash(t('SETTINGS_SCREEN.EDIT_SUCCESS')); + + onConfirm(); + }) + .catch((err) => { + dispatchMessage(err?.response?.data); + }) + .finally(() => { + setLoading(false); + }); + } + }; + + const confirmSuccessChangeEmail = () => { + setSuccessDialogIsOpen(false); + logout().then(() => history.replace('/auth')); + }; + + return ( + <> + + + setNameOfUser(field)} + /> + + setEmailOfUser(field)} + validation={isValidEmail} + invalidMessage={t('SETTINGS_SCREEN.INVALID_EMAIL')} + /> + + + + + + ); +}; + +export default EditAccount; diff --git a/horusec-manager/src/pages/Internal/Home/Settings/Edit/styled.ts b/horusec-manager/src/pages/Internal/Home/Settings/Edit/styled.ts new file mode 100644 index 000000000..698f31888 --- /dev/null +++ b/horusec-manager/src/pages/Internal/Home/Settings/Edit/styled.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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. + */ + +import styled from 'styled-components'; +import { Input } from 'components'; + +const Form = styled.form` + display: flex; + flex-direction: column; +`; + +const Field = styled(Input)` + margin-top: 25px; + margin-bottom: 10px; +`; + +export default { + Form, + Field, +}; diff --git a/horusec-manager/src/pages/Internal/Home/Settings/index.tsx b/horusec-manager/src/pages/Internal/Home/Settings/index.tsx new file mode 100644 index 000000000..28dce6ff0 --- /dev/null +++ b/horusec-manager/src/pages/Internal/Home/Settings/index.tsx @@ -0,0 +1,149 @@ +/** + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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. + */ + +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, Dialog } from 'components'; +import Styled from './styled'; +import { + getCurrentUser, + clearCurrentUser, +} from 'helpers/localStorage/currentUser'; +import accountService from 'services/account'; + +import EditAccount from './Edit'; +import ChangePassword from './ChangePassword'; +import useResponseMessage from 'helpers/hooks/useResponseMessage'; +import useFlashMessage from 'helpers/hooks/useFlashMessage'; +import { clearTokens } from 'helpers/localStorage/tokens'; +import { clearCurrentCompany } from 'helpers/localStorage/currentCompany'; +import { useHistory } from 'react-router-dom'; + +const Settings: React.FC = () => { + const { t } = useTranslation(); + const { email, username } = getCurrentUser(); + const { dispatchMessage } = useResponseMessage(); + const { showSuccessFlash } = useFlashMessage(); + const history = useHistory(); + + const [deleteDialogIsOpen, setOpenDeleteDialog] = useState(false); + const [deleteInProgress, setDeleteInProgress] = useState(false); + + const [editDialogIsOpen, setOpenEditDialog] = useState(false); + const [changePassDialogIsOpen, setOpenChangePassDialog] = useState(false); + + const handleConfirmDelete = () => { + setDeleteInProgress(true); + accountService + .deleteAccount() + .then(() => { + history.replace('/auth'); + clearCurrentUser(); + clearCurrentCompany(); + clearTokens(); + showSuccessFlash(t('SETTINGS_SCREEN.SUCCESS_DELETE')); + }) + .catch((err) => { + dispatchMessage(err?.response?.data); + }); + }; + + return ( + + + {t('SETTINGS_SCREEN.TITLE')} + + + + {t('SETTINGS_SCREEN.TABLE.USER')} + + {t('SETTINGS_SCREEN.TABLE.EMAIL')} + + {t('SETTINGS_SCREEN.TABLE.ACTION')} + + + + + {username} + + {email} + + + + + + + + + setOpenDeleteDialog(false)} + onConfirm={handleConfirmDelete} + /> + + setOpenEditDialog(false)} + onConfirm={() => setOpenEditDialog(false)} + /> + + setOpenChangePassDialog(false)} + onConfirm={() => setOpenChangePassDialog(false)} + /> + + ); +}; + +export default Settings; diff --git a/horusec-manager/src/pages/Internal/Home/Settings/styled.ts b/horusec-manager/src/pages/Internal/Home/Settings/styled.ts new file mode 100644 index 000000000..c526d9ade --- /dev/null +++ b/horusec-manager/src/pages/Internal/Home/Settings/styled.ts @@ -0,0 +1,125 @@ +/** + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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. + */ + +import styled from 'styled-components'; + +const Wrapper = styled.section` + padding: 35px; + width: 100%; + max-width: 1200px; +`; + +const Content = styled.div` + margin-top: 25px; + padding: 25px 15px 10px 25px; + background-color: ${({ theme }) => theme.colors.background.secundary}; + border-radius: 4px; + position: relative; +`; + +const Title = styled.h1` + color: ${({ theme }) => theme.colors.text.secundary}; + font-weight: normal; + font-size: ${({ theme }) => theme.metrics.fontSize.xlarge}; +`; + +const Table = styled.div` + margin-top: 30px; +`; + +const Head = styled.div` + display: flex; + flex-direction: row; + padding: 0px 20px; +`; + +const Column = styled.span` + text-align: left; + font-size: ${({ theme }) => theme.metrics.fontSize.small}; + color: ${({ theme }) => theme.colors.dataTable.column.text}; + font-weight: normal; + width: 100%; + display: block; + margin-right: 20px; + + &:nth-child(1) { + max-width: 180px; + } +`; + +const Cell = styled.span` + text-align: left; + font-size: ${({ theme }) => theme.metrics.fontSize.small}; + color: ${({ theme }) => theme.colors.dataTable.row.text}; + font-weight: normal; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + width: 100%; + margin-right: 20px; + line-height: 30px; + padding: 2px; + + &:nth-child(1) { + max-width: 180px; + } + + &.row { + display: flex; + flex-direction: row; + + button { + margin-right: 10px; + } + } +`; + +const Row = styled.div` + background-color: ${({ theme }) => theme.colors.dataTable.row.background}; + margin-bottom: 4px; + border-radius: 4px; + padding: 10px 20px; + display: flex; + flex-direction: row; +`; + +const Body = styled.div` + overflow-y: scroll; + margin-top: 10px; + padding-right: 10px; + + ::-webkit-scrollbar { + width: 6px; + } + + ::-webkit-scrollbar-thumb { + background: ${({ theme }) => theme.colors.scrollbar}; + border-radius: 4px; + } +`; + +export default { + Wrapper, + Content, + Title, + Body, + Cell, + Row, + Column, + Head, + Table, +}; diff --git a/horusec-manager/src/pages/Internal/Home/Webhooks/Add/index.tsx b/horusec-manager/src/pages/Internal/Home/Webhooks/Add/index.tsx index c21cd8694..10d1414f1 100644 --- a/horusec-manager/src/pages/Internal/Home/Webhooks/Add/index.tsx +++ b/horusec-manager/src/pages/Internal/Home/Webhooks/Add/index.tsx @@ -15,7 +15,7 @@ */ import React, { useState, useEffect } from 'react'; -import { Dialog, Select, Icon } from 'components'; +import { Dialog, Select } from 'components'; import { useTranslation } from 'react-i18next'; import { useTheme } from 'styled-components'; import Styled from './styled'; @@ -27,19 +27,25 @@ import { get } from 'lodash'; import { isValidURL } from 'helpers/validators'; import useResponseMessage from 'helpers/hooks/useResponseMessage'; import webhookService from 'services/webhook'; -import { WebhookHeader } from 'helpers/interfaces/Webhook'; +import { Webhook, WebhookHeader } from 'helpers/interfaces/Webhook'; import useFlashMessage from 'helpers/hooks/useFlashMessage'; import { cloneDeep } from 'lodash'; interface Props { isVisible: boolean; + webhookToCopy: Webhook; onCancel: () => void; onConfirm: () => void; } const webhookHttpMethods = [{ value: 'POST' }, { value: 'GET' }]; -const AddWebhook: React.FC = ({ isVisible, onCancel, onConfirm }) => { +const AddWebhook: React.FC = ({ + isVisible, + onCancel, + onConfirm, + webhookToCopy, +}) => { const { t } = useTranslation(); const { colors } = useTheme(); const { companyID } = getCurrentCompany(); @@ -63,6 +69,8 @@ const AddWebhook: React.FC = ({ isVisible, onCancel, onConfirm }) => { const resetFields = () => { setHeaders([{ key: '', value: '' }]); setSelectedRepository(null); + setDescription({ isValid: false, value: '' }); + setUrl({ isValid: false, value: '' }); }; const handleCancel = () => { @@ -95,6 +103,25 @@ const AddWebhook: React.FC = ({ isVisible, onCancel, onConfirm }) => { }); }; + const handleSetHeader = (index: number, key: string, value: string) => { + const headersCopy = cloneDeep(headers); + const header = { key, value }; + headersCopy[index] = header; + setHeaders(headersCopy); + }; + + const handleRemoveHeader = () => { + const headersCopy = cloneDeep(headers); + headersCopy.pop(); + setHeaders(headersCopy); + }; + + useEffect(() => { + setHeaders(webhookToCopy?.headers || [{ key: '', value: '' }]); + setDescription({ value: webhookToCopy?.description, isValid: true }); + setUrl({ value: webhookToCopy?.url, isValid: true }); + }, [webhookToCopy]); + useEffect(() => { const fetchRepositories = () => { repositoryService.getAll(companyID).then((result) => { @@ -105,13 +132,6 @@ const AddWebhook: React.FC = ({ isVisible, onCancel, onConfirm }) => { fetchRepositories(); }, [companyID]); - const handleSetHeader = (index: number, key: string, value: string) => { - const headersCopy = cloneDeep(headers); - const header = { key, value }; - headersCopy[index] = header; - setHeaders(headersCopy); - }; - return ( = ({ isVisible, onCancel, onConfirm }) => { name="description" type="text" width="100%" + initialValue={description.value} /> {t('WEBHOOK_SCREEN.RESPOSITORY_LABEL')} @@ -169,6 +190,7 @@ const AddWebhook: React.FC = ({ isVisible, onCancel, onConfirm }) => { width="400px" validation={isValidURL} invalidMessage={t('WEBHOOK_SCREEN.INVALID_URL')} + initialValue={url.value} /> @@ -178,24 +200,34 @@ const AddWebhook: React.FC = ({ isVisible, onCancel, onConfirm }) => { handleSetHeader(index, value, headers[index].value) } width="200px" + initialValue={headers[index]?.key} /> handleSetHeader(index, headers[index].key, value) } width="200px" + initialValue={headers[index]?.value} /> - {index + 1 === headers.length && headers.length !== 3 ? ( - + ) : null} + + {index + 1 === headers.length && headers.length !== 5 ? ( + setHeaders([...headers, { key: '', value: '' }])} diff --git a/horusec-manager/src/pages/Internal/Home/Webhooks/Add/styled.ts b/horusec-manager/src/pages/Internal/Home/Webhooks/Add/styled.ts index 289ec8758..1c60506a0 100644 --- a/horusec-manager/src/pages/Internal/Home/Webhooks/Add/styled.ts +++ b/horusec-manager/src/pages/Internal/Home/Webhooks/Add/styled.ts @@ -15,7 +15,7 @@ */ import styled from 'styled-components'; -import { Input, Select } from 'components'; +import { Input, Select, Icon } from 'components'; interface SelectProps { color: string; @@ -53,10 +53,15 @@ const URLSelect = styled(Select)` } `; +const OptionIcon = styled(Icon)` + margin-right: 10px; +`; + export default { Form, Field, Label, Wrapper, URLSelect, + OptionIcon, }; diff --git a/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/index.tsx b/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/index.tsx index b3924fd6f..e19d82601 100644 --- a/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/index.tsx +++ b/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/index.tsx @@ -15,7 +15,7 @@ */ import React, { useState, useEffect } from 'react'; -import { Dialog, Select, Icon } from 'components'; +import { Dialog, Select } from 'components'; import { useTranslation } from 'react-i18next'; import { useTheme } from 'styled-components'; import Styled from './styled'; @@ -125,10 +125,16 @@ const AddWebhook: React.FC = ({ setHeaders(headersCopy); }; + const handleRemoveHeader = () => { + const headersCopy = cloneDeep(headers); + headersCopy.pop(); + setHeaders(headersCopy); + }; + return ( = ({ {t('WEBHOOK_SCREEN.HEADERS_LABEL')} - {webhookToEdit?.headers?.map((header, index) => ( + {headers?.map((header, index) => ( handleSetHeader(index, value, headers[index].value) } @@ -204,7 +210,7 @@ const AddWebhook: React.FC = ({ handleSetHeader(index, headers[index].key, value) @@ -213,8 +219,16 @@ const AddWebhook: React.FC = ({ initialValue={headers[index]?.value} /> - {index + 1 === headers?.length && headers?.length !== 3 ? ( - + ) : null} + + {index + 1 === headers?.length && headers?.length !== 5 ? ( + setHeaders([...headers, { key: '', value: '' }])} diff --git a/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/styled.ts b/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/styled.ts index 289ec8758..1c60506a0 100644 --- a/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/styled.ts +++ b/horusec-manager/src/pages/Internal/Home/Webhooks/Edit/styled.ts @@ -15,7 +15,7 @@ */ import styled from 'styled-components'; -import { Input, Select } from 'components'; +import { Input, Select, Icon } from 'components'; interface SelectProps { color: string; @@ -53,10 +53,15 @@ const URLSelect = styled(Select)` } `; +const OptionIcon = styled(Icon)` + margin-right: 10px; +`; + export default { Form, Field, Label, Wrapper, URLSelect, + OptionIcon, }; diff --git a/horusec-manager/src/pages/Internal/Home/Webhooks/index.tsx b/horusec-manager/src/pages/Internal/Home/Webhooks/index.tsx index 5e2ea4c2c..968936c7f 100644 --- a/horusec-manager/src/pages/Internal/Home/Webhooks/index.tsx +++ b/horusec-manager/src/pages/Internal/Home/Webhooks/index.tsx @@ -17,7 +17,7 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import Styled from './styled'; -import { Button, Icon, Dialog } from 'components'; +import { Button, Icon, Dialog, SearchBar } from 'components'; import { Webhook } from 'helpers/interfaces/Webhook'; import { useTheme } from 'styled-components'; import { get } from 'lodash'; @@ -38,8 +38,10 @@ const Webhooks: React.FC = () => { const { showSuccessFlash } = useFlashMessage(); const [webhooks, setWebhooks] = useState([]); + const [filteredWebhooks, setFilteredWebhooks] = useState([]); const [webhookToDelete, setWebhookToDelete] = useState(); const [webhookToEdit, setWebhookToEdit] = useState(); + const [webhookToCopy, setWebhookToCopy] = useState(); const [isLoading, setLoading] = useState(false); const [deleteIsLoading, setDeleteIsLoading] = useState(false); @@ -52,6 +54,7 @@ const Webhooks: React.FC = () => { .getAll(companyID) .then((result) => { setWebhooks(result?.data?.content); + setFilteredWebhooks(result?.data?.content); }) .catch((err) => { dispatchMessage(err?.response?.data); @@ -83,6 +86,18 @@ const Webhooks: React.FC = () => { }); }; + const onSearchWebhook = (search: string) => { + if (search) { + const filtered = webhooks.filter((webhook) => + webhook?.url.toLocaleLowerCase().includes(search.toLocaleLowerCase()) + ); + + setFilteredWebhooks(filtered); + } else { + setFilteredWebhooks(webhooks); + } + }; + useEffect(() => { fetchData(); // eslint-disable-next-line @@ -90,17 +105,24 @@ const Webhooks: React.FC = () => { return ( + + onSearchWebhook(value)} + /> + + + {t('WEBHOOK_SCREEN.TITLE')} - - @@ -109,10 +131,6 @@ const Webhooks: React.FC = () => { - - {t('WEBHOOK_SCREEN.TABLE.REPOSITORY')} - - {t('WEBHOOK_SCREEN.TABLE.METHOD')} {t('WEBHOOK_SCREEN.TABLE.URL')} @@ -121,20 +139,22 @@ const Webhooks: React.FC = () => { {t('WEBHOOK_SCREEN.TABLE.DESCRIPTION')} + + {t('WEBHOOK_SCREEN.TABLE.REPOSITORY')} + + {t('WEBHOOK_SCREEN.TABLE.ACTION')} - {!webhooks || webhooks.length <= 0 ? ( + {!filteredWebhooks || filteredWebhooks.length <= 0 ? ( {t('WEBHOOK_SCREEN.TABLE.EMPTY')} ) : null} - {webhooks.map((webhook, index) => ( + {filteredWebhooks.map((webhook, index) => ( - {webhook?.repository?.name} - { {webhook.description} + {webhook?.repository?.name} + ))} @@ -182,7 +218,11 @@ const Webhooks: React.FC = () => { setAddWebhookVisible(false)} + onCancel={() => { + setAddWebhookVisible(false); + setWebhookToCopy(null); + }} + webhookToCopy={webhookToCopy} onConfirm={() => { setAddWebhookVisible(false); fetchData(); diff --git a/horusec-manager/src/pages/Internal/Home/Webhooks/styled.ts b/horusec-manager/src/pages/Internal/Home/Webhooks/styled.ts index 7c643cc73..b9f6a3c87 100644 --- a/horusec-manager/src/pages/Internal/Home/Webhooks/styled.ts +++ b/horusec-manager/src/pages/Internal/Home/Webhooks/styled.ts @@ -30,6 +30,14 @@ const Wrapper = styled.section` max-width: 1200px; `; +const Options = styled.div` + background-color: ${({ theme }) => theme.colors.background.secundary}; + border-radius: 4px; + padding: 22px; + display: flex; + align-items: center; +`; + const Content = styled.div` margin-top: 25px; padding: 25px 15px 10px 25px; @@ -97,21 +105,21 @@ const Cell = styled.span` } &:nth-child(1) { - max-width: 150px; + max-width: 120px; margin: 0; } &:nth-child(2) { - max-width: 100px; + max-width: 230px; } &:nth-child(3) { - max-width: 250px; + min-width: 270px; margin-right: 15px; } &:nth-child(4) { - min-width: 300px; + max-width: 100px; } &:nth-child(5) { @@ -130,21 +138,21 @@ const Column = styled.span` margin-right: 15px; &:nth-child(1) { - max-width: 150px; + max-width: 120px; margin: 0; } &:nth-child(2) { - max-width: 100px; + max-width: 230px; margin-left: 5px; } &:nth-child(3) { - max-width: 250px; + min-width: 270px; } &:nth-child(4) { - min-width: 300px; + max-width: 100px; } &:nth-child(5) { @@ -213,6 +221,7 @@ const Tag = styled.span` export default { Wrapper, + Options, Tag, Content, Title, diff --git a/horusec-manager/src/routes/home.routes.tsx b/horusec-manager/src/routes/home.routes.tsx index b7fcc6bc3..97714d212 100644 --- a/horusec-manager/src/routes/home.routes.tsx +++ b/horusec-manager/src/routes/home.routes.tsx @@ -34,6 +34,7 @@ import Repositories from 'pages/Internal/Home/Repositories'; import Users from 'pages/Internal/Home/Users'; import Vulnerabilities from 'pages/Internal/Home/Vulnerabilities'; import Webhooks from 'pages/Internal/Home/Webhooks'; +import Settings from 'pages/Internal/Home/Settings'; function HomeRoutes() { const history = useHistory(); @@ -86,6 +87,8 @@ function HomeRoutes() { /> } /> + + } /> ); diff --git a/horusec-manager/src/services/account.ts b/horusec-manager/src/services/account.ts index cde1d6913..9ea193bfb 100644 --- a/horusec-manager/src/services/account.ts +++ b/horusec-manager/src/services/account.ts @@ -40,6 +40,17 @@ const createAccount = (username: string, password: string, email: string) => { }); }; +const update = (username: string, email: string) => { + return http.patch(`${SERVICE_AUTH}/api/account/update`, { + username, + email, + }); +}; + +const deleteAccount = () => { + return http.delete(`${SERVICE_AUTH}/api/account/delete`); +}; + const createAccountFromKeycloak = (accessToken: string) => { return http.post(`${SERVICE_AUTH}/api/account/create-account-from-keycloak`, { accessToken, @@ -61,7 +72,15 @@ const changePassword = (token: string, password: string) => { return http.post(`${SERVICE_AUTH}/api/account/change-password`, password, { headers: { 'Content-Type': 'text/plain', - Authorization: `Bearer ${token}`, + 'X-Horusec-Authorization': `Bearer ${token}`, + }, + }); +}; + +const updatePassword = (password: string) => { + return http.post(`${SERVICE_AUTH}/api/account/change-password`, password, { + headers: { + 'Content-Type': 'text/plain', }, }); }; @@ -87,7 +106,7 @@ const callRenewToken = async (): Promise => { axios .post(`${SERVICE_AUTH}/api/account/renew-token`, refreshToken, { headers: { - Authorization: `Bearer ${accessToken}`, + 'X-Horusec-Authorization': `Bearer ${accessToken}`, 'Content-type': 'text/plain', }, }) @@ -125,4 +144,7 @@ export default { verifyUniqueUsernameEmail, getHorusecConfig, createAccountFromKeycloak, + update, + deleteAccount, + updatePassword, }; diff --git a/horusec-webhook/.semver.yaml b/horusec-webhook/.semver.yaml index 36a2accc3..1443f2773 100644 --- a/horusec-webhook/.semver.yaml +++ b/horusec-webhook/.semver.yaml @@ -1,4 +1,4 @@ alpha: 0 beta: 0 rc: 0 -release: v1.0.0 +release: v1.0.1 diff --git a/horusec-webhook/internal/controllers/webhook/webhook_test.go b/horusec-webhook/internal/controllers/webhook/webhook_test.go index f619a0a7c..880b615eb 100644 --- a/horusec-webhook/internal/controllers/webhook/webhook_test.go +++ b/horusec-webhook/internal/controllers/webhook/webhook_test.go @@ -16,6 +16,9 @@ package webhook import ( "errors" + "net/http" + "testing" + "github.com/ZupIT/horusec/development-kit/pkg/databases/relational" "github.com/ZupIT/horusec/development-kit/pkg/databases/relational/repository/webhook" entitiesWebhook "github.com/ZupIT/horusec/development-kit/pkg/entities/webhook" @@ -28,8 +31,6 @@ import ( "github.com/google/uuid" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" - "net/http" - "testing" _ "github.com/jinzhu/gorm/dialects/sqlite" // Required in gorm usage ) @@ -168,7 +169,7 @@ func TestMock_DispatchRequest(t *testing.T) { Method: http.MethodPost, Headers: []entitiesWebhook.Headers{ { - Key: "Authorization", + Key: "X-Horusec-Authorization", Value: "Bearer Token", }, },