graph(oidc): Consume UserSignedIn events in graph service

Pass them to the identity backend to update the last sign-in date of the user.
This commit is contained in:
Ralf Haferkamp
2024-09-17 16:02:47 +02:00
committed by Ralf Haferkamp
parent cb8934081f
commit 8e158d52bb
10 changed files with 158 additions and 12 deletions
+2
View File
@@ -3,6 +3,7 @@ package identity
import (
"context"
"net/url"
"time"
"github.com/CiscoM31/godata"
cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
@@ -36,6 +37,7 @@ type Backend interface {
UpdateUser(ctx context.Context, nameOrID string, user libregraph.UserUpdate) (*libregraph.User, error)
GetUser(ctx context.Context, nameOrID string, oreq *godata.GoDataRequest) (*libregraph.User, error)
GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error)
UpdateLastSignInDate(ctx context.Context, userID string, timestamp time.Time) error
// CreateGroup creates the supplied group in the identity backend.
CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error)
+6
View File
@@ -3,6 +3,7 @@ package identity
import (
"context"
"net/url"
"time"
"github.com/CiscoM31/godata"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
@@ -111,6 +112,11 @@ func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libr
return users, nil
}
// UpdateLastSignInDate implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) UpdateLastSignInDate(ctx context.Context, userID string, timestamp time.Time) error {
return errNotImplemented
}
// GetGroups implements the Backend Interface.
func (i *CS3) GetGroups(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.Group, error) {
logger := i.Logger.SubloggerWithRequestID(ctx)
+34
View File
@@ -8,6 +8,7 @@ import (
"slices"
"strconv"
"strings"
"time"
"github.com/CiscoM31/godata"
"github.com/go-ldap/ldap/v3"
@@ -24,6 +25,7 @@ const (
_givenNameAttribute = "givenname"
_surNameAttribute = "sn"
_identitiesAttribute = "oCExternalIdentity"
ldapDateFormat = "20060102150405Z0700"
)
// DisableUserMechanismType is used instead of directly using the string values from the configuration.
@@ -670,6 +672,38 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
return users, nil
}
// UpdateLastSignInDate implements the Backend Interface.
func (i *LDAP) UpdateLastSignInDate(ctx context.Context, userID string, timestamp time.Time) error {
if !i.writeEnabled {
i.logger.Debug().Str("backend", "ldap").Msg("The LDAP Server is readonly. Skipping update of last sign in date")
return nil
}
e, err := i.getLDAPUserByID(userID)
switch {
case errors.Is(err, ErrNotFound):
i.logger.Warn().Err(err).Str("userID", userID).Msg("Failed to update last sign in date for user")
return nil
case err != nil:
return err
}
mr := ldap.ModifyRequest{DN: e.DN}
mr.Replace("oCLastSignInTimestamp", []string{timestamp.UTC().Format(ldapDateFormat)})
if err := i.conn.Modify(&mr); err != nil {
msg := "error updating last sign in date for user"
i.logger.Error().Err(err).Str("userid", userID).Msg(msg)
errMap := ldapResultToErrMap{
ldap.LDAPResultNoSuchObject: errorcode.New(errorcode.ItemNotFound, msg),
ldap.LDAPResultUnwillingToPerform: errorcode.New(errorcode.NotAllowed, msg),
ldap.LDAPResultInsufficientAccessRights: errorcode.New(errorcode.NotAllowed, msg),
ldapGenericErr: errorcode.New(errorcode.GeneralException, msg),
}
return i.mapLDAPError(err, errMap)
}
return nil
}
func (i *LDAP) changeUserName(ctx context.Context, dn, originalUserName, newUserName string) (*ldap.Entry, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
@@ -47,8 +47,6 @@ const (
schoolPropertiesUpdated
)
const ldapDateFormat = "20060102150405Z0700"
var (
errNotSet = errors.New("attribute not set")
errSchoolNameExists = errorcode.New(errorcode.NameAlreadyExists, "A school with that name is already present")
@@ -11,6 +11,8 @@ import (
mock "github.com/stretchr/testify/mock"
time "time"
url "net/url"
)
@@ -681,6 +683,54 @@ func (_c *Backend_UpdateGroupName_Call) RunAndReturn(run func(context.Context, s
return _c
}
// UpdateLastSignInDate provides a mock function with given fields: ctx, userID, timestamp
func (_m *Backend) UpdateLastSignInDate(ctx context.Context, userID string, timestamp time.Time) error {
ret := _m.Called(ctx, userID, timestamp)
if len(ret) == 0 {
panic("no return value specified for UpdateLastSignInDate")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, time.Time) error); ok {
r0 = rf(ctx, userID, timestamp)
} else {
r0 = ret.Error(0)
}
return r0
}
// Backend_UpdateLastSignInDate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateLastSignInDate'
type Backend_UpdateLastSignInDate_Call struct {
*mock.Call
}
// UpdateLastSignInDate is a helper method to define mock.On call
// - ctx context.Context
// - userID string
// - timestamp time.Time
func (_e *Backend_Expecter) UpdateLastSignInDate(ctx interface{}, userID interface{}, timestamp interface{}) *Backend_UpdateLastSignInDate_Call {
return &Backend_UpdateLastSignInDate_Call{Call: _e.mock.On("UpdateLastSignInDate", ctx, userID, timestamp)}
}
func (_c *Backend_UpdateLastSignInDate_Call) Run(run func(ctx context.Context, userID string, timestamp time.Time)) *Backend_UpdateLastSignInDate_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(time.Time))
})
return _c
}
func (_c *Backend_UpdateLastSignInDate_Call) Return(_a0 error) *Backend_UpdateLastSignInDate_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Backend_UpdateLastSignInDate_Call) RunAndReturn(run func(context.Context, string, time.Time) error) *Backend_UpdateLastSignInDate_Call {
_c.Call.Return(run)
return _c
}
// UpdateUser provides a mock function with given fields: ctx, nameOrID, user
func (_m *Backend) UpdateUser(ctx context.Context, nameOrID string, user libregraph.UserUpdate) (*libregraph.User, error) {
ret := _m.Called(ctx, nameOrID, user)