Skip to content

Commit

Permalink
Fixed public link paths + OCM users in WOPI driver (#3694)
Browse files Browse the repository at this point in the history
* Fixed public link paths in wopi driver following frontend updates

* added changelog

* Extended changelog

* Decorate friendly name in case of OCM users

* wopi: introduced a usertype parameter on the open call

* Support OCM shares as well on wopi apps

* Updated docs
  • Loading branch information
glpatcern authored Mar 7, 2023
1 parent aa93c90 commit 1f4a9ad
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 41 deletions.
7 changes: 7 additions & 0 deletions changelog/unreleased/wopi-apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Bugfix: updated public links URLs and users' display names in WOPI apps

Public links have changed in the frontend and are reflected in folderurl
query parameter. Additionally, OCM shares are supported for the folderurl
and OCM users are decorated with their ID provider.

https://github.com/cs3org/reva/pull/3694
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: "ocminvitemanager"
linkTitle: "ocminvitemanager"
weight: 10
description: >
Configuration for the ocminvitemanager service
---

# _struct: config_

{{% dir name="provider_domain" type="string" default="The same domain registered in the provider authorizer" %}}
[[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/ocminvitemanager/ocminvitemanager.go#L55)
{{< highlight toml >}}
[grpc.services.ocminvitemanager]
provider_domain = "The same domain registered in the provider authorizer"
{{< /highlight >}}
{{% /dir %}}

Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,13 @@ description: >
Configuration for the OCM Share Provider service
---

{{% pageinfo %}}
TODO
{{% /pageinfo %}}

{{% dir name="driver" type="string" default="" %}}
Driver to use. If you use a Nextcloud or ownCloud 10 backend,
you should use the "nextcloud" driver, and than provide
further configuration for it in
grpc.services.ocmshareprovider.drivers.nextcloud.
# _struct: config_

{{% dir name="provider_domain" type="string" default="The same domain registered in the provider authorizer" %}}
[[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/ocmshareprovider/ocmshareprovider.go#L63)
{{< highlight toml >}}
[grpc.services.ocmshareprovider]
driver = "nextcloud"
provider_domain = "The same domain registered in the provider authorizer"
{{< /highlight >}}
{{% /dir %}}

22 changes: 11 additions & 11 deletions docs/content/en/docs/config/packages/app/provider/wopi/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,87 +9,87 @@ description: >
# _struct: config_

{{% dir name="mime_types" type="[]string" default=nil %}}
Inherited from the appprovider. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L64)
Inherited from the appprovider. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L78)
{{< highlight toml >}}
[app.provider.wopi]
mime_types = nil
{{< /highlight >}}
{{% /dir %}}

{{% dir name="iop_secret" type="string" default="" %}}
The IOP secret used to connect to the wopiserver. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L65)
The IOP secret used to connect to the wopiserver. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L79)
{{< highlight toml >}}
[app.provider.wopi]
iop_secret = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="wopi_url" type="string" default="" %}}
The wopiserver's URL. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L66)
The wopiserver's URL. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L80)
{{< highlight toml >}}
[app.provider.wopi]
wopi_url = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="app_name" type="string" default="" %}}
The App user-friendly name. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L67)
The App user-friendly name. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L81)
{{< highlight toml >}}
[app.provider.wopi]
app_name = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="app_icon_uri" type="string" default="" %}}
A URI to a static asset which represents the app icon. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L68)
A URI to a static asset which represents the app icon. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L82)
{{< highlight toml >}}
[app.provider.wopi]
app_icon_uri = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="folder_base_url" type="string" default="" %}}
The base URL to generate links to navigate back to the containing folder. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L69)
The base URL to generate links to navigate back to the containing folder. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L83)
{{< highlight toml >}}
[app.provider.wopi]
folder_base_url = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="app_url" type="string" default="" %}}
The App URL. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L70)
The App URL. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L84)
{{< highlight toml >}}
[app.provider.wopi]
app_url = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="app_int_url" type="string" default="" %}}
The internal app URL in case of dockerized deployments. Defaults to AppURL [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L71)
The internal app URL in case of dockerized deployments. Defaults to AppURL [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L85)
{{< highlight toml >}}
[app.provider.wopi]
app_int_url = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="app_api_key" type="string" default="" %}}
The API key used by the app, if applicable. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L72)
The API key used by the app, if applicable. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L86)
{{< highlight toml >}}
[app.provider.wopi]
app_api_key = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="jwt_secret" type="string" default="" %}}
The JWT secret to be used to retrieve the token TTL. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L73)
The JWT secret to be used to retrieve the token TTL. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L87)
{{< highlight toml >}}
[app.provider.wopi]
jwt_secret = ""
{{< /highlight >}}
{{% /dir %}}

