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
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2018-2020 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 group
import (
"context"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
)
// Manager is the interface to implement to manipulate groups.
type Manager interface {
GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error)
GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error)
FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error)
GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*userpb.UserId, error)
HasMember(ctx context.Context, gid *grouppb.GroupId, uid *userpb.UserId) (bool, error)
}
@@ -0,0 +1,172 @@
// Copyright 2018-2020 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"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/group"
"github.com/opencloud-eu/reva/v2/pkg/group/manager/registry"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
)
func init() {
registry.Register("json", New)
}
type manager struct {
groups []*grouppb.Group
}
type config struct {
// Groups holds a path to a file containing json conforming to the Groups struct
Groups string `mapstructure:"groups"`
}
func (c *config) init() {
if c.Groups == "" {
c.Groups = "/etc/revad/groups.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 group manager implementation that reads a json file to provide group metadata.
func New(m map[string]interface{}) (group.Manager, error) {
c, err := parseConfig(m)
if err != nil {
return nil, err
}
f, err := os.ReadFile(c.Groups)
if err != nil {
return nil, err
}
groups := []*grouppb.Group{}
err = json.Unmarshal(f, &groups)
if err != nil {
return nil, err
}
return &manager{
groups: groups,
}, nil
}
func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) {
for _, g := range m.groups {
if (g.Id.GetOpaqueId() == gid.OpaqueId || g.GroupName == gid.OpaqueId) && (gid.Idp == "" || gid.Idp == g.Id.GetIdp()) {
group := proto.Clone(g).(*grouppb.Group)
if skipFetchingMembers {
group.Members = nil
}
return group, nil
}
}
return nil, errtypes.NotFound(gid.OpaqueId)
}
func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) {
for _, g := range m.groups {
if groupClaim, err := extractClaim(g, claim); err == nil && value == groupClaim {
group := proto.Clone(g).(*grouppb.Group)
if skipFetchingMembers {
group.Members = nil
}
return group, nil
}
}
return nil, errtypes.NotFound(value)
}
func extractClaim(g *grouppb.Group, claim string) (string, error) {
switch claim {
case "group_name":
return g.GroupName, nil
case "gid_number":
return strconv.FormatInt(g.GidNumber, 10), nil
case "display_name":
return g.DisplayName, nil
case "mail":
return g.Mail, nil
}
return "", errors.New("json: invalid field")
}
func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) {
groups := []*grouppb.Group{}
for _, g := range m.groups {
if groupContains(g, query) {
group := proto.Clone(g).(*grouppb.Group)
if skipFetchingMembers {
group.Members = nil
}
groups = append(groups, group)
}
}
return groups, nil
}
func groupContains(g *grouppb.Group, query string) bool {
query = strings.ToLower(query)
return strings.Contains(strings.ToLower(g.GroupName), query) || strings.Contains(strings.ToLower(g.DisplayName), query) ||
strings.Contains(strings.ToLower(g.Mail), query) || strings.Contains(strings.ToLower(g.Id.OpaqueId), query)
}
func (m *manager) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*userpb.UserId, error) {
for _, g := range m.groups {
if g.Id.GetOpaqueId() == gid.OpaqueId || g.GroupName == gid.OpaqueId {
return g.Members, nil
}
}
return nil, errtypes.NotFound(gid.OpaqueId)
}
func (m *manager) HasMember(ctx context.Context, gid *grouppb.GroupId, uid *userpb.UserId) (bool, error) {
members, err := m.GetMembers(ctx, gid)
if err != nil {
return false, err
}
for _, u := range members {
if u.OpaqueId == uid.OpaqueId && u.Idp == uid.Idp {
return true, nil
}
}
return false, nil
}
@@ -0,0 +1,377 @@
// Copyright 2018-2020 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"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
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/group"
"github.com/opencloud-eu/reva/v2/pkg/group/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
"github.com/opencloud-eu/reva/v2/pkg/utils"
ldapIdentity "github.com/opencloud-eu/reva/v2/pkg/utils/ldap"
"go.opentelemetry.io/otel/attribute"
)
func init() {
registry.Register("ldap", New)
}
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 gid number for groups that don't have a gidNumber set in LDAP
Nobody int64 `mapstructure:"nobody"`
}
const tracerName = "pkg/group/manager/ldap"
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 group manager implementation that connects to a LDAP server to provide group metadata.
func New(m map[string]interface{}) (group.Manager, error) {
if sharedconf.MultiTenantEnabled() {
return nil, errtypes.NotSupported("ldap group manager does not support multi-tenancy")
}
mgr := &manager{}
err := mgr.Configure(m)
if err != nil {
return nil, err
}
mgr.ldapClient, err = utils.GetLDAPClientWithReconnect(&mgr.c.LDAPConn)
if err != nil {
return nil, err
}
return mgr, nil
}
// Configure initializes the configuration of the group 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
}
// GetGroup implements the group.Manager interface. Looks up a group by Id and return the group
func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "GetGroup")
defer span.End()
span.SetAttributes(
attribute.Stringer("group_id", gid),
attribute.Bool("skip_fetching_members", skipFetchingMembers),
)
log := appctx.GetLogger(ctx)
if gid.Idp != "" && gid.Idp != m.c.Idp {
return nil, errtypes.NotFound("idp mismatch")
}
groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByID(ctx, m.ldapClient, gid.OpaqueId)
if err != nil {
return nil, err
}
log.Debug().Interface("entry", groupEntry).Msg("entries")
g, err := m.ldapEntryToGroup(groupEntry)
if err != nil {
return nil, err
}
if skipFetchingMembers {
return g, nil
}
members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(ctx, m.ldapClient, groupEntry)
if err != nil {
return nil, err
}
memberIDs := make([]*userpb.UserId, 0, len(members))
for _, member := range members {
userid, err := m.ldapEntryToUserID(member)
if err != nil {
log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid")
continue
}
memberIDs = append(memberIDs, userid)
}
g.Members = memberIDs
return g, nil
}
// GetGroupByClaim implements the group.Manager interface. Looks up a group by
// claim ('group_name', 'group_id', 'display_name') and returns the group.
func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "GetGroupByClaim")
defer span.End()
span.SetAttributes(
attribute.String("claim", claim),
attribute.String("value", value),
attribute.Bool("skip_fetching_members", skipFetchingMembers),
)
log := appctx.GetLogger(ctx)
groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByAttribute(ctx, m.ldapClient, claim, value)
if err != nil {
log.Debug().Err(err).Msg("GetGroupByClaim")
return nil, err
}
log.Debug().Interface("entry", groupEntry).Msg("entries")
g, err := m.ldapEntryToGroup(groupEntry)
if err != nil {
return nil, err
}
if skipFetchingMembers {
return g, nil
}
members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(ctx, m.ldapClient, groupEntry)
if err != nil {
return nil, err
}
memberIDs := make([]*userpb.UserId, 0, len(members))
for _, member := range members {
userid, err := m.ldapEntryToUserID(member)
if err != nil {
log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid")
continue
}
memberIDs = append(memberIDs, userid)
}
g.Members = memberIDs
return g, nil
}
// FindGroups implements the group.Manager interface. Searches for groups using
// a prefix-substring search on the group attributes ('group_name',
// 'display_name', 'group_id') and returns the groups. FindGroups does NOT expand the
// members of the Groups.
func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "FindGroups")
defer span.End()
span.SetAttributes(
attribute.String("query", query),
attribute.Bool("skip_fetching_members", skipFetchingMembers),
)
entries, err := m.c.LDAPIdentity.GetLDAPGroups(ctx, m.ldapClient, query)
if err != nil {
return nil, err
}
groups := make([]*grouppb.Group, 0, len(entries))
for _, entry := range entries {
g, err := m.ldapEntryToGroup(entry)
if err != nil {
return nil, err
}
groups = append(groups, g)
}
return groups, nil
}
// GetMembers implements the group.Manager interface. It returns all the userids of the members
// of the group identified by the supplied id.
func (m *manager) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*userpb.UserId, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "GetMembers")
defer span.End()
span.SetAttributes(
attribute.Stringer("group_id", gid),
)
log := appctx.GetLogger(ctx)
if gid.Idp != "" && gid.Idp != m.c.Idp {
return nil, errtypes.NotFound("idp mismatch")
}
groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByID(ctx, m.ldapClient, gid.OpaqueId)
if err != nil {
return nil, err
}
log.Debug().Interface("entry", groupEntry).Msg("entries")
members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(ctx, m.ldapClient, groupEntry)
if err != nil {
return nil, err
}
memberIDs := make([]*userpb.UserId, 0, len(members))
for _, member := range members {
userid, err := m.ldapEntryToUserID(member)
if err != nil {
log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid")
continue
}
memberIDs = append(memberIDs, userid)
}
return memberIDs, nil
}
// HasMember implements the group.Member interface. Checks whether the supplied userid is a member
// of the supplied groupid.
func (m *manager) HasMember(ctx context.Context, gid *grouppb.GroupId, uid *userpb.UserId) (bool, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "HasMember")
defer span.End()
span.SetAttributes(
attribute.Stringer("group_id", gid),
attribute.Stringer("user_id", uid),
)
// It might be possible to do a somewhat more clever LDAP search here. (First lookup the user and then
// search for (&(objectclass=<groupoc>)(<groupid>=gid)(member=<username/userdn>)
// The GetMembers call used below can be quiet ineffecient for large groups
members, err := m.GetMembers(ctx, gid)
if err != nil {
return false, err
}
for _, u := range members {
if u.OpaqueId == uid.OpaqueId && u.Idp == uid.Idp {
return true, nil
}
}
return false, nil
}
func (m *manager) ldapEntryToGroup(entry *ldap.Entry) (*grouppb.Group, error) {
id, err := m.ldapEntryToGroupID(entry)
if err != nil {
return nil, err
}
gidNumber := m.c.Nobody
gidValue := entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.GIDNumber)
if gidValue != "" {
gidNumber, err = strconv.ParseInt(gidValue, 10, 64)
if err != nil {
return nil, err
}
}
g := &grouppb.Group{
Id: id,
GroupName: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.Groupname),
Mail: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.Mail),
DisplayName: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.DisplayName),
GidNumber: gidNumber,
}
return g, nil
}
func (m *manager) ldapEntryToGroupID(entry *ldap.Entry) (*grouppb.GroupId, error) {
var id string
if m.c.LDAPIdentity.Group.Schema.IDIsOctetString {
attribute := m.c.LDAPIdentity.Group.Schema.ID
rawValue := entry.GetEqualFoldRawAttributeValue(attribute)
if strings.EqualFold(attribute, "objectguid") {
rawValue = ldapIdentity.SwapObjectGUIDBytes(rawValue)
}
if value, err := uuid.FromBytes(rawValue); err == nil {
id = value.String()
} else {
return nil, err
}
} else {
id = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.ID)
}
return &grouppb.GroupId{
Idp: m.c.Idp,
OpaqueId: id,
}, 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,
Type: userpb.UserType_USER_TYPE_PRIMARY,
}, nil
}
@@ -0,0 +1,27 @@
// Copyright 2018-2020 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 group manager drivers.
_ "github.com/opencloud-eu/reva/v2/pkg/group/manager/json"
_ "github.com/opencloud-eu/reva/v2/pkg/group/manager/ldap"
_ "github.com/opencloud-eu/reva/v2/pkg/group/manager/null"
// Add your own here
)
@@ -0,0 +1,62 @@
// Copyright 2018-2020 CERN
// Copyright 2025 OpenCloud GmbH
//
// 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 null
import (
"context"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
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/group"
"github.com/opencloud-eu/reva/v2/pkg/group/manager/registry"
)
func init() {
registry.Register("null", New)
}
type manager struct {
}
// New returns a group manager implementation that return NOT FOUND or empty result set for every call
func New(m map[string]interface{}) (group.Manager, error) {
return &manager{}, nil
}
func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) {
return nil, errtypes.NotFound(gid.OpaqueId)
}
func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) {
return nil, errtypes.NotFound(value)
}
func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) {
return []*grouppb.Group{}, nil
}
func (m *manager) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*userpb.UserId, error) {
return nil, errtypes.NotFound(gid.OpaqueId)
}
func (m *manager) HasMember(ctx context.Context, gid *grouppb.GroupId, uid *userpb.UserId) (bool, error) {
return false, errtypes.NotFound(gid.OpaqueId)
}
@@ -0,0 +1,34 @@
// Copyright 2018-2020 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/group"
// NewFunc is the function that group managers
// should register at init time.
type NewFunc func(map[string]interface{}) (group.Manager, error)
// NewFuncs is a map containing all the registered group managers.
var NewFuncs = map[string]NewFunc{}
// Register registers a new group manager new function.
// Not safe for concurrent use. Safe for use from package init.
func Register(name string, f NewFunc) {
NewFuncs[name] = f
}