Skip to content

Commit

Permalink
Move token verification to auth interceptors
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 committed May 4, 2021
1 parent 2b7e790 commit f6be8c9
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 175 deletions.
114 changes: 52 additions & 62 deletions internal/grpc/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ import (
"fmt"
"strings"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/auth/scope"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/sharedconf"
Expand Down Expand Up @@ -117,7 +116,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI
return nil, status.Errorf(codes.Unauthenticated, "auth: core access token not found")
}

// validate the token
// validate the token and ensure access to the resource is allowed
u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr)
if err != nil {
log.Warn().Err(err).Msg("access token is invalid")
Expand Down Expand Up @@ -188,7 +187,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe
return status.Errorf(codes.Unauthenticated, "auth: core access token not found")
}

// validate the token
// validate the token and ensure access to the resource is allowed
u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr)
if err != nil {
log.Warn().Err(err).Msg("access token is invalid")
Expand Down Expand Up @@ -217,74 +216,65 @@ func (ss *wrappedServerStream) Context() context.Context {
}

func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string) (*userpb.User, error) {
u, scope, err := mgr.DismantleToken(ctx, tkn, req)
log := appctx.GetLogger(ctx)
u, tokenScope, err := mgr.DismantleToken(ctx, tkn)
if err != nil {
return nil, err
}

// Check if the err returned is PermissionDenied
if _, ok := err.(errtypes.PermissionDenied); ok {
// Check if req is of type *provider.Reference_Path
// If yes, the request might be coming from a share where the accessor is
// trying to impersonate the owner, since the share manager doesn't know the
// share path.
if ref, ok := extractRef(req); ok {
if ref.GetPath() != "" {

// Try to extract the resource ID from the scope resource.
// Currently, we only check for public shares, but this will be extended
// for OCM shares, guest accounts, etc.
log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath())
var share link.PublicShare
err = utils.UnmarshalJSONToProtoV1(scope["publicshare"].Resource.Value, &share)
if err != nil {
return nil, err
}
// Check if access to the resource is in the scope of the token
ok, err := scope.VerifyScope(tokenScope, req)
if err != nil {
return nil, errtypes.InternalError("error verifying scope of access token")
}
if ok {
return u, nil
}

client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return nil, err
}
// Check if req is of type *provider.Reference_Path
// If yes, the request might be coming from a share where the accessor is
// trying to impersonate the owner, since the share manager doesn't know the
// share path.
if ref, ok := extractRef(req); ok {
if ref.GetPath() != "" {

// Try to extract the resource ID from the scope resource.
// Currently, we only check for public shares, but this will be extended
// for OCM shares, guest accounts, etc.
log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath())
var share link.PublicShare
err = utils.UnmarshalJSONToProtoV1(tokenScope["publicshare"].Resource.Value, &share)
if err != nil {
return nil, err
}

// Since the public share is obtained from the scope, the current token
// has access to it.
statReq := &provider.StatRequest{
Ref: &provider.Reference{
Spec: &provider.Reference_Id{Id: share.ResourceId},
},
}
client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return nil, err
}

statResponse, err := client.Stat(ctx, statReq)
if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK {
return nil, err
}
// Since the public share is obtained from the scope, the current token
// has access to it.
statReq := &provider.StatRequest{
Ref: &provider.Reference{
Spec: &provider.Reference_Id{Id: share.ResourceId},
},
}

if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) {
// The path corresponds to the resource to which the token has access.
// Add it to the scope map
val, err := utils.MarshalProtoV1ToJSON(&provider.Reference{
Spec: &provider.Reference_Path{Path: statResponse.Info.Path},
})
if err != nil {
return nil, err
}
scopeVal := &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: scope["publicshare"].Role,
}

tkn, err = mgr.AddScopeToToken(ctx, tkn, "publicshare:path", scopeVal)
if err != nil {
return nil, err
}
return dismantleToken(ctx, tkn, req, mgr, gatewayAddr)
}
statResponse, err := client.Stat(ctx, statReq)
if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK {
return nil, err
}

if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) {
// The path corresponds to the resource to which the token has access.
// We allow access to it.
return u, nil
}
}
}

return u, err
return nil, err
}

func extractRef(req interface{}) (*provider.Reference, bool) {
Expand Down
2 changes: 1 addition & 1 deletion internal/grpc/services/gateway/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
}

