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, 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.
The following procedure describes how to modify or add ACEs for this permission.
To modify or add the ACEs for this permission
- Bind to the user object.
- Obtain the IADsSecurityDescriptor object from the ntSecurityDescriptor property of the user object.
- Obtain an IADsAccessControlList interface for the security descriptor from the IADsSecurityDescriptor.DiscretionaryAcl property.
- 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.
- 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.
- 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.
- 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.
- To update the ntSecurityDescriptor property of the object, call the IADs.Put method with the same IADsSecurityDescriptor obtained in Step 2.
- Commit the local changes to the server with the IADs.SetInfo method.
- 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 reordering will occur automatically when the ACEs are added, but on Windows 2000, the reordering must be performed manually.
| 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 obtain an interface to change a DACL. The IADsObjectOptions interface can be used by setting the ADS_SECURITY_INFO_DACL option.
Note To use the code documented in this example, you will need to be an Administrator. If you are not an Administrator, then you will need to add more code that will use an interface that will allow a user to change the way the client-side cache is flushed back to the Active Directory Domain Service.
// // Obtain an IADsObjectOptions interface from the object whose // DACL you wish to modify. // long CanReadSetDACL = ADS_SECURITY_INFO_DACL; CComPtr<IADsObjectOptions> spObjOps; hr = pads->QueryInterface(IID_IADsObjectOptions, (void**)&spObjOps); if(SUCCEEDED(hr)) { // // Set the option mask you want to change. In this case // we want to change the objects security information, so we'll // use the ADS_OPTION_SECURITY_MASK. Since we want to modify the // DACL, we'll set the variant to ADS_SEDCURITY_INFO_DACL flag. // CComVariant svar; svar = CanReadSetDACL; hr = spObjOps->SetOption(ADS_OPTION_SECURITY_MASK, svar); } // // The smart pointer declared for pObjOptions can be released // or it will be destroyed and released once the pointer goes // out of scope. //
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).
#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 nonzero 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 does not 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 does not 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).
'****************************************************************************** ' ' 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 user name 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
Send comments about this topic to Microsoft
Build date: 10/26/2012