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

     1  // Copyright 2018-2021 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  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    27  	"github.com/cs3org/reva/v2/pkg/appctx"
    28  	"github.com/cs3org/reva/v2/pkg/errtypes"
    29  	"github.com/cs3org/reva/v2/pkg/user"
    30  	"github.com/cs3org/reva/v2/pkg/user/manager/registry"
    31  	"github.com/cs3org/reva/v2/pkg/utils"
    32  	ldapIdentity "github.com/cs3org/reva/v2/pkg/utils/ldap"
    33  	"github.com/go-ldap/ldap/v3"
    34  	"github.com/google/uuid"
    35  	"github.com/mitchellh/mapstructure"
    36  	"github.com/pkg/errors"
    37  )
    38  
    39  func init() {
    40  	registry.Register("ldap", New)
    41  }
    42  
    43  type manager struct {
    44  	c          *config
    45  	ldapClient ldap.Client
    46  }
    47  
    48  type config struct {
    49  	utils.LDAPConn `mapstructure:",squash"`
    50  	LDAPIdentity   ldapIdentity.Identity `mapstructure:",squash"`
    51  	Idp            string                `mapstructure:"idp"`
    52  	// Nobody specifies the fallback uid number for users that don't have a uidNumber set in LDAP
    53  	Nobody int64 `mapstructure:"nobody"`
    54  }
    55  
    56  func parseConfig(m map[string]interface{}) (*config, error) {
    57  	c := config{
    58  		LDAPIdentity: ldapIdentity.New(),
    59  	}
    60  	if err := mapstructure.Decode(m, &c); err != nil {
    61  		err = errors.Wrap(err, "error decoding conf")
    62  		return nil, err
    63  	}
    64  
    65  	return &c, nil
    66  }
    67  
    68  // New returns a user manager implementation that connects to a LDAP server to provide user metadata.
    69  func New(m map[string]interface{}) (user.Manager, error) {
    70  	mgr := &manager{}
    71  	err := mgr.Configure(m)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	mgr.ldapClient, err = utils.GetLDAPClientWithReconnect(&mgr.c.LDAPConn)
    77  	return mgr, err
    78  }
    79  
    80  // Configure initializes the configuration of the user manager from the supplied config map
    81  func (m *manager) Configure(ml map[string]interface{}) error {
    82  	c, err := parseConfig(ml)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	if c.Nobody == 0 {
    87  		c.Nobody = 99
    88  	}
    89  
    90  	if err = c.LDAPIdentity.Setup(); err != nil {
    91  		return fmt.Errorf("error setting up Identity config: %w", err)
    92  	}
    93  	m.c = c
    94  	return nil
    95  }
    96  
    97  // GetUser implements the user.Manager interface. Looks up a user by Id and return the user
    98  func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) {
    99  	log := appctx.GetLogger(ctx)
   100  
   101  	log.Debug().Interface("id", uid).Msg("GetUser")
   102  	// If the Idp value in the uid does not match our config, we can't answer this request
   103  	if uid.Idp != "" && uid.Idp != m.c.Idp {
   104  		return nil, errtypes.NotFound("idp mismatch")
   105  	}
   106  
   107  	userEntry, err := m.c.LDAPIdentity.GetLDAPUserByID(log, m.ldapClient, uid.OpaqueId)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	log.Debug().Interface("entry", userEntry).Msg("entries")
   113  
   114  	u, err := m.ldapEntryToUser(userEntry)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	if skipFetchingGroups {
   120  		return u, nil
   121  	}
   122  
   123  	groups, err := m.c.LDAPIdentity.GetLDAPUserGroups(log, m.ldapClient, userEntry)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	u.Groups = groups
   129  	return u, nil
   130  }
   131  
   132  // GetUserByClaim implements the user.Manager interface. Looks up a user by
   133  // claim ('mail', 'username', 'userid') and returns the user.
   134  func (m *manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) {
   135  	log := appctx.GetLogger(ctx)
   136  
   137  	log.Debug().Str("claim", claim).Str("value", value).Msg("GetUserByClaim")
   138  	userEntry, err := m.c.LDAPIdentity.GetLDAPUserByAttribute(log, m.ldapClient, claim, value)
   139  	if err != nil {
   140  		log.Debug().Err(err).Msg("GetUserByClaim")
   141  		return nil, err
   142  	}
   143  
   144  	log.Debug().Interface("entry", userEntry).Msg("entries")
   145  
   146  	u, err := m.ldapEntryToUser(userEntry)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	if m.c.LDAPIdentity.IsLDAPUserInDisabledGroup(log, m.ldapClient, userEntry) {
   152  		return nil, errtypes.NotFound("user is locally disabled")
   153  	}
   154  
   155  	if skipFetchingGroups {
   156  		return u, nil
   157  	}
   158  
   159  	groups, err := m.c.LDAPIdentity.GetLDAPUserGroups(log, m.ldapClient, userEntry)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	u.Groups = groups
   165  
   166  	return u, nil
   167  }
   168  
   169  // FindUser implements the user.Manager interface. Searches for users using a prefix-substring search on
   170  // the user attributes ('mail', 'username', 'displayname', 'userid') and returns the users.
   171  func (m *manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) {
   172  	log := appctx.GetLogger(ctx)
   173  	entries, err := m.c.LDAPIdentity.GetLDAPUsers(log, m.ldapClient, query)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	users := []*userpb.User{}
   178  
   179  	for _, entry := range entries {
   180  		u, err := m.ldapEntryToUser(entry)
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  
   185  		if !skipFetchingGroups {
   186  			groups, err := m.c.LDAPIdentity.GetLDAPUserGroups(log, m.ldapClient, entry)
   187  			if err != nil {
   188  				return nil, err
   189  			}
   190  			u.Groups = groups
   191  		}
   192  
   193  		users = append(users, u)
   194  	}
   195  
   196  	return users, nil
   197  }
   198  
   199  // GetUserGroups implements the user.Manager interface. Looks up all group membership of
   200  // the user with the supplied Id. Returns a string slice with the group ids
   201  func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) {
   202  	log := appctx.GetLogger(ctx)
   203  	if uid.Idp != "" && uid.Idp != m.c.Idp {
   204  		log.Debug().Str("useridp", uid.Idp).Str("configured idp", m.c.Idp).Msg("IDP mismatch")
   205  		return nil, errtypes.NotFound("idp mismatch")
   206  	}
   207  	userEntry, err := m.c.LDAPIdentity.GetLDAPUserByID(log, m.ldapClient, uid.OpaqueId)
   208  	if err != nil {
   209  		log.Debug().Err(err).Interface("userid", uid).Msg("Failed to lookup user")
   210  		return []string{}, err
   211  	}
   212  	return m.c.LDAPIdentity.GetLDAPUserGroups(log, m.ldapClient, userEntry)
   213  }
   214  
   215  func (m *manager) ldapEntryToUser(entry *ldap.Entry) (*userpb.User, error) {
   216  	id, err := m.ldapEntryToUserID(entry)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	gidNumber := m.c.Nobody
   222  	gidValue := entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.GIDNumber)
   223  	if gidValue != "" {
   224  		gidNumber, err = strconv.ParseInt(gidValue, 10, 64)
   225  		if err != nil {
   226  			return nil, err
   227  		}
   228  	}
   229  	uidNumber := m.c.Nobody
   230  	uidValue := entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.UIDNumber)
   231  	if uidValue != "" {
   232  		uidNumber, err = strconv.ParseInt(uidValue, 10, 64)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  	}
   237  	u := &userpb.User{
   238  		Id:          id,
   239  		Username:    entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.Username),
   240  		Mail:        entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.Mail),
   241  		DisplayName: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.DisplayName),
   242  		GidNumber:   gidNumber,
   243  		UidNumber:   uidNumber,
   244  	}
   245  	return u, nil
   246  }
   247  
   248  func (m *manager) ldapEntryToUserID(entry *ldap.Entry) (*userpb.UserId, error) {
   249  	var uid string
   250  	if m.c.LDAPIdentity.User.Schema.IDIsOctetString {
   251  		rawValue := entry.GetEqualFoldRawAttributeValue(m.c.LDAPIdentity.User.Schema.ID)
   252  		if value, err := uuid.FromBytes(rawValue); err == nil {
   253  			uid = value.String()
   254  		} else {
   255  			return nil, err
   256  		}
   257  	} else {
   258  		uid = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.ID)
   259  	}
   260  
   261  	return &userpb.UserId{
   262  		Idp:      m.c.Idp,
   263  		OpaqueId: uid,
   264  		Type:     m.c.LDAPIdentity.GetUserType(entry),
   265  	}, nil
   266  }