Skip to content

Commit

Permalink
Merge pull request #7 from CheckmarxDev/feature/AST-3450-verify-expor…
Browse files Browse the repository at this point in the history
…t-permissions

Feature/ast 3450 verify export permissions
  • Loading branch information
constantinoantunes1 authored Sep 22, 2021
2 parents 110f916 + 2f4095a commit d2da980
Show file tree
Hide file tree
Showing 14 changed files with 425 additions and 112 deletions.
2 changes: 1 addition & 1 deletion cmd/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func GetArgs(cmd *cobra.Command, productName string) internal.Args {
if err != nil {
panic(err)
}
args.Export, err = cmd.Flags().GetString(exportArg)
args.Export, err = cmd.Flags().GetStringSlice(exportArg)
if err != nil {
panic(err)
}
Expand Down
18 changes: 7 additions & 11 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package cmd

import (
"fmt"
"os"

"github.com/checkmarxDev/ast-observability-library/pkg/aol"
"github.com/checkmarxDev/ast-sast-export/internal"
"github.com/checkmarxDev/ast-sast-export/internal/export"
"github.com/checkmarxDev/ast-sast-export/internal/logging"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -90,16 +90,15 @@ func Execute() {

//nolint:gochecknoinits
func init() {
resultsProjectActiveSinceUsage := fmt.Sprintf(
"SAST [optional] custom results project active since (days) - %d if nothing defined", resultsProjectActiveSinceDefaultValue)
resultsProjectActiveSinceUsage := "include only results from projects active in the last N days"

rootCmd.Flags().StringP(userArg, "", "", "SAST admin username")
rootCmd.Flags().StringP(passArg, "", "", "SAST admin password")
rootCmd.Flags().StringP(userArg, "", "", "SAST username")
rootCmd.Flags().StringP(passArg, "", "", "SAST password")
rootCmd.Flags().StringP(urlArg, "", "", "SAST url")
rootCmd.Flags().StringP(exportArg, "", "", "SAST [optional] export options --export users,results,teams, all if nothing defined")
rootCmd.Flags().StringSliceP(exportArg, "", export.GetOptions(), "SAST export options")
rootCmd.Flags().IntP(resultsProjectActiveSinceArg, "", resultsProjectActiveSinceDefaultValue, resultsProjectActiveSinceUsage)
rootCmd.Flags().Bool(debugArg, false, "Activate debug mode")
rootCmd.Flags().BoolP(verboseArg, "v", false, "Enable verbose logging to console")
rootCmd.Flags().Bool(debugArg, false, "activate debug mode")
rootCmd.Flags().BoolP(verboseArg, "v", false, "enable verbose logging to console")

if err := rootCmd.MarkFlagRequired(userArg); err != nil {
panic(err)
Expand All @@ -110,9 +109,6 @@ func init() {
if err := rootCmd.MarkFlagRequired(urlArg); err != nil {
panic(err)
}
if err := rootCmd.MarkFlagCustom(exportArg, "users,results,teams"); err != nil {
panic(err)
}
if err := rootCmd.MarkFlagCustom(resultsProjectActiveSinceArg, resultsProjectActiveSinceUsage); err != nil {
panic(err)
}
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module github.com/checkmarxDev/ast-sast-export
go 1.16

require (
github.com/checkmarxDev/ast-observability-library v1.2.0
github.com/checkmarxDev/ast-observability-library v1.2.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/rs/zerolog v1.25.0
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/checkmarxDev/ast-observability-library v1.2.0 h1:G2uE9mBQEJOSdSCV74SRO02/0qioAJ6ZukgHANYjJ1Y=
github.com/checkmarxDev/ast-observability-library v1.2.0/go.mod h1:es+kjxYDaFOFJIG3ADVx3pi8BUwZwLxBJMV5Qn/88/M=
github.com/checkmarxDev/ast-observability-library v1.2.1 h1:td6VoYhr2jYoiVMNDViTtuD3qItWPI+fzxtfHQ8CNOg=
github.com/checkmarxDev/ast-observability-library v1.2.1/go.mod h1:9AlqHrGF1TD4ileeF5w7y8irxeGH+YB8AOC9YwV8CBM=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
Expand All @@ -61,6 +61,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -202,9 +204,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo=
github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II=
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down
11 changes: 11 additions & 0 deletions internal/export/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package export

const (
UsersOption = "users"
TeamsOption = "teams"
ResultsOption = "results"
)

func GetOptions() []string {
return []string{UsersOption, TeamsOption, ResultsOption}
}
2 changes: 1 addition & 1 deletion internal/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ type Args struct {
URL,
Username,
Password,
Export,
OutputPath,
ProductName string
Export []string
ResultsProjectActiveSince int
Debug bool
}
Expand Down
88 changes: 88 additions & 0 deletions internal/permissions/permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package permissions

import (
"fmt"

"github.com/checkmarxDev/ast-sast-export/internal/export"
"github.com/checkmarxDev/ast-sast-export/internal/sliceutils"
"github.com/dgrijalva/jwt-go"
)

const (
useOdataPermission = "use-odata"
generateScanReportPermission = "generate-scan-report"
manageAuthProviderPermission = "manage-authentication-providers"
manageRolesPermission = "manage-roles"
)

var permissionDescription = map[interface{}]string{
useOdataPermission: "Sast > API > Use Odata",
generateScanReportPermission: "Sast > Reports > Generate Scan Report",
manageAuthProviderPermission: "Access Control > General > Manage Authentication Providers",
manageRolesPermission: "Access Control > General > Manage Roles",
}

func GetFromExportOptions(exportOptions []string) []interface{} {
var output []string

usersPermissions := []string{manageAuthProviderPermission, manageRolesPermission}
teamsPermissions := []string{manageAuthProviderPermission}
resultsPermissions := []string{useOdataPermission, generateScanReportPermission}

for _, exportOption := range exportOptions {
if exportOption == export.UsersOption {
output = append(output, usersPermissions...)
} else if exportOption == export.TeamsOption {
output = append(output, teamsPermissions...)
} else if exportOption == export.ResultsOption {
output = append(output, resultsPermissions...)
}
}
return sliceutils.Unique(sliceutils.ConvertStringToInterface(output))
}

func GetFromJwtClaims(jwtClaims jwt.MapClaims, keys []string) ([]interface{}, error) {
permissions := make([]interface{}, 0)
for _, key := range keys {
claimPermissions, permissionErr := getFromJwtClaim(jwtClaims, key)
if permissionErr != nil {
return nil, fmt.Errorf("could not parse %s permissions", key)
}
permissions = append(permissions, claimPermissions...)
}
return permissions, nil
}

func getFromJwtClaim(claims jwt.MapClaims, key string) ([]interface{}, error) {
claimValue, exists := claims[key]
if !exists {
return make([]interface{}, 0), nil
}
multiplePermissions, ok := claimValue.([]interface{})
if ok {
return multiplePermissions, nil
}
singlePermission, ok := claimValue.(interface{})
if ok {
return []interface{}{singlePermission}, nil
}
return make([]interface{}, 0), fmt.Errorf("could not parse permissions")
}

func GetDescription(permission interface{}) (string, error) {
description, ok := permissionDescription[permission]
if !ok {
return "", fmt.Errorf("unknown permission %s", permission)
}
return description, nil
}

func GetMissing(required, available []interface{}) []interface{} {
missing := make([]interface{}, 0)
for _, requiredPermission := range required {
if !sliceutils.Contains(requiredPermission, available) {
missing = append(missing, requiredPermission)
}
}
return missing
}
169 changes: 169 additions & 0 deletions internal/permissions/permissions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package permissions

import (
"testing"

"github.com/checkmarxDev/ast-sast-export/internal/export"

"github.com/dgrijalva/jwt-go"

"github.com/stretchr/testify/assert"
)

func TestGetFromExportOptions(t *testing.T) {
t.Run("users case", func(t *testing.T) {
exportOptions := []string{export.UsersOption}
result := GetFromExportOptions(exportOptions)

expected := []interface{}{manageAuthProviderPermission, manageRolesPermission}
assert.ElementsMatch(t, expected, result)
})

t.Run("teams case", func(t *testing.T) {
exportOptions := []string{export.TeamsOption}
result := GetFromExportOptions(exportOptions)

expected := []interface{}{manageAuthProviderPermission}
assert.ElementsMatch(t, expected, result)
})

t.Run("results case", func(t *testing.T) {
exportOptions := []string{export.ResultsOption}
result := GetFromExportOptions(exportOptions)

expected := []interface{}{useOdataPermission, generateScanReportPermission}
assert.ElementsMatch(t, expected, result)
})

t.Run("users+teams case", func(t *testing.T) {
exportOptions := []string{export.UsersOption, export.TeamsOption}
result := GetFromExportOptions(exportOptions)

expected := []interface{}{manageAuthProviderPermission, manageRolesPermission}
assert.ElementsMatch(t, expected, result)
})

t.Run("users+results case", func(t *testing.T) {
exportOptions := []string{export.UsersOption, export.ResultsOption}
result := GetFromExportOptions(exportOptions)

expected := []interface{}{manageAuthProviderPermission, manageRolesPermission, useOdataPermission, generateScanReportPermission}
assert.ElementsMatch(t, expected, result)
})

t.Run("results+users case", func(t *testing.T) {
exportOptions := []string{export.ResultsOption, export.UsersOption}
result := GetFromExportOptions(exportOptions)

expected := []interface{}{manageAuthProviderPermission, manageRolesPermission, useOdataPermission, generateScanReportPermission}
assert.ElementsMatch(t, expected, result)
})

t.Run("results+teams case", func(t *testing.T) {
exportOptions := []string{export.TeamsOption, export.ResultsOption}
result := GetFromExportOptions(exportOptions)

expected := []interface{}{manageAuthProviderPermission, useOdataPermission, generateScanReportPermission}
assert.ElementsMatch(t, expected, result)
})
}

func TestGetAllFromJwtClaims(t *testing.T) {
claims := jwt.MapClaims{
"aaa": []interface{}{"a", "b"},
"bbb": []interface{}{"c", "d"},
"ccc": []interface{}{"e", "f"},
}

result, err := GetFromJwtClaims(claims, []string{"aaa", "bbb"})

expected := []interface{}{"a", "b", "c", "d"}
assert.NoError(t, err)
assert.ElementsMatch(t, expected, result)
}

func TestGetFromJwtClaims(t *testing.T) {
key := "permissions"

t.Run("claims without permission", func(t *testing.T) {
claims := jwt.MapClaims{"test": "test"}

result, err := getFromJwtClaim(claims, key)

expected := make([]interface{}, 0)
assert.NoError(t, err)
assert.ElementsMatch(t, expected, result)
})

t.Run("claims with one permission", func(t *testing.T) {
claims := jwt.MapClaims{"test": "test", "permissions": "use-odata"}

result, err := getFromJwtClaim(claims, key)

expected := []interface{}{"use-odata"}
assert.NoError(t, err)
assert.ElementsMatch(t, expected, result)
})

t.Run("claims with more than one permission", func(t *testing.T) {
claims := jwt.MapClaims{"test": "test", "permissions": []interface{}{"use-odata", "generate-scan-report"}}

result, err := getFromJwtClaim(claims, key)

expected := []interface{}{"use-odata", "generate-scan-report"}
assert.NoError(t, err)
assert.ElementsMatch(t, expected, result)
})
}

func TestGetMissing(t *testing.T) {
t.Run("empty lists return empty list", func(t *testing.T) {
required := make([]interface{}, 0)
available := make([]interface{}, 0)

result := GetMissing(required, available)

expected := make([]interface{}, 0)
assert.ElementsMatch(t, expected, result)
})

t.Run("empty required return empty list", func(t *testing.T) {
required := make([]interface{}, 0)
available := []interface{}{"a", "b", "c"}

result := GetMissing(required, available)

expected := make([]interface{}, 0)
assert.ElementsMatch(t, expected, result)
})

t.Run("more available than required return empty list", func(t *testing.T) {
required := []interface{}{"a", "b"}
available := []interface{}{"a", "b", "c"}

result := GetMissing(required, available)

expected := make([]interface{}, 0)
assert.ElementsMatch(t, expected, result)
})

t.Run("missing one returns one item", func(t *testing.T) {
required := []interface{}{"a", "b", "c"}
available := []interface{}{"a", "b"}

result := GetMissing(required, available)

expected := []interface{}{"c"}
assert.ElementsMatch(t, expected, result)
})

t.Run("missing many returns many items", func(t *testing.T) {
required := []interface{}{"a", "b", "c", "d", "e", "f"}
available := []interface{}{"a", "b", "d", "f"}

result := GetMissing(required, available)

expected := []interface{}{"c", "e"}
assert.ElementsMatch(t, expected, result)
})
}
Loading

0 comments on commit d2da980

Please sign in to comment.