diff --git a/changelog/unreleased/scope-based-tokens.md b/changelog/unreleased/scope-based-tokens.md new file mode 100644 index 00000000000..fb1b9139526 --- /dev/null +++ b/changelog/unreleased/scope-based-tokens.md @@ -0,0 +1 @@ +Enhancement: Mint scope-based access tokens for RBAC diff --git a/examples/storage-references/gateway.toml b/examples/storage-references/gateway.toml index bb486363fec..402935c9ee6 100644 --- a/examples/storage-references/gateway.toml +++ b/examples/storage-references/gateway.toml @@ -9,10 +9,16 @@ home_provider = "/home" [grpc.services.storageregistry.drivers.static.rules] "/home" = {"address" = "localhost:17000"} "/reva" = {"address" = "localhost:18000"} +"/public" = {"address" = "localhost:16000"} "123e4567-e89b-12d3-a456-426655440000" = {"address" = "localhost:18000"} [grpc.services.authprovider] [grpc.services.authregistry] + +[grpc.services.authregistry.drivers.static.rules] +basic = "localhost:19000" +publicshares = "localhost:16000" + [grpc.services.userprovider] [grpc.services.usershareprovider] [grpc.services.groupprovider] diff --git a/examples/storage-references/storage-public.toml b/examples/storage-references/storage-public.toml new file mode 100644 index 00000000000..8d409908c54 --- /dev/null +++ b/examples/storage-references/storage-public.toml @@ -0,0 +1,15 @@ +[grpc] +address = "0.0.0.0:16000" + +[grpc.services.publicstorageprovider] +driver = "localhome" +mount_path = "/public" +mount_id = "123e4567-e89b-12d3-a456-426655440000" +data_server_url = "http://localhost:16001/data" +gateway_addr = "localhost:19000" + +[grpc.services.authprovider] +auth_manager = "publicshares" + +[grpc.services.authprovider.auth_managers.publicshares] +gateway_addr = "localhost:19000" diff --git a/go.mod b/go.mod index 70ed8d2b466..ee1e55d4812 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,7 @@ require ( go 1.16 replace ( + github.com/cs3org/go-cs3apis => ../cs3apis/build/go-cs3apis github.com/eventials/go-tus => github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1 google.golang.org/grpc => google.golang.org/grpc v1.26.0 // temporary downgrade diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 9b3599871a1..8252954779e 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -88,7 +88,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI // to decide the storage provider. tkn, ok := token.ContextGetToken(ctx) if ok { - u, err := tokenManager.DismantleToken(ctx, tkn) + u, err := tokenManager.DismantleToken(ctx, tkn, req) if err == nil { ctx = user.ContextSetUser(ctx, u) } @@ -96,6 +96,8 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI return handler(ctx, req) } + log.Info().Msgf("GRPC unary interceptor %s, %+v", info.FullMethod, req) + span.AddAttributes(trace.BoolAttribute("auth_enabled", true)) tkn, ok := token.ContextGetToken(ctx) @@ -106,7 +108,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI } // validate the token - u, err := tokenManager.DismantleToken(ctx, tkn) + u, err := tokenManager.DismantleToken(ctx, tkn, req) if err != nil { log.Warn().Msg("access token is invalid") return nil, status.Errorf(codes.Unauthenticated, "auth: core access token is invalid") @@ -151,6 +153,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { ctx := ss.Context() log := appctx.GetLogger(ctx) + log.Info().Msgf("GRPC stream interceptor %s, %+v", info.FullMethod, unprotected) if utils.Skip(info.FullMethod, unprotected) { log.Debug().Str("method", info.FullMethod).Msg("skipping auth") @@ -159,7 +162,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe // to decide the storage provider. tkn, ok := token.ContextGetToken(ctx) if ok { - u, err := tokenManager.DismantleToken(ctx, tkn) + u, err := tokenManager.DismantleToken(ctx, tkn, ss) if err == nil { ctx = user.ContextSetUser(ctx, u) ss = newWrappedServerStream(ctx, ss) @@ -177,7 +180,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe } // validate the token - claims, err := tokenManager.DismantleToken(ctx, tkn) + claims, err := tokenManager.DismantleToken(ctx, tkn, ss) if err != nil { log.Warn().Msg("access token invalid") return status.Errorf(codes.Unauthenticated, "auth: core access token is invalid") diff --git a/internal/grpc/services/appprovider/appprovider.go b/internal/grpc/services/appprovider/appprovider.go index 90dd2c4a91d..2186c935024 100644 --- a/internal/grpc/services/appprovider/appprovider.go +++ b/internal/grpc/services/appprovider/appprovider.go @@ -35,6 +35,7 @@ import ( "github.com/cs3org/reva/pkg/app" "github.com/cs3org/reva/pkg/app/provider/demo" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rhttp" @@ -274,3 +275,7 @@ func (s *service) OpenFileInAppProvider(ctx context.Context, req *providerpb.Ope AppProviderUrl: appProviderURL, }, nil } + +func (s *service) OpenInApp(ctx context.Context, req *providerpb.OpenInAppRequest) (*providerpb.OpenInAppResponse, error) { + return nil, errtypes.NotSupported("Unimplemented") +} diff --git a/internal/grpc/services/authprovider/authprovider.go b/internal/grpc/services/authprovider/authprovider.go index abf88f43bef..bb4d179b8e9 100644 --- a/internal/grpc/services/authprovider/authprovider.go +++ b/internal/grpc/services/authprovider/authprovider.go @@ -108,13 +108,14 @@ func (s *service) Authenticate(ctx context.Context, req *provider.AuthenticateRe username := req.ClientId password := req.ClientSecret - u, err := s.authmgr.Authenticate(ctx, username, password) + u, scope, err := s.authmgr.Authenticate(ctx, username, password) switch v := err.(type) { case nil: log.Info().Msgf("user %s authenticated", u.String()) return &provider.AuthenticateResponse{ - Status: status.NewOK(ctx), - User: u, + Status: status.NewOK(ctx), + User: u, + TokenScope: scope, }, nil case errtypes.InvalidCredentials: return &provider.AuthenticateResponse{ diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 487094fa6ad..40958f0e936 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -95,7 +95,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest user := res.User - token, err := s.tokenmgr.MintToken(ctx, user) + token, err := s.tokenmgr.MintToken(ctx, user, res.TokenScope) if err != nil { err = errors.Wrap(err, "authsvc: error in MintToken") res := &gateway.AuthenticateResponse{ @@ -145,7 +145,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) + u, err := s.tokenmgr.DismantleToken(ctx, req.Token, nil) if err != nil { err = errors.Wrap(err, "gateway: error getting user from token") return &gateway.WhoAmIResponse{ diff --git a/internal/http/interceptors/auth/auth.go b/internal/http/interceptors/auth/auth.go index 37bd8ae0d9b..c98496ecf0b 100644 --- a/internal/http/interceptors/auth/auth.go +++ b/internal/http/interceptors/auth/auth.go @@ -154,6 +154,7 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err } log := appctx.GetLogger(ctx) + log.Info().Msgf("HTTP interceptor %s, %+v", r.URL.Path, unprotected) // skip auth for urls set in the config. // TODO(labkode): maybe use method:url to bypass auth. @@ -234,7 +235,7 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err } // validate token - claims, err := tokenManager.DismantleToken(r.Context(), tkn) + claims, err := tokenManager.DismantleToken(r.Context(), tkn, r.URL.Path) if err != nil { log.Error().Err(err).Msg("error dismantling token") w.WriteHeader(http.StatusUnauthorized) diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 01ecbd4b93e..241228e9422 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -22,13 +22,14 @@ import ( "context" "net/http" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" ) // Manager is the interface to implement to authenticate users type Manager interface { - Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error) + Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, []*authpb.Scope, error) } // Credentials contains the auth type, client id and secret. diff --git a/pkg/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index 987ac2437ec..015d07a0ca3 100644 --- a/pkg/auth/manager/demo/demo.go +++ b/pkg/auth/manager/demo/demo.go @@ -20,8 +20,12 @@ package demo import ( "context" + "encoding/json" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" "github.com/cs3org/reva/pkg/errtypes" @@ -48,13 +52,32 @@ func New(m map[string]interface{}) (auth.Manager, error) { return &manager{credentials: creds}, nil } -func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error) { +func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, []*authpb.Scope, error) { + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/", + }, + } + val, err := json.Marshal(ref) + if err != nil { + return nil, nil, err + } + scope := []*authpb.Scope{ + &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: authpb.Role_ROLE_OWNER, + }, + } + if c, ok := m.credentials[clientID]; ok { if c.Secret == clientSecret { - return c.User, nil + return c.User, scope, nil } } - return nil, errtypes.InvalidCredentials(clientID) + return nil, nil, errtypes.InvalidCredentials(clientID) } func getCredentials() map[string]Credentials { diff --git a/pkg/auth/manager/demo/demo_test.go b/pkg/auth/manager/demo/demo_test.go index 5b5c5e0f317..2b73d153c92 100644 --- a/pkg/auth/manager/demo/demo_test.go +++ b/pkg/auth/manager/demo/demo_test.go @@ -30,13 +30,13 @@ func TestUserManager(t *testing.T) { manager, _ := New(nil) // Authenticate - positive test - _, err := manager.Authenticate(ctx, "einstein", "relativity") + _, _, err := manager.Authenticate(ctx, "einstein", "relativity") if err != nil { t.Fatalf("error while authenticate with correct credentials") } // Authenticate - negative test - _, err = manager.Authenticate(ctx, "einstein", "NotARealPassword") + _, _, err = manager.Authenticate(ctx, "einstein", "NotARealPassword") if err == nil { t.Fatalf("no error (but we expected one) while authenticate with bad credentials") } diff --git a/pkg/auth/manager/impersonator/impersonator.go b/pkg/auth/manager/impersonator/impersonator.go index 7478851c5b1..af61e0fe486 100644 --- a/pkg/auth/manager/impersonator/impersonator.go +++ b/pkg/auth/manager/impersonator/impersonator.go @@ -20,9 +20,13 @@ package impersonator import ( "context" + "encoding/json" "strings" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" ) @@ -38,7 +42,7 @@ func New(c map[string]interface{}) (auth.Manager, error) { return &mgr{}, nil } -func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error) { +func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, []*authpb.Scope, error) { // allow passing in uid as @ at := strings.LastIndex(clientID, "@") uid := &user.UserId{} @@ -48,8 +52,28 @@ func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) ( uid.OpaqueId = clientID[:at] uid.Idp = clientID[at+1:] } + + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/", + }, + } + val, err := json.Marshal(ref) + if err != nil { + return nil, nil, err + } + scope := []*authpb.Scope{ + &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: authpb.Role_ROLE_OWNER, + }, + } + return &user.User{ Id: uid, // not much else to provide - }, nil + }, scope, nil } diff --git a/pkg/auth/manager/impersonator/impersonator_test.go b/pkg/auth/manager/impersonator/impersonator_test.go index 41713cd4e42..31fabefab12 100644 --- a/pkg/auth/manager/impersonator/impersonator_test.go +++ b/pkg/auth/manager/impersonator/impersonator_test.go @@ -26,7 +26,7 @@ import ( func TestImpersonator(t *testing.T) { ctx := context.Background() i, _ := New(nil) - u, err := i.Authenticate(ctx, "admin", "pwd") + u, _, err := i.Authenticate(ctx, "admin", "pwd") if err != nil { t.Fatal(err) } @@ -39,7 +39,7 @@ func TestImpersonator(t *testing.T) { } ctx = context.Background() - u, err = i.Authenticate(ctx, "opaqueid@idp", "pwd") + u, _, err = i.Authenticate(ctx, "opaqueid@idp", "pwd") if err != nil { t.Fatal(err) } diff --git a/pkg/auth/manager/json/json.go b/pkg/auth/manager/json/json.go index b88f39432ea..87927879926 100644 --- a/pkg/auth/manager/json/json.go +++ b/pkg/auth/manager/json/json.go @@ -23,7 +23,9 @@ import ( "encoding/json" "io/ioutil" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" @@ -101,7 +103,26 @@ func New(m map[string]interface{}) (auth.Manager, error) { return manager, nil } -func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, error) { +func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, []*authpb.Scope, error) { + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/", + }, + } + val, err := json.Marshal(ref) + if err != nil { + return nil, nil, err + } + scope := []*authpb.Scope{ + &authpb.Scope{ + Resource: &typespb.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: authpb.Role_ROLE_OWNER, + }, + } + if c, ok := m.credentials[username]; ok { if c.Secret == secret { return &user.User{ @@ -113,8 +134,8 @@ func (m *manager) Authenticate(ctx context.Context, username string, secret stri Groups: c.Groups, Opaque: c.Opaque, // TODO add arbitrary keys as opaque data - }, nil + }, scope, nil } } - return nil, errtypes.InvalidCredentials(username) + return nil, nil, errtypes.InvalidCredentials(username) } diff --git a/pkg/auth/manager/json/json_test.go b/pkg/auth/manager/json/json_test.go index 4e9ee1e50f1..fa867dfaec2 100644 --- a/pkg/auth/manager/json/json_test.go +++ b/pkg/auth/manager/json/json_test.go @@ -190,7 +190,7 @@ func TestGetAuthenticatedManager(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - authenticated, err := manager.Authenticate(ctx, tt.username, tt.secret) + authenticated, _, err := manager.Authenticate(ctx, tt.username, tt.secret) if !tt.expectAuthenticated { assert.Empty(t, authenticated) assert.EqualError(t, err, tt.expectedError.message) diff --git a/pkg/auth/manager/ldap/ldap.go b/pkg/auth/manager/ldap/ldap.go index 4a490937c31..35c6ff1db5d 100644 --- a/pkg/auth/manager/ldap/ldap.go +++ b/pkg/auth/manager/ldap/ldap.go @@ -21,11 +21,14 @@ package ldap import ( "context" "crypto/tls" + "encoding/json" "fmt" "strings" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/auth" @@ -122,12 +125,12 @@ func New(m map[string]interface{}) (auth.Manager, error) { }, nil } -func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error) { +func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, []*authpb.Scope, error) { log := appctx.GetLogger(ctx) l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", am.c.Hostname, am.c.Port), &tls.Config{InsecureSkipVerify: true}) if err != nil { - return nil, err + return nil, nil, err } defer l.Close() @@ -135,7 +138,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) err = l.Bind(am.c.BindUsername, am.c.BindPassword) if err != nil { log.Error().Err(err).Msg("bind with system user failed") - return nil, err + return nil, nil, err } // Search for the given clientID @@ -149,11 +152,11 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) sr, err := l.Search(searchRequest) if err != nil { - return nil, err + return nil, nil, err } if len(sr.Entries) != 1 { - return nil, errtypes.NotFound(clientID) + return nil, nil, errtypes.NotFound(clientID) } userdn := sr.Entries[0].DN @@ -162,7 +165,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) err = l.Bind(userdn, clientSecret) if err != nil { log.Debug().Err(err).Interface("userdn", userdn).Msg("bind with user credentials failed") - return nil, err + return nil, nil, err } userID := &user.UserId{ @@ -171,16 +174,16 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) } gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { - return nil, errors.Wrap(err, "ldap: error getting gateway grpc client") + return nil, nil, errors.Wrap(err, "ldap: error getting gateway grpc client") } getGroupsResp, err := gwc.GetUserGroups(ctx, &user.GetUserGroupsRequest{ UserId: userID, }) if err != nil { - return nil, errors.Wrap(err, "ldap: error getting user groups") + return nil, nil, errors.Wrap(err, "ldap: error getting user groups") } if getGroupsResp.Status.Code != rpc.Code_CODE_OK { - return nil, errors.Wrap(err, "ldap: grpc getting user groups failed") + return nil, nil, errors.Wrap(err, "ldap: grpc getting user groups failed") } u := &user.User{ @@ -204,9 +207,29 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) }, }, } + + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/", + }, + } + val, err := json.Marshal(ref) + if err != nil { + return nil, nil, err + } + scope := []*authpb.Scope{ + &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: authpb.Role_ROLE_OWNER, + }, + } + log.Debug().Interface("entry", sr.Entries[0]).Interface("user", u).Msg("authenticated user") - return u, nil + return u, scope, nil } diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index 9690c548e05..8b01bb93d2c 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -22,12 +22,15 @@ package oidc import ( "context" + "encoding/json" "fmt" "time" oidc "github.com/coreos/go-oidc" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" @@ -90,27 +93,27 @@ func New(m map[string]interface{}) (auth.Manager, error) { // the clientID it would be empty as we only need to validate the clientSecret variable // which contains the access token that we can use to contact the UserInfo endpoint // and get the user claims. -func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, error) { +func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, []*authpb.Scope, error) { ctx = am.getOAuthCtx(ctx) - provider, err := am.getOIDCProvider(ctx) + oidcProvider, err := am.getOIDCProvider(ctx) if err != nil { - return nil, fmt.Errorf("error creating oidc provider: +%v", err) + return nil, nil, fmt.Errorf("error creating oidc provider: +%v", err) } oauth2Token := &oauth2.Token{ AccessToken: clientSecret, } - userInfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token)) + userInfo, err := oidcProvider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token)) if err != nil { - return nil, fmt.Errorf("oidc: error getting userinfo: +%v", err) + return nil, nil, fmt.Errorf("oidc: error getting userinfo: +%v", err) } // claims contains the standard OIDC claims like issuer, iat, aud, ... and any other non-standard one. // TODO(labkode): make claims configuration dynamic from the config file so we can add arbitrary mappings from claims to user struct. var claims map[string]interface{} if err := userInfo.Claims(&claims); err != nil { - return nil, fmt.Errorf("oidc: error unmarshaling userinfo claims: %v", err) + return nil, nil, fmt.Errorf("oidc: error unmarshaling userinfo claims: %v", err) } log.Debug().Interface("claims", claims).Interface("userInfo", userInfo).Msg("unmarshalled userinfo") @@ -122,11 +125,11 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) } if claims["email"] == nil { - return nil, fmt.Errorf("no \"email\" attribute found in userinfo: maybe the client did not request the oidc \"email\"-scope") + return nil, nil, fmt.Errorf("no \"email\" attribute found in userinfo: maybe the client did not request the oidc \"email\"-scope") } if claims["preferred_username"] == nil || claims["name"] == nil { - return nil, fmt.Errorf("no \"preferred_username\" or \"name\" attribute found in userinfo: maybe the client did not request the oidc \"profile\"-scope") + return nil, nil, fmt.Errorf("no \"preferred_username\" or \"name\" attribute found in userinfo: maybe the client did not request the oidc \"profile\"-scope") } opaqueObj := &types.Opaque{ @@ -157,16 +160,16 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) } gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { - return nil, errors.Wrap(err, "oidc: error getting gateway grpc client") + return nil, nil, errors.Wrap(err, "oidc: error getting gateway grpc client") } getGroupsResp, err := gwc.GetUserGroups(ctx, &user.GetUserGroupsRequest{ UserId: userID, }) if err != nil { - return nil, errors.Wrap(err, "oidc: error getting user groups") + return nil, nil, errors.Wrap(err, "oidc: error getting user groups") } if getGroupsResp.Status.Code != rpc.Code_CODE_OK { - return nil, errors.Wrap(err, "oidc: grpc getting user groups failed") + return nil, nil, errors.Wrap(err, "oidc: grpc getting user groups failed") } u := &user.User{ @@ -183,7 +186,26 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) Opaque: opaqueObj, } - return u, nil + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/", + }, + } + val, err := json.Marshal(ref) + if err != nil { + return nil, nil, err + } + scope := []*authpb.Scope{ + &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: authpb.Role_ROLE_OWNER, + }, + } + + return u, scope, nil } func (am *mgr) getOAuthCtx(ctx context.Context) context.Context { diff --git a/pkg/auth/manager/publicshares/publicshares.go b/pkg/auth/manager/publicshares/publicshares.go index fdffdaf5a15..7c057f3513d 100644 --- a/pkg/auth/manager/publicshares/publicshares.go +++ b/pkg/auth/manager/publicshares/publicshares.go @@ -20,14 +20,17 @@ package publicshares import ( "context" + "encoding/json" "strings" "time" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" userprovider "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" "github.com/cs3org/reva/pkg/errtypes" @@ -69,10 +72,10 @@ func New(m map[string]interface{}) (auth.Manager, error) { }, nil } -func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user.User, error) { +func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user.User, []*authpb.Scope, error) { gwConn, err := pool.GetGatewayServiceClient(m.c.GatewayAddr) if err != nil { - return nil, err + return nil, nil, err } var auth *link.PublicShareAuthentication @@ -93,7 +96,7 @@ func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user Spec: &link.PublicShareAuthentication_Signature{ Signature: &link.ShareSignature{ Signature: sig, - SignatureExpiration: &typesv1beta1.Timestamp{ + SignatureExpiration: &types.Timestamp{ Seconds: uint64(exp.UnixNano() / 1000000000), Nanos: uint32(exp.UnixNano() % 1000000000), }, @@ -109,23 +112,102 @@ func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user }) switch { case err != nil: - return nil, err + return nil, nil, err case publicShareResponse.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: - return nil, errtypes.NotFound(publicShareResponse.Status.Message) + return nil, nil, errtypes.NotFound(publicShareResponse.Status.Message) case publicShareResponse.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED: - return nil, errtypes.InvalidCredentials(publicShareResponse.Status.Message) + return nil, nil, errtypes.InvalidCredentials(publicShareResponse.Status.Message) case publicShareResponse.Status.Code != rpcv1beta1.Code_CODE_OK: - return nil, errtypes.InternalError(publicShareResponse.Status.Message) + return nil, nil, errtypes.InternalError(publicShareResponse.Status.Message) } getUserResponse, err := gwConn.GetUser(ctx, &userprovider.GetUserRequest{ UserId: publicShareResponse.GetShare().GetCreator(), }) + if err != nil { + return nil, nil, err + } + + scope, err := m.getScope(ctx, publicShareResponse.GetShare()) + if err != nil { + return nil, nil, err + } + + return getUserResponse.GetUser(), scope, nil +} + +func (m *manager) getScope(ctx context.Context, share *link.PublicShare) ([]*authpb.Scope, error) { + role := authpb.Role_ROLE_VIEWER + if share.Permissions.Permissions.InitiateFileUpload { + role = authpb.Role_ROLE_EDITOR + } + var refs [][]byte + + // 1. ref: + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/public/" + share.Token, + }, + } + val, err := json.Marshal(ref) + if err != nil { + return nil, err + } + refs = append(refs, val) + + // 2. ref: > + ref = &provider.Reference{ + Spec: &provider.Reference_Id{ + Id: share.ResourceId, + }, + } + val, err = json.Marshal(ref) if err != nil { return nil, err } + refs = append(refs, val) - return getUserResponse.GetUser(), nil + // 3. ref: + shareRef := &link.PublicShareReference{ + Spec: &link.PublicShareReference_Token{ + Token: share.Token, + }, + } + val, err = json.Marshal(shareRef) + if err != nil { + return nil, err + } + refs = append(refs, val) + + // 4. /dataprovider + val, err = json.Marshal("/dataprovider") + if err != nil { + return nil, err + } + refs = append(refs, val) + + // 5. /data + val, err = json.Marshal("/data") + if err != nil { + return nil, err + } + refs = append(refs, val) + + return encodeScope(refs, role), nil +} + +func encodeScope(refs [][]byte, role authpb.Role) []*authpb.Scope { + scope := make([]*authpb.Scope, len(refs)) + for i, r := range refs { + scope[i] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: r, + }, + Role: role, + } + } + return scope } // ErrPasswordNotProvided is returned when the public share is password protected, but there was no password on the request diff --git a/pkg/publicshare/manager/json/json.go b/pkg/publicshare/manager/json/json.go index eb2e17aa4b1..e09242754c1 100644 --- a/pkg/publicshare/manager/json/json.go +++ b/pkg/publicshare/manager/json/json.go @@ -508,6 +508,8 @@ func (m *manager) getByToken(ctx context.Context, token string) (*link.PublicSha // GetPublicShareByToken gets a public share by its opaque token. func (m *manager) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) { + log := appctx.GetLogger(ctx) + log.Info().Msgf("GetPublicShareByToken %s %+v", token, auth) db, err := m.readDb() if err != nil { return nil, err @@ -522,6 +524,7 @@ func (m *manager) GetPublicShareByToken(ctx context.Context, token string, auth if err := utils.UnmarshalJSONToProtoV1([]byte(v.(map[string]interface{})["share"].(string)), &local); err != nil { return nil, err } + log.Info().Msgf("GetPublicShareByToken listing keys %s", local.Token) if local.Token == token { if !notExpired(&local) { diff --git a/pkg/token/manager/demo/demo.go b/pkg/token/manager/demo/demo.go index 08ae96312c9..4f9b9d31a26 100644 --- a/pkg/token/manager/demo/demo.go +++ b/pkg/token/manager/demo/demo.go @@ -24,6 +24,7 @@ import ( "encoding/base64" "encoding/gob" + 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/token" "github.com/cs3org/reva/pkg/token/manager/registry" @@ -42,7 +43,7 @@ func New(m map[string]interface{}) (token.Manager, error) { type manager struct{} -func (m *manager) MintToken(ctx context.Context, u *user.User) (string, error) { +func (m *manager) MintToken(ctx context.Context, u *user.User, scope []*auth.Scope) (string, error) { token, err := encode(u) if err != nil { return "", errors.Wrap(err, "error encoding user") @@ -50,7 +51,7 @@ func (m *manager) MintToken(ctx context.Context, u *user.User) (string, error) { return token, nil } -func (m *manager) DismantleToken(ctx context.Context, token string) (*user.User, error) { +func (m *manager) DismantleToken(ctx context.Context, token string, resource interface{}) (*user.User, error) { u, err := decode(token) if err != nil { return nil, errors.Wrap(err, "error decoding claims") diff --git a/pkg/token/manager/demo/demo_test.go b/pkg/token/manager/demo/demo_test.go index 684be5887bb..fd88c69695e 100644 --- a/pkg/token/manager/demo/demo_test.go +++ b/pkg/token/manager/demo/demo_test.go @@ -20,9 +20,13 @@ package demo import ( "context" + "encoding/json" "testing" + auth "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) var ctx = context.Background() @@ -33,12 +37,30 @@ func TestEncodeDecode(t *testing.T) { Username: "marie", } - encoded, err := m.MintToken(ctx, u) + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/", + }, + } + val, err := json.Marshal(ref) + if err != nil { + t.Fatal(err) + } + + encoded, err := m.MintToken(ctx, u, []*auth.Scope{ + &auth.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: auth.Role_ROLE_OWNER, + }, + }) if err != nil { t.Fatal(err) } - decodedUser, err := m.DismantleToken(ctx, encoded) + decodedUser, err := m.DismantleToken(ctx, encoded, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/token/manager/jwt/jwt.go b/pkg/token/manager/jwt/jwt.go index 7ff7a72abf2..ba335392a1f 100644 --- a/pkg/token/manager/jwt/jwt.go +++ b/pkg/token/manager/jwt/jwt.go @@ -22,6 +22,7 @@ import ( "context" "time" + 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/errtypes" "github.com/cs3org/reva/pkg/sharedconf" @@ -43,6 +44,17 @@ type config struct { Expires int64 `mapstructure:"expires"` } +type manager struct { + conf *config +} + +// claims are custom claims for the JWT token. +type claims struct { + jwt.StandardClaims + User *user.User `json:"user"` + Scope []*auth.Scope `json:"scope"` +} + func parseConfig(m map[string]interface{}) (*config, error) { c := &config{} if err := mapstructure.Decode(m, c); err != nil { @@ -73,23 +85,7 @@ func New(value map[string]interface{}) (token.Manager, error) { return m, nil } -type manager struct { - conf *config -} - -// claims are custom claims for the JWT token. -type claims struct { - jwt.StandardClaims - User *user.User `json:"user"` -} - -// TODO(labkode): resulting JSON contains internal protobuf fields: -// "Username": "einstein", -// "XXX_NoUnkeyedLiteral": {}, -// "XXX_sizecache": 0, -// "XXX_unrecognized": null -//} -func (m *manager) MintToken(ctx context.Context, u *user.User) (string, error) { +func (m *manager) MintToken(ctx context.Context, u *user.User, scope []*auth.Scope) (string, error) { ttl := time.Duration(m.conf.Expires) * time.Second claims := claims{ StandardClaims: jwt.StandardClaims{ @@ -98,7 +94,8 @@ func (m *manager) MintToken(ctx context.Context, u *user.User) (string, error) { Audience: "reva", IssuedAt: time.Now().Unix(), }, - User: u, + User: u, + Scope: scope, } t := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims) @@ -111,11 +108,13 @@ func (m *manager) MintToken(ctx context.Context, u *user.User) (string, error) { return tkn, nil } -func (m *manager) DismantleToken(ctx context.Context, tkn string) (*user.User, error) { +func (m *manager) DismantleToken(ctx context.Context, tkn string, resource interface{}) (*user.User, error) { token, err := jwt.ParseWithClaims(tkn, &claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(m.conf.Secret), nil }) + // TODO(ishank011): Verify that the resource parameter is accessible within the scope encoded in the token + if err != nil { return nil, errors.Wrap(err, "error parsing token") } diff --git a/pkg/token/token.go b/pkg/token/token.go index 0d1c14f4550..c2e995250c2 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -21,6 +21,7 @@ package token import ( "context" + auth "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" ) @@ -32,13 +33,10 @@ type key int const tokenKey key = iota -// Claims is the map of attributes to encode into a token -type Claims map[string]interface{} - // Manager is the interface to implement to sign and verify tokens type Manager interface { - MintToken(ctx context.Context, u *user.User) (string, error) - DismantleToken(ctx context.Context, token string) (*user.User, error) + MintToken(ctx context.Context, u *user.User, scope []*auth.Scope) (string, error) + DismantleToken(ctx context.Context, token string, resource interface{}) (*user.User, error) } // ContextGetToken returns the token if set in the given context.