Directory Services

Modifying User Cannot Change Password (LDAP Provider)

The ability of a user to change their own password is a permission that can be grant or denied. To deny this permission, you must set two ACEs in the security descriptor discretionary access control list (DACL) of the user object with the ADS_ACETYPE_ACCESS_DENIED_OBJECT ace type. One ACE denies the permission to the user and another ACE denies the permission to the Everyone group. Both ACEs are object-specific deny ACEs that specify the GUID of the extended permission for changing passwords. To grant this permission, set the same ACEs with the ADS_ACETYPE_ACCESS_ALLOWED_OBJECT ace type.

To modify or add the ACEs for this permission

The following procedure describes how to modify or add ACEs for this permission.

  1. Bind to the user object.
  2. Obtain the IADsSecurityDescriptor object from the ntSecurityDescriptor property of the user object.
  3. Obtain an IADsAccessControlList interface for the security descriptor from the IADsSecurityDescriptor.DiscretionaryAcl property.
  4. Enumerate the ACEs for the object and search for the ACEs that have the change password GUID ({AB721A53-1E2F-11D0-9819-00AA0040529B}) for the IADsAccessControlEntry.ObjectType property and "Everyone" or "NT AUTHORITY\SELF" for the IADsAccessControlEntry.Trustee property.

    Note  The "Everyone" and "NT AUTHORITY\SELF" strings are localized based on the language of the first domain controller in the domain. Because of this, the strings should not be used directly. The account names should be obtained at run time by calling the LookupAccountSid function with the SID for "Everyone" ("S-1-1-0") and "NT AUTHORITY\SELF" ("S-1-5-10") well-known security principals. The GetSidAccountName, GetSidAccountName_Everyone, and GetSidAccountName_Self C++ example functions shown in Reading User Cannot Change Password (LDAP Provider) demonstrate how to do this.

  5. Modify the IADsAccessControlEntry.AceType property of the ACEs that were found to ADS_ACETYPE_ACCESS_DENIED_OBJECT if the user cannot change their password or ADS_ACETYPE_ACCESS_ALLOWED_OBJECT if the user can change their password.
  6. If the "Everyone" ACE is not found, create a new IADsAccessControlEntry object that contains the property values shown in the table below and add the new entry to the ACL with the IADsAccessControlList.AddAce method.
  7. If the "NT AUTHORITY\SELF" ACE is not found, create a new IADsAccessControlEntry object with the same property values shown in the table below except the Trustee property contains the account name for SID "S-1-5-10" ("NT AUTHORITY\SELF"). Add the entry to the ACL with the IADsAccessControlList.AddAce method.
  8. To update the ntSecurityDescriptor property of the object, call the IADs.Put method with the same IADsSecurityDescriptor obtained in Step 2.
  9. Commit the local changes to the server with the IADs.SetInfo method.
  10. If either of the ACEs were created, you must reorder the ACL so that the ACEs are in the correct order. To do this, call the GetNamedSecurityInfo function with the LDAP ADsPath of the object and then the SetNamedSecurityInfo function with the same DACL. On Windows XP and Windows Server 2003, this reodering will occur automatically when the ACEs are added, but on Windows 2000, the reordering must be performed manually.
The following table lists the IADsAccessControlEntry object property values.
IADsAccessControlEntry Property Value
AccessMask ADS_RIGHT_DS_CONTROL_ACCESS
AceType ADS_ACETYPE_ACCESS_DENIED_OBJECT if the user cannot change their password or ADS_ACETYPE_ACCESS_ALLOWED_OBJECT if the user can change their password.
AceFlags 0
Flags ADS_FLAG_OBJECT_TYPE_PRESENT
ObjectType "{AB721A53-1E2F-11D0-9819-00AA0040529B}" which is the change password GUID in string form.
InheritedObjectType Not used
Trustee Account name for SID "S-1-1-0" (Everyone).

Example Code

The following code example shows how to modify the User Cannot Change Password Permission using the LDAP provider. This code example uses the GetObjectACE utility function defined above.

This example uses the GetSidAccountName_Everyone and GetSidAccountName_Self C++ example functions shown in Reading User Cannot Change Password (LDAP Provider).

