Repository: owncloud/ocis (go)

Issue:
graph: Add accountEnabled property to /users
- Update the libregraph API spec
- implement support of the account Enabled flag in the LDAP identity backend
- allow to configure a single boolean LDAP attribute, reflecting the enabled/disable state of the user


Pull request:
graph: Add accountEnabled flag to ldap backend.
## Description
Add boolean accountEnabled property to users in the ldap backend.

## Related Issue
- Fixes #5553 

## How Has This Been Tested?
Tested manually with curl and added unit tests.

## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
- [x] Code changes
- [x] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c7a07daf42..9664c58efdc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ The following sections list the changes for unreleased.
 * Enhancement - Extended search: [#5221](https://github.com/owncloud/ocis/pull/5221)
 * Enhancement - Resource tags: [#5227](https://github.com/owncloud/ocis/pull/5227)
 * Enhancement - Expiration Notifications: [#5330](https://github.com/owncloud/ocis/pull/5330)
+* Enhancement - Add endpoint to list permissions: [#5594](https://github.com/owncloud/ocis/pull/5594)
 * Enhancement - Better config for postprocessing service: [#5457](https://github.com/owncloud/ocis/pull/5457)
 * Enhancement - Add expiration to user and group shares: [#5389](https://github.com/owncloud/ocis/pull/5389)
 * Enhancement - Space Management permissions: [#5441](https://github.com/owncloud/ocis/pull/5441)
diff --git a/changelog/unreleased/list-permissions.md b/changelog/unreleased/list-permissions.md
new file mode 100644
index 00000000000..791aa3e6914
--- /dev/null
+++ b/changelog/unreleased/list-permissions.md
@@ -0,0 +1,6 @@
+Enhancement: Add endpoint to list permissions
+
+We added 'https://cloud.ocis.test/api/v0/settings/permissions-list' to retrieve all permissions of the logged in user.
+
+https://github.com/owncloud/ocis/pull/5594
+https://github.com/owncloud/ocis/pull/5571
diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go
index f140389ab25..78978a75401 100644
--- a/services/graph/pkg/config/config.go
+++ b/services/graph/pkg/config/config.go
@@ -60,6 +60,7 @@ type LDAP struct {
 	UserDisplayNameAttribute string `yaml:"user_displayname_attribute" env:"LDAP_USER_SCHEMA_DISPLAY_NAME;GRAPH_LDAP_USER_DISPLAYNAME_ATTRIBUTE" desc:"LDAP Attribute to use for the displayname of users."`
 	UserNameAttribute        string `yaml:"user_name_attribute" env:"LDAP_USER_SCHEMA_USERNAME;GRAPH_LDAP_USER_NAME_ATTRIBUTE" desc:"LDAP Attribute to use for username of users."`
 	UserIDAttribute          string `yaml:"user_id_attribute" env:"LDAP_USER_SCHEMA_ID;GRAPH_LDAP_USER_UID_ATTRIBUTE" desc:"LDAP Attribute to use as the unique ID for users. This should be a stable globally unique ID like a UUID."`
+	UserEnabledAttribute     string `yaml:"user_enabled_attribute" env:"LDAP_USER_ENABLED_ATTRIBUTE;GRAPH_USER_ENABLED_ATTRIBUTE" desc:"LDAP Attribute to use as a flag telling if the user is enabled or disabled."`
 
 	GroupBaseDN        string `yaml:"group_base_dn" env:"LDAP_GROUP_BASE_DN;GRAPH_LDAP_GROUP_BASE_DN" desc:"Search base DN for looking up LDAP groups."`
 	GroupSearchScope   string `yaml:"group_search_scope" env:"LDAP_GROUP_SCOPE;GRAPH_LDAP_GROUP_SEARCH_SCOPE" desc:"LDAP search scope to use when looking up groups. Supported scopes are 'base', 'one' and 'sub'."`
diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go
index 1c494566ef1..0819334ebaf 100644
--- a/services/graph/pkg/config/defaults/defaultconfig.go
+++ b/services/graph/pkg/config/defaults/defaultconfig.go
@@ -68,6 +68,7 @@ func DefaultConfig() *config.Config {
 				// FIXME: switch this to some more widely available attribute by default
 				//        ideally this needs to	be constant for the lifetime of a users
 				UserIDAttribute:           "owncloudUUID",
+				UserEnabledAttribute:      "ownCloudUserEnabled",
 				GroupBaseDN:               "ou=groups,o=libregraph-idm",
 				GroupSearchScope:          "sub",
 				GroupFilter:               "",
diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go
index e54a6034af4..4bd7488c6de 100644
--- a/services/graph/pkg/identity/ldap.go
+++ b/services/graph/pkg/identity/ldap.go
@@ -4,6 +4,8 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strconv"
+	"strings"
 
 	"github.com/CiscoM31/godata"
 	"github.com/go-ldap/ldap/v3"
@@ -46,12 +48,13 @@ type LDAP struct {
 }
 
 type userAttributeMap struct {
-	displayName string
-	id          string
-	mail        string
-	userName    string
-	givenName   string
-	surname     string
+	displayName    string
+	id             string
+	mail           string
+	userName       string
+	givenName      string
+	surname        string
+	accountEnabled string
 }
 
 type ldapAttributeValues map[string][]string
@@ -62,12 +65,13 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
 		return nil, errors.New("invalid user attribute mappings")
 	}
 	uam := userAttributeMap{
-		displayName: config.UserDisplayNameAttribute,
-		id:          config.UserIDAttribute,
-		mail:        config.UserEmailAttribute,
-		userName:    config.UserNameAttribute,
-		givenName:   givenNameAttribute,
-		surname:     surNameAttribute,
+		displayName:    config.UserDisplayNameAttribute,
+		id:             config.UserIDAttribute,
+		mail:           config.UserEmailAttribute,
+		userName:       config.UserNameAttribute,
+		accountEnabled: config.UserEnabledAttribute,
+		givenName:      givenNameAttribute,
+		surname:        surNameAttribute,
 	}
 
 	if config.GroupNameAttribute == "" || config.GroupIDAttribute == "" {
@@ -245,7 +249,17 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph.
 			updateNeeded = true
 		}
 	}
-	// TODO implement account disabled/enabled
+
+	if user.AccountEnabled != nil {
+		boolString := strings.ToUpper(strconv.FormatBool(*user.AccountEnabled))
+		ldapValue := e.GetEqualFoldAttributeValue(i.userAttributeMap.accountEnabled)
+		updateNeeded = true
+		if ldapValue != "" {
+			mr.Replace(i.userAttributeMap.accountEnabled, []string{boolString})
+		} else {
+			mr.Add(i.userAttributeMap.accountEnabled, []string{boolString})
+		}
+	}
 
 	if updateNeeded {
 		if err := i.conn.Modify(&mr); err != nil {
@@ -269,6 +283,7 @@ func (i *LDAP) getUserByDN(dn string) (*ldap.Entry, error) {
 		i.userAttributeMap.userName,
 		i.userAttributeMap.surname,
 		i.userAttributeMap.givenName,
+		i.userAttributeMap.accountEnabled,
 	}
 
 	filter := fmt.Sprintf("(objectClass=%s)", i.userObjectClass)
@@ -365,6 +380,7 @@ func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) {
 		i.userAttributeMap.userName,
 		i.userAttributeMap.surname,
 		i.userAttributeMap.givenName,
+		i.userAttributeMap.accountEnabled,
 	}
 	return i.searchLDAPEntryByFilter(i.userBaseDN, attrs, filter)
 }
@@ -435,6 +451,7 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
 			i.userAttributeMap.userName,
 			i.userAttributeMap.surname,
 			i.userAttributeMap.givenName,
+			i.userAttributeMap.accountEnabled,
 		},
 		nil,
 	)
@@ -581,6 +598,7 @@ func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *libregraph.User {
 			Id:                       &id,
 			GivenName:                &givenName,
 			Surname:                  &surname,
+			AccountEnabled:           booleanOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.accountEnabled)),
 		}
 	}
 	i.logger.Warn().Str("dn", e.DN).Msg("Invalid User. Missing username or id attribute")
@@ -592,13 +610,18 @@ func (i *LDAP) userToLDAPAttrValues(user libregraph.User) (map[string][]string,
 		i.userAttributeMap.displayName: {user.GetDisplayName()},
 		i.userAttributeMap.userName:    {user.GetOnPremisesSamAccountName()},
 		i.userAttributeMap.mail:        {user.GetMail()},
-		"objectClass":                  {"inetOrgPerson", "organizationalPerson", "person", "top"},
+		"objectClass":                  {"inetOrgPerson", "organizationalPerson", "person", "top", "ownCloudUser"},
 		"cn":                           {user.GetOnPremisesSamAccountName()},
 	}
 
 	if !i.useServerUUID {
 		attrs["owncloudUUID"] = []string{uuid.Must(uuid.NewV4()).String()}
-		attrs["objectClass"] = append(attrs["objectClass"], "owncloud")
+	}
+
+	if user.AccountEnabled != nil {
+		attrs[i.userAttributeMap.accountEnabled] = []string{
+			strings.ToUpper(strconv.FormatBool(*user.AccountEnabled)),
+		}
 	}
 
 	// inetOrgPerson requires "sn" to be set. Set it to the Username if
@@ -636,6 +659,7 @@ func (i *LDAP) getUserAttrTypes() []string {
 		"cn",
 		"owncloudUUID",
 		"userPassword",
+		i.userAttributeMap.accountEnabled,
 	}
 }
 
@@ -666,6 +690,16 @@ func pointerOrNil(val string) *string {
 	return &val
 }
 
+func booleanOrNil(val string) *bool {
+	boolValue, err := strconv.ParseBool(val)
+
+	if err != nil {
+		return nil
+	}
+
+	return &boolValue
+}
+
 func stringToScope(scope string) (int, error) {
 	var s int
 	switch scope {
diff --git a/services/settings/pkg/service/v0/service.go b/services/settings/pkg/service/v0/service.go
index ec2966cf2c4..40c6187dd3d 100644
--- a/services/settings/pkg/service/v0/service.go
+++ b/services/settings/pkg/service/v0/service.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strings"
 
 	cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
 	rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
@@ -499,7 +500,7 @@ func (g Service) ListPermissions(ctx context.Context, req *settingssvc.ListPermi
 
 		if bundle != nil {
 			for _, setting := range bundle.GetSettings() {
-				permissionNames[setting.Name] = struct{}{}
+				permissionNames[formatPermissionName(setting)] = struct{}{}
 			}
 		}
 	}
@@ -667,3 +668,8 @@ func (g Service) isCurrentUser(ctx context.Context, accountID string) bool {
 func (g Service) canManageRoles(ctx context.Context) bool {
 	return g.hasStaticPermission(ctx, RoleManagementPermissionID)
 }
+
+func formatPermissionName(setting *settingsmsg.Setting) string {
+	constraint := strings.TrimPrefix(setting.GetPermissionValue().GetConstraint().String(), "CONSTRAINT_")
+	return setting.Name + "." + strings.ToLower(constraint)
+}


Test patch:
diff --git a/services/graph/pkg/identity/ldap_education_class_test.go b/services/graph/pkg/identity/ldap_education_class_test.go
index df48c36e85d..682e4d907c9 100644
--- a/services/graph/pkg/identity/ldap_education_class_test.go
+++ b/services/graph/pkg/identity/ldap_education_class_test.go
@@ -275,7 +275,7 @@ func TestGetEducationClassMembers(t *testing.T) {
 			Scope:      0,
 			SizeLimit:  1,
 			Filter:     "(objectClass=inetOrgPerson)",
-			Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+			Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 			Controls:   []ldap.Control(nil),
 		}
 		lm.On("Search", user_sr).Return(&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}}, nil)
diff --git a/services/graph/pkg/identity/ldap_education_school_test.go b/services/graph/pkg/identity/ldap_education_school_test.go
index a26982e5185..a97d4804d83 100644
--- a/services/graph/pkg/identity/ldap_education_school_test.go
+++ b/services/graph/pkg/identity/ldap_education_school_test.go
@@ -21,6 +21,7 @@ var eduConfig = config.LDAP{
 	UserIDAttribute:          "entryUUID",
 	UserEmailAttribute:       "mail",
 	UserNameAttribute:        "uid",
+	UserEnabledAttribute:     "userEnabledAttribute",
 
 	GroupBaseDN:        "ou=groups,dc=test",
 	GroupObjectClass:   "groupOfNames",
diff --git a/services/graph/pkg/identity/ldap_group_test.go b/services/graph/pkg/identity/ldap_group_test.go
index da79a9772a0..511076b01c5 100644
--- a/services/graph/pkg/identity/ldap_group_test.go
+++ b/services/graph/pkg/identity/ldap_group_test.go
@@ -100,14 +100,14 @@ func TestGetGroup(t *testing.T) {
 		BaseDN:     "uid=user,ou=people,dc=test",
 		SizeLimit:  1,
 		Filter:     "(objectClass=inetOrgPerson)",
-		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 		Controls:   []ldap.Control(nil),
 	}
 	sr3 := &ldap.SearchRequest{
 		BaseDN:     "uid=invalid,ou=people,dc=test",
 		SizeLimit:  1,
 		Filter:     "(objectClass=inetOrgPerson)",
-		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 		Controls:   []ldap.Control(nil),
 	}
 
@@ -195,14 +195,14 @@ func TestGetGroups(t *testing.T) {
 		BaseDN:     "uid=user,ou=people,dc=test",
 		SizeLimit:  1,
 		Filter:     "(objectClass=inetOrgPerson)",
-		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 		Controls:   []ldap.Control(nil),
 	}
 	sr3 := &ldap.SearchRequest{
 		BaseDN:     "uid=invalid,ou=people,dc=test",
 		SizeLimit:  1,
 		Filter:     "(objectClass=inetOrgPerson)",
-		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+		Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 		Controls:   []ldap.Control(nil),
 	}
 
diff --git a/services/graph/pkg/identity/ldap_test.go b/services/graph/pkg/identity/ldap_test.go
index 93cfe52ad7d..04f5f479f23 100644
--- a/services/graph/pkg/identity/ldap_test.go
+++ b/services/graph/pkg/identity/ldap_test.go
@@ -30,6 +30,7 @@ var lconfig = config.LDAP{
 	UserIDAttribute:          "entryUUID",
 	UserEmailAttribute:       "mail",
 	UserNameAttribute:        "uid",
+	UserEnabledAttribute:     "userEnabledAttribute",
 
 	GroupBaseDN:        "ou=groups,dc=test",
 	GroupObjectClass:   "groupOfNames",
@@ -43,12 +44,13 @@ var lconfig = config.LDAP{
 
 var userEntry = ldap.NewEntry("uid=user",
 	map[string][]string{
-		"uid":         {"user"},
-		"displayname": {"DisplayName"},
-		"mail":        {"user@example"},
-		"entryuuid":   {"abcd-defg"},
-		"sn":          {"surname"},
-		"givenname":   {"givenName"},
+		"uid":                  {"user"},
+		"displayname":          {"DisplayName"},
+		"mail":                 {"user@example"},
+		"entryuuid":            {"abcd-defg"},
+		"sn":                   {"surname"},
+		"givenname":            {"givenName"},
+		"userenabledattribute": {"TRUE"},
 	})
 
 var invalidUserEntry = ldap.NewEntry("uid=user",
@@ -105,8 +107,9 @@ func TestCreateUser(t *testing.T) {
 	ar.Attribute(lconfig.UserEmailAttribute, []string{mail})
 	ar.Attribute("sn", []string{surname})
 	ar.Attribute("givenname", []string{givenName})
-	ar.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"})
+	ar.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top", "ownCloudUser"})
 	ar.Attribute("cn", []string{userName})
+	ar.Attribute(lconfig.UserEnabledAttribute, []string{"TRUE"})
 
 	l := &mocks.Client{}
 	l.On("Search", mock.Anything).
@@ -124,6 +127,7 @@ func TestCreateUser(t *testing.T) {
 	user.SetOnPremisesSamAccountName(userName)
 	user.SetSurname(surname)
 	user.SetGivenName(givenName)
+	user.SetAccountEnabled(true)
 
 	c := lconfig
 	c.UseServerUUID = true
@@ -136,6 +140,7 @@ func TestCreateUser(t *testing.T) {
 	assert.Equal(t, userName, newUser.GetOnPremisesSamAccountName())
 	assert.Equal(t, givenName, newUser.GetGivenName())
 	assert.Equal(t, surname, newUser.GetSurname())
+	assert.True(t, newUser.GetAccountEnabled())
 }
 
 func TestCreateUserModelFromLDAP(t *testing.T) {
@@ -285,6 +290,7 @@ func TestUpdateUser(t *testing.T) {
 		mail                     string
 		displayName              string
 		onPremisesSamAccountName string
+		accountEnabled           bool
 	}
 	type args struct {
 		nameOrID  string
@@ -323,7 +329,7 @@ func TestUpdateUser(t *testing.T) {
 							ldap.ScopeWholeSubtree,
 							ldap.NeverDerefAliases, 1, 0, false,
 							"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
-							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 							nil,
 						),
 					},
@@ -353,6 +359,7 @@ func TestUpdateUser(t *testing.T) {
 				mail:                     "testuser@example.org",
 				displayName:              "testUser",
 				onPremisesSamAccountName: "testUser",
+				accountEnabled:           true,
 			},
 			assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
 				return assert.Nil(t, err, args...)
@@ -366,7 +373,7 @@ func TestUpdateUser(t *testing.T) {
 							ldap.ScopeWholeSubtree,
 							ldap.NeverDerefAliases, 1, 0, false,
 							"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
-							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 							nil,
 						),
 					},
@@ -380,24 +387,18 @@ func TestUpdateUser(t *testing.T) {
 											Name:   "displayname",
 											Values: []string{"oldmail@example.org"},
 										},
-									},
-								},
-								{
-									DN: "uid=oldName",
-									Attributes: []*ldap.EntryAttribute{
 										{
 											Name:   "entryUUID",
 											Values: []string{"testUser"},
 										},
-									},
-								},
-								{
-									DN: "uid=oldName",
-									Attributes: []*ldap.EntryAttribute{
 										{
 											Name:   "mail",
 											Values: []string{"oldmail@example.org"},
 										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"TRUE"},
+										},
 									},
 								},
 							},
@@ -416,7 +417,7 @@ func TestUpdateUser(t *testing.T) {
 							TimeLimit:    0,
 							TypesOnly:    false,
 							Filter:       "(objectClass=inetOrgPerson)",
-							Attributes:   []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+							Attributes:   []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 							Controls:     []ldap.Control(nil),
 						},
 					},
@@ -442,6 +443,10 @@ func TestUpdateUser(t *testing.T) {
 											Name:   lconfig.UserNameAttribute,
 											Values: []string{"testUser"},
 										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"TRUE"},
+										},
 									},
 								},
 							},
