switch to go vendoring
This commit is contained in:
+66
@@ -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/cs3org/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)
|
||||
}
|
||||
+99
@@ -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/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"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
|
||||
}
|
||||
+127
@@ -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/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/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",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
+67
@@ -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/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/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
|
||||
}
|
||||
+146
@@ -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/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"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)
|
||||
}
|
||||
+221
@@ -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/cs3org/reva/v2/pkg/appctx"
|
||||
"github.com/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/sharedconf"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
ldapIdentity "github.com/cs3org/reva/v2/pkg/utils/ldap"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"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(log, am.ldapClient, filter)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Bind as the user to verify their password
|
||||
la, err := utils.GetLDAPClientForAuth(&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,
|
||||
)
|
||||
}
|
||||
+34
@@ -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/cs3org/reva/v2/pkg/auth/manager/appauth"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/demo"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/impersonator"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/json"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/ldap"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/machine"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/nextcloud"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/oidc"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/publicshares"
|
||||
// Add your own here
|
||||
)
|
||||
+125
@@ -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/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"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
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
// 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 nextcloud verifies a clientID and clientSecret against a Nextcloud backend.
|
||||
package nextcloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||
"github.com/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register("nextcloud", New)
|
||||
}
|
||||
|
||||
// Manager is the Nextcloud-based implementation of the auth.Manager interface
|
||||
// see https://github.com/cs3org/reva/blob/v1.13.0/pkg/auth/auth.go#L32-L35
|
||||
type Manager struct {
|
||||
client *http.Client
|
||||
sharedSecret string
|
||||
endPoint string
|
||||
}
|
||||
|
||||
// AuthManagerConfig contains config for a Nextcloud-based AuthManager
|
||||
type AuthManagerConfig struct {
|
||||
EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"`
|
||||
SharedSecret string `mapstructure:"shared_secret"`
|
||||
MockHTTP bool `mapstructure:"mock_http"`
|
||||
}
|
||||
|
||||
// Action describes a REST request to forward to the Nextcloud backend
|
||||
type Action struct {
|
||||
verb string
|
||||
username string
|
||||
argS string
|
||||
}
|
||||
|
||||
func (c *AuthManagerConfig) init() {
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*AuthManagerConfig, error) {
|
||||
c := &AuthManagerConfig{}
|
||||
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 against a Nextcloud backend.
|
||||
func New(m map[string]interface{}) (auth.Manager, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
|
||||
return NewAuthManager(c)
|
||||
}
|
||||
|
||||
// NewAuthManager returns a new Nextcloud-based AuthManager
|
||||
func NewAuthManager(c *AuthManagerConfig) (*Manager, error) {
|
||||
var client *http.Client
|
||||
if c.MockHTTP {
|
||||
// called := make([]string, 0)
|
||||
// nextcloudServerMock := GetNextcloudServerMock(&called)
|
||||
// client, _ = TestingHTTPClient(nextcloudServerMock)
|
||||
|
||||
// Wait for SetHTTPClient to be called later
|
||||
client = nil
|
||||
} else {
|
||||
if len(c.EndPoint) == 0 {
|
||||
return nil, errors.New("Please specify 'endpoint' in '[grpc.services.authprovider.auth_managers.nextcloud]'")
|
||||
}
|
||||
client = &http.Client{}
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/"
|
||||
sharedSecret: c.SharedSecret,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Configure method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/auth/auth.go#L32-L35
|
||||
func (am *Manager) Configure(ml map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHTTPClient sets the HTTP client
|
||||
func (am *Manager) SetHTTPClient(c *http.Client) {
|
||||
am.client = c
|
||||
}
|
||||
|
||||
func (am *Manager) do(ctx context.Context, a Action) (int, []byte, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
url := am.endPoint + "~" + a.username + "/api/auth/" + a.verb
|
||||
log.Info().Msgf("am.do %s %s %s", url, a.argS, am.sharedSecret)
|
||||
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
req.Header.Set("X-Reva-Secret", am.sharedSecret)
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := am.client.Do(req)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
log.Info().Msgf("am.do response %d %s", resp.StatusCode, body)
|
||||
return resp.StatusCode, body, nil
|
||||
}
|
||||
|
||||
// Authenticate method as defined in https://github.com/cs3org/reva/blob/28500a8/pkg/auth/auth.go#L31-L33
|
||||
func (am *Manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
|
||||
type paramsObj struct {
|
||||
ClientID string `json:"clientID"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
// Scope authpb.Scope
|
||||
}
|
||||
bodyObj := ¶msObj{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
// Scope: authpb.Scope{
|
||||
// Resource: &types.OpaqueEntry{
|
||||
// Decoder: "json",
|
||||
// Value: []byte(`{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"}`),
|
||||
// },
|
||||
// Role: authpb.Role_ROLE_OWNER,
|
||||
// },
|
||||
}
|
||||
bodyStr, err := json.Marshal(bodyObj)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Msgf("Authenticate %s %s", clientID, bodyStr)
|
||||
|
||||
statusCode, body, err := am.do(ctx, Action{"Authenticate", clientID, string(bodyStr)})
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if statusCode != 200 {
|
||||
return nil, nil, errors.New("Username/password not recognized by Nextcloud backend")
|
||||
}
|
||||
|
||||
type resultsObj struct {
|
||||
User user.User `json:"user"`
|
||||
Scopes map[string]authpb.Scope `json:"scopes"`
|
||||
}
|
||||
result := &resultsObj{}
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var pointersMap = make(map[string]*authpb.Scope)
|
||||
for k := range result.Scopes {
|
||||
scope := result.Scopes[k]
|
||||
pointersMap[k] = &scope
|
||||
}
|
||||
return &result.User, pointersMap, nil
|
||||
}
|
||||
Generated
Vendored
+99
@@ -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 nextcloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Response contains data for the Nextcloud mock server to respond
|
||||
// and to switch to a new server state
|
||||
type Response struct {
|
||||
code int
|
||||
body string
|
||||
newServerState string
|
||||
}
|
||||
|
||||
const serverStateError = "ERROR"
|
||||
const serverStateEmpty = "EMPTY"
|
||||
const serverStateHome = "HOME"
|
||||
|
||||
var serverState = serverStateEmpty
|
||||
|
||||
var responses = map[string]Response{
|
||||
`POST /apps/sciencemesh/~einstein/api/auth/Authenticate {"clientID":"einstein","clientSecret":"relativity"}`: {200, `{"user":{"id":{"idp":"some-idp","opaque_id":"some-opaque-user-id","type":1}},"scopes":{"user":{"resource":{"decoder":"json","value":"eyJyZXNvdXJjZV9pZCI6eyJzdG9yYWdlX2lkIjoic3RvcmFnZS1pZCIsIm9wYXF1ZV9pZCI6Im9wYXF1ZS1pZCJ9LCJwYXRoIjoic29tZS9maWxlL3BhdGgudHh0In0="},"role":1}}}`, serverStateHome},
|
||||
}
|
||||
|
||||
// GetNextcloudServerMock returns a handler that pretends to be a remote Nextcloud server
|
||||
func GetNextcloudServerMock(called *[]string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf := new(strings.Builder)
|
||||
_, err := io.Copy(buf, r.Body)
|
||||
if err != nil {
|
||||
panic("Error reading response into buffer")
|
||||
}
|
||||
var key = fmt.Sprintf("%s %s %s", r.Method, r.URL, buf.String())
|
||||
*called = append(*called, key)
|
||||
response := responses[key]
|
||||
if (response == Response{}) {
|
||||
key = fmt.Sprintf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState)
|
||||
response = responses[key]
|
||||
}
|
||||
if (response == Response{}) {
|
||||
fmt.Printf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState)
|
||||
response = Response{500, fmt.Sprintf("response not defined! %s", key), serverStateEmpty}
|
||||
}
|
||||
serverState = responses[key].newServerState
|
||||
if serverState == `` {
|
||||
serverState = serverStateError
|
||||
}
|
||||
w.WriteHeader(response.code)
|
||||
// w.Header().Set("Etag", "mocker-etag")
|
||||
_, err = w.Write([]byte(responses[key].body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestingHTTPClient thanks to https://itnext.io/how-to-stub-requests-to-remote-hosts-with-go-6c2c1db32bf2
|
||||
// Ideally, this function would live in tests/helpers, but
|
||||
// if we put it there, it gets excluded by .dockerignore, and the
|
||||
// Docker build fails (see https://github.com/cs3org/reva/issues/1999)
|
||||
// So putting it here for now - open to suggestions if someone knows
|
||||
// a better way to inject this.
|
||||
func TestingHTTPClient(handler http.Handler) (*http.Client, func()) {
|
||||
s := httptest.NewServer(handler)
|
||||
|
||||
cli := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, network, _ string) (net.Conn, error) {
|
||||
return net.Dial(network, s.Listener.Addr().String())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return cli, s.Close
|
||||
}
|
||||
+363
@@ -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"
|
||||
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/cs3org/reva/v2/pkg/appctx"
|
||||
"github.com/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/rhttp"
|
||||
"github.com/cs3org/reva/v2/pkg/sharedconf"
|
||||
"github.com/juliangruber/go-intersect"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"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
|
||||
}
|
||||
Generated
Vendored
+165
@@ -0,0 +1,165 @@
|
||||
// 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 accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Accounts represents oc10-style Accounts
|
||||
type Accounts struct {
|
||||
driver string
|
||||
db *sql.DB
|
||||
joinUsername, joinUUID, enableMedialSearch bool
|
||||
selectSQL string
|
||||
}
|
||||
|
||||
// NewMysql returns a new accounts instance connecting to a MySQL database
|
||||
func NewMysql(dsn string, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) {
|
||||
sqldb, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error connecting to the database")
|
||||
}
|
||||
|
||||
// FIXME make configurable
|
||||
sqldb.SetConnMaxLifetime(time.Minute * 3)
|
||||
sqldb.SetConnMaxIdleTime(time.Second * 30)
|
||||
sqldb.SetMaxOpenConns(100)
|
||||
sqldb.SetMaxIdleConns(10)
|
||||
|
||||
err = sqldb.Ping()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error connecting to the database")
|
||||
}
|
||||
|
||||
return New("mysql", sqldb, joinUsername, joinUUID, enableMedialSearch)
|
||||
}
|
||||
|
||||
// New returns a new accounts instance connecting to the given sql.DB
|
||||
func New(driver string, sqldb *sql.DB, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) {
|
||||
|
||||
sel := "SELECT id, email, user_id, display_name, quota, last_login, backend, home, state, password"
|
||||
from := `
|
||||
FROM oc_accounts a
|
||||
LEFT JOIN oc_users u
|
||||
ON a.user_id=u.uid
|
||||
`
|
||||
if joinUsername {
|
||||
sel += ", p.configvalue AS username"
|
||||
from += `LEFT JOIN oc_preferences p
|
||||
ON a.user_id=p.userid
|
||||
AND p.appid='core'
|
||||
AND p.configkey='username'`
|
||||
} else {
|
||||
// fallback to user_id as username
|
||||
sel += ", user_id AS username"
|
||||
}
|
||||
if joinUUID {
|
||||
sel += ", p2.configvalue AS ownclouduuid"
|
||||
from += `LEFT JOIN oc_preferences p2
|
||||
ON a.user_id=p2.userid
|
||||
AND p2.appid='core'
|
||||
AND p2.configkey='ownclouduuid'`
|
||||
} else {
|
||||
// fallback to user_id as ownclouduuid
|
||||
sel += ", user_id AS ownclouduuid"
|
||||
}
|
||||
|
||||
return &Accounts{
|
||||
driver: driver,
|
||||
db: sqldb,
|
||||
joinUsername: joinUsername,
|
||||
joinUUID: joinUUID,
|
||||
enableMedialSearch: enableMedialSearch,
|
||||
selectSQL: sel + from,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Account stores information about accounts.
|
||||
type Account struct {
|
||||
ID uint64
|
||||
Email sql.NullString
|
||||
UserID string
|
||||
DisplayName sql.NullString
|
||||
Quota sql.NullString
|
||||
LastLogin int
|
||||
Backend string
|
||||
Home string
|
||||
State int8
|
||||
PasswordHash string // from oc_users
|
||||
Username sql.NullString // optional comes from the oc_preferences
|
||||
OwnCloudUUID sql.NullString // optional comes from the oc_preferences
|
||||
}
|
||||
|
||||
func (as *Accounts) rowToAccount(ctx context.Context, row Scannable) (*Account, error) {
|
||||
a := Account{}
|
||||
if err := row.Scan(&a.ID, &a.Email, &a.UserID, &a.DisplayName, &a.Quota, &a.LastLogin, &a.Backend, &a.Home, &a.State, &a.PasswordHash, &a.Username, &a.OwnCloudUUID); err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
// Scannable describes the interface providing a Scan method
|
||||
type Scannable interface {
|
||||
Scan(...interface{}) error
|
||||
}
|
||||
|
||||
// GetAccountByLogin fetches an account by mail or username
|
||||
func (as *Accounts) GetAccountByLogin(ctx context.Context, login string) (*Account, error) {
|
||||
var row *sql.Row
|
||||
username := strings.ToLower(login) // usernames are lowercased in owncloud classic
|
||||
if as.joinUsername {
|
||||
row = as.db.QueryRowContext(ctx, as.selectSQL+" WHERE a.email=? OR a.lower_user_id=? OR p.configvalue=?", login, username, login)
|
||||
} else {
|
||||
row = as.db.QueryRowContext(ctx, as.selectSQL+" WHERE a.email=? OR a.lower_user_id=?", login, username)
|
||||
}
|
||||
|
||||
return as.rowToAccount(ctx, row)
|
||||
}
|
||||
|
||||
// GetAccountGroups reads the groups for an account
|
||||
func (as *Accounts) GetAccountGroups(ctx context.Context, uid string) ([]string, error) {
|
||||
rows, err := as.db.QueryContext(ctx, "SELECT gid FROM oc_group_user WHERE uid=?", uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var group string
|
||||
groups := []string{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&group); err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping")
|
||||
continue
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
Generated
Vendored
BIN
Binary file not shown.
+192
@@ -0,0 +1,192 @@
|
||||
// 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 owncloudsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||
"github.com/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql/accounts"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
// Provides mysql drivers
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register("owncloudsql", NewMysql)
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
c *config
|
||||
db *accounts.Accounts
|
||||
}
|
||||
|
||||
type config struct {
|
||||
DbUsername string `mapstructure:"dbusername"`
|
||||
DbPassword string `mapstructure:"dbpassword"`
|
||||
DbHost string `mapstructure:"dbhost"`
|
||||
DbPort int `mapstructure:"dbport"`
|
||||
DbName string `mapstructure:"dbname"`
|
||||
Idp string `mapstructure:"idp"`
|
||||
Nobody int64 `mapstructure:"nobody"`
|
||||
LegacySalt string `mapstructure:"legacy_salt"`
|
||||
JoinUsername bool `mapstructure:"join_username"`
|
||||
JoinOwnCloudUUID bool `mapstructure:"join_ownclouduuid"`
|
||||
}
|
||||
|
||||
// NewMysql returns a new auth manager connection to an owncloud mysql database
|
||||
func NewMysql(m map[string]interface{}) (auth.Manager, error) {
|
||||
mgr := &manager{}
|
||||
err := mgr.Configure(m)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "error creating a new auth manager")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mgr.db, err = accounts.NewMysql(
|
||||
fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", mgr.c.DbUsername, mgr.c.DbPassword, mgr.c.DbHost, mgr.c.DbPort, mgr.c.DbName),
|
||||
mgr.c.JoinUsername,
|
||||
mgr.c.JoinOwnCloudUUID,
|
||||
false,
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
if c.Nobody == 0 {
|
||||
c.Nobody = 99
|
||||
}
|
||||
|
||||
m.c = c
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (m *manager) Authenticate(ctx context.Context, login, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
// 1. find user by login
|
||||
|
||||
account, err := m.db.GetAccountByLogin(ctx, login)
|
||||
if err != nil {
|
||||
return nil, nil, errtypes.NotFound(login)
|
||||
}
|
||||
// 2. verify the user password
|
||||
if !m.verify(clientSecret, account.PasswordHash) {
|
||||
return nil, nil, errtypes.InvalidCredentials(login)
|
||||
}
|
||||
|
||||
userID := &user.UserId{
|
||||
Idp: m.c.Idp,
|
||||
OpaqueId: account.OwnCloudUUID.String,
|
||||
Type: user.UserType_USER_TYPE_PRIMARY, // TODO: assign the appropriate user type for guest accounts
|
||||
}
|
||||
|
||||
u := &user.User{
|
||||
Id: userID,
|
||||
// TODO add more claims from the StandardClaims, eg EmailVerified and lastlogin
|
||||
Username: account.Username.String,
|
||||
Mail: account.Email.String,
|
||||
DisplayName: account.DisplayName.String,
|
||||
//UidNumber: uidNumber,
|
||||
//GidNumber: gidNumber,
|
||||
}
|
||||
|
||||
if u.Groups, err = m.db.GetAccountGroups(ctx, account.UserID); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
// do not log password hash
|
||||
account.PasswordHash = "***redacted***"
|
||||
log.Debug().Interface("account", account).Interface("user", u).Msg("authenticated user")
|
||||
|
||||
return u, scopes, nil
|
||||
}
|
||||
|
||||
func (m *manager) verify(password, hash string) bool {
|
||||
splitHash := strings.SplitN(hash, "|", 2)
|
||||
switch len(splitHash) {
|
||||
case 2:
|
||||
if splitHash[0] == "1" {
|
||||
return m.verifyHashV1(password, splitHash[1])
|
||||
}
|
||||
case 1:
|
||||
return m.legacyHashVerify(password, hash)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *manager) legacyHashVerify(password, hash string) bool {
|
||||
// TODO rehash $newHash = $this->hash($message);
|
||||
switch len(hash) {
|
||||
case 60: // legacy PHPass hash
|
||||
return nil == bcrypt.CompareHashAndPassword([]byte(hash), []byte(password+m.c.LegacySalt))
|
||||
case 40: // legacy sha1 hash
|
||||
h := sha1.Sum([]byte(password))
|
||||
return hmac.Equal([]byte(hash), []byte(hex.EncodeToString(h[:])))
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (m *manager) verifyHashV1(password, hash string) bool {
|
||||
// TODO implement password_needs_rehash
|
||||
return nil == bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
// 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"
|
||||
userprovider "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/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"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, &userprovider.GetUserRequest{
|
||||
UserId: publicShareResponse.GetShare().GetCreator(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
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),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
+34
@@ -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/cs3org/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
|
||||
}
|
||||
+25
@@ -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/cs3org/reva/v2/pkg/auth/registry/static"
|
||||
// Add your own here
|
||||
)
|
||||
+34
@@ -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/cs3org/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
|
||||
}
|
||||
+91
@@ -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/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/registry/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/sharedconf"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
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 := []*registrypb.ProviderInfo{}
|
||||
for k, v := range r.rules {
|
||||
providers = append(providers, ®istrypb.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 ®istrypb.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 ®{rules: c.Rules}, nil
|
||||
}
|
||||
+118
@@ -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"
|
||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||
"github.com/cs3org/reva/v2/pkg/plugin"
|
||||
hcplugin "github.com/hashicorp/go-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
|
||||
}
|
||||
+91
@@ -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/cs3org/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
|
||||
}
|
||||
+204
@@ -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/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/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
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// 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"
|
||||
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func receivedShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
|
||||
var share collaboration.ReceivedShare
|
||||
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch v := resource.(type) {
|
||||
case *collaboration.GetReceivedShareRequest:
|
||||
return checkShareRef(share.Share, v.GetRef()), nil
|
||||
case *collaboration.UpdateReceivedShareRequest:
|
||||
return checkShare(share.Share, v.GetShare().GetShare()), nil
|
||||
case string:
|
||||
return checkSharePath(v) || checkResourcePath(v), nil
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
|
||||
logger.Debug().Str("scope", "receivedShareScope").Msg(msg)
|
||||
return false, errtypes.InternalError(msg)
|
||||
}
|
||||
|
||||
// AddReceivedShareScope adds the scope to allow access to a received user/group share and
|
||||
// the shared resource.
|
||||
func AddReceivedShareScope(share *collaboration.ReceivedShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
|
||||
// Create a new "scope share" to only expose the required fields to the scope.
|
||||
scopeShare := &collaboration.Share{Id: share.Share.Id, Owner: share.Share.Owner, Creator: share.Share.Creator, ResourceId: share.Share.ResourceId}
|
||||
|
||||
val, err := utils.MarshalProtoV1ToJSON(&collaboration.ReceivedShare{Share: scopeShare})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if scopes == nil {
|
||||
scopes = make(map[string]*authpb.Scope)
|
||||
}
|
||||
scopes["receivedshare:"+share.Share.Id.OpaqueId] = &authpb.Scope{
|
||||
Resource: &types.OpaqueEntry{
|
||||
Decoder: "json",
|
||||
Value: val,
|
||||
},
|
||||
Role: role,
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
+166
@@ -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/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/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
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// 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/cs3org/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,
|
||||
"share": shareScope,
|
||||
"receivedshare": receivedShareScope,
|
||||
"lightweight": lightweightAccountScope,
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
// 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"
|
||||
|
||||
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"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func shareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
|
||||
var share collaboration.Share
|
||||
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch v := resource.(type) {
|
||||
// Viewer role
|
||||
case *registry.GetStorageProvidersRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
case *provider.StatRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
case *provider.ListContainerRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
case *provider.InitiateFileDownloadRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
|
||||
// Editor role
|
||||
// TODO(ishank011): Add role checks,
|
||||
// need to return appropriate status codes in the ocs/ocdav layers.
|
||||
case *provider.CreateContainerRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
case *provider.TouchFileRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
case *provider.DeleteRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
case *provider.MoveRequest:
|
||||
return checkShareStorageRef(&share, v.GetSource()) && checkShareStorageRef(&share, v.GetDestination()), nil
|
||||
case *provider.InitiateFileUploadRequest:
|
||||
return checkShareStorageRef(&share, v.GetRef()), nil
|
||||
|
||||
case *collaboration.ListReceivedSharesRequest:
|
||||
return true, nil
|
||||
case *collaboration.GetReceivedShareRequest:
|
||||
return checkShareRef(&share, v.GetRef()), nil
|
||||
case string:
|
||||
return checkSharePath(v) || checkResourcePath(v), nil
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
|
||||
logger.Debug().Str("scope", "shareScope").Msg(msg)
|
||||
return false, errtypes.InternalError(msg)
|
||||
}
|
||||
|
||||
func checkShareStorageRef(s *collaboration.Share, r *provider.Reference) bool {
|
||||
// ref: <id:<storage_id:$storageID opaque_id:$opaqueID > >
|
||||
if r.GetResourceId() != nil && r.Path == "" { // path must be empty
|
||||
return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkShareRef(s *collaboration.Share, ref *collaboration.ShareReference) bool {
|
||||
if ref.GetId() != nil {
|
||||
return ref.GetId().OpaqueId == s.Id.OpaqueId
|
||||
}
|
||||
if key := ref.GetKey(); key != nil {
|
||||
return (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) &&
|
||||
utils.ResourceIDEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee)
|
||||
}
|
||||
return false
|
||||
}
|
||||
func checkShare(s1 *collaboration.Share, s2 *collaboration.Share) bool {
|
||||
if s2.GetId() != nil {
|
||||
return s2.GetId().OpaqueId == s1.Id.OpaqueId
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSharePath(path string) bool {
|
||||
paths := []string{
|
||||
"/ocs/v2.php/apps/files_sharing/api/v1/shares",
|
||||
"/ocs/v1.php/apps/files_sharing/api/v1/shares",
|
||||
"/remote.php/webdav",
|
||||
"/remote.php/dav/files",
|
||||
}
|
||||
for _, p := range paths {
|
||||
if strings.HasPrefix(path, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddShareScope adds the scope to allow access to a user/group share and
|
||||
// the shared resource.
|
||||
func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
|
||||
// Create a new "scope share" to only expose the required fields to the scope.
|
||||
scopeShare := &collaboration.Share{Id: share.Id, Owner: share.Owner, Creator: share.Creator, ResourceId: share.ResourceId}
|
||||
|
||||
val, err := utils.MarshalProtoV1ToJSON(scopeShare)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if scopes == nil {
|
||||
scopes = make(map[string]*authpb.Scope)
|
||||
}
|
||||
scopes["share:"+share.Id.OpaqueId] = &authpb.Scope{
|
||||
Resource: &types.OpaqueEntry{
|
||||
Decoder: "json",
|
||||
Value: val,
|
||||
},
|
||||
Role: role,
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
+55
@@ -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/cs3org/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
@@ -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/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user