[C++]
#include <aclapi.h>

#define CHANGE_PASSWORD_GUID_W L"{AB721A53-1E2F-11D0-9819-00AA0040529B}"

/***************************************************************************

	CreateACE()

	Creates an ACE and returns the IDispatch pointer for the ACE. This 
	pointer can be passed directly to IADsAccessControlList::AddAce.

	bstrTrustee - Contains the Trustee for the ACE.

	bstrObjectType - Contains the ObjectType for the ACE.

	lAccessMask - Contains the AccessMask for the ACE.

	lACEType - Contains the ACEType for the ACE.

	lACEFlags - Contains the ACEFlags for the ACE.

	lFlags - Contains the Flags for the ACE.

***************************************************************************/

IDispatch* CreateACE(BSTR bstrTrustee, 
					 BSTR bstrObjectType, 
					 long lAccessMask, 
					 long lACEType, 
					 long lACEFlags, 
					 long lFlags)
{
	HRESULT hr;
	IDispatch *pDisp = NULL;
	IADsAccessControlEntry *pACE = NULL;

	hr = CoCreateInstance(CLSID_AccessControlEntry,
						NULL,
						CLSCTX_INPROC_SERVER,
						IID_IADsAccessControlEntry,
						(void**)&pACE);
	if(SUCCEEDED(hr)) 
	{
		hr = pACE->put_Trustee(bstrTrustee); 
		hr = pACE->put_ObjectType(bstrObjectType);
		hr = pACE->put_AccessMask(lAccessMask); 
		hr = pACE->put_AceType(lACEType);
		hr = pACE->put_AceFlags(lACEFlags);
		hr = pACE->put_Flags(lFlags);

		hr = pACE->QueryInterface(IID_IDispatch, (LPVOID*)&pDisp);

		pACE->Release();
}

	return pDisp;
}

/***************************************************************************

	ReorderACEs()

	Causes the ACEs of an object DACL to be reordered properly. On Windows 
	XP and later, the ACEs are automatically put in the proper order when 
	they are added to the DACL. On Windows 2000 however, this does not occur 
	automatically, so this function is necessary so the deny ACEs are 
	ordered in the list before the allow ACEs.

	pwszDN - A null-terminated Unicode string that contains the LDAP 
	ADsPath of the DS object to reorder the DACL for.

***************************************************************************/

HRESULT ReorderACEs(LPCWSTR pwszDN)
{
	HRESULT hr = E_FAIL;
	DWORD dwResult;
	ACL *pdacl;
	PSECURITY_DESCRIPTOR psd;

	dwResult = GetNamedSecurityInfoW(   (LPWSTR)pwszDN,
										SE_DS_OBJECT_ALL,
										DACL_SECURITY_INFORMATION,
										NULL,
										NULL,
										&pdacl,
										NULL,
										&psd);

	if(ERROR_SUCCESS == dwResult)
	{
		dwResult = SetNamedSecurityInfoW(   (LPWSTR)pwszDN,
											SE_DS_OBJECT_ALL,
											DACL_SECURITY_INFORMATION,
											NULL,
											NULL,
											pdacl,
											NULL);

		LocalFree(psd);
	
		if(ERROR_SUCCESS == dwResult)
		{
			hr = S_OK;
	}
}

	return hr;
}

/***************************************************************************

	SetUserCannotChangePassword()

	Sets the "User Cannot Change Password" permission using the LDAP provider 
	to the specified setting. To do this, find the existing 
	ACEs and modify the AceType. If the ACE is not found, a new one of the 
	proper type is created and added. The ACEs should always be present, but 
	it is possible that the default DACL excludes them, so this situation 
	will be handled correctly.

	pwszUserDN - A null-terminated Unicode string that contains the LDAP 
	ADsPath of the user object to modify.

	pwszUsername - A null-terminated Unicode string that contains the user 
	name to use for authorization. If this is NULL, the credentials of the 
	current user are used.

	pwszPassword - A null-terminated Unicode string that contains the 
	password to use for authorization. This is ignored if pwszUsername is 
	NULL.

	fCannotChangePassword - Contains the new setting for the privilege. 
	Contains non-zero if the user cannot change their password or zero if 
	the can change their password.

***************************************************************************/