func (s *svc) WhoAmI(ctx context.Context, req *gateway.WhoAmIRequest) (*gateway.WhoAmIResponse, error) {
u, _, err := s.tokenmgr.DismantleToken(ctx, req.Token, nil)
u, _, err := s.tokenmgr.DismantleToken(ctx, req.Token)
if err != nil {
err = errors.Wrap(err, "gateway: error getting user from token")
return &gateway.WhoAmIResponse{
Expand Down
14 changes: 13 additions & 1 deletion internal/http/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
tokenwriterregistry "github.com/cs3org/reva/internal/http/interceptors/auth/tokenwriter/registry"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/auth"
"github.com/cs3org/reva/pkg/auth/scope"
"github.com/cs3org/reva/pkg/rgrpc/status"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/rhttp/global"
Expand Down Expand Up @@ -233,12 +234,23 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err
}

// validate token
u, _, err := tokenManager.DismantleToken(r.Context(), tkn, r.URL.Path)
u, tokenScope, err := tokenManager.DismantleToken(r.Context(), tkn)
if err != nil {
log.Error().Err(err).Msg("error dismantling token")
w.WriteHeader(http.StatusUnauthorized)
return
}
// ensure access to the resource is allowed
ok, err := scope.VerifyScope(tokenScope, r.URL.Path)
if err != nil {
log.Error().Err(err).Msg("error verifying scope of access token")
w.WriteHeader(http.StatusInternalServerError)
}
if !ok {
log.Error().Err(err).Msg("access to resource not allowed")
w.WriteHeader(http.StatusUnauthorized)
return
}

// store user and core access token in context.
ctx = user.ContextSetUser(ctx, u)
Expand Down
64 changes: 0 additions & 64 deletions pkg/auth/scope/publicsharepath.go

This file was deleted.

5 changes: 2 additions & 3 deletions pkg/auth/scope/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ import (
type Verifier func(*authpb.Scope, interface{}) (bool, error)

var supportedScopes = map[string]Verifier{
"user": userScope,
"publicshare": publicshareScope,
"publicshare:path": publicsharepathScope,
"user": userScope,
"publicshare": publicshareScope,
}

// VerifyScope is the function to be called when dismantling tokens to check if
Expand Down
11 changes: 1 addition & 10 deletions pkg/token/manager/demo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,14 @@ func (m *manager) MintToken(ctx context.Context, u *user.User, scope map[string]
return token, nil
}

func (m *manager) DismantleToken(ctx context.Context, token string, resource interface{}) (*user.User, map[string]*auth.Scope, error) {
func (m *manager) DismantleToken(ctx context.Context, token string) (*user.User, map[string]*auth.Scope, error) {
c, err := decode(token)
if err != nil {
return nil, nil, errors.Wrap(err, "error decoding claims")
}
return c.User, c.Scope, nil
}

func (m *manager) AddScopeToToken(ctx context.Context, token string, scopeKey string, scope *auth.Scope) (string, error) {
c, err := decode(token)
if err != nil {
return "", errors.Wrap(err, "error decoding claims")
}
c.Scope[scopeKey] = scope
return encode(c)
}

// from https://stackoverflow.com/questions/28020070/golang-serialize-and-deserialize-back
// go binary encoder
func encode(c *claims) (string, error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/token/manager/demo/demo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestEncodeDecode(t *testing.T) {
t.Fatal(err)
}

decodedUser, decodedScope, err := m.DismantleToken(ctx, encoded, nil)
decodedUser, decodedScope, err := m.DismantleToken(ctx, encoded)
if err != nil {
t.Fatal(err)
}
Expand Down
32 changes: 1 addition & 31 deletions pkg/token/manager/jwt/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (

auth "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/cs3org/reva/pkg/auth/scope"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/sharedconf"
"github.com/cs3org/reva/pkg/token"
Expand Down Expand Up @@ -109,7 +108,7 @@ func (m *manager) MintToken(ctx context.Context, u *user.User, scope map[string]
return tkn, nil
}

func (m *manager) DismantleToken(ctx context.Context, tkn string, resource interface{}) (*user.User, map[string]*auth.Scope, error) {
func (m *manager) DismantleToken(ctx context.Context, tkn string) (*user.User, map[string]*auth.Scope, error) {
token, err := jwt.ParseWithClaims(tkn, &claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(m.conf.Secret), nil
})
Expand All @@ -119,37 +118,8 @@ func (m *manager) DismantleToken(ctx context.Context, tkn string, resource inter
}

if claims, ok := token.Claims.(*claims); ok && token.Valid {
ok, err = scope.VerifyScope(claims.Scope, resource)
if err != nil {
return nil, nil, errtypes.InternalError("error verifying scope of access token")
}
if !ok {
// Pass the allowed scope of the token. This might be needed for the
// path/resource ID resolution, because when the token was minted, the auth provider
// might be aware of only one of these references. In such cases, it's expectec that
// the caller will resolve the reference and pass the expected resource.
return nil, claims.Scope, errtypes.PermissionDenied("token missing necessary scope access")
}
return claims.User, claims.Scope, nil
}

return nil, nil, errtypes.InvalidCredentials("invalid token")
}

func (m *manager) AddScopeToToken(ctx context.Context, tkn string, scopeKey string, scope *auth.Scope) (string, error) {
token, err := jwt.ParseWithClaims(tkn, &claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(m.conf.Secret), nil
})

if err != nil {
return "", errors.Wrap(err, "error parsing token")
}

if claims, ok := token.Claims.(*claims); ok && token.Valid {
claims.Scope[scopeKey] = scope
return m.MintToken(ctx, claims.User, claims.Scope)
}

return "", errtypes.InvalidCredentials("invalid token")

}
3 changes: 1 addition & 2 deletions pkg/token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ const tokenKey key = iota
// Manager is the interface to implement to sign and verify tokens
type Manager interface {
MintToken(ctx context.Context, u *user.User, scope map[string]*auth.Scope) (string, error)
DismantleToken(ctx context.Context, token string, resource interface{}) (*user.User, map[string]*auth.Scope, error)
AddScopeToToken(ctx context.Context, token string, scopeKey string, scope *auth.Scope) (string, error)
DismantleToken(ctx context.Context, token string) (*user.User, map[string]*auth.Scope, error)
}

// ContextGetToken returns the token if set in the given context.
Expand Down

0 comments on commit f6be8c9

Please sign in to comment.