Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using AD UUID as userId values #2525

Merged
merged 1 commit into from
Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog/unreleased/ldap-guid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Allow using AD UUID as userId values

Active Directory UUID attributes (like e.g. objectGUID) use the LDAP octectString
Syntax. In order to be able to use them as userids in reva, they need to be converted
to their string representation.

https://github.com/cs3org/reva/pull/2525
31 changes: 22 additions & 9 deletions pkg/auth/manager/ldap/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/cs3org/reva/pkg/sharedconf"
"github.com/cs3org/reva/pkg/utils"
"github.com/go-ldap/ldap/v3"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -63,7 +64,8 @@ type attributes struct {
// DN is the distinguished name in ldap, e.g. `cn=einstein,ou=users,dc=example,dc=org`
DN string `mapstructure:"dn"`
// UID is an immutable user id, see https://docs.microsoft.com/en-us/azure/active-directory/hybrid/plan-connect-design-concepts
UID string `mapstructure:"uid"`
UID string `mapstructure:"uid"`
UIDIsOctetString bool `mapstructure:"uidIsOctetString"`
// CN is the username, typically `cn`, `uid` or `samaccountname`
CN string `mapstructure:"cn"`
// Mail is the email address of a user
Expand All @@ -78,13 +80,14 @@ type attributes struct {

// Default attributes (Active Directory)
var ldapDefaults = attributes{
DN: "dn",
UID: "ms-DS-ConsistencyGuid", // you can fall back to objectguid or even samaccountname but you will run into trouble when user names change. You have been warned.
CN: "cn",
Mail: "mail",
DisplayName: "displayName",
UIDNumber: "uidNumber",
GIDNumber: "gidNumber",
DN: "dn",
UID: "ms-DS-ConsistencyGuid", // you can fall back to objectguid or even samaccountname but you will run into trouble when user names change. You have been warned.
UIDIsOctetString: false,
CN: "cn",
Mail: "mail",
DisplayName: "displayName",
UIDNumber: "uidNumber",
GIDNumber: "gidNumber",
}

func parseConfig(m map[string]interface{}) (*config, error) {
Expand Down Expand Up @@ -166,9 +169,19 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string)
return nil, nil, err
}

var uid string
if am.c.Schema.UIDIsOctetString {
rawValue := sr.Entries[0].GetEqualFoldRawAttributeValue(am.c.Schema.UID)
if value, err := uuid.FromBytes(rawValue); err == nil {
uid = value.String()
}
} else {
uid = sr.Entries[0].GetEqualFoldAttributeValue(am.c.Schema.UID)
}

userID := &user.UserId{
Idp: am.c.Idp,
OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(am.c.Schema.UID),
OpaqueId: uid,
Type: user.UserType_USER_TYPE_PRIMARY, // TODO: assign the appropriate user type
}
gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc)
Expand Down
84 changes: 63 additions & 21 deletions pkg/user/manager/ldap/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/cs3org/reva/pkg/user/manager/registry"
"github.com/cs3org/reva/pkg/utils"
"github.com/go-ldap/ldap/v3"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -64,6 +65,9 @@ type attributes struct {
DN string `mapstructure:"dn"`
// UID is an immutable user id, see https://docs.microsoft.com/en-us/azure/active-directory/hybrid/plan-connect-design-concepts
UID string `mapstructure:"uid"`
// UIDIsOctetString set this to true if the values of the UID attribute are returned as OCTET STRING values (binary byte sequences)
// by the Directory Service. This is e.g. the case for the 'objectGUID' and 'ms-DS-ConsistencyGuid' Attributes in AD
UIDIsOctetString bool `mapstructure:"uidIsOctetString"`
// CN is the username, typically `cn`, `uid` or `samaccountname`
CN string `mapstructure:"cn"`
// Mail is the email address of a user
Expand All @@ -80,14 +84,15 @@ type attributes struct {

// Default attributes (Active Directory)
var ldapDefaults = attributes{
DN: "dn",
UID: "ms-DS-ConsistencyGuid", // you can fall back to objectguid or even samaccountname but you will run into trouble when user names change. You have been warned.
CN: "cn",
Mail: "mail",
DisplayName: "displayName",
UIDNumber: "uidNumber",
GIDNumber: "gidNumber",
GID: "cn",
DN: "dn",
UID: "ms-DS-ConsistencyGuid", // you can fall back to objectguid or even samaccountname but you will run into trouble when user names change. You have been warned.
UIDIsOctetString: false,
CN: "cn",
Mail: "mail",
DisplayName: "displayName",
UIDNumber: "uidNumber",
GIDNumber: "gidNumber",
GID: "cn",
}

func parseConfig(m map[string]interface{}) (*config, error) {
Expand Down Expand Up @@ -157,10 +162,9 @@ func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User

log.Debug().Interface("entry", userEntry).Msg("entries")

id := &userpb.UserId{
Idp: m.c.Idp,
OpaqueId: userEntry.GetEqualFoldAttributeValue(m.c.Schema.UID),
Type: userpb.UserType_USER_TYPE_PRIMARY,
id, err := m.ldapEntryToUserID(userEntry)
if err != nil {
return nil, err
}

groups, err := m.getLDAPUserGroups(ctx, l, userEntry)
Expand Down Expand Up @@ -241,10 +245,9 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use

log.Debug().Interface("entries", sr.Entries).Msg("entries")

id := &userpb.UserId{
Idp: m.c.Idp,
OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.UID),
Type: userpb.UserType_USER_TYPE_PRIMARY,
id, err := m.ldapEntryToUserID(sr.Entries[0])
if err != nil {
return nil, err
}
groups, err := m.getLDAPUserGroups(ctx, l, sr.Entries[0])
if err != nil {
Expand Down Expand Up @@ -304,10 +307,9 @@ func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User,
users := []*userpb.User{}

for _, entry := range sr.Entries {
id := &userpb.UserId{
Idp: m.c.Idp,
OpaqueId: entry.GetEqualFoldAttributeValue(m.c.Schema.UID),
Type: userpb.UserType_USER_TYPE_PRIMARY,
id, err := m.ldapEntryToUserID(entry)
if err != nil {
return nil, err
}
groups, err := m.getLDAPUserGroups(ctx, l, entry)
if err != nil {
Expand Down Expand Up @@ -358,6 +360,26 @@ func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]stri
return m.getLDAPUserGroups(ctx, l, userEntry)
}

func (m *manager) ldapEntryToUserID(entry *ldap.Entry) (*userpb.UserId, error) {
var uid string
if m.c.Schema.UIDIsOctetString {
rawValue := entry.GetEqualFoldRawAttributeValue(m.c.Schema.UID)
if value, err := uuid.FromBytes(rawValue); err == nil {
uid = value.String()
} else {
return nil, err
}
} else {
uid = entry.GetEqualFoldAttributeValue(m.c.Schema.UID)
}

return &userpb.UserId{
Idp: m.c.Idp,
OpaqueId: uid,
Type: userpb.UserType_USER_TYPE_PRIMARY,
}, nil
}

func (m *manager) getLDAPUserByID(ctx context.Context, conn *ldap.Conn, uid *userpb.UserId) (*ldap.Entry, error) {
log := appctx.GetLogger(ctx)
// Search for the given clientID, use a sizeLimit of 1 to be able
Expand Down Expand Up @@ -414,14 +436,34 @@ func (m *manager) getLDAPUserGroups(ctx context.Context, conn *ldap.Conn, userEn
}

func (m *manager) getUserFilter(uid *userpb.UserId) string {
uidTmp := uid
if m.c.Schema.UIDIsOctetString {
uuid, err := uuid.Parse(uid.OpaqueId)
if err != nil {
err := errors.Wrap(err, fmt.Sprintf("error parsing OpaqueID '%s' as UUID", uid.OpaqueId))
panic(err)
}
escapedUID := *uid
escapedUID.OpaqueId = filterEscapeBinaryUUID(uuid)
uidTmp = &escapedUID
}

b := bytes.Buffer{}
if err := m.userfilter.Execute(&b, uid); err != nil {
if err := m.userfilter.Execute(&b, uidTmp); err != nil {
err := errors.Wrap(err, fmt.Sprintf("error executing user template: userid:%+v", uid))
panic(err)
}
return b.String()
}

func filterEscapeBinaryUUID(value uuid.UUID) string {
filtered := ""
for _, b := range value {
filtered = fmt.Sprintf("%s\\%02x", filtered, b)
}
return filtered
}

func (m *manager) getAttributeFilter(attribute, value string) string {
attr := strings.ReplaceAll(m.c.AttributeFilter, "{{attr}}", ldap.EscapeFilter(attribute))
return strings.ReplaceAll(attr, "{{value}}", ldap.EscapeFilter(value))
Expand Down