github.com/cs3org/reva/v2@v2.27.7/pkg/utils/ldap/identity.go (about)

     1  // Copyright 2022 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  	"fmt"
    23  	"strings"
    24  
    25  	identityUser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    26  	"github.com/cs3org/reva/v2/pkg/errtypes"
    27  	"github.com/go-ldap/ldap/v3"
    28  	"github.com/google/uuid"
    29  	"github.com/pkg/errors"
    30  	"github.com/rs/zerolog"
    31  )
    32  
    33  // Identity provides methods to query users and groups from an LDAP server
    34  type Identity struct {
    35  	User  userConfig  `mapstructure:",squash"`
    36  	Group groupConfig `mapstructure:",squash"`
    37  }
    38  
    39  type userConfig struct {
    40  	BaseDN              string `mapstructure:"user_base_dn"`
    41  	Scope               string `mapstructure:"user_search_scope"`
    42  	scopeVal            int
    43  	Filter              string     `mapstructure:"user_filter"`
    44  	Objectclass         string     `mapstructure:"user_objectclass"`
    45  	DisableMechanism    string     `mapstructure:"user_disable_mechanism"`
    46  	EnabledProperty     string     `mapstructure:"user_enabled_property"`
    47  	UserTypeProperty    string     `mapstructure:"user_type_property"`
    48  	Schema              userSchema `mapstructure:"user_schema"`
    49  	SubstringFilterType string     `mapstructure:"user_substring_filter_type"`
    50  	substringFilterVal  int
    51  }
    52  
    53  type groupConfig struct {
    54  	BaseDN              string `mapstructure:"group_base_dn"`
    55  	Scope               string `mapstructure:"group_search_scope"`
    56  	scopeVal            int
    57  	Filter              string      `mapstructure:"group_filter"`
    58  	Objectclass         string      `mapstructure:"group_objectclass"`
    59  	Schema              groupSchema `mapstructure:"group_schema"`
    60  	SubstringFilterType string      `mapstructure:"group_substring_filter_type"`
    61  	substringFilterVal  int
    62  	// LocalDisabledDN contains the full DN of a group that contains disabled users.
    63  	LocalDisabledDN string `mapstructure:"group_local_disabled_dn"`
    64  }
    65  
    66  type groupSchema struct {
    67  	// GID is an immutable group id, see https://docs.microsoft.com/en-us/azure/active-directory/hybrid/plan-connect-design-concepts
    68  	ID              string `mapstructure:"id"`
    69  	IDIsOctetString bool   `mapstructure:"idIsOctetString"`
    70  	// CN is the group name, typically `cn`, `gid` or `samaccountname`
    71  	Groupname string `mapstructure:"groupName"`
    72  	// Mail is the email address of a group
    73  	Mail string `mapstructure:"mail"`
    74  	// Displayname is the Human readable name, e.g. `Database Admins`
    75  	DisplayName string `mapstructure:"displayName"`
    76  	// GIDNumber is a numeric id that maps to a filesystem gid, eg. 654321
    77  	GIDNumber string `mapstructure:"gidNumber"`
    78  	Member    string `mapstructure:"member"`
    79  }
    80  
    81  type userSchema struct {
    82  	// UID is an immutable user id, see https://docs.microsoft.com/en-us/azure/active-directory/hybrid/plan-connect-design-concepts
    83  	ID string `mapstructure:"id"`
    84  	// UIDIsOctetString set this to true i the values of the UID attribute are returned as OCTET STRING values (binary byte sequences)
    85  	// by the Directory Service. This is e.g. the case for the 'objectGUID' and	'ms-DS-ConsistencyGuid' Attributes in AD
    86  	IDIsOctetString bool `mapstructure:"idIsOctetString"`
    87  	// Name is the username, typically `cn`, `uid` or `samaccountname`
    88  	Username string `mapstructure:"userName"`
    89  	// Mail is the email address of a user
    90  	Mail string `mapstructure:"mail"`
    91  	// Displayname is the Human readable name, e.g. `Albert Einstein`
    92  	DisplayName string `mapstructure:"displayName"`
    93  	// UIDNumber is a numeric id that maps to a filesystem uid, eg. 123546
    94  	UIDNumber string `mapstructure:"uidNumber"`
    95  	// GIDNumber is a numeric id that maps to a filesystem gid, eg. 654321
    96  	GIDNumber string `mapstructure:"gidNumber"`
    97  }
    98  
    99  // Default userConfig (somewhat inspired by Active Directory)
   100  var userDefaults = userConfig{
   101  	Scope:       "sub",
   102  	Objectclass: "posixAccount",
   103  	Schema: userSchema{
   104  		ID:              "ms-DS-ConsistencyGuid",
   105  		IDIsOctetString: false,
   106  		Username:        "cn",
   107  		Mail:            "mail",
   108  		DisplayName:     "displayName",
   109  		UIDNumber:       "uidNumber",
   110  		GIDNumber:       "gidNumber",
   111  	},
   112  	SubstringFilterType: "initial",
   113  }
   114  
   115  // Default groupConfig (Active Directory)
   116  var groupDefaults = groupConfig{
   117  	Scope:       "sub",
   118  	Objectclass: "posixGroup",
   119  	Schema: groupSchema{
   120  		ID:              "objectGUID",
   121  		IDIsOctetString: false,
   122  		Groupname:       "cn",
   123  		Mail:            "mail",
   124  		DisplayName:     "cn",
   125  		GIDNumber:       "gidNumber",
   126  		Member:          "memberUid",
   127  	},
   128  	SubstringFilterType: "initial",
   129  }
   130  
   131  // New initializes the default config
   132  func New() Identity {
   133  	return Identity{
   134  		User:  userDefaults,
   135  		Group: groupDefaults,
   136  	}
   137  }
   138  
   139  // Setup initialzes some properties that can't be initialized from the
   140  // mapstructure based config. Currently it just converts the LDAP search scope
   141  // strings from the config to the integer constants expected by the ldap API
   142  func (i *Identity) Setup() error {
   143  	var err error
   144  	if i.User.scopeVal, err = stringToScope(i.User.Scope); err != nil {
   145  		return fmt.Errorf("error configuring user scope: %w", err)
   146  	}
   147  
   148  	if i.Group.scopeVal, err = stringToScope(i.Group.Scope); err != nil {
   149  		return fmt.Errorf("error configuring group scope: %w", err)
   150  	}
   151  
   152  	if i.User.substringFilterVal, err = stringToFilterType(i.User.SubstringFilterType); err != nil {
   153  		return fmt.Errorf("error configuring user substring filter type: %w", err)
   154  	}
   155  
   156  	if i.Group.substringFilterVal, err = stringToFilterType(i.Group.SubstringFilterType); err != nil {
   157  		return fmt.Errorf("error configuring group substring filter type: %w", err)
   158  	}
   159  
   160  	switch i.User.DisableMechanism {
   161  	case "group":
   162  		if i.Group.LocalDisabledDN == "" {
   163  			return fmt.Errorf("error configuring disable mechanism, disabled group DN not set")
   164  		}
   165  	case "attribute":
   166  		if i.User.EnabledProperty == "" {
   167  			return fmt.Errorf("error configuring disable mechanism, enabled property not set")
   168  		}
   169  	case "", "none":
   170  	default:
   171  		return fmt.Errorf("invalid disable mechanism setting: %s", i.User.DisableMechanism)
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  // GetLDAPUserByID looks up a user by the supplied Id. Returns the corresponding
   178  // ldap.Entry
   179  func (i *Identity) GetLDAPUserByID(log *zerolog.Logger, lc ldap.Client, id string) (*ldap.Entry, error) {
   180  	var filter string
   181  	var err error
   182  	if filter, err = i.getUserFilter(id); err != nil {
   183  		return nil, err
   184  	}
   185  	return i.GetLDAPUserByFilter(log, lc, filter)
   186  }
   187  
   188  // GetLDAPUserByAttribute looks up a single user by attribute (can be "mail",
   189  // "uid", "gid", "username" or "userid"). Returns the corresponding ldap.Entry
   190  func (i *Identity) GetLDAPUserByAttribute(log *zerolog.Logger, lc ldap.Client, attribute, value string) (*ldap.Entry, error) {
   191  	var filter string
   192  	var err error
   193  	if filter, err = i.getUserAttributeFilter(attribute, value); err != nil {
   194  		return nil, err
   195  	}
   196  	return i.GetLDAPUserByFilter(log, lc, filter)
   197  }
   198  
   199  // GetLDAPUserByFilter looks up a single user by the supplied LDAP filter
   200  // returns the corresponding ldap.Entry
   201  func (i *Identity) GetLDAPUserByFilter(log *zerolog.Logger, lc ldap.Client, filter string) (*ldap.Entry, error) {
   202  	searchRequest := ldap.NewSearchRequest(
   203  		i.User.BaseDN, i.User.scopeVal, ldap.NeverDerefAliases, 1, 0, false,
   204  		filter,
   205  		[]string{
   206  			i.User.Schema.DisplayName,
   207  			i.User.Schema.ID,
   208  			i.User.Schema.Mail,
   209  			i.User.Schema.Username,
   210  			i.User.Schema.UIDNumber,
   211  			i.User.Schema.GIDNumber,
   212  			i.User.EnabledProperty,
   213  			i.User.UserTypeProperty,
   214  		},
   215  		nil,
   216  	)
   217  	log.Debug().Str("backend", "ldap").Str("basedn", i.User.BaseDN).Str("filter", filter).Int("scope", i.User.scopeVal).Msg("LDAP Search")
   218  	res, err := lc.Search(searchRequest)
   219  	if err != nil {
   220  		log.Debug().Str("backend", "ldap").Err(err).Str("userfilter", filter).Msg("Error looking up user by filter")
   221  		var errmsg string
   222  		if lerr, ok := err.(*ldap.Error); ok {
   223  			if lerr.ResultCode == ldap.LDAPResultSizeLimitExceeded {
   224  				errmsg = fmt.Sprintf("too many results searching for user '%s'", filter)
   225  			}
   226  		}
   227  		return nil, errtypes.NotFound(errmsg)
   228  	}
   229  	if len(res.Entries) == 0 {
   230  		return nil, errtypes.NotFound(filter)
   231  	}
   232  
   233  	return res.Entries[0], nil
   234  }
   235  
   236  // GetLDAPUserByDN looks up a single user by the supplied LDAP DN
   237  // returns the corresponding ldap.Entry
   238  func (i *Identity) GetLDAPUserByDN(log *zerolog.Logger, lc ldap.Client, dn string) (*ldap.Entry, error) {
   239  	filter := fmt.Sprintf("(objectclass=%s)", i.User.Objectclass)
   240  	if i.User.Filter != "" {
   241  		filter = fmt.Sprintf("(&%s%s)", i.User.Filter, filter)
   242  	}
   243  	searchRequest := ldap.NewSearchRequest(
   244  		dn, i.User.scopeVal, ldap.NeverDerefAliases, 1, 0, false,
   245  		filter,
   246  		[]string{
   247  			i.User.Schema.DisplayName,
   248  			i.User.Schema.ID,
   249  			i.User.Schema.Mail,
   250  			i.User.Schema.Username,
   251  			i.User.Schema.UIDNumber,
   252  			i.User.Schema.GIDNumber,
   253  			i.User.EnabledProperty,
   254  		},
   255  		nil,
   256  	)
   257  	log.Debug().Str("backend", "ldap").Str("basedn", dn).Str("filter", filter).Int("scope", i.User.scopeVal).Msg("LDAP Search")
   258  	res, err := lc.Search(searchRequest)
   259  	if err != nil {
   260  		log.Debug().Str("backend", "ldap").Err(err).Str("dn", dn).Msg("Error looking up user by DN")
   261  		return nil, errtypes.NotFound(dn)
   262  	}
   263  	if len(res.Entries) == 0 {
   264  		return nil, errtypes.NotFound(dn)
   265  	}
   266  
   267  	return res.Entries[0], nil
   268  }
   269  
   270  // GetLDAPUsers searches for users using a prefix-substring match on the user
   271  // attributes. Returns a slice of matching ldap.Entries
   272  func (i *Identity) GetLDAPUsers(log *zerolog.Logger, lc ldap.Client, query string) ([]*ldap.Entry, error) {
   273  	filter := i.getUserFindFilter(query)
   274  	searchRequest := ldap.NewSearchRequest(
   275  		i.User.BaseDN,
   276  		i.User.scopeVal, ldap.NeverDerefAliases, 0, 0, false,
   277  		filter,
   278  		[]string{
   279  			i.User.Schema.ID,
   280  			i.User.Schema.Username,
   281  			i.User.Schema.Mail,
   282  			i.User.Schema.DisplayName,
   283  			i.User.Schema.UIDNumber,
   284  			i.User.Schema.GIDNumber,
   285  			i.User.EnabledProperty,
   286  			i.User.UserTypeProperty,
   287  		},
   288  		nil,
   289  	)
   290  
   291  	log.Debug().Str("backend", "ldap").Str("basedn", i.User.BaseDN).Str("filter", filter).Int("scope", i.User.scopeVal).Msg("LDAP Search")
   292  	sr, err := lc.Search(searchRequest)
   293  	if err != nil {
   294  		log.Debug().Str("backend", "ldap").Err(err).Str("filter", filter).Msg("Error searching users")
   295  		return nil, errtypes.NotFound(query)
   296  	}
   297  	return sr.Entries, nil
   298  }
   299  
   300  // IsLDAPUserInDisabledGroup checkes if the user is in the disabled group.
   301  func (i *Identity) IsLDAPUserInDisabledGroup(log *zerolog.Logger, lc ldap.Client, userEntry *ldap.Entry) bool {
   302  	// Check if we need to do this here because the configuration is local to Identity.
   303  	if i.User.DisableMechanism != "group" {
   304  		return false
   305  	}
   306  
   307  	filter := fmt.Sprintf("(&(objectClass=groupOfNames)(%s=%s))", i.Group.Schema.Member, userEntry.DN)
   308  	searchRequest := ldap.NewSearchRequest(
   309  		i.Group.LocalDisabledDN,
   310  		i.Group.scopeVal,
   311  		ldap.NeverDerefAliases, 0, 0, false,
   312  		filter,
   313  		[]string{i.Group.Schema.ID},
   314  		nil,
   315  	)
   316  	log.Debug().Str("backend", "ldap").Str("basedn", i.Group.LocalDisabledDN).Str("filter", filter).Int("scope", i.Group.scopeVal).Msg("LDAP Search")
   317  	sr, err := lc.Search(searchRequest)
   318  	if err != nil {
   319  		log.Error().Str("backend", "ldap").Err(err).Str("filter", filter).Msg("Error looking up error group")
   320  		// Err on the side of caution.
   321  		return true
   322  	}
   323  
   324  	return len(sr.Entries) > 0
   325  }
   326  
   327  // GetLDAPUserGroups looks up the group member ship of the supplied LDAP user entry.
   328  // Returns a slice of strings with groupids
   329  func (i *Identity) GetLDAPUserGroups(log *zerolog.Logger, lc ldap.Client, userEntry *ldap.Entry) ([]string, error) {
   330  	var memberValue string
   331  
   332  	if strings.ToLower(i.Group.Objectclass) == "posixgroup" {
   333  		// posixGroup usually means that the member attribute just contains the username
   334  		memberValue = userEntry.GetEqualFoldAttributeValue(i.User.Schema.Username)
   335  	} else {
   336  		// In all other case we assume the member Attribute to contain full LDAP DNs
   337  		memberValue = userEntry.DN
   338  	}
   339  
   340  	filter := i.getGroupMemberFilter(memberValue)
   341  	searchRequest := ldap.NewSearchRequest(
   342  		i.Group.BaseDN, i.Group.scopeVal,
   343  		ldap.NeverDerefAliases, 0, 0, false,
   344  		filter,
   345  		[]string{i.Group.Schema.ID},
   346  		nil,
   347  	)
   348  
   349  	log.Debug().Str("backend", "ldap").Str("basedn", i.Group.BaseDN).Str("filter", filter).Int("scope", i.Group.scopeVal).Msg("LDAP Search")
   350  	sr, err := lc.Search(searchRequest)
   351  	if err != nil {
   352  		log.Debug().Str("backend", "ldap").Err(err).Str("filter", filter).Msg("Error looking up group memberships")
   353  		return []string{}, err
   354  	}
   355  
   356  	groups := make([]string, 0, len(sr.Entries))
   357  	for _, entry := range sr.Entries {
   358  		// FIXME this makes the users groups use the cn, not an immutable id
   359  		// FIXME 1. use the memberof or members attribute of a user to get the groups
   360  		// FIXME 2. ook up the id for each group
   361  		var groupID string
   362  		if i.Group.Schema.IDIsOctetString {
   363  			raw := entry.GetEqualFoldRawAttributeValue(i.Group.Schema.ID)
   364  			value, err := uuid.FromBytes(raw)
   365  			if err != nil {
   366  				return nil, err
   367  			}
   368  			groupID = value.String()
   369  		} else {
   370  			groupID = entry.GetEqualFoldAttributeValue(i.Group.Schema.ID)
   371  		}
   372  
   373  		groups = append(groups, groupID)
   374  	}
   375  	return groups, nil
   376  }
   377  
   378  // GetLDAPGroupByID looks up a group by the supplied Id. Returns the corresponding
   379  // ldap.Entry
   380  func (i *Identity) GetLDAPGroupByID(log *zerolog.Logger, lc ldap.Client, id string) (*ldap.Entry, error) {
   381  	var filter string
   382  	var err error
   383  	if filter, err = i.getGroupFilter(id); err != nil {
   384  		return nil, err
   385  	}
   386  	return i.GetLDAPGroupByFilter(log, lc, filter)
   387  }
   388  
   389  // GetLDAPGroupByAttribute looks up a single group by attribute (can be "mail", "gid_number",
   390  // "display_name", "group_name", "group_id"). Returns the corresponding ldap.Entry
   391  func (i *Identity) GetLDAPGroupByAttribute(log *zerolog.Logger, lc ldap.Client, attribute, value string) (*ldap.Entry, error) {
   392  	var filter string
   393  	var err error
   394  	if filter, err = i.getGroupAttributeFilter(attribute, value); err != nil {
   395  		return nil, err
   396  	}
   397  	return i.GetLDAPGroupByFilter(log, lc, filter)
   398  }
   399  
   400  // GetLDAPGroupByFilter looks up a single group by the supplied LDAP filter
   401  // returns the corresponding ldap.Entry
   402  func (i *Identity) GetLDAPGroupByFilter(log *zerolog.Logger, lc ldap.Client, filter string) (*ldap.Entry, error) {
   403  	searchRequest := ldap.NewSearchRequest(
   404  		i.Group.BaseDN, i.Group.scopeVal, ldap.NeverDerefAliases, 1, 0, false,
   405  		filter,
   406  		[]string{
   407  			i.Group.Schema.DisplayName,
   408  			i.Group.Schema.ID,
   409  			i.Group.Schema.Mail,
   410  			i.Group.Schema.Groupname,
   411  			i.Group.Schema.Member,
   412  			i.Group.Schema.GIDNumber,
   413  		},
   414  		nil,
   415  	)
   416  
   417  	log.Debug().Str("backend", "ldap").Str("basedn", i.Group.BaseDN).Str("filter", filter).Int("scope", i.Group.scopeVal).Msg("LDAP Search")
   418  	res, err := lc.Search(searchRequest)
   419  	if err != nil {
   420  		log.Debug().Str("backend", "ldap").Err(err).Str("filter", filter).Msg("Error looking up group by filter")
   421  		var errmsg string
   422  		if lerr, ok := err.(*ldap.Error); ok {
   423  			if lerr.ResultCode == ldap.LDAPResultSizeLimitExceeded {
   424  				errmsg = fmt.Sprintf("too many results searching for group '%s'", filter)
   425  			}
   426  		}
   427  		return nil, errtypes.NotFound(errmsg)
   428  	}
   429  	if len(res.Entries) == 0 {
   430  		return nil, errtypes.NotFound(filter)
   431  	}
   432  
   433  	return res.Entries[0], nil
   434  }
   435  
   436  // GetLDAPGroups searches for groups using a prefix-substring match on the group
   437  // attributes. Returns a slice of matching ldap.Entries
   438  func (i *Identity) GetLDAPGroups(log *zerolog.Logger, lc ldap.Client, query string) ([]*ldap.Entry, error) {
   439  	searchRequest := ldap.NewSearchRequest(
   440  		i.Group.BaseDN,
   441  		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
   442  		i.getGroupFindFilter(query),
   443  		[]string{
   444  			i.Group.Schema.DisplayName,
   445  			i.Group.Schema.ID,
   446  			i.Group.Schema.Mail,
   447  			i.Group.Schema.Groupname,
   448  			i.Group.Schema.GIDNumber,
   449  		},
   450  		nil,
   451  	)
   452  
   453  	sr, err := lc.Search(searchRequest)
   454  	if err != nil {
   455  		log.Debug().Str("backend", "ldap").Err(err).Str("query", query).Msg("Error search for groups")
   456  		return nil, errtypes.NotFound(query)
   457  	}
   458  	return sr.Entries, nil
   459  }
   460  
   461  // GetLDAPGroupMembers looks up all members of the supplied LDAP group entry and returns the
   462  // corresponding LDAP user entries
   463  func (i *Identity) GetLDAPGroupMembers(log *zerolog.Logger, lc ldap.Client, group *ldap.Entry) ([]*ldap.Entry, error) {
   464  	members := group.GetEqualFoldAttributeValues(i.Group.Schema.Member)
   465  	log.Debug().Str("dn", group.DN).Interface("member", members).Msg("Get Group members")
   466  	memberEntries := make([]*ldap.Entry, 0, len(members))
   467  	for _, member := range members {
   468  		var e *ldap.Entry
   469  		var err error
   470  		if strings.ToLower(i.Group.Objectclass) == "posixgroup" {
   471  			e, err = i.GetLDAPUserByAttribute(log, lc, "username", member)
   472  		} else {
   473  			e, err = i.GetLDAPUserByDN(log, lc, member)
   474  		}
   475  		if err != nil {
   476  			log.Warn().Err(err).Interface("member", member).Msg("Failed read user entry for member")
   477  			continue
   478  		}
   479  		memberEntries = append(memberEntries, e)
   480  	}
   481  
   482  	return memberEntries, nil
   483  }
   484  
   485  func filterEscapeBinaryUUID(value uuid.UUID) string {
   486  	filtered := ""
   487  	for _, b := range value {
   488  		filtered = fmt.Sprintf("%s\\%02x", filtered, b)
   489  	}
   490  	return filtered
   491  }
   492  
   493  func (i *Identity) getUserFilter(uid string) (string, error) {
   494  	var escapedUUID string
   495  	if i.User.Schema.IDIsOctetString {
   496  		id, err := uuid.Parse(uid)
   497  		if err != nil {
   498  			err := errors.Wrap(err, fmt.Sprintf("error parsing OpaqueID '%s' as UUID", uid))
   499  			return "", err
   500  		}
   501  		escapedUUID = filterEscapeBinaryUUID(id)
   502  	} else {
   503  		escapedUUID = ldap.EscapeFilter(uid)
   504  	}
   505  
   506  	return fmt.Sprintf("(&%s(objectclass=%s)(%s=%s))",
   507  		i.User.Filter,
   508  		i.User.Objectclass,
   509  		i.User.Schema.ID,
   510  		escapedUUID,
   511  	), nil
   512  }
   513  
   514  func (i *Identity) getUserAttributeFilter(attribute, value string) (string, error) {
   515  	switch attribute {
   516  	case "mail":
   517  		attribute = i.User.Schema.Mail
   518  	case "uid":
   519  		attribute = i.User.Schema.UIDNumber
   520  	case "gid":
   521  		attribute = i.User.Schema.GIDNumber
   522  	case "username":
   523  		attribute = i.User.Schema.Username
   524  	case "userid":
   525  		attribute = i.User.Schema.ID
   526  	default:
   527  		return "", errors.New("ldap: invalid field " + attribute)
   528  	}
   529  	if attribute == i.User.Schema.ID && i.User.Schema.IDIsOctetString {
   530  		id, err := uuid.Parse(value)
   531  		if err != nil {
   532  			err := errors.Wrap(err, fmt.Sprintf("error parsing OpaqueID '%s' as UUID", value))
   533  			return "", err
   534  		}
   535  		value = filterEscapeBinaryUUID(id)
   536  	} else {
   537  		value = ldap.EscapeFilter(value)
   538  	}
   539  	return fmt.Sprintf("(&%s(objectclass=%s)(%s=%s)%s)",
   540  		i.User.Filter,
   541  		i.User.Objectclass,
   542  		attribute,
   543  		value,
   544  		i.disabledFilter(),
   545  	), nil
   546  }
   547  
   548  func (i *Identity) disabledFilter() string {
   549  	if i.User.DisableMechanism == "attribute" {
   550  		return fmt.Sprintf("(!(%s=FALSE))", i.User.EnabledProperty)
   551  	}
   552  	return ""
   553  }
   554  
   555  // getUserFindFilter construct a LDAP filter to perform a prefix-substring
   556  // search for users.
   557  func (i *Identity) getUserFindFilter(query string) string {
   558  	searchAttrs := []string{
   559  		i.User.Schema.Mail,
   560  		i.User.Schema.DisplayName,
   561  		i.User.Schema.Username,
   562  	}
   563  	var filter, squery string
   564  	switch i.User.substringFilterVal {
   565  	case ldap.FilterSubstringsInitial:
   566  		squery = fmt.Sprintf("%s*", ldap.EscapeFilter(query))
   567  	case ldap.FilterSubstringsAny:
   568  		squery = fmt.Sprintf("*%s*", ldap.EscapeFilter(query))
   569  	case ldap.FilterSubstringsFinal:
   570  		squery = fmt.Sprintf("*%s", ldap.EscapeFilter(query))
   571  	}
   572  	for _, attr := range searchAttrs {
   573  		filter = fmt.Sprintf("%s(%s=%s)", filter, attr, squery)
   574  	}
   575  	// substring search for UUID is not possible
   576  	filter = fmt.Sprintf("%s(%s=%s)", filter, i.User.Schema.ID, ldap.EscapeFilter(query))
   577  
   578  	return fmt.Sprintf("(&%s(objectclass=%s)(|%s))",
   579  		i.User.Filter,
   580  		i.User.Objectclass,
   581  		filter,
   582  	)
   583  }
   584  
   585  // getGroupFindFilter construct a LDAP filter to perform a prefix-substring
   586  // search for groups.
   587  func (i *Identity) getGroupFindFilter(query string) string {
   588  	searchAttrs := []string{
   589  		i.Group.Schema.Mail,
   590  		i.Group.Schema.DisplayName,
   591  		i.Group.Schema.Groupname,
   592  	}
   593  	var filter, squery string
   594  	switch i.Group.substringFilterVal {
   595  	case ldap.FilterSubstringsInitial:
   596  		squery = fmt.Sprintf("%s*", ldap.EscapeFilter(query))
   597  	case ldap.FilterSubstringsAny:
   598  		squery = fmt.Sprintf("*%s*", ldap.EscapeFilter(query))
   599  	case ldap.FilterSubstringsFinal:
   600  		squery = fmt.Sprintf("*%s", ldap.EscapeFilter(query))
   601  	}
   602  	for _, attr := range searchAttrs {
   603  		filter = fmt.Sprintf("%s(%s=%s)", filter, attr, squery)
   604  	}
   605  	// substring search for UUID is not possible
   606  	filter = fmt.Sprintf("%s(%s=%s)", filter, i.Group.Schema.ID, ldap.EscapeFilter(query))
   607  
   608  	return fmt.Sprintf("(&%s(objectclass=%s)(|%s))",
   609  		i.Group.Filter,
   610  		i.Group.Objectclass,
   611  		filter,
   612  	)
   613  }
   614  
   615  func stringToScope(scope string) (int, error) {
   616  	var s int
   617  	switch scope {
   618  	case "sub":
   619  		s = ldap.ScopeWholeSubtree
   620  	case "one":
   621  		s = ldap.ScopeSingleLevel
   622  	case "base":
   623  		s = ldap.ScopeBaseObject
   624  	default:
   625  		return 0, fmt.Errorf("invalid Scope '%s'", scope)
   626  	}
   627  	return s, nil
   628  }
   629  
   630  func stringToFilterType(t string) (int, error) {
   631  	var s int
   632  	switch t {
   633  	case "initial":
   634  		s = ldap.FilterSubstringsInitial
   635  	case "any":
   636  		s = ldap.FilterSubstringsAny
   637  	case "final":
   638  		s = ldap.FilterSubstringsFinal
   639  	default:
   640  		return 0, fmt.Errorf("invalid filter type '%s'", t)
   641  	}
   642  	return s, nil
   643  }
   644  
   645  func (i *Identity) getGroupMemberFilter(memberName string) string {
   646  	return fmt.Sprintf("(&%s(objectclass=%s)(%s=%s))",
   647  		i.Group.Filter,
   648  		i.Group.Objectclass,
   649  		i.Group.Schema.Member,
   650  		ldap.EscapeFilter(memberName),
   651  	)
   652  }
   653  
   654  func (i *Identity) getGroupFilter(id string) (string, error) {
   655  	var escapedUUID string
   656  	if i.Group.Schema.IDIsOctetString {
   657  		id, err := uuid.Parse(id)
   658  		if err != nil {
   659  			err := errors.Wrap(err, fmt.Sprintf("error parsing OpaqueID '%s' as UUID", id))
   660  			return "", err
   661  		}
   662  		escapedUUID = filterEscapeBinaryUUID(id)
   663  	} else {
   664  		escapedUUID = ldap.EscapeFilter(id)
   665  	}
   666  
   667  	return fmt.Sprintf("(&%s(objectclass=%s)(%s=%s))",
   668  		i.Group.Filter,
   669  		i.Group.Objectclass,
   670  		i.Group.Schema.ID,
   671  		escapedUUID,
   672  	), nil
   673  }
   674  
   675  func (i *Identity) getGroupAttributeFilter(attribute, value string) (string, error) {
   676  	switch attribute {
   677  	case "mail":
   678  		attribute = i.Group.Schema.Mail
   679  	case "gid_number":
   680  		attribute = i.Group.Schema.GIDNumber
   681  	case "display_name":
   682  		attribute = i.Group.Schema.DisplayName
   683  	case "group_name":
   684  		attribute = i.Group.Schema.Groupname
   685  	case "group_id":
   686  		attribute = i.Group.Schema.ID
   687  	default:
   688  		return "", errors.New("ldap: invalid field " + attribute)
   689  	}
   690  	if attribute == i.Group.Schema.ID && i.Group.Schema.IDIsOctetString {
   691  		id, err := uuid.Parse(value)
   692  		if err != nil {
   693  			err := errors.Wrap(err, fmt.Sprintf("error parsing OpaqueID '%s' as UUID", value))
   694  			return "", err
   695  		}
   696  		value = filterEscapeBinaryUUID(id)
   697  	} else {
   698  		value = ldap.EscapeFilter(value)
   699  	}
   700  	return fmt.Sprintf("(&%s(objectclass=%s)(%s=%s))",
   701  		i.Group.Filter,
   702  		i.Group.Objectclass,
   703  		attribute,
   704  		value,
   705  	), nil
   706  }
   707  
   708  // GetUserType is used to get the proper UserType from ldap entry string
   709  func (i *Identity) GetUserType(userEntry *ldap.Entry) identityUser.UserType {
   710  	userTypeString := userEntry.GetEqualFoldAttributeValue(i.User.UserTypeProperty)
   711  	switch strings.ToLower(userTypeString) {
   712  	case "member":
   713  		return identityUser.UserType_USER_TYPE_PRIMARY
   714  	case "guest":
   715  		return identityUser.UserType_USER_TYPE_GUEST
   716  	default:
   717  		return identityUser.UserType_USER_TYPE_PRIMARY
   718  	}
   719  }