Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,200 @@
// 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"
"errors"
"strconv"
"strings"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/user"
"github.com/opencloud-eu/reva/v2/pkg/user/manager/registry"
"google.golang.org/protobuf/proto"
)
func init() {
registry.Register("demo", New)
}
type manager struct {
catalog map[string]*userpb.User
}
// New returns a new user manager.
func New(m map[string]interface{}) (user.Manager, error) {
mgr := &manager{}
err := mgr.Configure(m)
if err != nil {
return nil, err
}
return mgr, err
}
func (m *manager) Configure(ml map[string]interface{}) error {
cat := getUsers()
m.catalog = cat
return nil
}
func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) {
if user, ok := m.catalog[uid.OpaqueId]; ok {
if user.GetId().GetTenantId() == uid.GetTenantId() && (uid.Idp == "" || user.Id.Idp == uid.Idp) {
u := proto.Clone(user).(*userpb.User)
if skipFetchingGroups {
u.Groups = nil
}
return u, nil
}
}
return nil, errtypes.NotFound(uid.OpaqueId)
}
func (m *manager) GetUserByClaim(ctx context.Context, claim, value, tenantID string, skipFetchingGroups bool) (*userpb.User, error) {
for _, u := range m.catalog {
if userClaim, err := extractClaim(u, claim); err == nil && value == userClaim && tenantID == u.Id.TenantId {
user := proto.Clone(u).(*userpb.User)
if skipFetchingGroups {
user.Groups = nil
}
return user, nil
}
}
return nil, errtypes.NotFound(value)
}
func extractClaim(u *userpb.User, claim string) (string, error) {
switch claim {
case "mail":
return u.Mail, nil
case "username":
return u.Username, nil
case "userid":
return u.Id.OpaqueId, nil
case "uid":
if u.UidNumber != 0 {
return strconv.FormatInt(u.UidNumber, 10), nil
}
}
return "", errors.New("demo: invalid field")
}
// TODO(jfd) compare sub?
func userContains(u *userpb.User, query, tenantID string) bool {
if tenantID != "" && u.Id.TenantId != tenantID {
return false
}
return strings.Contains(u.Username, query) ||
strings.Contains(u.DisplayName, query) ||
strings.Contains(u.Mail, query) ||
strings.Contains(u.Id.OpaqueId, query)
}
func (m *manager) FindUsers(ctx context.Context, query, tenantID string, skipFetchingGroups bool) ([]*userpb.User, error) {
users := []*userpb.User{}
for _, u := range m.catalog {
if userContains(u, query, tenantID) {
user := proto.Clone(u).(*userpb.User)
if skipFetchingGroups {
user.Groups = nil
}
users = append(users, user)
}
}
return users, nil
}
func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) {
user, err := m.GetUser(ctx, uid, false)
if err != nil {
return nil, err
}
return user.Groups, nil
}
func getUsers() map[string]*userpb.User {
return map[string]*userpb.User{
"4c510ada-c86b-4815-8820-42cdf82c3d51": {
Id: &userpb.UserId{
Idp: "http://localhost:9998",
OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51",
Type: userpb.UserType_USER_TYPE_PRIMARY,
TenantId: "c239389d-c249-499d-ae80-07558429769a",
},
Username: "einstein",
Groups: []string{"sailing-lovers", "violin-haters", "physics-lovers"},
Mail: "einstein@example.org",
DisplayName: "Albert Einstein",
UidNumber: 123,
GidNumber: 987,
},
"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c": {
Id: &userpb.UserId{
Idp: "http://localhost:9998",
OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
Type: userpb.UserType_USER_TYPE_PRIMARY,
TenantId: "c239389d-c249-499d-ae80-07558429769a",
},
Username: "marie",
Groups: []string{"radium-lovers", "polonium-lovers", "physics-lovers"},
Mail: "marie@example.org",
DisplayName: "Marie Curie",
UidNumber: 456,
GidNumber: 987,
},
"932b4540-8d16-481e-8ef4-588e4b6b151c": {
Id: &userpb.UserId{
Idp: "http://localhost:9998",
OpaqueId: "932b4540-8d16-481e-8ef4-588e4b6b151c",
Type: userpb.UserType_USER_TYPE_PRIMARY,
TenantId: "c239389d-c249-499d-ae80-07558429769a",
},
Username: "richard",
Groups: []string{"quantum-lovers", "philosophy-haters", "physics-lovers"},
Mail: "richard@example.org",
DisplayName: "Richard Feynman",
},
"bf2ee2f2-67bc-418f-ac4e-2f6427f9fb2f": {
Id: &userpb.UserId{
Idp: "http://localhost:9998",
OpaqueId: "bf2ee2f2-67bc-418f-ac4e-2f6427f9fb2f",
Type: userpb.UserType_USER_TYPE_PRIMARY,
TenantId: "d375ba5e-1140-472a-b199-ca7d671a9fe1",
},
Username: "jen",
Groups: []string{},
Mail: "jen@example.org",
DisplayName: "Jen Barber",
},
"79a91ae3-13da-4bab-9f91-63c60295c7cf": {
Id: &userpb.UserId{
Idp: "http://localhost:9998",
OpaqueId: "79a91ae3-13da-4bab-9f91-63c60295c7cf",
Type: userpb.UserType_USER_TYPE_PRIMARY,
TenantId: "d375ba5e-1140-472a-b199-ca7d671a9fe1",
},
Username: "ralph",
Groups: []string{},
Mail: "ralphi@example.org",
DisplayName: "Ralph Wiggum",
},
}
}
@@ -0,0 +1,170 @@
// 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"
"strconv"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/user"
"github.com/opencloud-eu/reva/v2/pkg/user/manager/registry"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
)
func init() {
registry.Register("json", New)
}
type manager struct {
users []*userpb.User
}
type config struct {
// Users holds a path to a file containing json conforming to 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 user manager implementation that reads a json file to provide user metadata.
func New(m map[string]interface{}) (user.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
}
f, err := os.ReadFile(c.Users)
if err != nil {
return err
}
users := []*userpb.User{}
err = json.Unmarshal(f, &users)
if err != nil {
return err
}
m.users = users
return nil
}
func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) {
for _, u := range m.users {
if (u.Id.GetOpaqueId() == uid.OpaqueId || u.Username == uid.OpaqueId) && (uid.Idp == "" || uid.Idp == u.Id.GetIdp()) && (uid.GetTenantId() == u.Id.GetTenantId()) {
user := proto.Clone(u).(*userpb.User)
if skipFetchingGroups {
user.Groups = nil
}
return user, nil
}
}
return nil, errtypes.NotFound(uid.OpaqueId)
}
func (m *manager) GetUserByClaim(ctx context.Context, claim, value, tenantID string, skipFetchingGroups bool) (*userpb.User, error) {
for _, u := range m.users {
if userClaim, err := extractClaim(u, claim); err == nil && value == userClaim && tenantID == u.Id.TenantId {
user := proto.Clone(u).(*userpb.User)
if skipFetchingGroups {
user.Groups = nil
}
return user, nil
}
}
return nil, errtypes.NotFound(value)
}
func extractClaim(u *userpb.User, claim string) (string, error) {
switch claim {
case "mail":
return u.Mail, nil
case "username":
return u.Username, nil
case "userid":
return u.Id.OpaqueId, nil
case "uid":
if u.UidNumber != 0 {
return strconv.FormatInt(u.UidNumber, 10), nil
}
}
return "", errors.New("json: invalid field")
}
// TODO(jfd) search Opaque? compare sub?
func userContains(u *userpb.User, query, tenantID string) bool {
if tenantID != "" && u.Id.TenantId != tenantID {
return false
}
query = strings.ToLower(query)
return strings.Contains(strings.ToLower(u.Username), query) || strings.Contains(strings.ToLower(u.DisplayName), query) ||
strings.Contains(strings.ToLower(u.Mail), query) || strings.Contains(strings.ToLower(u.Id.OpaqueId), query)
}
func (m *manager) FindUsers(ctx context.Context, query, tenantID string, skipFetchingGroups bool) ([]*userpb.User, error) {
users := []*userpb.User{}
for _, u := range m.users {
if userContains(u, query, tenantID) {
user := proto.Clone(u).(*userpb.User)
if skipFetchingGroups {
user.Groups = nil
}
users = append(users, user)
}
}
return users, nil
}
func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) {
user, err := m.GetUser(ctx, uid, false)
if err != nil {
return nil, err
}
return user.Groups, nil
}
@@ -0,0 +1,294 @@
// 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"
"strings"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/go-ldap/ldap/v3"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/user"
"github.com/opencloud-eu/reva/v2/pkg/user/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/utils"
ldapIdentity "github.com/opencloud-eu/reva/v2/pkg/utils/ldap"
"go.opentelemetry.io/otel/attribute"
)
func init() {
registry.Register("ldap", New)
}
const tracerName = "pkg/user/manager/ldap"
type manager struct {
c *config
ldapClient ldap.Client
}
type config struct {
utils.LDAPConn `mapstructure:",squash"`
LDAPIdentity ldapIdentity.Identity `mapstructure:",squash"`
Idp string `mapstructure:"idp"`
// Nobody specifies the fallback uid number for users that don't have a uidNumber set in LDAP
Nobody int64 `mapstructure:"nobody"`
}
func parseConfig(m map[string]interface{}) (*config, error) {
c := config{
LDAPIdentity: ldapIdentity.New(),
}
if err := mapstructure.Decode(m, &c); err != nil {
return nil, fmt.Errorf("error decoding conf: %w", err)
}
return &c, nil
}
// New returns a user manager implementation that connects to a LDAP server to provide user metadata.
func New(m map[string]interface{}) (user.Manager, error) {
mgr := &manager{}
err := mgr.Configure(m)
if err != nil {
return nil, err
}
mgr.ldapClient, err = utils.GetLDAPClientWithReconnect(&mgr.c.LDAPConn)
return mgr, err
}
// Configure initializes the configuration of the user manager from the supplied config map
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
}
if err = c.LDAPIdentity.Setup(); err != nil {
return fmt.Errorf("error setting up Identity config: %w", err)
}
m.c = c
return nil
}
// GetUser implements the user.Manager interface. Looks up a user by Id and return the user
func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "GetUser")
defer span.End()
span.SetAttributes(
attribute.Stringer("parameter.userid", uid),
attribute.Bool("parameter.skipFetchingGroups", skipFetchingGroups),
)
log := appctx.GetLogger(ctx)
log.Debug().Interface("id", uid).Msg("GetUser")
// If the Idp value in the uid does not match our config, we can't answer this request
if uid.Idp != "" && uid.Idp != m.c.Idp {
return nil, errtypes.NotFound("idp mismatch")
}
userEntry, err := m.c.LDAPIdentity.GetLDAPUserByID(ctx, m.ldapClient, uid)
if err != nil {
return nil, err
}
log.Debug().Interface("entry", userEntry).Msg("entries")
u, err := m.ldapEntryToUser(userEntry)
if err != nil {
return nil, err
}
if skipFetchingGroups {
return u, nil
}
groups, err := m.c.LDAPIdentity.GetLDAPUserGroups(ctx, m.ldapClient, userEntry)
if err != nil {
return nil, err
}
u.Groups = groups
return u, nil
}
// GetUserByClaim implements the user.Manager interface. Looks up a user by
// claim ('mail', 'username', 'userid') and returns the user.
func (m *manager) GetUserByClaim(ctx context.Context, claim, value, tenantID string, skipFetchingGroups bool) (*userpb.User, error) {
log := appctx.GetLogger(ctx)
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "GetUserByClaim")
defer span.End()
span.SetAttributes(
attribute.String("parameter.claim", claim),
attribute.String("paramter.value", value),
attribute.Bool("parameter.skipFetchingGroups", skipFetchingGroups),
)
log.Debug().Str("claim", claim).Str("value", value).Msg("GetUserByClaim")
userEntry, err := m.c.LDAPIdentity.GetLDAPUserByAttribute(ctx, m.ldapClient, claim, value, tenantID)
if err != nil {
log.Debug().Err(err).Msg("GetUserByClaim")
return nil, err
}
log.Debug().Interface("entry", userEntry).Msg("entries")
u, err := m.ldapEntryToUser(userEntry)
if err != nil {
return nil, err
}
if m.c.LDAPIdentity.IsLDAPUserInDisabledGroup(ctx, m.ldapClient, userEntry) {
return nil, errtypes.NotFound("user is locally disabled")
}
if skipFetchingGroups {
return u, nil
}
groups, err := m.c.LDAPIdentity.GetLDAPUserGroups(ctx, m.ldapClient, userEntry)
if err != nil {
return nil, err
}
u.Groups = groups
return u, nil
}
// FindUser implements the user.Manager interface. Searches for users using a prefix-substring search on
// the user attributes ('mail', 'username', 'displayname', 'userid') and returns the users.
func (m *manager) FindUsers(ctx context.Context, query, tenantID string, skipFetchingGroups bool) ([]*userpb.User, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "FindUsers")
defer span.End()
span.SetAttributes(
attribute.String("parameter.query", query),
attribute.String("parameter.tenantID", tenantID),
attribute.Bool("parameter.skipFetchingGroups", skipFetchingGroups),
)
entries, err := m.c.LDAPIdentity.GetLDAPUsers(ctx, m.ldapClient, query, tenantID)
if err != nil {
return nil, err
}
users := []*userpb.User{}
for _, entry := range entries {
u, err := m.ldapEntryToUser(entry)
if err != nil {
return nil, err
}
if !skipFetchingGroups {
groups, err := m.c.LDAPIdentity.GetLDAPUserGroups(ctx, m.ldapClient, entry)
if err != nil {
return nil, err
}
u.Groups = groups
}
users = append(users, u)
}
return users, nil
}
// GetUserGroups implements the user.Manager interface. Looks up all group membership of
// the user with the supplied Id. Returns a string slice with the group ids
func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) {
log := appctx.GetLogger(ctx)
if uid.Idp != "" && uid.Idp != m.c.Idp {
log.Debug().Str("useridp", uid.Idp).Str("configured idp", m.c.Idp).Msg("IDP mismatch")
return nil, errtypes.NotFound("idp mismatch")
}
userEntry, err := m.c.LDAPIdentity.GetLDAPUserByID(ctx, m.ldapClient, uid)
if err != nil {
log.Debug().Err(err).Interface("userid", uid).Msg("Failed to lookup user")
return []string{}, err
}
return m.c.LDAPIdentity.GetLDAPUserGroups(ctx, m.ldapClient, userEntry)
}
func (m *manager) ldapEntryToUser(entry *ldap.Entry) (*userpb.User, error) {
id, err := m.ldapEntryToUserID(entry)
if err != nil {
return nil, err
}
gidNumber := m.c.Nobody
gidValue := entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.GIDNumber)
if gidValue != "" {
gidNumber, err = strconv.ParseInt(gidValue, 10, 64)
if err != nil {
return nil, err
}
}
uidNumber := m.c.Nobody
uidValue := entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.UIDNumber)
if uidValue != "" {
uidNumber, err = strconv.ParseInt(uidValue, 10, 64)
if err != nil {
return nil, err
}
}
u := &userpb.User{
Id: id,
Username: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.Username),
Mail: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.Mail),
DisplayName: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.DisplayName),
GidNumber: gidNumber,
UidNumber: uidNumber,
}
return u, nil
}
func (m *manager) ldapEntryToUserID(entry *ldap.Entry) (*userpb.UserId, error) {
var uid string
if m.c.LDAPIdentity.User.Schema.IDIsOctetString {
attribute := m.c.LDAPIdentity.User.Schema.ID
rawValue := entry.GetEqualFoldRawAttributeValue(attribute)
if strings.EqualFold(attribute, "objectguid") {
rawValue = ldapIdentity.SwapObjectGUIDBytes(rawValue)
}
if value, err := uuid.FromBytes(rawValue); err == nil {
uid = value.String()
} else {
return nil, err
}
} else {
uid = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.ID)
}
return &userpb.UserId{
Idp: m.c.Idp,
OpaqueId: uid,
TenantId: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.TenantID),
Type: m.c.LDAPIdentity.GetUserType(entry),
}, nil
}
@@ -0,0 +1,28 @@
// 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 user manager drivers.
_ "github.com/opencloud-eu/reva/v2/pkg/user/manager/demo"
_ "github.com/opencloud-eu/reva/v2/pkg/user/manager/json"
_ "github.com/opencloud-eu/reva/v2/pkg/user/manager/ldap"
_ "github.com/opencloud-eu/reva/v2/pkg/user/manager/memory"
// Add your own here
)
@@ -0,0 +1,194 @@
// 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 memory
import (
"context"
"strconv"
"strings"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/user"
"github.com/opencloud-eu/reva/v2/pkg/user/manager/registry"
"github.com/pkg/errors"
)
func init() {
registry.Register("memory", New)
}
type config struct {
// Users holds a map with userid and user
Users map[string]*User `mapstructure:"users"`
}
// User holds a user but uses _ in mapstructure names
type User struct {
ID *userpb.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"`
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"`
}
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
}
type manager struct {
catalog map[string]*User
}
// New returns a new user manager.
func New(m map[string]interface{}) (user.Manager, error) {
mgr := &manager{}
err := mgr.Configure(m)
return mgr, err
}
func (m *manager) Configure(ml map[string]interface{}) error {
c, err := parseConfig(ml)
if err != nil {
return err
}
m.catalog = c.Users
return nil
}
func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) {
if uid.GetTenantId() != "" {
return nil, errtypes.NotSupported("tenant filter not supported in memory user manager")
}
if user, ok := m.catalog[uid.OpaqueId]; ok {
if uid.Idp == "" || user.ID.Idp == uid.Idp {
u := *user
if skipFetchingGroups {
u.Groups = nil
}
return &userpb.User{
Id: u.ID,
Username: u.Username,
Mail: u.Mail,
DisplayName: u.DisplayName,
MailVerified: u.MailVerified,
Groups: u.Groups,
Opaque: u.Opaque,
UidNumber: u.UIDNumber,
GidNumber: u.GIDNumber,
}, nil
}
}
return nil, errtypes.NotFound(uid.OpaqueId)
}
func (m *manager) GetUserByClaim(ctx context.Context, claim, value, tenantID string, skipFetchingGroups bool) (*userpb.User, error) {
if tenantID != "" {
return nil, errtypes.NotSupported("tenant filter not supported in memory user manager")
}
for _, u := range m.catalog {
if userClaim, err := extractClaim(u, claim); err == nil && value == userClaim {
user := &userpb.User{
Id: u.ID,
Username: u.Username,
Mail: u.Mail,
DisplayName: u.DisplayName,
MailVerified: u.MailVerified,
Groups: u.Groups,
Opaque: u.Opaque,
UidNumber: u.UIDNumber,
GidNumber: u.GIDNumber,
}
if skipFetchingGroups {
user.Groups = nil
}
return user, nil
}
}
return nil, errtypes.NotFound(value)
}
func extractClaim(u *User, claim string) (string, error) {
switch claim {
case "mail":
return u.Mail, nil
case "username":
return u.Username, nil
case "userid":
return u.ID.OpaqueId, nil
case "uid":
if u.UIDNumber != 0 {
return strconv.FormatInt(u.UIDNumber, 10), nil
}
}
return "", errors.New("memory: invalid field")
}
// TODO(jfd) compare sub?
func userContains(u *User, query string) bool {
return strings.Contains(u.Username, query) || strings.Contains(u.DisplayName, query) || strings.Contains(u.Mail, query) || strings.Contains(u.ID.OpaqueId, query)
}
func (m *manager) FindUsers(ctx context.Context, query, tenantID string, skipFetchingGroups bool) ([]*userpb.User, error) {
if tenantID != "" {
return nil, errtypes.NotSupported("tenant filter not supported in memory user manager")
}
users := []*userpb.User{}
for _, u := range m.catalog {
if userContains(u, query) {
user := &userpb.User{
Id: u.ID,
Username: u.Username,
Mail: u.Mail,
DisplayName: u.DisplayName,
MailVerified: u.MailVerified,
Groups: u.Groups,
Opaque: u.Opaque,
UidNumber: u.UIDNumber,
GidNumber: u.GIDNumber,
}
if skipFetchingGroups {
user.Groups = nil
}
users = append(users, user)
}
}
return users, nil
}
func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) {
user, err := m.GetUser(ctx, uid, false)
if err != nil {
return nil, err
}
return user.Groups, nil
}
@@ -0,0 +1,36 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package registry
import (
"github.com/opencloud-eu/reva/v2/pkg/user"
)
// NewFunc is the function that user managers
// should register at init time.
type NewFunc func(map[string]interface{}) (user.Manager, error)
// NewFuncs is a map containing all the registered user managers.
var NewFuncs = map[string]NewFunc{}
// Register registers a new user manager new function.
// Not safe for concurrent use. Safe for use from package init.
func Register(name string, f NewFunc) {
NewFuncs[name] = f
}
+226
View File
@@ -0,0 +1,226 @@
// 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 user
import (
"context"
"encoding/gob"
"net/rpc"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
hcplugin "github.com/hashicorp/go-plugin"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/plugin"
)
func init() {
gob.Register(&userpb.User{})
plugin.Register("userprovider", &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
}
// GetUserArg for RPC
type GetUserArg struct {
Ctx map[interface{}]interface{}
UID *userpb.UserId
SkipFetchingGroups bool
}
// GetUserReply for RPC
type GetUserReply struct {
User *userpb.User
Err error
}
// GetUser RPCClient GetUser method
func (m *RPCClient) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) {
if uid.GetTenantId() != "" {
return nil, errtypes.NotSupported("tenant filter not supported in rpc_user user manager")
}
ctxVal := appctx.GetKeyValuesFromCtx(ctx)
args := GetUserArg{Ctx: ctxVal, UID: uid, SkipFetchingGroups: skipFetchingGroups}
resp := GetUserReply{}
err := m.Client.Call("Plugin.GetUser", args, &resp)
if err != nil {
return nil, err
}
return resp.User, resp.Err
}
// GetUserByClaimArg for RPC
type GetUserByClaimArg struct {
Ctx map[interface{}]interface{}
Claim string
Value string
SkipFetchingGroups bool
}
// GetUserByClaimReply for RPC
type GetUserByClaimReply struct {
User *userpb.User
Err error
}
// GetUserByClaim RPCClient GetUserByClaim method
func (m *RPCClient) GetUserByClaim(ctx context.Context, claim, value, tenantID string, skipFetchingGroups bool) (*userpb.User, error) {
if tenantID != "" {
return nil, errtypes.NotSupported("tenant filter not supported in rpc_user user manager")
}
ctxVal := appctx.GetKeyValuesFromCtx(ctx)
args := GetUserByClaimArg{Ctx: ctxVal, Claim: claim, Value: value, SkipFetchingGroups: skipFetchingGroups}
resp := GetUserByClaimReply{}
err := m.Client.Call("Plugin.GetUserByClaim", args, &resp)
if err != nil {
return nil, err
}
return resp.User, resp.Err
}
// GetUserGroupsArg for RPC
type GetUserGroupsArg struct {
Ctx map[interface{}]interface{}
User *userpb.UserId
}
// GetUserGroupsReply for RPC
type GetUserGroupsReply struct {
Group []string
Err error
}
// GetUserGroups RPCClient GetUserGroups method
func (m *RPCClient) GetUserGroups(ctx context.Context, user *userpb.UserId) ([]string, error) {
ctxVal := appctx.GetKeyValuesFromCtx(ctx)
args := GetUserGroupsArg{Ctx: ctxVal, User: user}
resp := GetUserGroupsReply{}
err := m.Client.Call("Plugin.GetUserGroups", args, &resp)
if err != nil {
return nil, err
}
return resp.Group, resp.Err
}
// FindUsersArg for RPC
type FindUsersArg struct {
Ctx map[interface{}]interface{}
Query string
SkipFetchingGroups bool
}
// FindUsersReply for RPC
type FindUsersReply struct {
User []*userpb.User
Err error
}
// FindUsers RPCClient FindUsers method
func (m *RPCClient) FindUsers(ctx context.Context, query, tenantID string, skipFetchingGroups bool) ([]*userpb.User, error) {
if tenantID != "" {
return nil, errtypes.NotSupported("tenant filter not supported in rpc user manager")
}
ctxVal := appctx.GetKeyValuesFromCtx(ctx)
args := FindUsersArg{Ctx: ctxVal, Query: query, SkipFetchingGroups: skipFetchingGroups}
resp := FindUsersReply{}
err := m.Client.Call("Plugin.FindUsers", args, &resp)
if err != nil {
return nil, err
}
return resp.User, resp.Err
}
// 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
}
// GetUser RPCServer GetUser method
func (m *RPCServer) GetUser(args GetUserArg, resp *GetUserReply) error {
ctx := appctx.PutKeyValuesToCtx(args.Ctx)
resp.User, resp.Err = m.Impl.GetUser(ctx, args.UID, args.SkipFetchingGroups)
return nil
}
// GetUserByClaim RPCServer GetUserByClaim method
func (m *RPCServer) GetUserByClaim(args GetUserByClaimArg, resp *GetUserByClaimReply) error {
ctx := appctx.PutKeyValuesToCtx(args.Ctx)
resp.User, resp.Err = m.Impl.GetUserByClaim(ctx, args.Claim, args.Value, "", args.SkipFetchingGroups)
return nil
}
// GetUserGroups RPCServer GetUserGroups method
func (m *RPCServer) GetUserGroups(args GetUserGroupsArg, resp *GetUserGroupsReply) error {
ctx := appctx.PutKeyValuesToCtx(args.Ctx)
resp.Group, resp.Err = m.Impl.GetUserGroups(ctx, args.User)
return nil
}
// FindUsers RPCServer FindUsers method
func (m *RPCServer) FindUsers(args FindUsersArg, resp *FindUsersReply) error {
ctx := appctx.PutKeyValuesToCtx(args.Ctx)
resp.User, resp.Err = m.Impl.FindUsers(ctx, args.Query, "", args.SkipFetchingGroups)
return nil
}
+41
View File
@@ -0,0 +1,41 @@
// 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 user
import (
"context"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/plugin"
)
// Manager is the interface to implement to manipulate users.
type Manager interface {
plugin.Plugin
// GetUser returns the user metadata identified by a uid.
// The groups of the user are omitted if specified, as these might not be required for certain operations
// and might involve computational overhead.
GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error)
// GetUserByClaim returns the user identified by a specific value for a given claim.
GetUserByClaim(ctx context.Context, claim, value, tenantID string, skipFetchingGroups bool) (*userpb.User, error)
// GetUserGroups returns the groups a user identified by a uid belongs to.
GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error)
// FindUsers returns all the user objects which match a query parameter.
FindUsers(ctx context.Context, query, tenantID string, skipFetchingGroups bool) ([]*userpb.User, error)
}