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 25, 2024
1 parent 1b7c69b commit b42a20c
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 13 deletions.
12 changes: 12 additions & 0 deletions changelog/unreleased/tokenInfo-endpoint-replacement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Enhancement: Replacement for TokenInfo Endpoint

The client should basically always send a PROPFIND to /dav/public-files/{sharetoken}

* authenticated clients accessing an internal link are redirected to the "real" resource (`/dav/spaces/{target-resource-id}
* authenticated clients accessing a pubic link (password protected or not) for a resource they already have access to are also redirected to the "real" resource. (and always need to supply the password)
* unauthenticated clients accessing an internal link get a 401 returned with WWW-Authenticate set to Bearer (so that the client knows that it need to get a token via the IDP login page.
* unauthenticated clients accessing a password protected link get a 401 returned with WWW-Authenticate set to Basic to indicate the requirement for needing the link's password.

https://github.com/cs3org/reva/pull/4653
https://github.com/owncloud/ocis/issues/8858
96 changes: 83 additions & 13 deletions internal/http/services/owncloud/ocdav/dav.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package ocdav

import (
"context"
"fmt"
"net/http"
"path"
"path/filepath"
Expand All @@ -28,20 +29,26 @@ 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"
)

const (
_trashbinPath = "trash-bin"

// WwwAuthenticate captures the Www-Authenticate header string.
WwwAuthenticate = "Www-Authenticate"
)

// DavHandler routes to the different sub handlers
Expand Down Expand Up @@ -269,13 +276,15 @@ 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
}
b, err := errors.Marshal(http.StatusUnauthorized, "No 'Authorization: Basic' header found", "")
w.Header().Add(WwwAuthenticate, fmt.Sprintf("Basic realm=\"%s\", charset=\"UTF-8\"", r.Host))
w.WriteHeader(http.StatusUnauthorized)
b, err := errors.Marshal(http.StatusUnauthorized, "Authorization failed: Basic header found", "")
errors.HandleWebdavError(log, w, b, err)
return
case res.Status.Code == rpc.Code_CODE_NOT_FOUND:
Expand All @@ -286,14 +295,36 @@ 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
var sRes *provider.StatResponse
if r.Method == MethodPropfind {
// FIXME: We check the method because we want to handel the PROPFIND for the internal links
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(WwwAuthenticate, fmt.Sprintf("Bearer realm=\"%s\", charset=\"UTF-8\"", r.Host))
w.WriteHeader(http.StatusUnauthorized)
b, err := errors.Marshal(http.StatusUnauthorized, "Authorization failed: 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 +347,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 +371,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
Loading

0 comments on commit b42a20c

Please sign in to comment.