Skip to content

Commit

Permalink
Replacement for TokenInfo endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
2403905 committed Apr 23, 2024
1 parent ef59ba2 commit 28b7b0d
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 12 deletions.
86 changes: 74 additions & 12 deletions internal/http/services/owncloud/ocdav/dav.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ import (
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "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"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
"github.com/cs3org/reva/v2/pkg/appctx"
"github.com/cs3org/reva/v2/pkg/conversions"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/rhttp/router"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"google.golang.org/grpc/metadata"
)
Expand Down Expand Up @@ -269,12 +272,14 @@ func (h *DavHandler) Handler(s *svc) http.Handler {
case res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED:
fallthrough
case res.Status.Code == rpc.Code_CODE_UNAUTHENTICATED:
w.WriteHeader(http.StatusUnauthorized)
if hasValidBasicAuthHeader {
w.WriteHeader(http.StatusUnauthorized)
b, err := errors.Marshal(http.StatusUnauthorized, "Username or password was incorrect", "")
errors.HandleWebdavError(log, w, b, err)
return
}
w.Header().Add("WWW-Authenticate", "Basic")
w.WriteHeader(http.StatusUnauthorized)
b, err := errors.Marshal(http.StatusUnauthorized, "No 'Authorization: Basic' header found", "")
errors.HandleWebdavError(log, w, b, err)
return
Expand All @@ -286,14 +291,32 @@ func (h *DavHandler) Handler(s *svc) http.Handler {
return
}

ctx = ctxpkg.ContextSetToken(ctx, res.Token)
ctx = ctxpkg.ContextSetUser(ctx, res.User)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, res.Token)
// force the internal link
var isInternal bool
sRes, err := getInternalLinkStat(ctx, s.gatewaySelector, token)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if sRes.GetStatus().GetCode() == rpc.Code_CODE_UNAUTHENTICATED {
w.Header().Add("WWW-Authenticate", "Bearer")
w.WriteHeader(http.StatusUnauthorized)
b, err := errors.Marshal(http.StatusUnauthorized, "No 'Authorization: Bearer' header found", "")
errors.HandleWebdavError(log, w, b, err)
return
}
if sRes.GetStatus().GetCode() == rpc.Code_CODE_OK {
isInternal = true
} else {
ctx = ctxpkg.ContextSetToken(ctx, res.Token)
ctx = ctxpkg.ContextSetUser(ctx, res.User)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, res.Token)

r = r.WithContext(ctx)
r = r.WithContext(ctx)
// the public share manager knew the token, but does the referenced target still exist?
sRes, err = getTokenStatInfo(ctx, s.gatewaySelector, token)
}

// the public share manager knew the token, but does the referenced target still exist?
sRes, err := getTokenStatInfo(ctx, s.gatewaySelector, token)
switch {
case err != nil:
log.Error().Err(err).Msg("error sending grpc stat request")
Expand All @@ -316,12 +339,20 @@ func (h *DavHandler) Handler(s *svc) http.Handler {
}
log.Debug().Interface("statInfo", sRes.Info).Msg("Stat info from public link token path")

ctx := ContextWithTokenStatInfo(ctx, sRes.Info)
r = r.WithContext(ctx)
if sRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER {
h.PublicFileHandler.Handler(s).ServeHTTP(w, r)
if isInternal {
base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "spaces")
ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base)
r = r.WithContext(ctx)
r.URL.Path = "/" + storagespace.FormatResourceID(*sRes.GetInfo().GetId())
h.SpacesHandler.Handler(s, nil).ServeHTTP(w, r)
} else {
h.PublicFolderHandler.Handler(s).ServeHTTP(w, r)
ctx := ContextWithTokenStatInfo(ctx, sRes.Info)
r = r.WithContext(ctx)
if sRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER {
h.PublicFileHandler.Handler(s).ServeHTTP(w, r)
} else {
h.PublicFolderHandler.Handler(s).ServeHTTP(w, r)
}
}

default:
Expand All @@ -332,6 +363,37 @@ func (h *DavHandler) Handler(s *svc) http.Handler {
})
}

