變更使用者無法變更密碼 (LDAP 提供者)

用戶變更自己的密碼的能力是可以授與或拒絕的許可權。 若要拒絕此許可權,請使用 ADS_ACETYPE_ACCESS_DENIED_OBJECT ace 類型,在用戶物件的安全描述項選擇性訪問控制清單 (DACL) 中設定兩個 ACE。 其中一個 ACE 拒絕使用者的許可權,另一個 ACE 拒絕所有人群組的許可權。 這兩個 ACE 都是物件特定的拒絕 ACE,可指定變更密碼之擴充許可權的 GUID。 若要授與此許可權,請使用 ADS_ACETYPE_ACCESS_ALLOWED_OBJECT ace 類型設定相同的 ACE

下列程式描述如何修改或新增此許可權的 ACE。

若要修改或新增此許可權的 ACE

  1. 系結至用戶物件。

  2. 從用戶物件的 ntSecurityDescriptor 屬性取得 IADsSecurityDescriptor 物件。

  3. 從 IADsSecurityDescriptor.DiscretionaryAcl 屬性取得安全性描述元的 IADsAccessControlList 介面。

  4. 列舉物件的 ACE,並搜尋具有變更密碼 GUID 的 ACE ({AB721A53-1E2F-11D0-9819-00AA00405 IADsAccessControlEntry.ObjectType 屬性的 IADsAccessControlEntry.ObjectType 屬性和 “Everyone” 或 “NT AUTHORITY\SELF” 的 IADsAccessControlEntry.Trustee 属性。

    注意

    “Everyone” 和 “NT AUTHORITY\SELF” 字串會根據網域中第一個域控制器的語言進行當地語系化。 因此,不應該直接使用字串。 帳戶名稱應在運行時間取得,方法是 使用 “Everyone” 的 SID 呼叫 LookupAccountSid 函式(“S-1-1-0”) 和 “NT AUTHORITY\SELF” (“S-1-5-10”) 已知的安全性主體。 讀取使用者無法變更密碼 (LDAP 提供者) 中顯示的 GetSidAccountNameGetSidAccountName_EveryoneGetSidAccountName_Self C++ 範例函式會示範如何執行這項操作。

     

  5. 如果使用者無法變更其密碼,請修改 ACE 的 IADsAccessControlEntry.AceType 屬性,以ADS_ACETYPE_ACCESS_DENIED_OBJECT如果使用者可以變更其密碼,則ADS_ACETYPE_ACCESS_ALLOWED_OBJECT。

  6. 如果找不到 「Everyone」 ACE,請建立新的 IADsAccessControlEntry 物件,其中包含下表所示的屬性值,並使用 IADsAccessControlList.AddAce 方法將新專案新增至 ACL。

  7. 如果找不到 「NT AUTHORITY\SELF」 ACE,請以下表所示的相同屬性值建立新的 IADsAccessControlEntry 物件,但 受託人 屬性包含 SID “S-1-5-10” 的帳戶名稱(“NT AUTHORITY\SELF”)。 使用 IADsAccessControlList.AddAce 方法將專案新增至 ACL。

  8. 若要更新 物件的 ntSecurityDescriptor 屬性,請使用步驟 2 中取得的相同 IADsSecurityDescriptor 呼叫 IADs.Put 方法。

  9. 使用 IADs.SetInfo 方法認可伺服器的本機變更。

  10. 如果已建立其中一個 ACE,您必須重新排序 ACL,讓 ACE 依正確順序排列。 若要這樣做,請使用 物件的LDAP ADsPath呼叫 GetNamedSecurityInfo 函式,然後使用相同的DACL呼叫 SetNamedSecurityInfo 函式。 新增 ACE 時,會自動重新排序。

下表列出 IADsAccessControlEntry 物件屬性值。

IADsAccessControlEntry 屬性
AccessMask ADS_RIGHT_DS_CONTROL_ACCESS
AceType ADS_ACETYPE_ACCESS_DENIED_OBJECT,如果使用者無法變更其密碼,或ADS_ACETYPE_ACCESS_ALLOWED_OBJECT使用者可以變更其密碼。
AceFlags 0
旗標 ADS_FLAG_OBJECT_TYPE_PRESENT
ObjectType “{AB721A53-1E2F-11D0-9819-00AA0040529B}”,這是變更字串格式的密碼 GUID。
InheritedObjectType 未使用
受託 人 SID “S-1-1-0” 的帳戶名稱(所有人)。

 

範例程式碼

下列程式代碼範例示範如何取得介面來變更 DACL。 您可以藉由設定 ADS_SECURITY_INFO_DACL 選項,來使用 IADsObjectOptions 介面。

注意

若要使用本範例中所述的程序代碼,您必須是 管理員 istrator。 如果您不是 管理員 istrator,則必須新增更多程式碼,以使用介面,讓使用者變更用戶端快取重新排回 Active Directory 網域 服務的方式。

 

//
// 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.
//

下列程式代碼範例示範如何使用LDAP提供者修改使用者無法變更密碼許可權。 此程式代碼範例會使用 上面定義的 GetObjectACE 公用程式函式。

此範例使用讀取使用者無法變更密碼(LDAP 提供者)中顯示的GetSidAccountName_EveryoneGetSidAccountName_Self 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. The ACEs are 
    automatically put in the proper order when they are added to the DACL. 
    On older systems 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;
}

下列程式代碼範例示範如何使用LDAP提供者修改使用者無法變更密碼許可權。

注意

下列範例僅適用於主要語言為英文的網域,因為 “Everyone” 和 “NT AUTHORITY\SELF” 字串會根據網域中第一個域控制器的語言進行當地語系化。 Visual Basic 無法取得已知安全性主體的帳戶名稱,而不需呼叫 LookupAccountSid 函式。 如果使用 Visual Basic,建議您使用 WinNT 提供者來修改使用者無法變更密碼許可權,如修改使用者無法變更密碼 (WinNT 提供者)所示

 

'******************************************************************************
'
'    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