HRESULT SetUserCannotChangePassword(LPCWSTR pwszUserDN, 
									LPCWSTR pwszUsername, 
									LPCWSTR pwszPassword,
									BOOL fCannotChangePassword)
{
	HRESULT hr;

	CComBSTR sbstrEveryone;
	hr = GetSidAccountName_Everyone(&sbstrEveryone);
	if(FAILED(hr))
	{
		return hr;
}

	CComBSTR sbstrSelf;
	hr = GetSidAccountName_Self(&sbstrSelf);
	if(FAILED(hr))
	{
		return hr;
}

	if(NULL == pwszUserDN)
	{
		return E_INVALIDARG;
}

	IADs *pads;

	hr = ADsOpenObject( pwszUserDN,
						pwszUsername,
						pwszPassword,
						ADS_SECURE_AUTHENTICATION,
						IID_IADs, 
						(LPVOID*)&pads);

	if(SUCCEEDED(hr))
	{
		CComBSTR sbstrSecDesc = "ntSecurityDescriptor";
		CComVariant svar;
	
		hr = pads->Get(sbstrSecDesc, &svar);
		if(SUCCEEDED(hr))
		{
			IADsSecurityDescriptor *psd;

			hr = svar.pdispVal->QueryInterface(IID_IADsSecurityDescriptor, (LPVOID*)&psd);
			if(SUCCEEDED(hr))
			{
				IDispatch *pDisp;

				hr = psd->get_DiscretionaryAcl(&pDisp);
				if(SUCCEEDED(hr))
				{
					IADsAccessControlList *pACL;

					hr = pDisp->QueryInterface(IID_IADsAccessControlList, (void**)&pACL);
					if(SUCCEEDED(hr)) 
					{
						BOOL fMustReorder = FALSE;
						/*
						Get the existing ACE for the change password permission 
						for Everyone. If it exists, just modify the existing 
						ACE. If it doesn't exist, create a new one and add it 
						to the ACL.
						*/
						IADsAccessControlEntry *pACEEveryone = NULL;
						hr = GetObjectACE(pACL, CHANGE_PASSWORD_GUID_W, sbstrEveryone, &pACEEveryone);
						if(pACEEveryone)
						{
							hr = pACEEveryone->put_AceType(fCannotChangePassword ? 
								ADS_ACETYPE_ACCESS_DENIED_OBJECT : 
								ADS_ACETYPE_ACCESS_ALLOWED_OBJECT);

							pACEEveryone->Release();
					}
						else
						{
							IDispatch *pDispEveryone = NULL;
						
							pDispEveryone = CreateACE(sbstrEveryone, 
								CComBSTR(CHANGE_PASSWORD_GUID_W),
								ADS_RIGHT_DS_CONTROL_ACCESS, 
								fCannotChangePassword ? 
									ADS_ACETYPE_ACCESS_DENIED_OBJECT : 
									ADS_ACETYPE_ACCESS_ALLOWED_OBJECT, 
								0,
								ADS_FLAG_OBJECT_TYPE_PRESENT);
						
							if(pDispEveryone)
							{
								//add the new ACE for everyone
								hr = pACL->AddAce(pDispEveryone);

								pDispEveryone->Release();

								fMustReorder = TRUE;
						}						
					}
					
						/*
						Get the existing ACE for the change password permission 
						for Self. If it exists, just modify the existing 
						ACE. If it doesn't exist, create a new one and add it 
						to the ACL.
						*/
						IADsAccessControlEntry *pACESelf = NULL;
						hr = GetObjectACE(pACL, CHANGE_PASSWORD_GUID_W, sbstrSelf, &pACESelf);
						if(pACESelf)
						{
							hr = pACESelf->put_AceType(fCannotChangePassword ? 
								ADS_ACETYPE_ACCESS_DENIED_OBJECT : 
								ADS_ACETYPE_ACCESS_ALLOWED_OBJECT);
					
							pACESelf->Release();
					}
						else
						{
							IDispatch *pDispSelf = NULL;
						
							pDispSelf = CreateACE(sbstrSelf, 
								CComBSTR(CHANGE_PASSWORD_GUID_W),
								ADS_RIGHT_DS_CONTROL_ACCESS, 
								fCannotChangePassword ? 
									ADS_ACETYPE_ACCESS_DENIED_OBJECT : 
									ADS_ACETYPE_ACCESS_ALLOWED_OBJECT, 
								0,
								ADS_FLAG_OBJECT_TYPE_PRESENT);

							if(pDispSelf)
							{
								//add the new ACE for self
								hr = pACL->AddAce(pDispSelf);

								pDispSelf->Release();

								fMustReorder = TRUE;
						}						
					}

						//update the security descriptor property
						hr = pads->Put(sbstrSecDesc, svar);
					
						//commit the changes
						hr = pads->SetInfo();

						if(fMustReorder)
						{
							ReorderACEs(pwszUserDN);
					}

						pACL->Release();
				}

					pDisp->Release();
			}
			
				psd->Release();
		}
	}
}

	return hr;
}

