feat(graph): Add $filter support for lastSuccessfulSignInDateTime

It is now possible to filter users based on the lastSuccessfulSignInDateTime attribute
using query filter like:
 '$filter=signInActivity/lastSuccessfulSignInDateTime le 2021-09-01T00:00:00Z'

Note: This does only work with LDAP servers actually supporting '<=' filters.
The built-in LDAP server (idm) does not support this feature.
This commit is contained in:
Ralf Haferkamp
2024-09-23 10:49:49 +02:00
committed by Ralf Haferkamp
parent 231128950f
commit 34cc7b2e56
6 changed files with 157 additions and 10 deletions
+68 -6
View File
@@ -510,7 +510,7 @@ func (i *LDAP) getLDAPUserByNameOrID(nameOrID string) (*ldap.Entry, error) {
idString, err := filterEscapeUUID(i.userIDisOctetString, nameOrID)
// err != nil just means that this is not an uuid, so we can skip the uuid filter part
// and just filter by name
filter := ""
var filter string
if err == nil {
filter = fmt.Sprintf("(|(%s=%s)(%s=%s))", i.userAttributeMap.userName, ldap.EscapeFilter(nameOrID), i.userAttributeMap.id, idString)
} else {
@@ -564,9 +564,19 @@ func (i *LDAP) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoData
// GetUsers implements the Backend Interface.
func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error) {
return i.FilterUsers(ctx, oreq, nil)
}
// FilterUsers implements the Backend Interface.
func (i *LDAP) FilterUsers(ctx context.Context, oreq *godata.GoDataRequest, filter *godata.ParseNode) ([]*libregraph.User, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("GetUsers")
queryFilter, err := i.oDataFilterToLDAPFilter(filter)
if err != nil {
return nil, err
}
search, err := GetSearchValues(oreq.Query)
if err != nil {
return nil, err
@@ -587,7 +597,7 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
i.userAttributeMap.displayName, search,
)
}
userFilter = fmt.Sprintf("(&%s(objectClass=%s)%s)", i.userFilter, i.userObjectClass, userFilter)
userFilter = fmt.Sprintf("(&%s(objectClass=%s)%s%s)", i.userFilter, i.userObjectClass, queryFilter, userFilter)
searchRequest := ldap.NewSearchRequest(
i.userBaseDN, i.userScope, ldap.NeverDerefAliases, 0, 0, false,
userFilter,
@@ -612,13 +622,16 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
return nil, i.mapLDAPError(err, errMap)
}
users := make([]*libregraph.User, 0, len(res.Entries))
usersEnabledState, err := i.usersEnabledState(res.Entries)
return i.usersFromLDAPEntries(res.Entries, exp)
}
func (i *LDAP) usersFromLDAPEntries(entries []*ldap.Entry, exp []string) ([]*libregraph.User, error) {
usersEnabledState, err := i.usersEnabledState(entries)
if err != nil {
return nil, err
}
for _, e := range res.Entries {
users := make([]*libregraph.User, 0, len(entries))
for _, e := range entries {
u := i.createUserModelFromLDAP(e)
// Skip invalid LDAP users
if u == nil {
@@ -1286,6 +1299,55 @@ func (i *LDAP) getLastSignTime(e *ldap.Entry) (*time.Time, error) {
return &t, nil
}
func (i *LDAP) oDataFilterToLDAPFilter(filter *godata.ParseNode) (string, error) {
if filter == nil {
return "", nil
}
if filter.Token.Type != godata.ExpressionTokenLogical {
return "", ErrUnsupportedFilter
}
if filter.Token.Value != "le" {
return "", ErrUnsupportedFilter
}
if !isLastSuccessFullSignInDateTimeFilter(filter.Children[0]) {
return "", ErrUnsupportedFilter
}
if filter.Children[1].Token.Type != godata.ExpressionTokenDateTime {
return "", ErrUnsupportedFilter
}
parsed, err := time.Parse(time.RFC3339, filter.Children[1].Token.Value)
if err != nil {
return "", godata.BadRequestError("invalid date format")
}
ldapDateTime := parsed.UTC().Format(ldapDateFormat)
return fmt.Sprintf("(%s<=%s)", i.userAttributeMap.lastSignIn, ldap.EscapeFilter(ldapDateTime)), nil
}
func isLastSuccessFullSignInDateTimeFilter(node *godata.ParseNode) bool {
if node.Token.Type != godata.ExpressionTokenNav {
return false
}
if len(node.Children) != 2 {
return false
}
if node.Children[0].Token.Value != "signInActivity" {
return false
}
if node.Children[1].Token.Value != "lastSuccessfulSignInDateTime" {
return false
}
return true
}
func isUserEnabledUpdate(user libregraph.UserUpdate) bool {
switch {
case user.Id != nil, user.DisplayName != nil,