@@ -483,6 +488,7 @@ func TestUpdateUser(t *testing.T) {
 				mail:                     "testuser@example.org",
 				displayName:              "newName",
 				onPremisesSamAccountName: "testUser",
+				accountEnabled:           true,
 			},
 			assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
 				return assert.Nil(t, err, args...)
@@ -496,7 +502,7 @@ func TestUpdateUser(t *testing.T) {
 							ldap.ScopeWholeSubtree,
 							ldap.NeverDerefAliases, 1, 0, false,
 							"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
-							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 							nil,
 						),
 					},
@@ -510,24 +516,18 @@ func TestUpdateUser(t *testing.T) {
 											Name:   "displayname",
 											Values: []string{"testUser"},
 										},
-									},
-								},
-								{
-									DN: "uid=oldName",
-									Attributes: []*ldap.EntryAttribute{
 										{
 											Name:   "entryUUID",
 											Values: []string{"testUser"},
 										},
-									},
-								},
-								{
-									DN: "uid=oldName",
-									Attributes: []*ldap.EntryAttribute{
 										{
 											Name:   "mail",
 											Values: []string{"testuser@example.org"},
 										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"TRUE"},
+										},
 									},
 								},
 							},
@@ -546,7 +546,7 @@ func TestUpdateUser(t *testing.T) {
 							TimeLimit:    0,
 							TypesOnly:    false,
 							Filter:       "(objectClass=inetOrgPerson)",
-							Attributes:   []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+							Attributes:   []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 							Controls:     []ldap.Control(nil),
 						},
 					},
