如何确定用户帐户是否是AD组的成员,尤其是当用户不是该组的直接成员时
一个例子:
> user1是group1的成员
> group1是group2的成员
>(虚构)函数调用的结果IsUserMemberOf(‘user1′,’group2’)应为TRUE
对于.NET,有一个解决方案:
static bool IsUserMemberOf(string userName,string groupName) { using (var ctx = new PrincipalContext(ContextType.Domain)) using (var groupPrincipal = GroupPrincipal.FindByIdentity(ctx,groupName)) using (var userPrincipal = UserPrincipal.FindByIdentity(ctx,userName)) { return userPrincipal.IsMemberOf(groupPrincipal); } }
我怎么能用Delphi(Delphi-2007)做到这一点?
解:
我接受了Remko的答案,但由于他的代码在Delphi-2007(某些字符串/ WideString问题)下不起作用,这里是我正在运行的D2007版本:
unit Unit1; interface uses // Jedi ApiLib SysUtils,Classes,Windows,JwaActiveDs,JwaAdsTlb,JwaNative,JwaWinNT,JwaWinBase,JwaNtSecApi,JwaNtStatus,JwaWinType; type // Some Helper Types TSidArray = array of PSID; PSidArray = ^TSidArray; TAdsValueArray = array[0..ANYSIZE_ARRAY-1] of ADSVALUE; PAdsValueArray = ^TAdsValueArray; TLsaTranslatedNameArray = array[0..ANYSIZE_ARRAY-1] of LSA_TRANSLATED_NAME; PLsaTranslatedNameArray = ^TLsaTranslatedNameArray; function GetPolicyHandle(const Computer: WideString=''): PLSA_HANDLE; function GetGroupMembership(const AdsPath: WideString; var Groups: TStringList): Boolean; implementation function GetPolicyHandle(const Computer: WideString=''): PLSA_HANDLE; var ObjectAttributes: LSA_OBJECT_ATTRIBUTES; lusSystemName: LSA_UNICODE_STRING; nts: NTSTATUS; dwError: DWORD; begin ZeroMemory(@ObjectAttributes,SizeOf(ObjectAttributes)); RtlInitUnicodeString(@lusSystemName,PWideChar(Computer)); nts := LsaOpenPolicy(@lusSystemName,ObjectAttributes,POLICY_LOOKUP_NAMES,Pointer(Result)); if nts <> STATUS_SUCCESS then begin dwError := LsaNtStatusToWinError(nts); raise EOSError.Create(SysErrorMessage(dwError)); end; end; function GetGroupMembership(const AdsPath: WideString; var Groups: TStringList): Boolean; const Username: PChar = 'Administrator'; Password: PChar = 'password'; var hr: HRESULT; nts: NTSTATUS; PolicyHandle: PLSA_HANDLE; DirObject: IDirectoryObject; Domains: PLSA_REFERENCED_DOMAIN_LIST; Names: PLsaTranslatedNameArray; SidArray: TSidArray; Attributes: array of PChar; AdValue: PAdsValueArray; AdAttrInfo: PADS_ATTR_INFO; dwNumAttributes: DWORD; i: Integer; s: WideString; begin Result := False; Assert(Assigned(Groups)); // Get Lsa Policy Handle PolicyHandle := GetPolicyHandle; try // Open AD object,note that I am using username,password because I am // connecting from a machine that's not a domain member. // I am also passing the ADS_SERVER_BIND flag because I am directly // connecting to a specific Domain Controller hr := ADsOpenObject(PWideChar(AdsPath),Username,Password,ADS_SERVER_BIND or ADS_SECURE_AUTHENTICATION,IID_IDirectoryObject,Pointer(DirObject)); if Failed(hr) then Exit; // Attribute array SetLength(Attributes,1); s := 'tokenGroups'; Attributes[0] := @s[1]; hr := DirObject.GetObjectAttributes(@Attributes[0],Length(Attributes),AdAttrInfo,dwNumAttributes); if Failed(hr) then Exit; // Setup an Array for the PSID's SetLength(SidArray,AdAttrInfo^.dwNumValues); AdValue := PAdsValueArray(AdAttrInfo^.pADsValues); for i := 0 to AdAttrInfo^.dwNumValues-1 do begin // Copy Pointer to Array Assert(AdValue^[i].OctetString.dwLength > 0); SidArray[i] := PSid(AdValue^[i].OctetString.lpValue); end; nts := LsaLookupSids(PolicyHandle,Length(SidArray),@SidArray[0],Domains,PLSA_TRANSLATED_NAME(Names)); if nts >= STATUS_SUCCESS then begin for i := 0 to AdAttrInfo^.dwNumValues - 1 do begin SetLength(s,Names[i].Name.Length div SizeOf(WideChar)); CopyMemory(@s[1],Names[i].Name.Buffer,Names[i].Name.Length); Groups.Add(s); end; // even if nts returns STATUS_NONE_MAPPED or STATUS_SOME_NOT_MAPPED we // must Free the Mem! LsaFreeMemory(Names); LsaFreeMemory(Domains); Result := True; end; FreeAdsMem(AdAttrInfo); finally // Close the Lsa Policy Handle LsaClose(PolicyHandle); end; end; end.
解决方法
tokenGroups属性包含用户是其直接或间接成员的所有组的SID数组.
LsaLookupSids可用于将一个SID数组转换为一次调用中的名称.
示例代码:
uses // Jedi ApiLib JwaActiveDs,JwaWinType; type // Some Helper Types TSidArray = array of PSID; PSidArray = ^TSidArray; TAdsValueArray = array[0..ANYSIZE_ARRAY-1] of ADSVALUE; PAdsValueArray = ^TAdsValueArray; TLsaTranslatedNameArray = array[0..ANYSIZE_ARRAY-1] of LSA_TRANSLATED_NAME; PLsaTranslatedNameArray = ^TLsaTranslatedNameArray; function GetPolicyHandle(const Computer: String=''): PLSA_HANDLE; var ObjectAttributes: LSA_OBJECT_ATTRIBUTES; lusSystemName: LSA_UNICODE_STRING; nts: NTSTATUS; dwError: DWORD; begin ZeroMemory(@ObjectAttributes,PChar(Computer)); nts := LsaOpenPolicy(@lusSystemName,Pointer(Result)); if nts <> STATUS_SUCCESS then begin dwError := LsaNtStatusToWinError(nts); raise EOSError.Create(SysErrorMessage(dwError)); end; end; function GetGroupMembership(const AdsPath: String; var Groups: TStringList): Boolean; const Username: PChar = 'Administrator'; Password: PChar = 'password'; var hr: HRESULT; nts: NTSTATUS; PolicyHandle: PLSA_HANDLE; DirObject: IDirectoryObject; Domains: PLSA_REFERENCED_DOMAIN_LIST; Names: PLsaTranslatedNameArray; SidArray: TSidArray; Attributes: array of PChar; AdValue: PAdsValueArray; AdAttrInfo: PADS_ATTR_INFO; dwNumAttributes: DWORD; i: Integer; s: String; begin Result := False; Assert(Assigned(Groups)); // Get Lsa Policy Handle PolicyHandle := GetPolicyHandle; try // Open AD object,password because I am // connecting from a machine that's not a domain member. // I am also passing the ADS_SERVER_BIND flag because I am directly // connecting to a specific Domain Controller hr := ADsOpenObject(PChar(AdsPath),1); Attributes[0] := 'tokenGroups'; hr := DirObject.GetObjectAttributes(@Attributes[0],PADS_ATTR_INFO(AdAttrInfo),Names[i].Name.Length div SizeOf(Char)); CopyMemory(@s[1],Names[i].Name.Length); Groups.Add(s); end; // even if nts returns STATUS_NONE_MAPPED or STATUS_SOME_NOT_MAPPED we // must Free the Mem! LsaFreeMemory(Names); LsaFreeMemory(Domains); Result := True; end; FreeAdsMem(AdAttrInfo); finally // Close the Lsa Policy Handle LsaClose(PolicyHandle); end; end;
用法示例:
procedure TForm4.Button1Click(Sender: TObject); var Groups: TStringList; bRes: Boolean; i: Integer; begin Groups := TStringList.Create; Groups.Duplicates := dupIgnore; Groups.Sorted := True; try bRes := GetGroupMembership('LDAP://2003DC/CN=Administrator,CN=Users,DC=contoso,DC=com',Groups); if bRes then begin for i := 0 to Groups.Count - 1 do begin Memo1.Lines.Add(Groups[i]); end; end; Memo1.Lines.Add(Format('IsMemberOf Administrators: %s',[BoolToStr(Groups.IndexOf('Administrators') <> -1,True)])); finally Groups.Free; end; end;