func getInternalLinkStat(ctx context.Context, selector pool.Selectable[gatewayv1beta1.GatewayAPIClient], token string) (*provider.StatResponse, error) {
client, err := selector.Next()
if err != nil {
return nil, err
}
psRes, err := client.GetPublicShare(ctx, &link.GetPublicShareRequest{
Ref: &link.PublicShareReference{
Spec: &link.PublicShareReference_Token{
Token: token,
},
}})
if err != nil {
if strings.Contains(err.Error(), "core access token not found") {
return nil, nil
}
return nil, err
}
if psRes.Status.Code != rpc.Code_CODE_OK {
return nil, nil
}

role := conversions.RoleFromResourcePermissions(psRes.Share.Permissions.GetPermissions(), true)
if role.OCSPermissions() == 0 {
return client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{
ResourceId: psRes.GetShare().GetResourceId(),
}})

}
return nil, nil
}

func getTokenStatInfo(ctx context.Context, selector pool.Selectable[gatewayv1beta1.GatewayAPIClient], token string) (*provider.StatResponse, error) {
client, err := selector.Next()
if err != nil {
Expand Down
215 changes: 215 additions & 0 deletions internal/http/services/owncloud/ocdav/ocdav_blackbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,221 @@ var _ = Describe("ocdav", func() {
})
})

Describe("PROPFIND to public-file", func() {

BeforeEach(func() {
// set the dav endpoint to test
basePath = "/dav/public-files"

// setup the request
rr = httptest.NewRecorder()
req, err = http.NewRequest("PROPFIND", basePath+"/aySTemFFUcNudVD", strings.NewReader(""))
Expect(err).ToNot(HaveOccurred())
req = req.WithContext(ctx)
})

When("the the link is public", func() {
It("returns a status Unauthorized", func() {

client.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *cs3gateway.AuthenticateRequest) bool {
return req.Type == "publicshares" &&
strings.HasPrefix(req.ClientId, "aySTemFFUcNudVD") &&
strings.HasPrefix(req.ClientSecret, "signature||")
})).Return(&cs3gateway.AuthenticateResponse{
Status: status.NewUnauthenticated(ctx, nil, ""),
}, nil)

handler.Handler().ServeHTTP(rr, req)
Expect(rr).To(HaveHTTPStatus(http.StatusUnauthorized))
Expect(rr).To(HaveHTTPHeaderWithValue("WWW-Authenticate", "Basic"))
})
It("returns a Multistatus with the file properties", func() {
req.Header.Set("Authorization", "Basic cHVibGljOmAxcWF6WHN3Mg==")
ctx = ctxpkg.ContextSetUser(context.Background(), user)

user = &cs3user.User{Id: &cs3user.UserId{OpaqueId: "username"}, Username: "username"}
expectedPathPrefix := "/"

userspace = &cs3storageprovider.StorageSpace{
Opaque: &cs3types.Opaque{
Map: map[string]*cs3types.OpaqueEntry{
"path": {
Decoder: "plain",
Value: []byte("/public/aySTemFFUcNudVD"),
},
},
},

Id: &cs3storageprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(cs3storageprovider.ResourceId{StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD"})},
Root: &cs3storageprovider.ResourceId{StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD"},
Name: "",
SpaceType: "mountpoint",
}

client.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *cs3gateway.AuthenticateRequest) bool {
return req.Type == "publicshares" &&
strings.HasPrefix(req.ClientId, "aySTemFFUcNudVD") &&
strings.HasPrefix(req.ClientSecret, "password|`1qazXsw2")
})).Return(&cs3gateway.AuthenticateResponse{
Status: status.NewOK(ctx),
Token: "valid-token",
User: user,
}, nil)

client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
return utils.ResourceIDEqual(req.Ref.ResourceId, &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
})
})).Return(&cs3storageprovider.StatResponse{
Status: status.NewOK(ctx),
Info: &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
},
Type: cs3storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER},
}, nil).Once()

client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
p := string(req.Opaque.Map["path"].Value)
return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
})).Return(&cs3storageprovider.ListStorageSpacesResponse{
Status: status.NewOK(ctx),
StorageSpaces: []*cs3storageprovider.StorageSpace{userspace}, // FIXME we may need to return the /public storage provider id and mock it
}, nil)

