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

OIDC and WOPI changes for lightweight users #2278

Merged
merged 2 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/unreleased/oidc-lw-users.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: OIDC driver changes for lightweight users

https://github.com/cs3org/reva/pull/2278
3 changes: 3 additions & 0 deletions internal/grpc/interceptors/auth/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) {
return nil
}
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok {
return nil
}
}
} else if strings.HasPrefix(k, "publicshare") {
var share link.PublicShare
Expand Down
15 changes: 9 additions & 6 deletions internal/grpc/services/gateway/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,20 @@ package gateway
import (
"context"
"fmt"
"strings"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/auth/scope"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/rgrpc/status"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/sharedconf"
"github.com/cs3org/reva/pkg/utils"
"github.com/pkg/errors"
"google.golang.org/grpc/metadata"
)
Expand Down Expand Up @@ -121,13 +116,19 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
ctx = ctxpkg.ContextSetToken(ctx, token)
ctx = ctxpkg.ContextSetUser(ctx, res.User)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token)
scope, err := s.expandScopes(ctx, res.TokenScope)

// Commenting out as the token size can get too big
// For now, we'll try to resolve all resources on every request
// TODO(ishank011): Add a cache for these
/* scope, err := s.expandScopes(ctx, res.TokenScope)
if err != nil {
err = errors.Wrap(err, "authsvc: error expanding token scope")
return &gateway.AuthenticateResponse{
Status: status.NewUnauthenticated(ctx, err, "error expanding access token scope"),
}, nil
}
*/
scope := res.TokenScope