The following code example shows how to modify the User Cannot Change Password Permission using the LDAP provider.

Note  The example below will only work on domains where the primary language is English because the "Everyone" and "NT AUTHORITY\SELF" strings are localized based on the language of the first domain controller in the domain. There is no way in Visual Basic to obtain the account names for a well-known security principal without calling the LookupAccountSid function. If using Visual Basic, it is suggested that you use the WinNT provider to modify the User Cannot Change Password Permission as shown in Modifying User Cannot Change Password (WinNT Provider).

[Visual Basic]
'******************************************************************************
'
'	SetUserCannotChangePassword
'
'	Sets the "User Cannot Change Password" permission using the LDAP provider
'	to the specified setting. This is accomplished by finding the existing
'	ACEs and modifying the AceType. The ACEs should always be present, but
'	it is possible that the default DACL excludes them. This function will not
'	work correctly if both ACEs are not present.
'
'	strUserDN - A string that contains the LDAP ADsPath of the user object to
'	modify.
'
'	strUsername - A string that contains the user name to use for
'	authorization. If this is an empty string, the credentials of the current
'	user are used.
'
'	strPassword - A string that contains the password to use for authorization.
'	This is ignored if strUsername is empty.
'
'	fCannotChangePassword - Contains the new setting for the privilege.
'	Contains True if the user cannot change their password or False if
'	the can change their password.
'
'******************************************************************************
Sub SetUserCannotChangePassword(strUserDN As String, strUsername As String, strPassword As String, fUserCannotChangePassword As Boolean)
	Dim oUser As IADs
	Dim oSecDesc As IADsSecurityDescriptor
	Dim oACL As IADsAccessControlList
	Dim oACE As IADsAccessControlEntry

	fEveryone = False
	fSelf = False

	If "" <> strUsername Then
		Dim dso As IADsOpenDSObject
	
		' Bind to the group with the specified username and password.
		Set dso = GetObject("LDAP:")
		Set oUser = dso.OpenDSObject(strUserDN, strUsername, strPassword, 1)
	Else
		' Bind to the group with the current credentials.
		Set oUser = GetObject(strUserDN)
	End If

	Set oSecDesc = oUser.Get("ntSecurityDescriptor")
	Set oACL = oSecDesc.DiscretionaryAcl

	' Modify the existing entries.
	For Each oACE In oACL
		If UCase(oACE.ObjectType) = UCase(CHANGE_PASSWORD_GUID) Then
			If oACE.Trustee = "Everyone" Then
				' Modify the ace type of the entry.
				If fUserCannotChangePassword Then
					oACE.AceType = ADS_ACETYPE_ACCESS_DENIED_OBJECT
				Else
					oACE.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
				End If
			End If
	
			If oACE.Trustee = "NT AUTHORITY\SELF" Then
				' Modify the ace type of the entry.
				If fUserCannotChangePassword Then
					oACE.AceType = ADS_ACETYPE_ACCESS_DENIED_OBJECT
				Else
					oACE.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
				End If
			End If
		End If
	Next

	' Update the ntSecurityDescriptor property.
	oUser.Put "ntSecurityDescriptor", oSecDesc

	' Commit the changes to the server.
	oUser.SetInfo
End Sub