client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
return utils.ResourceIDEqual(req.Ref.ResourceId, &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
})
})).Return(&cs3storageprovider.StatResponse{
Status: status.NewOK(ctx),
Info: &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
},
Type: cs3storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER},
}, nil).Once()

client.On("ListContainer", mock.Anything, mock.Anything).Return(&cs3storageprovider.ListContainerResponse{
Status: status.NewOK(context.Background()),
}, nil)

handler.Handler().ServeHTTP(rr, req)
Expect(rr).To(HaveHTTPStatus(http.StatusMultiStatus))
})
})

When("the the link is internal", func() {
FIt("returns a status Unauthorized", func() {

client.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *cs3gateway.AuthenticateRequest) bool {
return req.Type == "publicshares" &&
strings.HasPrefix(req.ClientId, "aySTemFFUcNudVD") &&
strings.HasPrefix(req.ClientSecret, "signature||")
})).Return(&cs3gateway.AuthenticateResponse{
Status: status.NewUnauthenticated(ctx, nil, ""),
}, nil)

handler.Handler().ServeHTTP(rr, req)
Expect(rr).To(HaveHTTPStatus(http.StatusUnauthorized))
Expect(rr).To(HaveHTTPHeaderWithValue("WWW-Authenticate", "Bearer"))
})
It("returns a Multistatus with the file properties", func() {
req.Header.Set("Authorization", "Basic cHVibGljOmAxcWF6WHN3Mg==")
ctx = ctxpkg.ContextSetUser(context.Background(), user)

user = &cs3user.User{Id: &cs3user.UserId{OpaqueId: "username"}, Username: "username"}
expectedPathPrefix := "/"

userspace = &cs3storageprovider.StorageSpace{
Opaque: &cs3types.Opaque{
Map: map[string]*cs3types.OpaqueEntry{
"path": {
Decoder: "plain",
Value: []byte("/public/aySTemFFUcNudVD"),
},
},
},

Id: &cs3storageprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(cs3storageprovider.ResourceId{StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD"})},
Root: &cs3storageprovider.ResourceId{StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD"},
Name: "",
SpaceType: "mountpoint",
}

client.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *cs3gateway.AuthenticateRequest) bool {
return req.Type == "publicshares" &&
strings.HasPrefix(req.ClientId, "aySTemFFUcNudVD") &&
strings.HasPrefix(req.ClientSecret, "password|`1qazXsw2")
})).Return(&cs3gateway.AuthenticateResponse{
Status: status.NewOK(ctx),
Token: "valid-token",
User: user,
}, nil)

client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
return utils.ResourceIDEqual(req.Ref.ResourceId, &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
})
})).Return(&cs3storageprovider.StatResponse{
Status: status.NewOK(ctx),
Info: &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
},
Type: cs3storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER},
}, nil).Once()

client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
p := string(req.Opaque.Map["path"].Value)
return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
})).Return(&cs3storageprovider.ListStorageSpacesResponse{
Status: status.NewOK(ctx),
StorageSpaces: []*cs3storageprovider.StorageSpace{userspace}, // FIXME we may need to return the /public storage provider id and mock it
}, nil)

client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
return utils.ResourceIDEqual(req.Ref.ResourceId, &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
})
})).Return(&cs3storageprovider.StatResponse{
Status: status.NewOK(ctx),
Info: &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{
StorageId: utils.PublicStorageProviderID,
SpaceId: utils.PublicStorageSpaceID,
OpaqueId: "aySTemFFUcNudVD",
},
Type: cs3storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER},
}, nil).Once()

client.On("ListContainer", mock.Anything, mock.Anything).Return(&cs3storageprovider.ListContainerResponse{
Status: status.NewOK(context.Background()),
}, nil)

handler.Handler().ServeHTTP(rr, req)
Expect(rr).To(HaveHTTPStatus(http.StatusMultiStatus))
})
})
})
Describe("MKCOL", func() {

BeforeEach(func() {
Expand Down

0 comments on commit 28b7b0d

Please sign in to comment.