Skip to content

Commit

Permalink
Merge pull request #1 from ihippik/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ihippik authored Dec 3, 2019
2 parents 3986406 + 8c77b21 commit 8b5936f
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 97 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.13

require (
github.com/devopsfaith/krakend v0.0.0-20191120165832-696db14166d2
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.5.0
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/devopsfaith/flatmap v0.0.0-20190628155411-90b768d6668b h1:mToxoDhxMLi
github.com/devopsfaith/flatmap v0.0.0-20190628155411-90b768d6668b/go.mod h1:J9Y/58s7wx7HbHT3i4UKNwLGuBB9qCf0/JUdEFGDPmA=
github.com/devopsfaith/krakend v0.0.0-20191120165832-696db14166d2 h1:OatZk2yQt4qdRgi7QslXNNtvd5ZLkno6gYUCID0E9hI=
github.com/devopsfaith/krakend v0.0.0-20191120165832-696db14166d2/go.mod h1:xM0O7nJcXuJWWL0PollS0NFBQLf1KJgA1BI2XAPgdmo=
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/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
Expand Down
44 changes: 44 additions & 0 deletions relyingparty/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package relyingparty

type err struct {
Code string `json:"code"`
ErrMsg string `json:"errMessage"`
}

const (
invalidTokenClaims = "INVALID_TOKEN_CLAIMS"
tokenExpired = "TOKEN_EXPIRED"
invalidToken = "INVALID_TOKEN"
roleNotMatch = "ROLE_NOT_MATCH"
)

// custom errors.
var (
tokenExpiredErr = &err{
Code: tokenExpired,
ErrMsg: "token expired",
}

invalidUserIDErr = &err{
Code: invalidTokenClaims,
ErrMsg: "invalid user id err",
}

invalidUserRoleErr = &err{
Code: invalidTokenClaims,
ErrMsg: "user role not exists",
}

accessDenied = &err{
Code: roleNotMatch,
ErrMsg: "access for the role denied",
}
)

// newErr create new err with specific code & msg.
func newErr(code string, msg string) *err {
return &err{
Code: code,
ErrMsg: msg,
}
}
7 changes: 5 additions & 2 deletions relyingparty/relying_party.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import (

// RelyingParty checks access to endpoint by access_token.
type RelyingParty struct {
cfg *sfConfig
cfg *rpConfig
endpointCfg *epConfig
}

// New creates a new RelyingParty.
func New(e config.ExtraConfig) (*RelyingParty, error) {
// TODO initialize MW.

// common config
return &RelyingParty{
cfg: getSFConfig(e),
cfg: getRpConfig(e),
}, nil
}
97 changes: 82 additions & 15 deletions relyingparty/router.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,112 @@
package relyingparty

import (
"fmt"
"net/http"
"strings"

"github.com/devopsfaith/krakend/config"
"github.com/devopsfaith/krakend/proxy"
krakendgin "github.com/devopsfaith/krakend/router/gin"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)

// EndpointMw is a function that decorates the received handlerFunc with
// gateway's own logic.
type EndpointMw func(gin.HandlerFunc) gin.HandlerFunc

const (
namespace = "gitlab.com/r-stat/krakend-mw/relyingparty"
namespace = "github.com/ihippik/krakend-mw/relyingparty"
HeaderAuthorization = "Authorization"
HeaderUserID = "User-Id"
TokenType = "Bearer"
)

type EndpointMw func(gin.HandlerFunc) gin.HandlerFunc

// NewHandlerFactory builds a oauth2 wrapper over the received handler factory.
func NewHandlerFactory(next krakendgin.HandlerFactory, sf *RelyingParty) krakendgin.HandlerFactory {
// Run for each endpoints.
func NewHandlerFactory(next krakendgin.HandlerFactory, rp *RelyingParty) krakendgin.HandlerFactory {
return func(remote *config.EndpointConfig, p proxy.Proxy) gin.HandlerFunc {
handlerFunc := next(remote, p)

_, ok := remote.ExtraConfig[namespace]
eCfg, ok := remote.ExtraConfig[namespace]
if !ok {
return handlerFunc
}

return newEndpointSelfHostMw(sf)(handlerFunc)
rp.endpointCfg = getEpConfig(eCfg)
return newEndpointRelyingPartyMw(rp)(handlerFunc)
}
}

// newEndpointSelfHostMw is the handler middlware that represents endpoints of
// newEndpointRelyingPartyMw is the handler middlware that represents endpoints of
// gateway itself.
func newEndpointSelfHostMw(sf *RelyingParty) EndpointMw {
func newEndpointRelyingPartyMw(rp *RelyingParty) EndpointMw {
return func(next gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
// Return config in response.
c.AbortWithStatusJSON(http.StatusOK, sf.cfg)
logrus.Info("SF: config provided.")
userToken := c.GetHeader(HeaderAuthorization)
if len(userToken) == 0 {
logrus.Warnln("empty user token")
c.AbortWithStatusJSON(http.StatusUnauthorized, newErr(invalidToken, "token not exists"))
return
}
items := strings.Split(userToken, " ")
if len(items) != 2 || items[0] != TokenType {
logrus.Warnln("invalid token")
c.AbortWithStatusJSON(http.StatusUnauthorized, newErr(invalidToken, "token is malformed"))
return
}

token, err := jwt.Parse(items[1], 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(rp.cfg.TokenSecret), nil
})
if err != nil {
logrus.WithError(err).Warnln("parse token err")
c.AbortWithStatusJSON(http.StatusUnauthorized, tokenExpiredErr)
return
}

if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
userID, ok := claims["user_id"].(string)
if !ok {
logrus.Warnln("user id in claims not exist")
c.AbortWithStatusJSON(http.StatusUnauthorized, invalidUserIDErr)
return
}
userRole, ok := claims["user_role"].(string)
if !ok {
logrus.Warnln("user role in claims not exist")
c.AbortWithStatusJSON(http.StatusUnauthorized, invalidUserRoleErr)
return
}
if !matchRoles(userRole, rp.endpointCfg.Roles) {
logrus.WithField("role", userRole).Warnln("access denied")
c.AbortWithStatusJSON(http.StatusForbidden, accessDenied)
return
}
c.Request.Header.Set(HeaderUserID, userID)
} else {
logrus.WithError(err).Warnln("claims err")
c.AbortWithStatusJSON(
http.StatusUnauthorized,
newErr(
invalidTokenClaims,
err.Error(),
),
)
return
}
next(c)
}
}
}

// matchRoles looking for matching roles
func matchRoles(userRole string, acceptRoles []string) bool {
for _, r := range acceptRoles {
if r == userRole {
return true
}
}
return false
}
55 changes: 0 additions & 55 deletions relyingparty/router_test.go

This file was deleted.

62 changes: 37 additions & 25 deletions relyingparty/rp_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,56 @@ import (
"github.com/devopsfaith/krakend/config"
)

// sfConfig is the custom config struct containing the params for the Auth Checker.
type sfConfig struct {
// AuthServerURI is URI of admin API of OIDC.
AuthServerURI string `json:"authServerURI"`
// AuthorizationEndpoint is a relative path to authorization endpoint.
AuthorizationEndpoint string `json:"authorizationEndpoint"`
// TokenEndpoint is a relative path to authorization endpoint.
TokenEndpoint string `json:"tokenEndpoint"`
// rpConfig is the custom config struct containing the params for the Auth Checker.
type rpConfig struct {
TokenSecret string `json:"token_secret"`
}

// sfZeroCfg is the zero value for the sfConfig struct.
var sfZeroCfg = sfConfig{}
type epConfig struct {
Roles []string `json:"roles"`
}

// sfNamespace is the key for getting config from extraConfig global section.
// rpNamespace is the key for getting config from extraConfig global section.
// Use underscores instead of dots.
const sfNamespace = "git_omprussia_ru/auth/krakendself"
const rpNamespace = "github_com/ihippik/krakend-mw/relyingparty"

// rpZeroCfg is the zero value for the rpConfig struct.
var rpZeroCfg = rpConfig{}

// getSFConfig parses the extra config for the Auth Checker.
func getSFConfig(e config.ExtraConfig) *sfConfig {
v, ok := e[sfNamespace]
// getRpConfig parses the extra config for the RP.
func getRpConfig(e config.ExtraConfig) *rpConfig {
v, ok := e[rpNamespace]
if !ok {
return &sfZeroCfg
return &rpZeroCfg
}
tmp, ok := v.(map[string]interface{})
if !ok {
return &sfZeroCfg
return &rpZeroCfg
}

cfg := &sfConfig{}
if v, ok := tmp["auth_server_uri"]; ok {
cfg.AuthServerURI = fmt.Sprintf("%v", v)
cfg := &rpConfig{}
if v, ok := tmp["token_secret"]; ok {
cfg.TokenSecret = fmt.Sprintf("%v", v)
}
if v, ok := tmp["authorization_endpoint"]; ok {
cfg.AuthorizationEndpoint = fmt.Sprintf("%v", v)
return cfg
}

// getRpConfig parses the extra config for the Endpoint.
func getEpConfig(extra interface{}) *epConfig {
cfg := new(epConfig)
var roles []string
e := extra.(map[string]interface{})
tmp, ok := e["roles"]
if !ok {
return cfg
}
rolesTmp, ok := tmp.([]interface{})
if !ok {
return cfg
}
if v, ok := tmp["token_endpoint"]; ok {
cfg.TokenEndpoint = fmt.Sprintf("%v", v)
for _, val := range rolesTmp {
roles = append(roles, val.(string))
}

cfg.Roles = roles
return cfg
}

0 comments on commit 8b5936f

Please sign in to comment.