Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PG- 1624 accounts in authx claim #32

Merged
merged 12 commits into from
Jan 19, 2024
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()
}