github.com/cs3org/reva/v2@v2.27.7/pkg/group/manager/ldap/ldap.go (about)

     1  // Copyright 2018-2020 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package ldap
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"strconv"
    25  
    26  	grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
    27  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    28  	"github.com/cs3org/reva/v2/pkg/appctx"
    29  	"github.com/cs3org/reva/v2/pkg/errtypes"
    30  	"github.com/cs3org/reva/v2/pkg/group"
    31  	"github.com/cs3org/reva/v2/pkg/group/manager/registry"
    32  	"github.com/cs3org/reva/v2/pkg/utils"
    33  	ldapIdentity "github.com/cs3org/reva/v2/pkg/utils/ldap"
    34  	"github.com/go-ldap/ldap/v3"
    35  	"github.com/google/uuid"
    36  	"github.com/mitchellh/mapstructure"
    37  	"github.com/pkg/errors"
    38  )
    39  
    40  func init() {
    41  	registry.Register("ldap", New)
    42  }
    43  
    44  type manager struct {
    45  	c          *config
    46  	ldapClient ldap.Client
    47  }
    48  
    49  type config struct {
    50  	utils.LDAPConn `mapstructure:",squash"`
    51  	LDAPIdentity   ldapIdentity.Identity `mapstructure:",squash"`
    52  	Idp            string                `mapstructure:"idp"`
    53  	// Nobody specifies the fallback gid number for groups that don't have a gidNumber set in LDAP
    54  	Nobody int64 `mapstructure:"nobody"`
    55  }
    56  
    57  func parseConfig(m map[string]interface{}) (*config, error) {
    58  	c := config{
    59  		LDAPIdentity: ldapIdentity.New(),
    60  	}
    61  	if err := mapstructure.Decode(m, &c); err != nil {
    62  		err = errors.Wrap(err, "error decoding conf")
    63  		return nil, err
    64  	}
    65  
    66  	return &c, nil
    67  }
    68  
    69  // New returns a group manager implementation that connects to a LDAP server to provide group metadata.
    70  func New(m map[string]interface{}) (group.Manager, error) {
    71  	mgr := &manager{}
    72  	err := mgr.Configure(m)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	mgr.ldapClient, err = utils.GetLDAPClientWithReconnect(&mgr.c.LDAPConn)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return mgr, nil
    82  }
    83  
    84  // Configure initializes the configuration of the group manager from the supplied config map
    85  func (m *manager) Configure(ml map[string]interface{}) error {
    86  	c, err := parseConfig(ml)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	if c.Nobody == 0 {
    91  		c.Nobody = 99
    92  	}
    93  
    94  	if err = c.LDAPIdentity.Setup(); err != nil {
    95  		return fmt.Errorf("error setting up Identity config: %w", err)
    96  	}
    97  	m.c = c
    98  	return nil
    99  }
   100  
   101  // GetGroup implements the group.Manager interface. Looks up a group by Id and return the group
   102  func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) {
   103  	log := appctx.GetLogger(ctx)
   104  	if gid.Idp != "" && gid.Idp != m.c.Idp {
   105  		return nil, errtypes.NotFound("idp mismatch")
   106  	}
   107  
   108  	groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByID(log, m.ldapClient, gid.OpaqueId)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	log.Debug().Interface("entry", groupEntry).Msg("entries")
   114  
   115  	g, err := m.ldapEntryToGroup(groupEntry)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	if skipFetchingMembers {
   121  		return g, nil
   122  	}
   123  
   124  	members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	memberIDs := make([]*userpb.UserId, 0, len(members))
   130  	for _, member := range members {
   131  		userid, err := m.ldapEntryToUserID(member)
   132  		if err != nil {
   133  			log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid")
   134  			continue
   135  		}
   136  		memberIDs = append(memberIDs, userid)
   137  	}
   138  
   139  	g.Members = memberIDs
   140  
   141  	return g, nil
   142  }
   143  
   144  // GetGroupByClaim implements the group.Manager interface. Looks up a group by
   145  // claim ('group_name', 'group_id', 'display_name') and returns the group.
   146  func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) {
   147  	log := appctx.GetLogger(ctx)
   148  	groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByAttribute(log, m.ldapClient, claim, value)
   149  	if err != nil {
   150  		log.Debug().Err(err).Msg("GetGroupByClaim")
   151  		return nil, err
   152  	}
   153  
   154  	log.Debug().Interface("entry", groupEntry).Msg("entries")
   155  
   156  	g, err := m.ldapEntryToGroup(groupEntry)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	if skipFetchingMembers {
   162  		return g, nil
   163  	}
   164  
   165  	members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	memberIDs := make([]*userpb.UserId, 0, len(members))
   171  	for _, member := range members {
   172  		userid, err := m.ldapEntryToUserID(member)
   173  		if err != nil {
   174  			log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid")
   175  			continue
   176  		}
   177  		memberIDs = append(memberIDs, userid)
   178  	}
   179  
   180  	g.Members = memberIDs
   181  
   182  	return g, nil
   183  }
   184  
   185  // FindGroups implements the group.Manager interface. Searches for groups using
   186  // a prefix-substring search on the group attributes ('group_name',
   187  // 'display_name', 'group_id') and returns the groups. FindGroups does NOT expand the
   188  // members of the Groups.
   189  func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) {
   190  	log := appctx.GetLogger(ctx)
   191  	entries, err := m.c.LDAPIdentity.GetLDAPGroups(log, m.ldapClient, query)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	groups := make([]*grouppb.Group, 0, len(entries))
   197  
   198  	for _, entry := range entries {
   199  		g, err := m.ldapEntryToGroup(entry)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  
   204  		groups = append(groups, g)
   205  	}
   206  
   207  	return groups, nil
   208  }
   209  
   210  // GetMembers implements the group.Manager interface. It returns all the userids of the members
   211  // of the group identified by the supplied id.
   212  func (m *manager) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*userpb.UserId, error) {
   213  	log := appctx.GetLogger(ctx)
   214  	if gid.Idp != "" && gid.Idp != m.c.Idp {
   215  		return nil, errtypes.NotFound("idp mismatch")
   216  	}
   217  
   218  	groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByID(log, m.ldapClient, gid.OpaqueId)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	log.Debug().Interface("entry", groupEntry).Msg("entries")
   224  
   225  	members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	memberIDs := make([]*userpb.UserId, 0, len(members))
   231  	for _, member := range members {
   232  		userid, err := m.ldapEntryToUserID(member)
   233  		if err != nil {
   234  			log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid")
   235  			continue
   236  		}
   237  		memberIDs = append(memberIDs, userid)
   238  	}
   239  
   240  	return memberIDs, nil
   241  }
   242  
   243  // HasMember implements the group.Member interface. Checks whether the supplied userid is a member
   244  // of the supplied groupid.
   245  func (m *manager) HasMember(ctx context.Context, gid *grouppb.GroupId, uid *userpb.UserId) (bool, error) {
   246  	// It might be possible to do a somewhat more clever LDAP search here. (First lookup the user and then
   247  	// search for (&(objectclass=<groupoc>)(<groupid>=gid)(member=<username/userdn>)
   248  	// The GetMembers call used below can be quiet ineffecient for large groups
   249  	members, err := m.GetMembers(ctx, gid)
   250  	if err != nil {
   251  		return false, err
   252  	}
   253  
   254  	for _, u := range members {
   255  		if u.OpaqueId == uid.OpaqueId && u.Idp == uid.Idp {
   256  			return true, nil
   257  		}
   258  	}
   259  	return false, nil
   260  }
   261  
   262  func (m *manager) ldapEntryToGroup(entry *ldap.Entry) (*grouppb.Group, error) {
   263  	id, err := m.ldapEntryToGroupID(entry)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	gidNumber := m.c.Nobody
   269  	gidValue := entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.GIDNumber)
   270  	if gidValue != "" {
   271  		gidNumber, err = strconv.ParseInt(gidValue, 10, 64)
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  	}
   276  
   277  	g := &grouppb.Group{
   278  		Id:          id,
   279  		GroupName:   entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.Groupname),
   280  		Mail:        entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.Mail),
   281  		DisplayName: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.DisplayName),
   282  		GidNumber:   gidNumber,
   283  	}
   284  
   285  	return g, nil
   286  }
   287  
   288  func (m *manager) ldapEntryToGroupID(entry *ldap.Entry) (*grouppb.GroupId, error) {
   289  	var id string
   290  	if m.c.LDAPIdentity.Group.Schema.IDIsOctetString {
   291  		rawValue := entry.GetEqualFoldRawAttributeValue(m.c.LDAPIdentity.Group.Schema.ID)
   292  		if value, err := uuid.FromBytes(rawValue); err == nil {
   293  			id = value.String()
   294  		} else {
   295  			return nil, err
   296  		}
   297  	} else {
   298  		id = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.ID)
   299  	}
   300  
   301  	return &grouppb.GroupId{
   302  		Idp:      m.c.Idp,
   303  		OpaqueId: id,
   304  	}, nil
   305  }
   306  
   307  func (m *manager) ldapEntryToUserID(entry *ldap.Entry) (*userpb.UserId, error) {
   308  	var uid string
   309  	if m.c.LDAPIdentity.User.Schema.IDIsOctetString {
   310  		rawValue := entry.GetEqualFoldRawAttributeValue(m.c.LDAPIdentity.User.Schema.ID)
   311  		var value uuid.UUID
   312  		var err error
   313  		if value, err = uuid.FromBytes(rawValue); err != nil {
   314  			return nil, err
   315  		}
   316  		uid = value.String()
   317  	} else {
   318  		uid = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.ID)
   319  	}
   320  
   321  	return &userpb.UserId{
   322  		Idp:      m.c.Idp,
   323  		OpaqueId: uid,
   324  		Type:     userpb.UserType_USER_TYPE_PRIMARY,
   325  	}, nil
   326  }