Skip to content

Commit

Permalink
Merge pull request #32 from napptive/feature/PG-1624_accounts_in_auth…
Browse files Browse the repository at this point in the history
…x_claim

PG- 1624 accounts in authx claim
  • Loading branch information
Carmendelope authored Jan 19, 2024
2 parents f2fe9ed + d70953b commit cd28e5a
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 100 deletions.
33 changes: 16 additions & 17 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,39 +1,38 @@
module github.com/napptive/njwt

go 1.19
go 1.21

require (
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/mock v1.6.0
github.com/napptive/go-utils v0.0.0-20230302113223-949e47c65976
github.com/napptive/grpc-jwt-go v0.1.0
github.com/napptive/grpc-ping-go v0.1.0
github.com/napptive/nerrors v1.1.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.27.2
github.com/rs/xid v1.4.0
github.com/rs/zerolog v1.29.0
google.golang.org/grpc v1.53.0
github.com/onsi/gomega v1.30.0
github.com/rs/xid v1.5.0
github.com/rs/zerolog v1.31.0
google.golang.org/grpc v1.59.0
syreclabs.com/go/faker v1.2.3
)

require (
github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/napptive/grpc-common-go v0.8.0 // indirect
github.com/nxadm/tail v1.4.8 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 // indirect
google.golang.org/protobuf v1.28.1 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.12.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
83 changes: 43 additions & 40 deletions go.sum

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion pkg/helper/metadata_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@ const (
// OriginalUsernameKey with the key that will be injected in the context metadata for signup claims corresponding with the orignal username in the target provider
OriginalUsernameKey = "original_username"
// IdentityProviderKey with the key that will be injected in the context metadata for signup claims corresponding with the the target provider
IdentityProviderKey = "identity_provider"
IdentityProviderKey = "identity_provider"
EnvironmentAccountKey = "environment_account_id"
AccountsKey = "accounts"
)
29 changes: 21 additions & 8 deletions pkg/interceptors/jwt_interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ func GetClaimFromContext(ctx context.Context) (*njwt.ExtendedAuthxClaim, error)
if exists {
envIDVal = envID[0]
}
envAccountIDVal := ""
envAccountID, exists := md[helper.EnvironmentAccountKey]
if exists {
envAccountIDVal = envAccountID[0]
}
accountAdminVal := false
accountAdmin, exists := md[helper.AccountAdminKey]
if exists && accountAdmin[0] == "true" {
Expand All @@ -173,20 +178,28 @@ func GetClaimFromContext(ctx context.Context) (*njwt.ExtendedAuthxClaim, error)
zoneURLVal = zoneURL[0]
}

var userAccounts []njwt.UserAccountClaim
accounts, exists := md[helper.AccountsKey]
if exists {
userAccounts = njwt.StringToAccounts(accounts[0])
}

return &njwt.ExtendedAuthxClaim{
StandardClaims: jwt.StandardClaims{
Id: tokenID[0],
IssuedAt: issuedAt,
},
AuthxClaim: njwt.AuthxClaim{
UserID: userID[0],
Username: username[0],
AccountID: accountIDVal,
AccountName: accountNameVal,
EnvironmentID: envIDVal,
AccountAdmin: accountAdminVal,
ZoneID: zoneIDVal,
ZoneURL: zoneURLVal,
UserID: userID[0],
Username: username[0],
AccountID: accountIDVal,
AccountName: accountNameVal,
EnvironmentID: envIDVal,
EnvironmentAccountID: envAccountIDVal,
AccountAdmin: accountAdminVal,
ZoneID: zoneIDVal,
ZoneURL: zoneURLVal,
Accounts: userAccounts,
},
}, nil
}
Expand Down
19 changes: 16 additions & 3 deletions pkg/interceptors/mocks_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@ func GetTestJWTConfig() config.JWTConfig {

// GetTestAuthxClaim returns a random AuthxClaim to use in the tests
func GetTestAuthxClaim() *njwt.AuthxClaim {
return njwt.NewAuthxClaim(utils.GetTestUserId(), utils.GetTestUserName(),
utils.GetTestAccountId(), utils.GetTestUserName(),
utils.GetTestEnvironmentId(), false, "zone_id", "zone_url")
accounts := make([]njwt.UserAccountClaim, 0)
accounts = append(accounts, njwt.UserAccountClaim{
Id: utils.GetTestAccountId(),
Name: utils.GetTestAccountName(),
Role: "Admin",
})
return njwt.NewAuthxClaim(
utils.GetTestUserId(),
utils.GetTestUserName(),
accounts[0].Id,
accounts[0].Name,
utils.GetTestEnvironmentId(),
accounts[0].Role == "Admin",
"zone_id",
"zone_url",
accounts)
}

func CreateTestIncomingContext(header string, token string) (context.Context, context.CancelFunc) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/interceptors/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ var _ = ginkgo.Describe("Zone aware secrets manager", func() {

ginkgo.It("should be able to retrieve a zone secret", func() {
response := &grpc_jwt_go.SecretResponse{
JwtSecret: "zoneSecet",
JwtSecret: "zoneSecret",
}
secretsClientMock.EXPECT().Get(gomock.Any(), gomock.Any()).Return(response, nil)
secret, err := secretsManager.GetZoneSecret("uncached")
Expand Down
119 changes: 97 additions & 22 deletions pkg/njwt/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package njwt

import (
"encoding/json"
"github.com/napptive/nerrors/pkg/nerrors"
"strconv"
"time"

Expand Down Expand Up @@ -49,50 +51,92 @@ func NewClaim(issuer string, expiration time.Duration, pc interface{}) *Claim {
type AuthxClaim struct {
// UserID internal napptive user identifier.
UserID string
// Username is the unique name of the user, currently the github account name.
// Username is the unique name of the user, currently the GitHub account name.
Username string
// AccountID with the actual account identifier
// Deprecated: uses EnvironmentAccountID
AccountID string
// AccountName with the name of the account
// Deprecated: uses Accounts info
AccountName string
// EnvironmentID with the actual environment identifier
EnvironmentID string
// EnvironmentAccountID with the actual account identifier
EnvironmentAccountID string
// AccountAdmin with the admin account
// Deprecated: uses Accounts info
AccountAdmin bool
// ZoneID with the zone identifier
ZoneID string
// ZoneURL with the base URL of the current zone.
ZoneURL string
// Accounts with the information of the accounts to which a user belongs
Accounts []UserAccountClaim
}

// ToMap transforms the claim into a key-value map.
func (ac *AuthxClaim) ToMap() map[string]string {
return map[string]string{
helper.UserIDKey: ac.UserID,
helper.UsernameKey: ac.Username,
helper.AccountNameKey: ac.AccountName,
helper.AccountIDKey: ac.AccountID,
helper.EnvironmentIDKey: ac.EnvironmentID,
helper.AccountAdminKey: strconv.FormatBool(ac.AccountAdmin),
helper.ZoneIDKey: ac.ZoneID,
helper.ZoneURLKey: ac.ZoneURL,
}
type UserAccountClaim struct {
// Id with the account identifier
Id string
// Name with the account name
Name string
// Role with the user role in the account
Role string
}

// NewAuthxClaim creates a new instance of AuthxClaim.
func NewAuthxClaim(userID string, username string,
accountID string, accountName string,
environmentID string, accountAdmin bool,
zoneID string, zoneURL string) *AuthxClaim {
zoneID string, zoneURL string, accounts []UserAccountClaim) *AuthxClaim {
return &AuthxClaim{
UserID: userID,
Username: username,
AccountID: accountID,
AccountName: accountName,
EnvironmentID: environmentID,
AccountAdmin: accountAdmin,
ZoneID: zoneID,
ZoneURL: zoneURL,
UserID: userID,
Username: username,
AccountID: accountID,
AccountName: accountName,
EnvironmentID: environmentID,
AccountAdmin: accountAdmin,
ZoneID: zoneID,
ZoneURL: zoneURL,
EnvironmentAccountID: accountID,
Accounts: accounts,
}
}

func (ac *AuthxClaim) AccountsToString() (string, error) {
account, err := json.Marshal(ac.Accounts)
if err != nil {
return "", err
}
accountStr := string(account)
return accountStr, nil
}

func StringToAccounts(str string) []UserAccountClaim {
var accounts []UserAccountClaim
err := json.Unmarshal([]byte(str), &accounts)
if err != nil {
log.Error().Err(err).Msg("error converting string to authx claim")
}
return accounts
}

// ToMap transforms the claim into a key-value map.
func (ac *AuthxClaim) ToMap() map[string]string {
accounts, err := ac.AccountsToString()
if err != nil {
log.Error().Err(err).Msg("error converting authx claim to a map")
}
return map[string]string{
helper.UserIDKey: ac.UserID,
helper.UsernameKey: ac.Username,
helper.AccountNameKey: ac.AccountName,
helper.AccountIDKey: ac.AccountID,
helper.EnvironmentIDKey: ac.EnvironmentID,
helper.AccountAdminKey: strconv.FormatBool(ac.AccountAdmin),
helper.ZoneIDKey: ac.ZoneID,
helper.ZoneURLKey: ac.ZoneURL,
helper.EnvironmentAccountKey: ac.EnvironmentAccountID,
helper.AccountsKey: accounts,
}
}

Expand All @@ -103,6 +147,37 @@ func (ac *AuthxClaim) Print() {
Str("environment_id", ac.EnvironmentID).Bool("account_admin", ac.AccountAdmin).Str("zone_id", ac.ZoneID).Str("zone_url", ac.ZoneURL).Msg("AuthxClaim")
}

// IsAuthorized checks if the user (claim) has permissions to operate in an account
func (ac *AuthxClaim) IsAuthorized(accountName string, adminRoleRequired bool) bool {

authorized := false

for _, account := range ac.Accounts {
if account.Name == accountName {
if adminRoleRequired {
authorized = account.Role == "Admin"
} else {
authorized = true
}
return authorized
}
}
return authorized
}

// GetCurrentAccountName returns the EnvironmentAccountID name
// The AccountName is a deprecated field, the name can be retrieved from the accounts list
func (ac *AuthxClaim) GetCurrentAccountName() (*string, error) {
accountName := ""
for _, account := range ac.Accounts {
if account.Id == ac.EnvironmentAccountID {
accountName = account.Name
return &accountName, nil
}
}
return nil, nerrors.NewInternalError("error getting account name from claim. Account %s not found in the user accounts", ac.EnvironmentAccountID)
}

// GetAuthxClaim returns the AuthxClaim section of the claim.
func (c *Claim) GetAuthxClaim() *AuthxClaim {
return c.PersonalClaim.(*AuthxClaim)
Expand Down
29 changes: 24 additions & 5 deletions pkg/njwt/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,27 @@ import (
"github.com/onsi/gomega"
)

var _ = ginkgo.Describe("NJWT Token Manager tests", func() {
// GenerateTestAuthxClaim returns a random AuthxClaim to use in the tests
func GenerateTestAuthxClaim() *AuthxClaim {
accounts := make([]UserAccountClaim, 0)
accounts = append(accounts, UserAccountClaim{
Id: utils.GetTestAccountId(),
Name: utils.GetTestAccountName(),
Role: "Admin",
})
return NewAuthxClaim(
utils.GetTestUserId(),
utils.GetTestUserName(),
accounts[0].Id,
accounts[0].Name,
utils.GetTestEnvironmentId(),
accounts[0].Role == "Admin",
"zone_id",
"zone_url",
accounts)
}

var _ = ginkgo.Describe("njwt Token Manager tests", func() {
ginkgo.Context("Check token manager generator", func() {
tokenMgr := New()

Expand Down Expand Up @@ -54,8 +74,8 @@ var _ = ginkgo.Describe("NJWT Token Manager tests", func() {
})

ginkgo.It("The recover claim with personal claim", func() {
pc := NewAuthxClaim("userID", "username", utils.GetTestAccountId(), utils.GetTestUserName(),
utils.GetTestEnvironmentId(), true, "zoneID", "zoneURL")

pc := GenerateTestAuthxClaim()
claim := NewClaim("tt", time.Hour, pc)

secret := "secret"
Expand Down Expand Up @@ -94,8 +114,7 @@ var _ = ginkgo.Describe("NJWT Token Manager tests", func() {
ginkgo.Context("working with unverified tokens", func() {
tokenMgr := New()
ginkgo.It("should be able to retrieve raw information from a token", func() {
pc := NewAuthxClaim("userID", "username", utils.GetTestAccountId(), utils.GetTestUserName(),
utils.GetTestEnvironmentId(), true, "zoneID", "zoneURL")
pc := GenerateTestAuthxClaim()
claim := NewClaim("tt", time.Hour, pc)

secret := "secret"
Expand Down
8 changes: 5 additions & 3 deletions pkg/utils/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (
"syreclabs.com/go/faker"
)



// GetTestUserId returns a random UserId
func GetTestUserId() string {
return xid.New().String()
Expand All @@ -41,4 +39,8 @@ func GetTestEnvironmentId() string {
// GetTestUserName returns a random Username
func GetTestUserName() string {
return faker.Internet().UserName()
}
}

func GetTestAccountName() string {
return faker.Internet().UserName()
}

0 comments on commit cd28e5a

Please sign in to comment.