@@ -572,6 +572,10 @@ func TestUpdateUser(t *testing.T) {
 											Name:   lconfig.UserNameAttribute,
 											Values: []string{"testUser"},
 										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"TRUE"},
+										},
 									},
 								},
 							},
@@ -613,6 +617,7 @@ func TestUpdateUser(t *testing.T) {
 				mail:                     "testuser@example.org",
 				displayName:              "newName",
 				onPremisesSamAccountName: "newName",
+				accountEnabled:           true,
 			},
 			assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
 				return assert.Nil(t, err, args...)
@@ -626,7 +631,7 @@ func TestUpdateUser(t *testing.T) {
 							ldap.ScopeWholeSubtree,
 							ldap.NeverDerefAliases, 1, 0, false,
 							"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
-							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 							nil,
 						),
 					},
@@ -652,6 +657,10 @@ func TestUpdateUser(t *testing.T) {
 											Name:   lconfig.UserNameAttribute,
 											Values: []string{"oldName"},
 										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"TRUE"},
+										},
 									},
 								},
 							},
@@ -718,7 +727,7 @@ func TestUpdateUser(t *testing.T) {
 							TimeLimit:    0,
 							TypesOnly:    false,
 							Filter:       "(objectClass=inetOrgPerson)",
-							Attributes:   []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
+							Attributes:   []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
 							Controls:     []ldap.Control(nil),
 						},
 					},
