Skip to content

Commit

Permalink
OIDC driver and WOPI changes for lightweight users
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 committed Jan 10, 2022
1 parent 3ac52f9 commit 4ad4b08
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 17 deletions.
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
3 changes: 3 additions & 0 deletions internal/grpc/services/gateway/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,16 @@ 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)
/* Commenting out as the token size can get too big
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
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
15 changes: 13 additions & 2 deletions pkg/storage/utils/eosfs/eosfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,18 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st
if err != nil {
return nil, err
}
auth, err := fs.getUserAuth(ctx, u, "")

fn := ""
if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT {
p, err := fs.resolve(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "eosfs: error resolving reference")
}

fn = fs.wrap(ctx, p)
}

auth, err := fs.getUserAuth(ctx, u, fn)
if err != nil {
return nil, err
}
Expand All @@ -838,7 +849,7 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st
}
}

fn := fs.wrap(ctx, p)
fn = fs.wrap(ctx, p)
eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn)
if err != nil {
return nil, err
Expand Down

0 comments on commit 4ad4b08

Please sign in to comment.