mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-14 22:15:13 +00:00
feat: disable/enable users (#437)
Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
@@ -93,6 +93,7 @@ func (s *AppConfigService) getDefaultDbConfig() *model.AppConfig {
|
||||
LdapAttributeGroupUniqueIdentifier: model.AppConfigVariable{},
|
||||
LdapAttributeGroupName: model.AppConfigVariable{},
|
||||
LdapAttributeAdminGroup: model.AppConfigVariable{},
|
||||
LdapSoftDeleteUsers: model.AppConfigVariable{Value: "true"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -279,6 +279,22 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
|
||||
Where("ldap_id = ?", ldapId).
|
||||
First(&databaseUser).
|
||||
Error
|
||||
|
||||
// If a user is found (even if disabled), enable them since they're now back in LDAP
|
||||
if databaseUser.ID != "" && databaseUser.Disabled {
|
||||
// Use the transaction instead of the direct context
|
||||
err = tx.
|
||||
WithContext(ctx).
|
||||
Model(&model.User{}).
|
||||
Where("id = ?", databaseUser.ID).
|
||||
Update("disabled", false).
|
||||
Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to enable user %s: %v", databaseUser.Username, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// This could error with ErrRecordNotFound and we want to ignore that here
|
||||
return fmt.Errorf("failed to query for LDAP user ID '%s': %w", ldapId, err)
|
||||
@@ -336,24 +352,32 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
|
||||
err = tx.
|
||||
WithContext(ctx).
|
||||
Find(&ldapUsersInDb, "ldap_id IS NOT NULL").
|
||||
Select("ldap_id").
|
||||
Select("id, username, ldap_id, disabled").
|
||||
Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch users from database: %w", err)
|
||||
}
|
||||
|
||||
// Delete users that no longer exist in LDAP
|
||||
// Mark users as disabled or delete users that no longer exist in LDAP
|
||||
for _, user := range ldapUsersInDb {
|
||||
// Skip if the user ID exists in the fetched LDAP results
|
||||
if _, exists := ldapUserIDs[*user.LdapID]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.userService.deleteUserInternal(ctx, user.ID, true, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete user '%s': %w", user.Username, err)
|
||||
if dbConfig.LdapSoftDeleteUsers.IsTrue() {
|
||||
err = s.userService.DisableUser(ctx, user.ID, tx)
|
||||
if err != nil {
|
||||
log.Printf("Failed to disable user %s: %v", user.Username, err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
err = s.userService.DeleteUser(ctx, user.ID, true)
|
||||
if err != nil {
|
||||
log.Printf("Failed to delete user %s: %v", user.Username, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Deleted user '%s'", user.Username)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -38,14 +38,19 @@ func NewUserService(db *gorm.DB, jwtService *JwtService, auditLogService *AuditL
|
||||
|
||||
func (s *UserService) ListUsers(ctx context.Context, searchTerm string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.User, utils.PaginationResponse, error) {
|
||||
var users []model.User
|
||||
query := s.db.WithContext(ctx).Model(&model.User{})
|
||||
query := s.db.WithContext(ctx).
|
||||
Model(&model.User{}).
|
||||
Preload("UserGroups").
|
||||
Preload("CustomClaims")
|
||||
|
||||
if searchTerm != "" {
|
||||
searchPattern := "%" + searchTerm + "%"
|
||||
query = query.Where("email LIKE ? OR first_name LIKE ? OR username LIKE ?", searchPattern, searchPattern, searchPattern)
|
||||
query = query.Where("email LIKE ? OR first_name LIKE ? OR last_name LIKE ? OR username LIKE ?",
|
||||
searchPattern, searchPattern, searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &users)
|
||||
|
||||
return users, pagination, err
|
||||
}
|
||||
|
||||
@@ -170,9 +175,28 @@ func (s *UserService) UpdateProfilePicture(userID string, file io.Reader) error
|
||||
}
|
||||
|
||||
func (s *UserService) DeleteUser(ctx context.Context, userID string, allowLdapDelete bool) error {
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
return s.deleteUserInternal(ctx, userID, allowLdapDelete, tx)
|
||||
})
|
||||
tx := s.db.Begin()
|
||||
|
||||
var user model.User
|
||||
if err := tx.WithContext(ctx).First(&user, "id = ?", userID).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Only soft-delete if user is LDAP and soft-delete is enabled and not allowing hard delete
|
||||
if user.LdapID != nil && s.appConfigService.GetDbConfig().LdapSoftDeleteUsers.IsTrue() && !allowLdapDelete {
|
||||
if !user.Disabled {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("LDAP user must be disabled before deletion")
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, hard delete (local users or LDAP users when allowed)
|
||||
if err := s.deleteUserInternal(ctx, userID, allowLdapDelete, tx); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
func (s *UserService) deleteUserInternal(ctx context.Context, userID string, allowLdapDelete bool, tx *gorm.DB) error {
|
||||
@@ -187,8 +211,8 @@ func (s *UserService) deleteUserInternal(ctx context.Context, userID string, all
|
||||
return fmt.Errorf("failed to load user to delete: %w", err)
|
||||
}
|
||||
|
||||
// Disallow deleting the user if it is an LDAP user and LDAP is enabled
|
||||
if !allowLdapDelete && user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
||||
// Disallow deleting the user if it is an LDAP user, LDAP is enabled, and the user is not disabled
|
||||
if !allowLdapDelete && !user.Disabled && user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
||||
return &common.LdapUserUpdateError{}
|
||||
}
|
||||
|
||||
@@ -299,6 +323,7 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
||||
user.Locale = updatedUser.Locale
|
||||
if !updateOwnUser {
|
||||
user.IsAdmin = updatedUser.IsAdmin
|
||||
user.Disabled = updatedUser.Disabled
|
||||
}
|
||||
|
||||
err = tx.
|
||||
@@ -606,3 +631,11 @@ func (s *UserService) ResetProfilePicture(userID string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UserService) DisableUser(ctx context.Context, userID string, tx *gorm.DB) error {
|
||||
return tx.WithContext(ctx).
|
||||
Model(&model.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("disabled", true).
|
||||
Error
|
||||
}
|
||||
|
||||
@@ -244,6 +244,10 @@ func (s *WebAuthnService) VerifyLogin(ctx context.Context, sessionID string, cre
|
||||
return model.User{}, "", err
|
||||
}
|
||||
|
||||
if user.Disabled {
|
||||
return model.User{}, "", &common.UserDisabledError{}
|
||||
}
|
||||
|
||||
token, err := s.jwtService.GenerateAccessToken(*user)
|
||||
if err != nil {
|
||||
return model.User{}, "", err
|
||||
|
||||
Reference in New Issue
Block a user