@@ -744,6 +753,10 @@ func TestUpdateUser(t *testing.T) {
 											Name:   lconfig.UserNameAttribute,
 											Values: []string{"newName"},
 										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"TRUE"},
+										},
 									},
 								},
 							},
@@ -779,6 +792,135 @@ func TestUpdateUser(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "Test changing accountEnabled",
+			args: args{
+				nameOrID: "testUser",
+				userProps: userProps{
+					accountEnabled: false,
+				},
+			},
+			want: &userProps{
+				id:                       "testUser",
+				mail:                     "testuser@example.org",
+				displayName:              "testUser",
+				onPremisesSamAccountName: "testUser",
+				accountEnabled:           false,
+			},
+			assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
+				return assert.Nil(t, err, args...)
+			},
+			ldapMocks: []mockInputs{
+				{
+					funcName: "Search",
+					args: []interface{}{
+						ldap.NewSearchRequest(
+							"ou=people,dc=test",
+							ldap.ScopeWholeSubtree,
+							ldap.NeverDerefAliases, 1, 0, false,
+							"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
+							[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
+							nil,
+						),
+					},
+					returns: []interface{}{
+						&ldap.SearchResult{
+							Entries: []*ldap.Entry{
+								{
+									DN: "uid=name",
+									Attributes: []*ldap.EntryAttribute{
+										{
+											Name:   "displayname",
+											Values: []string{"testuser@example.org"},
+										},
+										{
+											Name:   "entryUUID",
+											Values: []string{"testUser"},
+										},
+										{
+											Name:   "mail",
+											Values: []string{"testuser@example.org"},
+										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"TRUE"},
+										},
+									},
+								},
+							},
+						},
+						nil,
+					},
+				},
+				{
+					funcName: "Search",
+					args: []interface{}{
+						&ldap.SearchRequest{
+							BaseDN:       "uid=name",
+							Scope:        0,
+							DerefAliases: 0,
+							SizeLimit:    1,
+							TimeLimit:    0,
+							TypesOnly:    false,
+							Filter:       "(objectClass=inetOrgPerson)",
+							Attributes:   []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
+							Controls:     []ldap.Control(nil),
+						},
+					},
+					returns: []interface{}{
+						&ldap.SearchResult{
+							Entries: []*ldap.Entry{
+								{
+									DN: "uid=name",
+									Attributes: []*ldap.EntryAttribute{
+										{
+											Name:   lconfig.UserIDAttribute,
+											Values: []string{"testUser"},
+										},
+										{
+											Name:   lconfig.UserEmailAttribute,
+											Values: []string{"testuser@example.org"},
+										},
+										{
+											Name:   lconfig.UserDisplayNameAttribute,
+											Values: []string{"testUser"},
+										},
+										{
+											Name:   lconfig.UserNameAttribute,
+											Values: []string{"testUser"},
+										},
+										{
+											Name:   lconfig.UserEnabledAttribute,
+											Values: []string{"FALSE"},
+										},
+									},
+								},
+							},
+						},
+						nil,
+					},
+				},
+				{
+					funcName: "Modify",
+					args: []interface{}{
+						&ldap.ModifyRequest{
+							DN: "uid=name",
+							Changes: []ldap.Change{
+								{
+									Operation: 0x2,
+									Modification: ldap.PartialAttribute{
+										Type: lconfig.UserEnabledAttribute,
+										Vals: []string{"FALSE"},
+									},
+								},
+							},
+							Controls: []ldap.Control(nil),
+						},
+					},
+					returns: []interface{}{nil},
+				},
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -805,6 +947,7 @@ func TestUpdateUser(t *testing.T) {
 					OnPremisesSamAccountName: &tt.want.onPremisesSamAccountName,
 					Surname:                  &emptyString,
 					GivenName:                &emptyString,
+					AccountEnabled:           &tt.want.accountEnabled,
 				}
 			}