token, err = s.tokenmgr.MintToken(ctx, &u, scope)
if err != nil {
Expand Down Expand Up @@ -241,6 +242,7 @@ func (s *svc) findAuthProvider(ctx context.Context, authType string) (provider.P
return nil, errtypes.InternalError("gateway: error finding an auth provider for type: " + authType)
}

/*
func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
log := appctx.GetLogger(ctx)
newMap := make(map[string]*authpb.Scope)
Expand Down Expand Up @@ -311,3 +313,4 @@ func (s *svc) statAndAddResource(ctx context.Context, r *storageprovider.Resourc

return scope.AddResourceInfoScope(statResponse.Info, role, scopeMap)
}
*/
4 changes: 3 additions & 1 deletion internal/grpc/services/gateway/usershareprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import (
"fmt"
"path"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/rgrpc/status"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
Expand Down Expand Up @@ -321,7 +323,7 @@ func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.Update
}

// if we don't need to create/delete references then we return early.
if !s.c.CommitShareToStorageRef {
if !s.c.CommitShareToStorageRef || ctxpkg.ContextMustGetUser(ctx).Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT {
return res, nil
}

Expand Down
7 changes: 6 additions & 1 deletion pkg/app/provider/wopi/wopi.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/beevik/etree"
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/app"
"github.com/cs3org/reva/pkg/app/provider/registry"
Expand Down Expand Up @@ -139,6 +140,11 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc

u, ok := ctxpkg.ContextGetUser(ctx)
if ok { // else defaults to "Guest xyz"
if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT {
q.Add("userid", resource.Owner.OpaqueId+"@"+resource.Owner.Idp)
} else {
q.Add("userid", u.Id.OpaqueId+"@"+u.Id.Idp)
}
var isPublicShare bool
if u.Opaque != nil {
if _, ok := u.Opaque.Map["public-share-role"]; ok {
Expand All @@ -148,7 +154,6 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc

if !isPublicShare {
q.Add("username", u.Username)
q.Add("userid", u.Id.OpaqueId+"@"+u.Id.Idp)
}
}

Expand Down
23 changes: 22 additions & 1 deletion pkg/auth/manager/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package oidc
import (
"context"
"fmt"
"strings"
"time"

oidc "github.com/coreos/go-oidc"
Expand Down Expand Up @@ -130,6 +131,12 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string)
if claims["email_verified"] == nil { // This is not set in simplesamlphp
claims["email_verified"] = false
}
if claims["preferred_username"] == nil {
claims["preferred_username"] = claims[am.c.IDClaim]
}
if claims["name"] == nil {
claims["name"] = claims[am.c.IDClaim]
}

if claims["email"] == nil {
return nil, nil, fmt.Errorf("no \"email\" attribute found in userinfo: maybe the client did not request the oidc \"email\"-scope")
Expand Down Expand Up @@ -158,7 +165,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string)
userID := &user.UserId{
OpaqueId: claims[am.c.IDClaim].(string), // a stable non reassignable id
Idp: claims["issuer"].(string), // in the scope of this issuer
Type: user.UserType_USER_TYPE_PRIMARY,
Type: getUserType(claims[am.c.IDClaim].(string)),
}
gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc)
if err != nil {
Expand Down Expand Up @@ -236,3 +243,17 @@ func (am *mgr) getOIDCProvider(ctx context.Context) (*oidc.Provider, error) {
am.provider = provider
return am.provider, nil
}

func getUserType(upn string) user.UserType {
var t user.UserType
switch {
case strings.HasPrefix(upn, "guest"):
t = user.UserType_USER_TYPE_LIGHTWEIGHT
case strings.Contains(upn, "@"):
t = user.UserType_USER_TYPE_FEDERATED
default:
t = user.UserType_USER_TYPE_PRIMARY
}
return t

}
32 changes: 31 additions & 1 deletion pkg/auth/scope/lightweight.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package scope

import (
"context"
"strings"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
Expand All @@ -34,12 +35,41 @@ func lightweightAccountScope(_ context.Context, scope *authpb.Scope, resource in
// These cannot be resolved from here, but need to be added to the scope from
// where the call to mint tokens is made.
// From here, we only allow ListReceivedShares calls
if _, ok := resource.(*collaboration.ListReceivedSharesRequest); ok {
switch v := resource.(type) {
case *collaboration.ListReceivedSharesRequest:
return true, nil
case string:
return checkLightweightPath(v), nil
}
return false, nil
}

func checkLightweightPath(path string) bool {
paths := []string{
"/ocs/v2.php/apps/files_sharing/api/v1/shares",
"/ocs/v1.php/apps/files_sharing/api/v1/shares",
"/ocs/v2.php/apps/files_sharing//api/v1/shares",
"/ocs/v1.php/apps/files_sharing//api/v1/shares",
"/ocs/v2.php/cloud/capabilities",
"/ocs/v1.php/cloud/capabilities",
"/ocs/v2.php/cloud/user",
"/ocs/v1.php/cloud/user",
"/remote.php/webdav",
"/remote.php/dav/files",
"/app/open",
"/app/new",
"/archiver",
"/dataprovider",
"/data",
}
for _, p := range paths {
if strings.HasPrefix(path, p) {
return true
}
}
return false
}

// AddLightweightAccountScope adds the scope to allow access to lightweight user.
func AddLightweightAccountScope(role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
ref := &provider.Reference{Path: "/"}
Expand Down
5 changes: 4 additions & 1 deletion pkg/auth/scope/receivedshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ func receivedShareScope(_ context.Context, scope *authpb.Scope, resource interfa
// AddReceivedShareScope adds the scope to allow access to a received user/group share and
// the shared resource.
func AddReceivedShareScope(share *collaboration.ReceivedShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
val, err := utils.MarshalProtoV1ToJSON(share)
// Create a new "scope share" to only expose the required fields to the scope.
scopeShare := &collaboration.Share{Id: share.Share.Id, Owner: share.Share.Owner, Creator: share.Share.Creator, ResourceId: share.Share.ResourceId}

val, err := utils.MarshalProtoV1ToJSON(&collaboration.ReceivedShare{Share: scopeShare})
if err != nil {
return nil, err
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/auth/scope/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ func checkSharePath(path string) bool {
// AddShareScope adds the scope to allow access to a user/group share and
// the shared resource.
func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
val, err := utils.MarshalProtoV1ToJSON(share)
// Create a new "scope share" to only expose the required fields to the scope.
scopeShare := &collaboration.Share{Id: share.Id, Owner: share.Owner, Creator: share.Creator, ResourceId: share.ResourceId}

val, err := utils.MarshalProtoV1ToJSON(scopeShare)
if err != nil {
return nil, err
}
Expand Down
47 changes: 40 additions & 7 deletions pkg/cbox/user/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,7 @@ func (m *manager) Configure(ml map[string]interface{}) error {
return nil
}

func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/Identity?filter=%s:%s&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type",
m.conf.APIBaseURL, param, url.QueryEscape(val))
func (m *manager) getUser(ctx context.Context, url string) (map[string]interface{}, error) {
responseData, err := m.apiTokenManager.SendAPIGetRequest(ctx, url, false)
if err != nil {
return nil, err
Expand All @@ -151,17 +149,38 @@ func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[st
}

if len(users) != 1 {
return nil, errors.New("rest: user not found: " + param + ": " + val)
return nil, errors.New("rest: user not found for URL: " + url)
}

return users[0], nil
}

func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/Identity?filter=%s:%s&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type",
m.conf.APIBaseURL, param, url.QueryEscape(val))
return m.getUser(ctx, url)
}

func (m *manager) getLightweightUser(ctx context.Context, mail string) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/Identity?filter=primaryAccountEmail:%s&filter=upn:contains:guest&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type",
m.conf.APIBaseURL, url.QueryEscape(mail))
return m.getUser(ctx, url)
}

func (m *manager) getInternalUserID(ctx context.Context, uid *userpb.UserId) (string, error) {

internalID, err := m.fetchCachedInternalID(uid)
if err != nil {
userData, err := m.getUserByParam(ctx, "upn", uid.OpaqueId)
var (
userData map[string]interface{}
err error
)
if uid.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT {
// Lightweight accounts need to be fetched by email
userData, err = m.getLightweightUser(ctx, strings.TrimPrefix(uid.OpaqueId, "guest:"))
} else {
userData, err = m.getUserByParam(ctx, "upn", uid.OpaqueId)
}
if err != nil {
return "", err
}
Expand Down Expand Up @@ -217,7 +236,16 @@ func (m *manager) parseAndCacheUser(ctx context.Context, userData map[string]int
func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) {
u, err := m.fetchCachedUserDetails(uid)
if err != nil {
userData, err := m.getUserByParam(ctx, "upn", uid.OpaqueId)
var (
userData map[string]interface{}
err error
)
if uid.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT {
// Lightweight accounts need to be fetched by email
userData, err = m.getLightweightUser(ctx, strings.TrimPrefix(uid.OpaqueId, "guest:"))
} else {
userData, err = m.getUserByParam(ctx, "upn", uid.OpaqueId)
}
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -252,7 +280,12 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use

userData, err := m.getUserByParam(ctx, claim, value)
if err != nil {
return nil, err
// Lightweight accounts need to be fetched by email
if strings.HasPrefix(value, "guest:") {
if userData, err = m.getLightweightUser(ctx, strings.TrimPrefix(value, "guest:")); err != nil {
return nil, err
}
}
}
u := m.parseAndCacheUser(ctx, userData)

Expand Down
2 changes: 1 addition & 1 deletion pkg/eosclient/eosbinary/eosbinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -1001,7 +1001,7 @@ func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) {
})
var previousXAttr = ""
for _, p := range partsBySpace {
partsByEqual := strings.Split(p, "=") // we have kv pairs like [size 14]
partsByEqual := strings.SplitN(p, "=", 2) // we have kv pairs like [size 14]
if len(partsByEqual) == 2 {
// handle xattrn and xattrv special cases
switch {
Expand Down
26 changes: 25 additions & 1 deletion pkg/storage/utils/acl/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ func Parse(acls string, delimiter string) (*ACLs, error) {
if t == "" || isComment(t) {
continue
}
entry, err := ParseEntry(t)
var err error
var entry *Entry
if strings.HasPrefix(t, TypeLightweight) {
entry, err = ParseLWEntry(t)
} else {
entry, err = ParseEntry(t)
}
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -133,6 +139,24 @@ func ParseEntry(singleSysACL string) (*Entry, error) {
}, nil
}

// ParseLWEntry parses a single lightweight ACL
func ParseLWEntry(singleSysACL string) (*Entry, error) {
if !strings.HasPrefix(singleSysACL, TypeLightweight+":") {
return nil, errInvalidACL
}
singleSysACL = strings.TrimPrefix(singleSysACL, TypeLightweight+":")

tokens := strings.Split(singleSysACL, "=")
if len(tokens) != 2 {
return nil, errInvalidACL
}
return &Entry{
Type: TypeLightweight,
Qualifier: tokens[0],
Permissions: tokens[1],
}, nil
}

// CitrineSerialize serializes an ACL entry for citrine EOS ACLs
func (a *Entry) CitrineSerialize() string {
return fmt.Sprintf("%s:%s=%s", a.Type, a.Qualifier, a.Permissions)
Expand Down
Loading