From 30d370a36d926eff50de7779da7f6664a2a29c20 Mon Sep 17 00:00:00 2001 From: anandrgitnirman Date: Thu, 21 May 2020 02:46:34 +0530 Subject: [PATCH 1/2] #312 - Support for Token Generation and Verification --- config/config.go | 2 ++ token/jwttoken.go | 60 ++++++++++++++++++++++++++++++++++++++ token/jwttoken_test.go | 51 ++++++++++++++++++++++++++++++++ token/token_service_api.go | 10 +++++++ 4 files changed, 123 insertions(+) create mode 100644 token/jwttoken.go create mode 100644 token/jwttoken_test.go create mode 100644 token/token_service_api.go diff --git a/config/config.go b/config/config.go index 5135f1ff..269af0fd 100644 --- a/config/config.go +++ b/config/config.go @@ -56,6 +56,8 @@ const ( PvtKeyForMetering = "pvt_key_for_metering" NotificationServiceEndpoint = "notification_svc_end_point" ServiceHeartbeatType = "service_heartbeat_type" + TokenExpiryInSeconds = "token_expiry_in_seconds" + TokenSecretKey = "token_secret_key" //none|grpc|http //This defaultConfigJson will eventually be replaced by DefaultDaemonConfigurationSchema defaultConfigJson string = ` diff --git a/token/jwttoken.go b/token/jwttoken.go new file mode 100644 index 00000000..a1de142e --- /dev/null +++ b/token/jwttoken.go @@ -0,0 +1,60 @@ +package token + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/singnet/snet-daemon/config" + "strings" + "time" +) + +type customJWTokenClaimsImpl struct { + getGroupId func() string +} + +func (service customJWTokenClaimsImpl) CreateToken(payLoad PayLoad) (CustomToken, error) { + atClaims := jwt.MapClaims{} + atClaims["payload"] = fmt.Sprintf("%v", payLoad) + atClaims["orgId"] = config.GetString(config.OrganizationId) + atClaims["groupId"] = service.getGroupId() + //set the Expiry of the Token generated + atClaims["exp"] = time.Now().UTC(). + Add(time.Second * time.Duration(config.GetInt(config.TokenExpiryInSeconds))).Unix() + jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + return jwtToken.SignedString([]byte(config.GetString(config.TokenSecretKey))) +} + +func (service customJWTokenClaimsImpl) VerifyToken(receivedToken CustomToken, payLoad PayLoad) (err error) { + tokenString := fmt.Sprintf("%v", receivedToken) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method : %v", token.Header["alg"]) + } + return []byte(config.GetString(config.TokenSecretKey)), nil + }) + if err != nil { + return err + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + if err = service.checkJwtTokenClaims(claims, payLoad); err != nil { + return err + } + } + return nil +} + +func (service customJWTokenClaimsImpl) checkJwtTokenClaims(claims jwt.MapClaims, payload PayLoad) (err error) { + if strings.Compare(fmt.Sprintf("%v", claims["payload"]), fmt.Sprintf("%v", payload)) != 0 { + return fmt.Errorf("payload %v used to generate the Token doesnt match expected values", claims["payload"]) + } + + if strings.Compare(fmt.Sprintf("%v", claims["orgId"]), config.GetString(config.OrganizationId)) != 0 { + return fmt.Errorf("organization %v is not associated with this Daemon", claims["orgId"]) + } + + if strings.Compare(fmt.Sprintf("%v", claims["groupId"]), service.getGroupId()) != 0 { + return fmt.Errorf("groupId %v is not associated with this Daemon", claims["groupId"]) + } + return nil +} diff --git a/token/jwttoken_test.go b/token/jwttoken_test.go new file mode 100644 index 00000000..b81ffc69 --- /dev/null +++ b/token/jwttoken_test.go @@ -0,0 +1,51 @@ +package token + +import ( + "testing" + "time" + + "github.com/singnet/snet-daemon/config" + "github.com/stretchr/testify/assert" +) + +func Test_customJWTokenClaimsImpl_CreateToken(t *testing.T) { + tokenImpl := &customJWTokenClaimsImpl{ + getGroupId: func() string { + return "GroupID" + }, + } + token, err := tokenImpl.CreateToken("any struct") + assert.Nil(t, err) + assert.NotNil(t, token) + err = tokenImpl.VerifyToken(token, "any struct") + config.Vip().Set(config.TokenExpiryInSeconds, 1) + token, err = tokenImpl.CreateToken("any struct") + time.Sleep(time.Second * 5) + assert.Nil(t, err) + err = tokenImpl.VerifyToken(token, "any struct") + assert.Equal(t, "Token is expired", err.Error()) + +} + +func Test_customJWTokenClaimsImpl_checkJwtTokenClaims(t *testing.T) { + tokenImpl := &customJWTokenClaimsImpl{ + getGroupId: func() string { + return "GroupID" + }, + } + config.Vip().Set(config.TokenExpiryInSeconds, 50) + token, err := tokenImpl.CreateToken("any struct") + err = tokenImpl.VerifyToken(token, "different struct") + assert.Equal(t, "payload any struct used to generate the Token doesnt match expected values", err.Error()) + config.Vip().Set(config.OrganizationId, "differentOrganization") + err = tokenImpl.VerifyToken(token, "any struct") + assert.Equal(t, "organization ExampleOrganizationId is not associated with this Daemon", err.Error()) + config.Vip().Set(config.OrganizationId, "ExampleOrganizationId") + tokenImpl2 := &customJWTokenClaimsImpl{ + getGroupId: func() string { + return "GroupID2" + }, + } + err = tokenImpl2.VerifyToken(token, "any struct") + assert.Equal(t, "groupId GroupID is not associated with this Daemon", err.Error()) +} diff --git a/token/token_service_api.go b/token/token_service_api.go new file mode 100644 index 00000000..b1131882 --- /dev/null +++ b/token/token_service_api.go @@ -0,0 +1,10 @@ +package token + +type PayLoad interface{} +type CustomToken interface{} + +// Token.Service interface is an API for creating and verifying tokens +type Service interface { + CreateToken(key PayLoad) (token CustomToken, err error) + VerifyToken(token CustomToken, key PayLoad) (err error) +} From 2e52f983086d8e55fc8ae0ae197106985b57b253 Mon Sep 17 00:00:00 2001 From: anandrgitnirman Date: Thu, 21 May 2020 14:23:29 +0530 Subject: [PATCH 2/2] #312 - Renamed the struct to be in line with interface , added initialization in components --- snetd/cmd/components.go | 12 ++++++++++++ token/jwttoken.go | 18 ++++++++++++++---- token/jwttoken_test.go | 6 +++--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/snetd/cmd/components.go b/snetd/cmd/components.go index 01daf0ee..682c54dc 100644 --- a/snetd/cmd/components.go +++ b/snetd/cmd/components.go @@ -9,6 +9,7 @@ import ( "github.com/singnet/snet-daemon/configuration_service" "github.com/singnet/snet-daemon/metrics" "github.com/singnet/snet-daemon/pricing" + "github.com/singnet/snet-daemon/token" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -50,6 +51,7 @@ type Components struct { freeCallUserService escrow.FreeCallUserService freeCallUserStorage *escrow.FreeCallUserStorage freeCallLockerStorage *escrow.PrefixedAtomicStorage + tokenService token.Service } func InitComponents(cmd *cobra.Command) (components *Components) { @@ -505,3 +507,13 @@ func (components *Components) ConfigurationService() *configuration_service.Conf return components.configurationService } + +func (components *Components) TokenService() token.Service { + if components.tokenService != nil { + return components.tokenService + } + + components.tokenService = token.NewJWTTokenService(*components.OrganizationMetaData()) + + return components.tokenService +} diff --git a/token/jwttoken.go b/token/jwttoken.go index a1de142e..a653fe3b 100644 --- a/token/jwttoken.go +++ b/token/jwttoken.go @@ -3,16 +3,26 @@ package token import ( "fmt" "github.com/dgrijalva/jwt-go" + "github.com/singnet/snet-daemon/blockchain" "github.com/singnet/snet-daemon/config" "strings" "time" ) -type customJWTokenClaimsImpl struct { +type customJWTokenServiceImpl struct { getGroupId func() string } -func (service customJWTokenClaimsImpl) CreateToken(payLoad PayLoad) (CustomToken, error) { +//This will be used in components as a service to Create and Validate tokens +func NewJWTTokenService(data blockchain.OrganizationMetaData) Service { + return &customJWTokenServiceImpl{ + getGroupId: func() string { + return data.GetGroupIdString() + }, + } +} + +func (service customJWTokenServiceImpl) CreateToken(payLoad PayLoad) (CustomToken, error) { atClaims := jwt.MapClaims{} atClaims["payload"] = fmt.Sprintf("%v", payLoad) atClaims["orgId"] = config.GetString(config.OrganizationId) @@ -24,7 +34,7 @@ func (service customJWTokenClaimsImpl) CreateToken(payLoad PayLoad) (CustomToken return jwtToken.SignedString([]byte(config.GetString(config.TokenSecretKey))) } -func (service customJWTokenClaimsImpl) VerifyToken(receivedToken CustomToken, payLoad PayLoad) (err error) { +func (service customJWTokenServiceImpl) VerifyToken(receivedToken CustomToken, payLoad PayLoad) (err error) { tokenString := fmt.Sprintf("%v", receivedToken) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { @@ -44,7 +54,7 @@ func (service customJWTokenClaimsImpl) VerifyToken(receivedToken CustomToken, pa return nil } -func (service customJWTokenClaimsImpl) checkJwtTokenClaims(claims jwt.MapClaims, payload PayLoad) (err error) { +func (service customJWTokenServiceImpl) checkJwtTokenClaims(claims jwt.MapClaims, payload PayLoad) (err error) { if strings.Compare(fmt.Sprintf("%v", claims["payload"]), fmt.Sprintf("%v", payload)) != 0 { return fmt.Errorf("payload %v used to generate the Token doesnt match expected values", claims["payload"]) } diff --git a/token/jwttoken_test.go b/token/jwttoken_test.go index b81ffc69..8bf75c26 100644 --- a/token/jwttoken_test.go +++ b/token/jwttoken_test.go @@ -9,7 +9,7 @@ import ( ) func Test_customJWTokenClaimsImpl_CreateToken(t *testing.T) { - tokenImpl := &customJWTokenClaimsImpl{ + tokenImpl := &customJWTokenServiceImpl{ getGroupId: func() string { return "GroupID" }, @@ -28,7 +28,7 @@ func Test_customJWTokenClaimsImpl_CreateToken(t *testing.T) { } func Test_customJWTokenClaimsImpl_checkJwtTokenClaims(t *testing.T) { - tokenImpl := &customJWTokenClaimsImpl{ + tokenImpl := &customJWTokenServiceImpl{ getGroupId: func() string { return "GroupID" }, @@ -41,7 +41,7 @@ func Test_customJWTokenClaimsImpl_checkJwtTokenClaims(t *testing.T) { err = tokenImpl.VerifyToken(token, "any struct") assert.Equal(t, "organization ExampleOrganizationId is not associated with this Daemon", err.Error()) config.Vip().Set(config.OrganizationId, "ExampleOrganizationId") - tokenImpl2 := &customJWTokenClaimsImpl{ + tokenImpl2 := &customJWTokenServiceImpl{ getGroupId: func() string { return "GroupID2" },