Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package auth
import (
"context"
"net/http"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/plugin"
)
// Manager is the interface to implement to authenticate users
type Manager interface {
plugin.Plugin
Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error)
}
// Credentials contains the auth type, client id and secret.
type Credentials struct {
Type string
ClientID string
ClientSecret string
}
// CredentialStrategy obtains Credentials from the request.
type CredentialStrategy interface {
GetCredentials(w http.ResponseWriter, r *http.Request) (*Credentials, error)
AddWWWAuthenticate(w http.ResponseWriter, r *http.Request, realm string)
}
// TokenStrategy obtains a token from the request.
// If token does not exist returns an empty string.
type TokenStrategy interface {
GetToken(r *http.Request) string
}
// TokenWriter stores the token in a http response.
type TokenWriter interface {
WriteToken(token string, w http.ResponseWriter)
}
// Registry is the interface that auth registries implement
// for discovering auth providers
type Registry interface {
ListProviders(ctx context.Context) ([]*registry.ProviderInfo, error)
GetProvider(ctx context.Context, authType string) (*registry.ProviderInfo, error)
}
@@ -0,0 +1,99 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package appauth
import (
"context"
appauthpb "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/pkg/errors"
)
func init() {
registry.Register("appauth", New)
}
type manager struct {
GatewayAddr string `mapstructure:"gateway_addr"`
}
// New returns a new auth Manager.
func New(m map[string]interface{}) (auth.Manager, error) {
mgr := &manager{}
err := mgr.Configure(m)
if err != nil {
return nil, err
}
return mgr, nil
}
func (m *manager) Configure(ml map[string]interface{}) error {
err := mapstructure.Decode(ml, m)
if err != nil {
return errors.Wrap(err, "error decoding conf")
}
return nil
}
func (m *manager) Authenticate(ctx context.Context, username, password string) (*user.User, map[string]*authpb.Scope, error) {
gtw, err := pool.GetGatewayServiceClient(m.GatewayAddr)
if err != nil {
return nil, nil, err
}
// get user info
userResponse, err := gtw.GetUserByClaim(ctx, &user.GetUserByClaimRequest{
Claim: "username",
Value: username,
})
switch {
case err != nil:
return nil, nil, err
case userResponse.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND:
return nil, nil, errtypes.NotFound(userResponse.Status.Message)
case userResponse.Status.Code != rpcv1beta1.Code_CODE_OK:
return nil, nil, errtypes.InternalError(userResponse.Status.Message)
}
// get the app password associated with the user and password
appAuthResponse, err := gtw.GetAppPassword(ctx, &appauthpb.GetAppPasswordRequest{
User: userResponse.GetUser().Id,
Password: password,
})
switch {
case err != nil:
return nil, nil, err
case appAuthResponse.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND:
return nil, nil, errtypes.NotFound(appAuthResponse.Status.Message)
case appAuthResponse.Status.Code != rpcv1beta1.Code_CODE_OK:
return nil, nil, errtypes.InternalError(appAuthResponse.Status.Message)
}
return userResponse.GetUser(), appAuthResponse.GetAppPassword().TokenScope, nil
}
@@ -0,0 +1,127 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package demo
import (
"context"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
)
func init() {
registry.Register("demo", New)
}
type manager struct {
credentials map[string]Credentials
}
// Credentials holds a pair of secret and userid
type Credentials struct {
User *user.User
Secret string
}
// New returns a new auth Manager.
func New(m map[string]interface{}) (auth.Manager, error) {
// m not used
mgr := &manager{}
err := mgr.Configure(m)
return mgr, err
}
func (m *manager) Configure(ml map[string]interface{}) error {
creds := getCredentials()
m.credentials = creds
return nil
}
func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
if c, ok := m.credentials[clientID]; ok {
if c.Secret == clientSecret {
var scopes map[string]*authpb.Scope
var err error
if c.User.Id != nil && (c.User.Id.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || c.User.Id.Type == user.UserType_USER_TYPE_FEDERATED) {
scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil)
if err != nil {
return nil, nil, err
}
} else {
scopes, err = scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
}
return c.User, scopes, nil
}
}
return nil, nil, errtypes.InvalidCredentials(clientID)
}
func getCredentials() map[string]Credentials {
return map[string]Credentials{
"einstein": {
Secret: "relativity",
User: &user.User{
Id: &user.UserId{
Idp: "http://localhost:9998",
OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51",
Type: user.UserType_USER_TYPE_PRIMARY,
},
Username: "einstein",
Groups: []string{"sailing-lovers", "violin-haters", "physics-lovers"},
Mail: "einstein@example.org",
DisplayName: "Albert Einstein",
},
},
"marie": {
Secret: "radioactivity",
User: &user.User{
Id: &user.UserId{
Idp: "http://localhost:9998",
OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
Type: user.UserType_USER_TYPE_PRIMARY,
},
Username: "marie",
Groups: []string{"radium-lovers", "polonium-lovers", "physics-lovers"},
Mail: "marie@example.org",
DisplayName: "Marie Curie",
},
},
"richard": {
Secret: "superfluidity",
User: &user.User{
Id: &user.UserId{
Idp: "http://localhost:9998",
OpaqueId: "932b4540-8d16-481e-8ef4-588e4b6b151c",
Type: user.UserType_USER_TYPE_PRIMARY,
},
Username: "richard",
Groups: []string{"quantum-lovers", "philosophy-haters", "physics-lovers"},
Mail: "richard@example.org",
DisplayName: "Richard Feynman",
},
},
}
}
@@ -0,0 +1,67 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package impersonator
import (
"context"
"strings"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
)
func init() {
registry.Register("impersonator", New)
}
type mgr struct{}
// New returns an auth manager implementation that allows to authenticate with any credentials.
func New(c map[string]interface{}) (auth.Manager, error) {
return &mgr{}, nil
}
func (m *mgr) Configure(ml map[string]interface{}) error {
return nil
}
func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
// allow passing in uid as <opaqueid>@<idp>
at := strings.LastIndex(clientID, "@")
uid := &user.UserId{Type: user.UserType_USER_TYPE_PRIMARY}
if at < 0 {
uid.OpaqueId = clientID
} else {
uid.OpaqueId = clientID[:at]
uid.Idp = clientID[at+1:]
}
scope, err := scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
return &user.User{
Id: uid,
// not much else to provide
}, scope, nil
}
@@ -0,0 +1,146 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package json
import (
"context"
"encoding/json"
"os"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/pkg/errors"
)
func init() {
registry.Register("json", New)
}
// Credentials holds a pair of secret and userid
type Credentials struct {
ID *user.UserId `mapstructure:"id" json:"id"`
Username string `mapstructure:"username" json:"username"`
Mail string `mapstructure:"mail" json:"mail"`
MailVerified bool `mapstructure:"mail_verified" json:"mail_verified"`
DisplayName string `mapstructure:"display_name" json:"display_name"`
Secret string `mapstructure:"secret" json:"secret"`
Groups []string `mapstructure:"groups" json:"groups"`
UIDNumber int64 `mapstructure:"uid_number" json:"uid_number"`
GIDNumber int64 `mapstructure:"gid_number" json:"gid_number"`
Opaque *typespb.Opaque `mapstructure:"opaque" json:"opaque"`
}
type manager struct {
credentials map[string]*Credentials
}
type config struct {
// Users holds a path to a file containing json conforming the Users struct
Users string `mapstructure:"users"`
}
func (c *config) init() {
if c.Users == "" {
c.Users = "/etc/revad/users.json"
}
}
func parseConfig(m map[string]interface{}) (*config, error) {
c := &config{}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf")
return nil, err
}
c.init()
return c, nil
}
// New returns a new auth Manager.
func New(m map[string]interface{}) (auth.Manager, error) {
mgr := &manager{}
err := mgr.Configure(m)
if err != nil {
return nil, err
}
return mgr, nil
}
func (m *manager) Configure(ml map[string]interface{}) error {
c, err := parseConfig(ml)
if err != nil {
return err
}
m.credentials = map[string]*Credentials{}
f, err := os.ReadFile(c.Users)
if err != nil {
return err
}
credentials := []*Credentials{}
err = json.Unmarshal(f, &credentials)
if err != nil {
return err
}
for _, c := range credentials {
m.credentials[c.Username] = c
}
return nil
}
func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, map[string]*authpb.Scope, error) {
if c, ok := m.credentials[username]; ok {
if c.Secret == secret {
var scopes map[string]*authpb.Scope
var err error
if c.ID != nil && (c.ID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || c.ID.Type == user.UserType_USER_TYPE_FEDERATED) {
scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil)
if err != nil {
return nil, nil, err
}
} else {
scopes, err = scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
}
return &user.User{
Id: c.ID,
Username: c.Username,
Mail: c.Mail,
MailVerified: c.MailVerified,
DisplayName: c.DisplayName,
Groups: c.Groups,
UidNumber: c.UIDNumber,
GidNumber: c.GIDNumber,
Opaque: c.Opaque,
// TODO add arbitrary keys as opaque data
}, scopes, nil
}
}
return nil, nil, errtypes.InvalidCredentials(username)
}
@@ -0,0 +1,221 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package ldap
import (
"context"
"fmt"
"strconv"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/go-ldap/ldap/v3"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
"github.com/opencloud-eu/reva/v2/pkg/utils"
ldapIdentity "github.com/opencloud-eu/reva/v2/pkg/utils/ldap"
"github.com/pkg/errors"
)
func init() {
registry.Register("ldap", New)
}
type mgr struct {
c *config
ldapClient ldap.Client
}
type config struct {
utils.LDAPConn `mapstructure:",squash"`
LDAPIdentity ldapIdentity.Identity `mapstructure:",squash"`
Idp string `mapstructure:"idp"`
GatewaySvc string `mapstructure:"gatewaysvc"`
Nobody int64 `mapstructure:"nobody"`
LoginAttributes []string `mapstructure:"login_attributes"`
}
func parseConfig(m map[string]interface{}) (*config, error) {
c := &config{
LDAPIdentity: ldapIdentity.New(),
LoginAttributes: []string{"cn"},
}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf")
return nil, err
}
return c, nil
}
// New returns an auth manager implementation that connects to a LDAP server to validate the user.
func New(m map[string]interface{}) (auth.Manager, error) {
manager := &mgr{}
err := manager.Configure(m)
if err != nil {
return nil, err
}
manager.ldapClient, err = utils.GetLDAPClientWithReconnect(&manager.c.LDAPConn)
if err != nil {
return nil, err
}
return manager, nil
}
func (am *mgr) Configure(m map[string]interface{}) error {
c, err := parseConfig(m)
if err != nil {
return err
}
if c.Nobody == 0 {
c.Nobody = 99
}
if err = c.LDAPIdentity.Setup(); err != nil {
return fmt.Errorf("error setting up Identity config: %w", err)
}
c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc)
am.c = c
return nil
}
func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
log := appctx.GetLogger(ctx)
filter := am.getLoginFilter(clientID)
userEntry, err := am.c.LDAPIdentity.GetLDAPUserByFilter(ctx, am.ldapClient, filter)
if err != nil {
return nil, nil, err
}
// Bind as the user to verify their password
la, err := utils.GetLDAPClientForAuth(ctx, &am.c.LDAPConn)
if err != nil {
return nil, nil, err
}
defer la.Close()
err = la.Bind(userEntry.DN, clientSecret)
switch {
case err == nil:
break
case ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials):
return nil, nil, errtypes.InvalidCredentials(clientID)
default:
log.Debug().Err(err).Interface("userdn", userEntry.DN).Msg("bind with user credentials failed")
return nil, nil, err
}
var uid string
if am.c.LDAPIdentity.User.Schema.IDIsOctetString {
rawValue := userEntry.GetEqualFoldRawAttributeValue(am.c.LDAPIdentity.User.Schema.ID)
if value, err := uuid.FromBytes(rawValue); err == nil {
uid = value.String()
}
} else {
uid = userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.ID)
}
userID := &user.UserId{
Idp: am.c.Idp,
OpaqueId: uid,
Type: am.c.LDAPIdentity.GetUserType(userEntry),
}
gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc)
if err != nil {
return nil, nil, errors.Wrap(err, "ldap: error getting gateway grpc client")
}
getGroupsResp, err := gwc.GetUserGroups(ctx, &user.GetUserGroupsRequest{
UserId: userID,
})
if err != nil {
log.Warn().Err(err).Msg("error getting user groups")
return nil, nil, errors.Wrap(err, "ldap: error getting user groups")
}
if getGroupsResp.Status.Code != rpc.Code_CODE_OK {
log.Warn().Err(err).Str("msg", getGroupsResp.Status.Message).Msg("grpc getting user groups failed")
return nil, nil, fmt.Errorf("ldap: grpc getting user groups failed: '%s'", getGroupsResp.Status.Message)
}
gidNumber := am.c.Nobody
gidValue := userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.GIDNumber)
if gidValue != "" {
gidNumber, err = strconv.ParseInt(gidValue, 10, 64)
if err != nil {
return nil, nil, err
}
}
uidNumber := am.c.Nobody
uidValue := userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.UIDNumber)
if uidValue != "" {
uidNumber, err = strconv.ParseInt(uidValue, 10, 64)
if err != nil {
return nil, nil, err
}
}
u := &user.User{
Id: userID,
// TODO add more claims from the StandardClaims, eg EmailVerified
Username: userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.Username),
// TODO groups
Groups: getGroupsResp.Groups,
Mail: userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.Mail),
DisplayName: userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.DisplayName),
UidNumber: uidNumber,
GidNumber: gidNumber,
}
var scopes map[string]*authpb.Scope
if userID != nil && userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT {
scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil)
if err != nil {
return nil, nil, err
}
} else {
scopes, err = scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
}
log.Debug().Interface("entry", userEntry).Interface("user", u).Msg("authenticated user")
return u, scopes, nil
}
func (am *mgr) getLoginFilter(login string) string {
var filter string
for _, attr := range am.c.LoginAttributes {
filter = fmt.Sprintf("%s(%s=%s)", filter, attr, ldap.EscapeFilter(login))
}
return fmt.Sprintf("(&%s(objectclass=%s)(|%s))",
am.c.LDAPIdentity.User.Filter,
am.c.LDAPIdentity.User.Objectclass,
filter,
)
}
@@ -0,0 +1,34 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package loader
import (
// Load core authentication managers.
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/appauth"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/demo"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/impersonator"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/json"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/ldap"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/machine"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/ocmshares"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/oidc"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/publicshares"
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/serviceaccounts"
// Add your own here
)
@@ -0,0 +1,125 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package machine
import (
"context"
"strings"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/pkg/errors"
)
// 'machine' is an authentication method used to impersonate users.
// To impersonate the given user it's only needed an api-key, saved
// in a config file.
// supported claims
var claims = []string{"mail", "uid", "username", "gid", "userid"}
type manager struct {
APIKey string `mapstructure:"api_key"`
GatewayAddr string `mapstructure:"gateway_addr"`
}
func init() {
registry.Register("machine", New)
}
// Configure parses the map conf
func (m *manager) Configure(conf map[string]interface{}) error {
err := mapstructure.Decode(conf, m)
if err != nil {
return errors.Wrap(err, "error decoding conf")
}
return nil
}
// New creates a new manager for the 'machine' authentication
func New(conf map[string]interface{}) (auth.Manager, error) {
m := &manager{}
err := m.Configure(conf)
if err != nil {
return nil, err
}
return m, nil
}
// Authenticate impersonate an user if the provided secret is equal to the api-key
func (m *manager) Authenticate(ctx context.Context, user, secret string) (*userpb.User, map[string]*authpb.Scope, error) {
if m.APIKey != secret {
return nil, nil, errtypes.InvalidCredentials("")
}
gtw, err := pool.GetGatewayServiceClient(m.GatewayAddr)
if err != nil {
return nil, nil, err
}
// username could be either a normal username or a string <claim>:<value>
// in the first case the claim is "username"
claim, value := parseUser(user)
userResponse, err := gtw.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{
Claim: claim,
Value: value,
})
switch {
case err != nil:
return nil, nil, err
case userResponse.Status.Code == rpc.Code_CODE_NOT_FOUND:
return nil, nil, errtypes.NotFound(userResponse.Status.Message)
case userResponse.Status.Code != rpc.Code_CODE_OK:
return nil, nil, errtypes.InternalError(userResponse.Status.Message)
}
scope, err := scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
return userResponse.GetUser(), scope, nil
}
func contains(lst []string, s string) bool {
for _, e := range lst {
if e == s {
return true
}
}
return false
}
func parseUser(user string) (string, string) {
s := strings.SplitN(user, ":", 2)
if len(s) == 2 && contains(claims, s[0]) {
return s[0], s[1]
}
return "username", user
}
@@ -0,0 +1,196 @@
// Copyright 2018-2023 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package ocmshares
import (
"context"
provider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
ocminvite "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/opencloud-eu/reva/v2/pkg/utils/cfg"
"github.com/pkg/errors"
)
func init() {
registry.Register("ocmshares", New)
}
type manager struct {
c *config
gw gateway.GatewayAPIClient
}
type config struct {
GatewayAddr string `mapstructure:"gatewaysvc"`
}
func (c *config) ApplyDefaults() {
c.GatewayAddr = sharedconf.GetGatewaySVC(c.GatewayAddr)
}
// New creates a new ocmshares authentication manager.
func New(m map[string]interface{}) (auth.Manager, error) {
var mgr manager
if err := mgr.Configure(m); err != nil {
return nil, err
}
gw, err := pool.GetGatewayServiceClient(mgr.c.GatewayAddr)
if err != nil {
return nil, err
}
mgr.gw = gw
return &mgr, nil
}
func (m *manager) Configure(ml map[string]interface{}) error {
var c config
if err := cfg.Decode(ml, &c); err != nil {
return errors.Wrap(err, "ocmshares: error decoding config")
}
m.c = &c
return nil
}
func (m *manager) Authenticate(ctx context.Context, ocmshare, sharedSecret string) (*userpb.User, map[string]*authpb.Scope, error) {
log := appctx.GetLogger(ctx).With().Str("ocmshare", ocmshare).Logger()
// We need to use GetOCMShareByToken, as GetOCMShare would require a user in the context
shareRes, err := m.gw.GetOCMShareByToken(ctx, &ocm.GetOCMShareByTokenRequest{
Token: sharedSecret,
})
switch {
case err != nil:
log.Error().Err(err).Msg("error getting ocm share by token")
return nil, nil, err
case shareRes.Status.Code == rpc.Code_CODE_NOT_FOUND:
log.Debug().Msg("ocm share not found")
return nil, nil, errtypes.NotFound(shareRes.Status.Message)
case shareRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED:
log.Debug().Msg("permission denied")
return nil, nil, errtypes.InvalidCredentials(shareRes.Status.Message)
case shareRes.Status.Code != rpc.Code_CODE_OK:
log.Error().Interface("status", shareRes.Status).Msg("got unexpected error in the grpc call to GetOCMShare")
return nil, nil, errtypes.InternalError(shareRes.Status.Message)
}
// compare ocm share id
if shareRes.GetShare().GetId().GetOpaqueId() != ocmshare {
log.Error().Str("persisted", ocmshare).Str("requested", shareRes.GetShare().GetId().GetOpaqueId()).Msg("mismatching ocm share id for existing secret")
return nil, nil, errtypes.InvalidCredentials("invalid shared secret")
}
// the user authenticated using the ocmshares authentication method
// is the recipient of the share
u := shareRes.Share.Grantee.GetUserId()
d, err := utils.MarshalProtoV1ToJSON(shareRes.GetShare().Creator)
if err != nil {
return nil, nil, err
}
o := &types.Opaque{
Map: map[string]*types.OpaqueEntry{
"user-filter": {
Decoder: "json",
Value: d,
},
},
}
userRes, err := m.gw.GetAcceptedUser(ctx, &ocminvite.GetAcceptedUserRequest{
RemoteUserId: u,
Opaque: o,
})
switch {
case err != nil:
return nil, nil, err
case userRes.Status.Code == rpc.Code_CODE_NOT_FOUND:
return nil, nil, errtypes.NotFound(shareRes.Status.Message)
case userRes.Status.Code != rpc.Code_CODE_OK:
return nil, nil, errtypes.InternalError(userRes.Status.Message)
}
role, roleStr := getRole(shareRes.Share)
scope, err := scope.AddOCMShareScope(shareRes.Share, role, nil)
if err != nil {
return nil, nil, err
}
user := userRes.RemoteUser
user.Opaque = &types.Opaque{
Map: map[string]*types.OpaqueEntry{
"ocm-share-role": {
Decoder: "plain",
Value: []byte(roleStr),
},
},
}
user.Opaque = utils.AppendJSONToOpaque(user.Opaque, "impersonating-user", userRes.RemoteUser)
return user, scope, nil
}
func getRole(s *ocm.Share) (authpb.Role, string) {
// TODO: consider to somehow merge the permissions from all the access methods?
// it's not clear infact which should be the role when webdav is editor role while
// webapp is only view mode for example
// this implementation considers only the simple case in which when a client creates
// a share with multiple access methods, the permissions are matching in all of them.
for _, m := range s.AccessMethods {
switch v := m.Term.(type) {
case *ocm.AccessMethod_WebdavOptions:
p := v.WebdavOptions.Permissions
if p.InitiateFileUpload {
return authpb.Role_ROLE_EDITOR, "editor"
}
if p.InitiateFileDownload {
return authpb.Role_ROLE_VIEWER, "viewer"
}
case *ocm.AccessMethod_WebappOptions:
viewMode := v.WebappOptions.ViewMode
if viewMode == provider.ViewMode_VIEW_MODE_VIEW_ONLY ||
viewMode == provider.ViewMode_VIEW_MODE_READ_ONLY ||
viewMode == provider.ViewMode_VIEW_MODE_PREVIEW {
return authpb.Role_ROLE_VIEWER, "viewer"
}
if viewMode == provider.ViewMode_VIEW_MODE_READ_WRITE {
return authpb.Role_ROLE_EDITOR, "editor"
}
}
}
return authpb.Role_ROLE_INVALID, "invalid"
}
@@ -0,0 +1,363 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
// Package oidc verifies an OIDC token against the configured OIDC provider
// and obtains the necessary claims to obtain user information.
package oidc
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"
oidc "github.com/coreos/go-oidc/v3/oidc"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/juliangruber/go-intersect"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/rhttp"
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
func init() {
registry.Register("oidc", New)
}
type mgr struct {
provider *oidc.Provider // cached on first request
c *config
oidcUsersMapping map[string]*oidcUserMapping
}
type config struct {
Insecure bool `mapstructure:"insecure" docs:"false;Whether to skip certificate checks when sending requests."`
Issuer string `mapstructure:"issuer" docs:";The issuer of the OIDC token."`
IDClaim string `mapstructure:"id_claim" docs:"sub;The claim containing the ID of the user."`
UIDClaim string `mapstructure:"uid_claim" docs:";The claim containing the UID of the user."`
GIDClaim string `mapstructure:"gid_claim" docs:";The claim containing the GID of the user."`
GatewaySvc string `mapstructure:"gatewaysvc" docs:";The endpoint at which the GRPC gateway is exposed."`
UsersMapping string `mapstructure:"users_mapping" docs:"; The optional OIDC users mapping file path"`
GroupClaim string `mapstructure:"group_claim" docs:"; The group claim to be looked up to map the user (default to 'groups')."`
}
type oidcUserMapping struct {
OIDCIssuer string `mapstructure:"oidc_issuer" json:"oidc_issuer"`
OIDCGroup string `mapstructure:"oidc_group" json:"oidc_group"`
Username string `mapstructure:"username" json:"username"`
}
func (c *config) init() {
if c.IDClaim == "" {
// sub is stable and defined as unique. the user manager needs to take care of the sub to user metadata lookup
c.IDClaim = "sub"
}
if c.GroupClaim == "" {
c.GroupClaim = "groups"
}
if c.UIDClaim == "" {
c.UIDClaim = "uid"
}
if c.GIDClaim == "" {
c.GIDClaim = "gid"
}
c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc)
}
func parseConfig(m map[string]interface{}) (*config, error) {
c := &config{}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf")
return nil, err
}
return c, nil
}
// New returns an auth manager implementation that verifies the oidc token and obtains the user claims.
func New(m map[string]interface{}) (auth.Manager, error) {
manager := &mgr{}
err := manager.Configure(m)
if err != nil {
return nil, err
}
return manager, nil
}
func (am *mgr) Configure(m map[string]interface{}) error {
c, err := parseConfig(m)
if err != nil {
return err
}
c.init()
am.c = c
am.oidcUsersMapping = map[string]*oidcUserMapping{}
if c.UsersMapping == "" {
// no mapping defined, leave the map empty and move on
return nil
}
f, err := os.ReadFile(c.UsersMapping)
if err != nil {
return fmt.Errorf("oidc: error reading the users mapping file: +%v", err)
}
oidcUsers := []*oidcUserMapping{}
err = json.Unmarshal(f, &oidcUsers)
if err != nil {
return fmt.Errorf("oidc: error unmarshalling the users mapping file: +%v", err)
}
for _, u := range oidcUsers {
if _, found := am.oidcUsersMapping[u.OIDCGroup]; found {
return fmt.Errorf("oidc: mapping error, group \"%s\" is mapped to multiple users", u.OIDCGroup)
}
am.oidcUsersMapping[u.OIDCGroup] = u
}
return nil
}
// The clientID would be empty as we only need to validate the clientSecret variable
// which contains the access token that we can use to contact the UserInfo endpoint
// and get the user claims.
func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
ctx = am.getOAuthCtx(ctx)
log := appctx.GetLogger(ctx)
oidcProvider, err := am.getOIDCProvider(ctx)
if err != nil {
return nil, nil, fmt.Errorf("oidc: error creating oidc provider: +%v", err)
}
oauth2Token := &oauth2.Token{
AccessToken: clientSecret,
}
// query the oidc provider for user info
userInfo, err := oidcProvider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
return nil, nil, fmt.Errorf("oidc: error getting userinfo: +%v", err)
}
// claims contains the standard OIDC claims like iss, iat, aud, ... and any other non-standard one.
// TODO(labkode): make claims configuration dynamic from the config file so we can add arbitrary mappings from claims to user struct.
// For now, only the group claim is dynamic.
// TODO(labkode): may do like K8s does it: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go
var claims map[string]interface{}
if err := userInfo.Claims(&claims); err != nil {
return nil, nil, fmt.Errorf("oidc: error unmarshaling userinfo claims: %v", err)
}
log.Debug().Interface("claims", claims).Interface("userInfo", userInfo).Msg("unmarshalled userinfo")
if claims["iss"] == nil { // This is not set in simplesamlphp
claims["iss"] = am.c.Issuer
}
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["preferred_username"] == nil {
claims["preferred_username"] = claims["email"]
}
if claims["name"] == nil {
claims["name"] = claims[am.c.IDClaim]
}
if claims["name"] == nil {
return nil, nil, fmt.Errorf("no \"name\" attribute found in userinfo: maybe the client did not request the oidc \"profile\"-scope")
}
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")
}
uid, _ := claims[am.c.UIDClaim].(float64)
claims[am.c.UIDClaim] = int64(uid) // in case the uid claim is missing and a mapping is to be performed, resolveUser() will populate it
// Note that if not, will silently carry a user with 0 uid, potentially problematic with storage providers
gid, _ := claims[am.c.GIDClaim].(float64)
claims[am.c.GIDClaim] = int64(gid)
err = am.resolveUser(ctx, claims)
if err != nil {
return nil, nil, errors.Wrapf(err, "oidc: error resolving username for external user '%v'", claims["email"])
}
userID := &user.UserId{
OpaqueId: claims[am.c.IDClaim].(string), // a stable non reassignable id
Idp: claims["iss"].(string), // in the scope of this issuer
Type: getUserType(claims[am.c.IDClaim].(string)),
}
gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc)
if err != nil {
return nil, nil, errors.Wrap(err, "oidc: error getting gateway grpc client")
}
getGroupsResp, err := gwc.GetUserGroups(ctx, &user.GetUserGroupsRequest{
UserId: userID,
})
if err != nil {
return nil, nil, errors.Wrapf(err, "oidc: error getting user groups for '%+v'", userID)
}
if getGroupsResp.Status.Code != rpc.Code_CODE_OK {
return nil, nil, status.NewErrorFromCode(getGroupsResp.Status.Code, "oidc")
}
u := &user.User{
Id: userID,
Username: claims["preferred_username"].(string),
Groups: getGroupsResp.Groups,
Mail: claims["email"].(string),
MailVerified: claims["email_verified"].(bool),
DisplayName: claims["name"].(string),
UidNumber: claims[am.c.UIDClaim].(int64),
GidNumber: claims[am.c.GIDClaim].(int64),
}
var scopes map[string]*authpb.Scope
if userID != nil && (userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || userID.Type == user.UserType_USER_TYPE_FEDERATED) {
scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil)
if err != nil {
return nil, nil, err
}
} else {
scopes, err = scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
}
return u, scopes, nil
}
func (am *mgr) getOAuthCtx(ctx context.Context) context.Context {
// Sometimes for testing we need to skip the TLS check, that's why we need a
// custom HTTP client.
customHTTPClient := rhttp.GetHTTPClient(
rhttp.Context(ctx),
rhttp.Timeout(time.Second*10),
rhttp.Insecure(am.c.Insecure),
// Fixes connection fd leak which might be caused by provider-caching
rhttp.DisableKeepAlive(true),
)
ctx = context.WithValue(ctx, oauth2.HTTPClient, customHTTPClient)
return ctx
}
// getOIDCProvider returns a singleton OIDC provider
func (am *mgr) getOIDCProvider(ctx context.Context) (*oidc.Provider, error) {
ctx = am.getOAuthCtx(ctx)
log := appctx.GetLogger(ctx)
if am.provider != nil {
return am.provider, nil
}
// Initialize a provider by specifying the issuer URL.
// Once initialized this is a singleton that is reused for further requests.
// The provider is responsible to verify the token sent by the client
// against the security keys oftentimes available in the .well-known endpoint.
provider, err := oidc.NewProvider(ctx, am.c.Issuer)
if err != nil {
log.Error().Err(err).Msg("oidc: error creating a new oidc provider")
return nil, fmt.Errorf("oidc: error creating a new oidc provider: %+v", err)
}
am.provider = provider
return am.provider, nil
}
func (am *mgr) resolveUser(ctx context.Context, claims map[string]interface{}) error {
if len(am.oidcUsersMapping) > 0 {
var username string
// map and discover the user's username when a mapping is defined
if claims[am.c.GroupClaim] == nil {
// we are required to perform a user mapping but the group claim is not available
return fmt.Errorf("no \"%s\" claim found in userinfo to map user", am.c.GroupClaim)
}
mappings := make([]string, 0, len(am.oidcUsersMapping))
for _, m := range am.oidcUsersMapping {
if m.OIDCIssuer == claims["iss"] {
mappings = append(mappings, m.OIDCGroup)
}
}
intersection := intersect.Simple(claims[am.c.GroupClaim], mappings)
if len(intersection) > 1 {
// multiple mappings are not implemented as we cannot decide which one to choose
return errtypes.PermissionDenied("more than one user mapping entry exists for the given group claims")
}
if len(intersection) == 0 {
return errtypes.PermissionDenied("no user mapping found for the given group claim(s)")
}
for _, m := range intersection {
username = am.oidcUsersMapping[m.(string)].Username
}
upsc, err := pool.GetUserProviderServiceClient(am.c.GatewaySvc)
if err != nil {
return errors.Wrap(err, "error getting user provider grpc client")
}
getUserByClaimResp, err := upsc.GetUserByClaim(ctx, &user.GetUserByClaimRequest{
Claim: "username",
Value: username,
})
if err != nil {
return errors.Wrapf(err, "error getting user by username '%v'", username)
}
if getUserByClaimResp.Status.Code != rpc.Code_CODE_OK {
return status.NewErrorFromCode(getUserByClaimResp.Status.Code, "oidc")
}
// take the properties of the mapped target user to override the claims
claims["preferred_username"] = username
claims[am.c.IDClaim] = getUserByClaimResp.GetUser().GetId().OpaqueId
claims["iss"] = getUserByClaimResp.GetUser().GetId().Idp
claims[am.c.UIDClaim] = getUserByClaimResp.GetUser().UidNumber
claims[am.c.GIDClaim] = getUserByClaimResp.GetUser().GidNumber
appctx.GetLogger(ctx).Debug().Str("username", username).Interface("claims", claims).Msg("resolveUser: claims overridden from mapped user")
}
return 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
}
@@ -0,0 +1,183 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package publicshares
import (
"context"
"strings"
"time"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/pkg/errors"
)
func init() {
registry.Register("publicshares", New)
}
type manager struct {
c *config
}
type config struct {
GatewayAddr string `mapstructure:"gateway_addr"`
}
func parseConfig(m map[string]interface{}) (*config, error) {
c := &config{}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf")
return nil, err
}
return c, nil
}
// New returns a new auth Manager.
func New(m map[string]interface{}) (auth.Manager, error) {
mgr := &manager{}
err := mgr.Configure(m)
if err != nil {
return nil, err
}
return mgr, nil
}
func (m *manager) Configure(ml map[string]interface{}) error {
conf, err := parseConfig(ml)
if err != nil {
return err
}
m.c = conf
return nil
}
func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user.User, map[string]*authpb.Scope, error) {
gwConn, err := pool.GetGatewayServiceClient(m.c.GatewayAddr)
if err != nil {
return nil, nil, err
}
var auth *link.PublicShareAuthentication
if strings.HasPrefix(secret, "password|") {
secret = strings.TrimPrefix(secret, "password|")
auth = &link.PublicShareAuthentication{
Spec: &link.PublicShareAuthentication_Password{
Password: secret,
},
}
} else if strings.HasPrefix(secret, "signature|") {
secret = strings.TrimPrefix(secret, "signature|")
parts := strings.Split(secret, "|")
sig, expiration := parts[0], parts[1]
exp, _ := time.Parse(time.RFC3339, expiration)
auth = &link.PublicShareAuthentication{
Spec: &link.PublicShareAuthentication_Signature{
Signature: &link.ShareSignature{
Signature: sig,
SignatureExpiration: &types.Timestamp{
Seconds: uint64(exp.UnixNano() / 1000000000),
Nanos: uint32(exp.UnixNano() % 1000000000),
},
},
},
}
}
publicShareResponse, err := gwConn.GetPublicShareByToken(ctx, &link.GetPublicShareByTokenRequest{
Token: token,
Authentication: auth,
Sign: true,
})
switch {
case err != nil:
return nil, nil, err
case publicShareResponse.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND:
return nil, nil, errtypes.NotFound(publicShareResponse.Status.Message)
case publicShareResponse.Status.Code == rpcv1beta1.Code_CODE_PERMISSION_DENIED:
return nil, nil, errtypes.InvalidCredentials(publicShareResponse.Status.Message)
case publicShareResponse.Status.Code != rpcv1beta1.Code_CODE_OK:
return nil, nil, errtypes.InternalError(publicShareResponse.Status.Message)
}
var owner *user.User
// FIXME use new user type SPACE_OWNER
if publicShareResponse.GetShare().GetOwner().GetType() == 8 {
owner = &user.User{Id: publicShareResponse.GetShare().GetOwner(), DisplayName: "Public", Username: "public"}
} else {
getUserResponse, err := gwConn.GetUser(ctx, &user.GetUserRequest{
UserId: publicShareResponse.GetShare().GetCreator(),
})
switch {
case err != nil:
return nil, nil, err
case getUserResponse.GetStatus().GetCode() == rpcv1beta1.Code_CODE_NOT_FOUND:
return nil, nil, errtypes.NotFound(getUserResponse.GetStatus().GetMessage())
case getUserResponse.GetStatus().GetCode() == rpcv1beta1.Code_CODE_PERMISSION_DENIED:
return nil, nil, errtypes.InvalidCredentials(getUserResponse.GetStatus().GetMessage())
case getUserResponse.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK:
return nil, nil, errtypes.InternalError(getUserResponse.GetStatus().GetMessage())
}
owner = getUserResponse.GetUser()
}
share := publicShareResponse.GetShare()
role := authpb.Role_ROLE_VIEWER
roleStr := "viewer"
if share.Permissions.Permissions.InitiateFileUpload && !share.Permissions.Permissions.InitiateFileDownload {
role = authpb.Role_ROLE_UPLOADER
roleStr = "uploader"
} else if share.Permissions.Permissions.InitiateFileUpload {
role = authpb.Role_ROLE_EDITOR
roleStr = "editor"
}
scope, err := scope.AddPublicShareScope(share, role, nil)
if err != nil {
return nil, nil, err
}
owner.Opaque = &types.Opaque{
Map: map[string]*types.OpaqueEntry{
"public-share-role": {
Decoder: "plain",
Value: []byte(roleStr),
},
},
}
u := &user.User{Id: &user.UserId{OpaqueId: token, Idp: "public", Type: user.UserType_USER_TYPE_GUEST}, DisplayName: "Public", Username: "public"}
owner.Opaque = utils.AppendJSONToOpaque(owner.Opaque, "impersonating-user", u)
return owner, scope, nil
}
// ErrPasswordNotProvided is returned when the public share is password protected, but there was no password on the request
var ErrPasswordNotProvided = errors.New("public share is password protected, but password was not provided")
@@ -0,0 +1,36 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package registry
import (
"github.com/opencloud-eu/reva/v2/pkg/auth"
)
// NewFunc is the function that auth implementations
// should register to at init time.
type NewFunc func(map[string]interface{}) (auth.Manager, error)
// NewFuncs is a map containing all the registered auth managers.
var NewFuncs = map[string]NewFunc{}
// Register registers a new auth manager new function.
// Not safe for concurrent use. Safe for use from package init.
func Register(name string, f NewFunc) {
NewFuncs[name] = f
}
@@ -0,0 +1,90 @@
package serviceaccounts
import (
"context"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
"github.com/pkg/errors"
)
type conf struct {
ServiceUsers []serviceuser `mapstructure:"service_accounts"`
}
type serviceuser struct {
ID string `mapstructure:"id"`
Secret string `mapstructure:"secret"`
}
type manager struct {
authenticate func(userID, secret string) error
}
func init() {
registry.Register("serviceaccounts", New)
}
// Configure parses the map conf
func (m *manager) Configure(config map[string]interface{}) error {
c := &conf{}
if err := mapstructure.Decode(config, c); err != nil {
return errors.Wrap(err, "error decoding conf")
}
// only inmem authenticator for now
a := &inmemAuthenticator{make(map[string]string)}
for _, s := range c.ServiceUsers {
a.m[s.ID] = s.Secret
}
m.authenticate = a.Authenticate
return nil
}
// New creates a new manager for the 'service' authentication
func New(conf map[string]interface{}) (auth.Manager, error) {
m := &manager{}
err := m.Configure(conf)
if err != nil {
return nil, err
}
return m, nil
}
// Authenticate authenticates the service account
func (m *manager) Authenticate(ctx context.Context, userID string, secret string) (*userpb.User, map[string]*authpb.Scope, error) {
if err := m.authenticate(userID, secret); err != nil {
return nil, nil, err
}
scope, err := scope.AddOwnerScope(nil)
if err != nil {
return nil, nil, err
}
return &userpb.User{
// TODO: more details for service users?
Id: &userpb.UserId{
OpaqueId: userID,
Type: userpb.UserType_USER_TYPE_SERVICE,
Idp: "none",
},
}, scope, nil
}
type inmemAuthenticator struct {
m map[string]string
}
func (a *inmemAuthenticator) Authenticate(userID string, secret string) error {
if secret == "" || a.m[userID] == "" {
return errors.New("unknown user")
}
if a.m[userID] == secret {
return nil
}
return errors.New("secrets do not match")
}
@@ -0,0 +1,25 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package loader
import (
// Load core storage broker drivers.
_ "github.com/opencloud-eu/reva/v2/pkg/auth/registry/static"
// Add your own here
)
@@ -0,0 +1,34 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package registry
import "github.com/opencloud-eu/reva/v2/pkg/auth"
// NewFunc is the function that auth registry implementations
// should register at init time.
type NewFunc func(map[string]interface{}) (auth.Registry, error)
// NewFuncs is a map containing all the registered auth registries.
var NewFuncs = map[string]NewFunc{}
// Register registers a new auth registry new function.
// Not safe for concurrent use. Safe for use from package init.
func Register(name string, f NewFunc) {
NewFuncs[name] = f
}
@@ -0,0 +1,91 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package static
import (
"context"
registrypb "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/auth"
"github.com/opencloud-eu/reva/v2/pkg/auth/registry/registry"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
)
func init() {
registry.Register("static", New)
}
type config struct {
Rules map[string]string `mapstructure:"rules"`
}
func (c *config) init() {
if len(c.Rules) == 0 {
c.Rules = map[string]string{
"basic": sharedconf.GetGatewaySVC(""),
}
}
}
type reg struct {
rules map[string]string
}
func (r *reg) ListProviders(ctx context.Context) ([]*registrypb.ProviderInfo, error) {
providers := make([]*registrypb.ProviderInfo, 0, len(r.rules))
for k, v := range r.rules {
providers = append(providers, &registrypb.ProviderInfo{
ProviderType: k,
Address: v,
})
}
return providers, nil
}
func (r *reg) GetProvider(ctx context.Context, authType string) (*registrypb.ProviderInfo, error) {
for k, v := range r.rules {
if k == authType {
return &registrypb.ProviderInfo{
ProviderType: k,
Address: v,
}, nil
}
}
return nil, errtypes.NotFound("static: auth type not found: " + authType)
}
func parseConfig(m map[string]interface{}) (*config, error) {
c := &config{}
if err := mapstructure.Decode(m, c); err != nil {
return nil, err
}
return c, nil
}
// New returns an implementation of the auth.Registry interface.
func New(m map[string]interface{}) (auth.Registry, error) {
c, err := parseConfig(m)
if err != nil {
return nil, err
}
c.init()
return &reg{rules: c.Rules}, nil
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package auth
import (
"context"
"net/rpc"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
hcplugin "github.com/hashicorp/go-plugin"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/plugin"
)
func init() {
plugin.Register("authprovider", &ProviderPlugin{})
}
// ProviderPlugin is the implementation of plugin.Plugin so we can serve/consume this.
type ProviderPlugin struct {
Impl Manager
}
// Server returns the RPC Server which serves the methods that the Client calls over net/rpc
func (p *ProviderPlugin) Server(*hcplugin.MuxBroker) (interface{}, error) {
return &RPCServer{Impl: p.Impl}, nil
}
// Client returns interface implementation for the plugin that communicates to the server end of the plugin
func (p *ProviderPlugin) Client(b *hcplugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &RPCClient{Client: c}, nil
}
// RPCClient is an implementation of Manager that talks over RPC.
type RPCClient struct{ Client *rpc.Client }
// ConfigureArg for RPC
type ConfigureArg struct {
Ml map[string]interface{}
}
// ConfigureReply for RPC
type ConfigureReply struct {
Err error
}
// Configure RPCClient configure method
func (m *RPCClient) Configure(ml map[string]interface{}) error {
args := ConfigureArg{Ml: ml}
resp := ConfigureReply{}
err := m.Client.Call("Plugin.Configure", args, &resp)
if err != nil {
return err
}
return resp.Err
}
// AuthenticateArgs for RPC
type AuthenticateArgs struct {
Ctx map[interface{}]interface{}
ClientID string
ClientSecret string
}
// AuthenticateReply for RPC
type AuthenticateReply struct {
User *user.User
Auth map[string]*authpb.Scope
Error error
}
// Authenticate RPCClient Authenticate method
func (m *RPCClient) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
ctxVal := appctx.GetKeyValuesFromCtx(ctx)
args := AuthenticateArgs{Ctx: ctxVal, ClientID: clientID, ClientSecret: clientSecret}
reply := AuthenticateReply{}
err := m.Client.Call("Plugin.Authenticate", args, &reply)
if err != nil {
return nil, nil, err
}
return reply.User, reply.Auth, reply.Error
}
// RPCServer is the server that RPCClient talks to, conforming to the requirements of net/rpc
type RPCServer struct {
// This is the real implementation
Impl Manager
}
// Configure RPCServer Configure method
func (m *RPCServer) Configure(args ConfigureArg, resp *ConfigureReply) error {
resp.Err = m.Impl.Configure(args.Ml)
return nil
}
// Authenticate RPCServer Authenticate method
func (m *RPCServer) Authenticate(args AuthenticateArgs, resp *AuthenticateReply) error {
ctx := appctx.PutKeyValuesToCtx(args.Ctx)
resp.User, resp.Auth, resp.Error = m.Impl.Authenticate(ctx, args.ClientID, args.ClientSecret)
return nil
}
@@ -0,0 +1,91 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
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"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
func lightweightAccountScope(_ context.Context, scope *authpb.Scope, resource interface{}, _ *zerolog.Logger) (bool, error) {
// Lightweight accounts have access to resources shared with them.
// 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
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: "/"}
val, err := utils.MarshalProtoV1ToJSON(ref)
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["lightweight"] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
return scopes, nil
}
@@ -0,0 +1,201 @@
// Copyright 2018-2023 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"path/filepath"
"strings"
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
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/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
// FIXME: the namespace here is hardcoded
// find a way to pass it from the config.
const ocmNamespace = "/ocm"
func ocmShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, _ *zerolog.Logger) (bool, error) {
var share ocmv1beta1.Share
if err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share); err != nil {
return false, err
}
switch v := resource.(type) {
// viewer role
case *registry.ListStorageProvidersRequest:
ref := &provider.Reference{}
if v.Opaque != nil && v.Opaque.Map != nil {
if e, ok := v.Opaque.Map["storage_id"]; ok {
if ref.ResourceId == nil {
ref.ResourceId = &provider.ResourceId{}
}
ref.ResourceId.StorageId = string(e.Value)
}
if e, ok := v.Opaque.Map["space_id"]; ok {
if ref.ResourceId == nil {
ref.ResourceId = &provider.ResourceId{}
}
ref.ResourceId.SpaceId = string(e.Value)
}
if e, ok := v.Opaque.Map["opaque_id"]; ok {
if ref.ResourceId == nil {
ref.ResourceId = &provider.ResourceId{}
}
ref.ResourceId.OpaqueId = string(e.Value)
}
if e, ok := v.Opaque.Map["path"]; ok {
ref.Path = string(e.Value)
}
}
return checkStorageRefForOCMShare(&share, ref, ocmNamespace), nil
case *registry.GetStorageProvidersRequest:
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.StatRequest:
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.ListContainerRequest:
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.InitiateFileDownloadRequest:
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *appprovider.OpenInAppRequest:
return checkStorageRefForOCMShare(&share, &provider.Reference{ResourceId: v.ResourceInfo.Id}, ocmNamespace), nil
case *gateway.OpenInAppRequest:
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.GetLockRequest:
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
// editor role
case *provider.CreateContainerRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.TouchFileRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.DeleteRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.MoveRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetSource(), ocmNamespace) && checkStorageRefForOCMShare(&share, v.GetDestination(), ocmNamespace), nil
case *provider.InitiateFileUploadRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.SetArbitraryMetadataRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.UnsetArbitraryMetadataRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.SetLockRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.RefreshLockRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
case *provider.UnlockRequest:
return hasRoleEditor(scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
// App provider requests
case *appregistry.GetDefaultAppProviderForMimeTypeRequest:
return true, nil
case *appregistry.GetAppProvidersRequest:
return true, nil
case *userv1beta1.GetUserByClaimRequest:
return true, nil
case *userv1beta1.GetUserRequest:
return true, nil
case *provider.ListStorageSpacesRequest:
return true, nil
// FIXME why do we need to add them? I think the whole listing of received OCM shares change might be unnecessary ... need to reevaluate that after switching the dav namespace for from /ocm back to /public
case *ocmv1beta1.ListReceivedOCMSharesRequest:
return true, nil
case *ocmv1beta1.ListOCMSharesRequest:
return true, nil
case *collaboration.ListReceivedSharesRequest:
return true, nil
case *ocmv1beta1.GetOCMShareRequest:
return checkOCMShareRef(&share, v.GetRef()), nil
case *ocmv1beta1.GetOCMShareByTokenRequest:
return share.Token == v.GetToken(), nil
case string:
return checkResourcePath(v), nil
}
return false, nil
}
func checkStorageRefForOCMShare(s *ocmv1beta1.Share, r *provider.Reference, ns string) bool {
if r.ResourceId != nil {
return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) || strings.HasPrefix(r.ResourceId.OpaqueId, s.GetId().GetOpaqueId())
}
// FIXME: the paths here are hardcoded
if strings.HasPrefix(r.GetPath(), "/public/"+s.GetId().GetOpaqueId()) {
return true
}
return strings.HasPrefix(r.GetPath(), filepath.Join(ns, s.GetId().GetOpaqueId()))
}
func checkOCMShareRef(s *ocmv1beta1.Share, ref *ocmv1beta1.ShareReference) bool {
return ref.GetId().GetOpaqueId() == s.GetId().GetOpaqueId()
}
// AddOCMShareScope adds the scope to allow access to an OCM share and the share resource.
func AddOCMShareScope(share *ocmv1beta1.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
// Create a new "scope share" to only expose the required fields `ResourceId`, `Id` and `Token` to the scope.
scopeShare := ocmv1beta1.Share{ResourceId: share.ResourceId, Id: share.GetId(), Token: share.Token}
val, err := utils.MarshalProtoV1ToJSON(&scopeShare)
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["ocmshare:"+share.Id.OpaqueId] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
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
}
@@ -0,0 +1,204 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"strings"
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
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/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
// PublicStorageProviderID is the space id used for the public links storage space
const PublicStorageProviderID = "7993447f-687f-490d-875c-ac95e89a62a4"
func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
var share link.PublicShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return false, err
}
switch v := resource.(type) {
// Viewer role
case *registry.GetStorageProvidersRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *registry.ListStorageProvidersRequest:
ref := &provider.Reference{}
if v.Opaque != nil && v.Opaque.Map != nil {
if e, ok := v.Opaque.Map["storage_id"]; ok {
if ref.ResourceId == nil {
ref.ResourceId = &provider.ResourceId{}
}
ref.ResourceId.StorageId = string(e.Value)
}
if e, ok := v.Opaque.Map["space_id"]; ok {
if ref.ResourceId == nil {
ref.ResourceId = &provider.ResourceId{}
}
ref.ResourceId.SpaceId = string(e.Value)
}
if e, ok := v.Opaque.Map["opaque_id"]; ok {
if ref.ResourceId == nil {
ref.ResourceId = &provider.ResourceId{}
}
ref.ResourceId.OpaqueId = string(e.Value)
}
if e, ok := v.Opaque.Map["path"]; ok {
ref.Path = string(e.Value)
}
}
return checkStorageRef(ctx, &share, ref), nil
case *provider.CreateHomeRequest:
return false, nil
case *provider.GetPathRequest:
return checkStorageRef(ctx, &share, &provider.Reference{ResourceId: v.GetResourceId()}), nil
case *provider.StatRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.GetLockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.UnlockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.RefreshLockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.SetLockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.ListContainerRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.InitiateFileDownloadRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *appprovider.OpenInAppRequest:
return checkStorageRef(ctx, &share, &provider.Reference{ResourceId: v.ResourceInfo.Id}), nil
case *gateway.OpenInAppRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *permissionsv1beta1.CheckPermissionRequest:
return true, nil
// Editor role
// need to return appropriate status codes in the ocs/ocdav layers.
case *provider.CreateContainerRequest:
return hasRoleEditor(scope) && checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.TouchFileRequest:
return hasRoleEditor(scope) && checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.DeleteRequest:
return hasRoleEditor(scope) && checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.MoveRequest:
return hasRoleEditor(scope) && checkStorageRef(ctx, &share, v.GetSource()) && checkStorageRef(ctx, &share, v.GetDestination()), nil
case *provider.InitiateFileUploadRequest:
return hasRoleEditor(scope) && checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.SetArbitraryMetadataRequest:
return hasRoleEditor(scope) && checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.UnsetArbitraryMetadataRequest:
return hasRoleEditor(scope) && checkStorageRef(ctx, &share, v.GetRef()), nil
// App provider requests
case *appregistry.GetDefaultAppProviderForMimeTypeRequest:
return true, nil
case *appregistry.GetAppProvidersRequest:
return true, nil
case *userv1beta1.GetUserByClaimRequest:
return true, nil
case *userv1beta1.GetUserRequest:
return true, nil
case *provider.ListStorageSpacesRequest:
return true, nil
case *link.GetPublicShareRequest:
return checkPublicShareRef(&share, v.GetRef()), nil
case *link.ListPublicSharesRequest:
// public links must not leak info about other links
return false, nil
case *collaboration.ListReceivedSharesRequest:
// public links must not leak info about collaborative shares
return false, nil
case string:
return checkResourcePath(v), nil
}
msg := "public resource type assertion failed"
logger.Debug().Str("scope", "publicshareScope").Interface("resource", resource).Msg(msg)
return false, errtypes.InternalError(msg)
}
func checkStorageRef(ctx context.Context, s *link.PublicShare, r *provider.Reference) bool {
// r: <resource_id:<storage_id:$storageID space_id:$spaceID opaque_id:$opaqueID> path:$path > >
if utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) {
return true
}
// r: <path:"/public/$token" >
if strings.HasPrefix(r.GetPath(), "/public/"+s.Token) || strings.HasPrefix(r.GetPath(), "./"+s.Token) {
return true
}
// r: <resource_id:<storage_id: space_id: opaque_id:$token> path:$path>
if id := r.GetResourceId(); id.GetStorageId() == PublicStorageProviderID {
// access to /public
if id.GetOpaqueId() == PublicStorageProviderID {
return true
}
// access relative to /public/$token
if id.GetOpaqueId() == s.Token {
return true
}
}
return false
}
func checkPublicShareRef(s *link.PublicShare, ref *link.PublicShareReference) bool {
// ref: <token:$token >
return ref.GetToken() == s.Token
}
// AddPublicShareScope adds the scope to allow access to a public share and
// the shared resource.
func AddPublicShareScope(share *link.PublicShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
// Create a new "scope share" to only expose the required fields `ResourceId` and `Token` to the scope.
scopeShare := &link.PublicShare{ResourceId: share.ResourceId, Token: share.Token}
val, err := utils.MarshalProtoV1ToJSON(scopeShare)
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["publicshare:"+share.Id.OpaqueId] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
return scopes, nil
}
@@ -0,0 +1,166 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"fmt"
"strings"
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
"github.com/rs/zerolog"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
)
func resourceinfoScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
var r provider.ResourceInfo
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &r)
if err != nil {
return false, err
}
switch v := resource.(type) {
// Viewer role
case *registry.GetStorageProvidersRequest:
return checkResourceInfo(&r, v.GetRef()), nil
case *registry.ListStorageProvidersRequest:
// the call will only return spaces the current user has access to
ref := &provider.Reference{}
if v.Opaque != nil && v.Opaque.Map != nil {
if e, ok := v.Opaque.Map["storage_id"]; ok {
ref.ResourceId = &provider.ResourceId{
StorageId: string(e.Value),
}
}
if e, ok := v.Opaque.Map["opaque_id"]; ok {
if ref.ResourceId == nil {
ref.ResourceId = &provider.ResourceId{}
}
ref.ResourceId.OpaqueId = string(e.Value)
}
if e, ok := v.Opaque.Map["path"]; ok {
ref.Path = string(e.Value)
}
}
return checkResourceInfo(&r, ref), nil
case *provider.ListStorageSpacesRequest:
// the call will only return spaces the current user has access to
return true, nil
case *provider.StatRequest:
return checkResourceInfo(&r, v.GetRef()), nil
case *provider.ListContainerRequest:
return checkResourceInfo(&r, v.GetRef()), nil
case *provider.InitiateFileDownloadRequest:
return checkResourceInfo(&r, v.GetRef()), nil
case *appprovider.OpenInAppRequest:
return checkResourceInfo(&r, &provider.Reference{ResourceId: v.ResourceInfo.Id}), nil
case *gateway.OpenInAppRequest:
return checkResourceInfo(&r, v.GetRef()), nil
// Editor role
// need to return appropriate status codes in the ocs/ocdav layers.
case *provider.CreateContainerRequest:
return hasRoleEditor(scope) && checkResourceInfo(&r, v.GetRef()), nil
case *provider.TouchFileRequest:
return hasRoleEditor(scope) && checkResourceInfo(&r, v.GetRef()), nil
case *provider.DeleteRequest:
return hasRoleEditor(scope) && checkResourceInfo(&r, v.GetRef()), nil
case *provider.MoveRequest:
return hasRoleEditor(scope) && checkResourceInfo(&r, v.GetSource()) && checkResourceInfo(&r, v.GetDestination()), nil
case *provider.InitiateFileUploadRequest:
return hasRoleEditor(scope) && checkResourceInfo(&r, v.GetRef()), nil
case *provider.SetArbitraryMetadataRequest:
return hasRoleEditor(scope) && checkResourceInfo(&r, v.GetRef()), nil
case *provider.UnsetArbitraryMetadataRequest:
return hasRoleEditor(scope) && checkResourceInfo(&r, v.GetRef()), nil
case string:
return checkResourcePath(v), nil
}
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
logger.Debug().Str("scope", "resourceinfoScope").Msg(msg)
return false, errtypes.InternalError(msg)
}
func checkResourceInfo(inf *provider.ResourceInfo, ref *provider.Reference) bool {
// ref: <resource_id:<storage_id:$storageID opaque_id:$opaqueID path:$path> >
if ref.ResourceId != nil { // path can be empty or a relative path
if inf.Id.SpaceId == ref.ResourceId.SpaceId && inf.Id.OpaqueId == ref.ResourceId.OpaqueId {
if ref.Path == "" {
// id only reference
return true
}
// check path has same prefix below
} else {
return false
}
}
// ref: <path:$path >
if strings.HasPrefix(ref.GetPath(), inf.Path) {
return true
}
return false
}
func checkResourcePath(path string) bool {
paths := []string{
"/dataprovider",
"/data",
"/app/open",
"/app/new",
"/archiver",
"/ocs/v2.php/cloud/capabilities",
"/ocs/v1.php/cloud/capabilities",
}
for _, p := range paths {
if strings.HasPrefix(path, p) {
return true
}
}
return false
}
// AddResourceInfoScope adds the scope to allow access to a resource info object.
func AddResourceInfoScope(r *provider.ResourceInfo, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
// Create a new "scope info" to only expose the required fields `Id` and `Path` to the scope.
scopeInfo := &provider.ResourceInfo{Id: r.Id, Path: r.Path}
val, err := utils.MarshalProtoV1ToJSON(scopeInfo)
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["resourceinfo:"+r.Id.String()] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
return scopes, nil
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"strings"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/rs/zerolog"
)
// Verifier is the function signature which every scope verifier should implement.
type Verifier func(context.Context, *authpb.Scope, interface{}, *zerolog.Logger) (bool, error)
var supportedScopes = map[string]Verifier{
"user": userScope,
"publicshare": publicshareScope,
"resourceinfo": resourceinfoScope,
"lightweight": lightweightAccountScope,
"ocmshare": ocmShareScope,
}
// VerifyScope is the function to be called when dismantling tokens to check if
// the token has access to a particular resource.
func VerifyScope(ctx context.Context, scopeMap map[string]*authpb.Scope, resource interface{}) (bool, error) {
logger := appctx.GetLogger(ctx)
for k, scope := range scopeMap {
for s, f := range supportedScopes {
if strings.HasPrefix(k, s) {
if valid, err := f(ctx, scope, resource, logger); err == nil && valid {
return true, nil
}
}
}
}
return false, nil
}
func hasRoleEditor(scope *authpb.Scope) bool {
return scope.Role == authpb.Role_ROLE_OWNER || scope.Role == authpb.Role_ROLE_EDITOR || scope.Role == authpb.Role_ROLE_UPLOADER
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
func userScope(_ context.Context, scope *authpb.Scope, resource interface{}, _ *zerolog.Logger) (bool, error) {
// Always return true. Registered users can access all paths.
// TODO(ishank011): Add checks for read/write permissions.
return true, nil
}
// AddOwnerScope adds the default owner scope with access to all resources.
func AddOwnerScope(scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
ref := &provider.Reference{Path: "/"}
val, err := utils.MarshalProtoV1ToJSON(ref)
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["user"] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: authpb.Role_ROLE_OWNER,
}
return scopes, nil
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"fmt"
"strings"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
)
// FormatScope create a pretty print of the scope
func FormatScope(scopeType string, scope *authpb.Scope) (string, error) {
// TODO(gmgigi96): check decoder type
switch {
case strings.HasPrefix(scopeType, "user"):
// user scope
var ref provider.Reference
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &ref)
if err != nil {
return "", err
}
return fmt.Sprintf("%s %s", ref.String(), scope.Role.String()), nil
case strings.HasPrefix(scopeType, "publicshare"):
// public share
var pShare link.PublicShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &pShare)
if err != nil {
return "", err
}
return fmt.Sprintf("share:\"%s\" %s", pShare.Id.OpaqueId, scope.Role.String()), nil
case strings.HasPrefix(scopeType, "resourceinfo"):
var resInfo provider.ResourceInfo
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &resInfo)
if err != nil {
return "", err
}
return fmt.Sprintf("path:\"%s\" %s", resInfo.Path, scope.Role.String()), nil
default:
return "", errtypes.NotSupported("scope not yet supported")
}
}