{{% dir name="app_desktop_only" type="bool" default=false %}}
Specifies if the app can be opened only on desktop. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L74)
Specifies if the app can be opened only on desktop. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/app/provider/wopi/wopi.go#L88)
{{< highlight toml >}}
[app.provider.wopi]
app_desktop_only = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: "repository"
linkTitle: "repository"
weight: 10
description: >
Configuration for the repository service
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: "nextcloud"
linkTitle: "nextcloud"
weight: 10
description: >
Configuration for the nextcloud service
---

# _struct: ShareManagerConfig_

{{% dir name="endpoint" type="string" default="" %}}
The Nextcloud backend endpoint for user check [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/ocm/share/repository/nextcloud/nextcloud.go#L60)
{{< highlight toml >}}
[ocm.share.repository.nextcloud]
endpoint = ""
{{< /highlight >}}
{{% /dir %}}

79 changes: 62 additions & 17 deletions pkg/app/provider/wopi/wopi.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ import (
"github.com/pkg/errors"
)

const publicLinkURLPrefix = "/files/link/public/"

const ocmLinkURLPrefix = "/files/spaces/sciencemesh/"

type userType string

const (
invalid userType = "invalid"
regular userType = "regular"
federated userType = "federated"
ocm userType = "ocm"
anonymous userType = "anonymous"
)

func init() {
registry.Register("wopi", New)
}
Expand Down Expand Up @@ -111,7 +125,7 @@ func New(m map[string]interface{}) (app.Provider, error) {
}

wopiClient := rhttp.GetHTTPClient(
rhttp.Timeout(time.Duration(5*int64(time.Second))),
rhttp.Timeout(time.Duration(10*int64(time.Second))),
rhttp.Insecure(c.InsecureConnections),
)
wopiClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
Expand Down Expand Up @@ -146,17 +160,18 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc
q.Add("viewmode", viewMode.String())
q.Add("appname", p.conf.AppName)

var ut = invalid
u, ok := ctxpkg.ContextGetUser(ctx)
if !ok {
// we must have been authenticated
return nil, errors.New("wopi: ContextGetUser failed")
}
if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED {
q.Add("userid", resource.Owner.OpaqueId+"@"+resource.Owner.Idp)
ut = federated
} else {
q.Add("userid", u.Id.OpaqueId+"@"+u.Id.Idp)
}
q.Add("username", u.DisplayName)

scopes, ok := ctxpkg.ContextGetScopes(ctx)
if !ok {
Expand All @@ -166,19 +181,35 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc

// TODO (lopresti) consolidate with the templating implemented in the edge branch;
// here we assume the FolderBaseURL looks like `https://<hostname>` and we
// either append `/files/spaces/<full_path>` or `/s/<pltoken>/<relative_path>`
// either append `/files/spaces/<full_path>` or the proper URL prefix + `/<relative_path>`
var rPath string
if _, ok := utils.HasPublicShareRole(u); ok {
// we are in a public link
q.Del("username") // on public shares default to "Guest xyz"
var err error
rPath, err = getPathForPublicLink(ctx, scopes, resource)
if err != nil {
log.Warn().Err(err).Msg("wopi: failed to extract relative path from public link scope")
var pathErr error
_, pubrole := utils.HasPublicShareRole(u)
_, ocmrole := utils.HasOCMShareRole(u)
switch {
case pubrole:
// we are in a public link, username is not set so it will default to "Guest xyz"
ut = anonymous
rPath, pathErr = getPathForExternalLink(ctx, scopes, resource, publicLinkURLPrefix)
if pathErr != nil {
log.Warn().Err(pathErr).Msg("wopi: failed to extract relative path from public link scope")
}
} else {
case ocmrole:
// OCM users have no username: use displayname@Idp
ut = ocm
q.Add("username", u.DisplayName+" @ "+u.Id.Idp)
// and resolve the folder
rPath, pathErr = getPathForExternalLink(ctx, scopes, resource, ocmLinkURLPrefix)
if pathErr != nil {
log.Warn().Err(pathErr).Msg("wopi: failed to extract relative path from ocm link scope")
}
default:
// in all other cases use the resource's path
if ut == invalid {
ut = regular
}
rPath = "/files/spaces/" + path.Dir(resource.Path)
q.Add("username", u.DisplayName)
}
if rPath != "" {
fu, err := url.JoinPath(p.conf.FolderBaseURL, rPath)
Expand All @@ -188,6 +219,7 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc
q.Add("folderurl", fu)
}
}
q.Add("usertype", string(ut))

var viewAppURL string
if viewAppURLs, ok := p.appURLs["view"]; ok {
Expand Down Expand Up @@ -470,13 +502,26 @@ func parseWopiDiscovery(body io.Reader) (map[string]map[string]string, error) {
return appURLs, nil
}

func getPathForPublicLink(ctx context.Context, scopes map[string]*authpb.Scope, resource *provider.ResourceInfo) (string, error) {
func getPathForExternalLink(ctx context.Context, scopes map[string]*authpb.Scope, resource *provider.ResourceInfo, pathPrefix string) (string, error) {
pubShares, err := scope.GetPublicSharesFromScopes(scopes)
if err != nil {
return "", err
}
if len(pubShares) > 1 {
return "", errors.New("More than one public share found in the scope, lookup not implemented")
ocmShares, err := scope.GetOCMSharesFromScopes(scopes)
if err != nil {
return "", err
}
var resID *provider.ResourceId
var token string
switch {
case len(pubShares) == 1:
resID = pubShares[0].ResourceId
token = pubShares[0].Token
case len(ocmShares) == 1:
resID = ocmShares[0].ResourceId
token = ocmShares[0].Token
default:
return "", errors.New("Either one public xor OCM share is supported, lookups not implemented")
}

client, err := pool.GetGatewayServiceClient(pool.Endpoint(sharedconf.GetGatewaySVC("")))
Expand All @@ -485,7 +530,7 @@ func getPathForPublicLink(ctx context.Context, scopes map[string]*authpb.Scope,
}
statRes, err := client.Stat(ctx, &provider.StatRequest{
Ref: &provider.Reference{
ResourceId: pubShares[0].ResourceId,
ResourceId: resID,
},
})
if err != nil {
Expand All @@ -494,7 +539,7 @@ func getPathForPublicLink(ctx context.Context, scopes map[string]*authpb.Scope,

if statRes.Info.Path == resource.Path {
// this is a direct link to the resource
return "/s/" + pubShares[0].Token, nil
return pathPrefix + token, nil
}
// otherwise we are in a subfolder of the public link
relPath, err := filepath.Rel(statRes.Info.Path, resource.Path)
Expand All @@ -504,5 +549,5 @@ func getPathForPublicLink(ctx context.Context, scopes map[string]*authpb.Scope,
if strings.HasPrefix(relPath, "../") {
return "", errors.New("Scope path does not contain target resource")
}
return path.Join("/files/public/show/"+pubShares[0].Token, path.Dir(relPath)), nil
return path.Join(pathPrefix+token, path.Dir(relPath)), nil
}
21 changes: 21 additions & 0 deletions pkg/auth/scope/ocmshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/utils"
"github.com/rs/zerolog"
)
Expand Down Expand Up @@ -134,3 +135,23 @@ func AddOCMShareScope(share *ocmv1beta1.Share, role authpb.Role, scopes map[stri
}
return scopes, nil
}

// GetOCMSharesFromScopes returns all OCM shares in the given scope.
func GetOCMSharesFromScopes(scopes map[string]*authpb.Scope) ([]*ocmv1beta1.Share, error) {
var shares []*ocmv1beta1.Share
for k, s := range scopes {
if strings.HasPrefix(k, "ocmshare:") {
res := s.Resource
if res.Decoder != "json" {
return nil, errtypes.InternalError("resource should be json encoded")
}
var share ocmv1beta1.Share
err := utils.UnmarshalJSONToProtoV1(res.Value, &share)
if err != nil {
return nil, err
}
shares = append(shares, &share)
}
}
return shares, nil
}
2 changes: 1 addition & 1 deletion pkg/auth/scope/publicshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func AddPublicShareScope(share *link.PublicShare, role authpb.Role, scopes map[s
return scopes, nil
}

// GetPublicSharesFromScopes returns all the public shares in the given scope.
// GetPublicSharesFromScopes returns all public shares in the given scope.
func GetPublicSharesFromScopes(scopes map[string]*authpb.Scope) ([]*link.PublicShare, error) {
var shares []*link.PublicShare
for k, s := range scopes {
Expand Down
4 changes: 2 additions & 2 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ func HasOCMShareRole(u *userpb.User) (string, bool) {
return "", false
}

// HasPermissions returns true if all permissions defined in the stuict toCheck
// HasPermissions returns true if all permissions defined in the struct toCheck
// are set in the target.
func HasPermissions(target, toCheck *provider.ResourcePermissions) bool {
targetStruct := reflect.ValueOf(target).Elem()
Expand All @@ -395,7 +395,7 @@ func HasPermissions(target, toCheck *provider.ResourcePermissions) bool {
return true
}

// UserIsLightweight returns true if the user is a lightweith
// UserIsLightweight returns true if the user is a lightweight
// or federated account.
func UserIsLightweight(u *userpb.User) bool {
return u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED ||
Expand Down

0 comments on commit 1f4a9ad

Please sign in to comment.