2013-09-16 16 views
11

Konstruktor WindowsIdentity(string) wymaga nazwy użytkownika w formacie [email protected]. Ale w moim przypadku otrzymuję nazwy użytkowników od DB w starym formacie DOMAIN\user (a następnie muszę sprawdzić ich przynależność do roli Windows).Jak utworzyć WindowsIdentity/WindowsPrincipal na podstawie nazwy użytkownika w DOMAIN format użytkownika

Jaki jest najlepszy sposób tworzenia WindowsPrincipal ze starej nazwy użytkownika?

+0

można użyć DsCrackNames API do konwersji formatu. – user1985513

Odpowiedz

12

Wygląda na to, że nie ma sposobu na konwersję formatu nazwy użytkownika bez angażowania zapytania w Active Directory. Ponieważ tak jest, nie ma potrzeby tworzenia WindowsPrincipal do sprawdzania członkostwa w grupie, ponieważ prawdopodobnie potrzebowałoby to jeszcze jednego połączenia z AD.

Korzystając z przestrzeni nazw System.DirectoryServices.AccountManagement, można uzyskać zarówno numer UPN użytkownika, jak i członkostwo w grupie.

string accountName = @"DOMAIN\user"; 
var groupNames = new[] { "DOMAIN\Domain Users", "DOMAIN\Group2" }; // the groups that we need to verify if the user is member of 

// cannot create WindowsIdentity because it requires username in form [email protected] but the passed value will be DOMAIN\user. 
using (var pc = new PrincipalContext(System.DirectoryServices.AccountManagement.ContextType.Domain, Environment.UserDomainName)) 
{ 
    using (var p = UserPrincipal.FindByIdentity(pc, accountName)) 
    { 
     // if the account does not exist or is not an user account 
     if (p == null) 
      return new string[0]; 

     // if you need just the UPN of the user, you can use this 
     ////return p.UserPrincipalName; 

     // find all groups the user is member of (the check is recursive). 
     // Guid != null check is intended to remove all built-in objects that are not really AD gorups. 
     // the Sid.Translate method gets the DOMAIN\Group name format. 
     var userIsMemberOf = p.GetAuthorizationGroups().Where(o => o.Guid != null).Select(o => o.Sid.Translate(typeof(NTAccount)).ToString()); 

     // use a HashSet to find the group the user is member of. 
     var groups = new HashSet<string>(userIsMemberOf), StringComparer.OrdinalIgnoreCase); 
     groups.IntersectWith(groupNames); 

     return groups; 
    } 
} 
+5

która wersja platformy .NET? i zjazdy? czyli klasa AccountManagement, PrincipalContext, UserPrincipal, StringComparer? – Kiquenet

1

Wziąłem nazwę DsCrackNames na przykładzie pinvoke.net i zmodyfikowałem ją, aby przekonwertować nazwę z nt4 na nazwę UPN. To trochę niechlujne i może chcesz posprzątać. Do tego też musi trafić w DS. Mają flagę DS_NAME_FLAG_SYNTACTICAL_ONLY, której można użyć do nie trafienia w katalog, ale nie sądzę, że zadziała tutaj.

class Entry 
{ 

    const uint NO_ERROR = 0; 
    [DllImport("ntdsapi.dll", CharSet = CharSet.Auto)] 
    static public extern uint DsCrackNames(
     IntPtr hDS, 
     DS_NAME_FLAGS flags, 
     DS_NAME_FORMAT formatOffered, 
     DS_NAME_FORMAT formatDesired, 
     uint cNames, 
     string[] rpNames, 
     out IntPtr ppResult // PDS_NAME_RESULT 
    ); 

    [DllImport("ntdsapi.dll", CharSet = CharSet.Auto)] 
    static public extern void DsFreeNameResult(IntPtr pResult /* DS_NAME_RESULT* */); 

    public enum DS_NAME_ERROR 
    { 
     DS_NAME_NO_ERROR = 0, 

     // Generic processing error. 
     DS_NAME_ERROR_RESOLVING = 1, 

     // Couldn't find the name at all - or perhaps caller doesn't have 
     // rights to see it. 
     DS_NAME_ERROR_NOT_FOUND = 2, 

     // Input name mapped to more than one output name. 
     DS_NAME_ERROR_NOT_UNIQUE = 3, 

     // Input name found, but not the associated output format. 
     // Can happen if object doesn't have all the required attributes. 
     DS_NAME_ERROR_NO_MAPPING = 4, 

     // Unable to resolve entire name, but was able to determine which 
     // domain object resides in. Thus DS_NAME_RESULT_ITEM?.pDomain 
     // is valid on return. 
     DS_NAME_ERROR_DOMAIN_ONLY = 5, 

     // Unable to perform a purely syntactical mapping at the client 
     // without going out on the wire. 
     DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING = 6, 

     // The name is from an external trusted forest. 
     DS_NAME_ERROR_TRUST_REFERRAL = 7 

    } 

    [Flags] 
    public enum DS_NAME_FLAGS 
    { 
     DS_NAME_NO_FLAGS = 0x0, 

     // Perform a syntactical mapping at the client (if possible) without 
     // going out on the wire. Returns DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING 
     // if a purely syntactical mapping is not possible. 
     DS_NAME_FLAG_SYNTACTICAL_ONLY = 0x1, 

     // Force a trip to the DC for evaluation, even if this could be 
     // locally cracked syntactically. 
     DS_NAME_FLAG_EVAL_AT_DC = 0x2, 

     // The call fails if the DC is not a GC 
     DS_NAME_FLAG_GCVERIFY = 0x4, 

     // Enable cross forest trust referral 
     DS_NAME_FLAG_TRUST_REFERRAL = 0x8 

    } 

    public enum DS_NAME_FORMAT 
    { 
     // unknown name type 
     DS_UNKNOWN_NAME = 0, 

     // eg: CN=User Name,OU=Users,DC=Example,DC=Microsoft,DC=Com 
     DS_FQDN_1779_NAME = 1, 

     // eg: Example\UserN 
     // Domain-only version includes trailing '\\'. 
     DS_NT4_ACCOUNT_NAME = 2, 

     // Probably "User Name" but could be something else. I.e. The 
     // display name is not necessarily the defining RDN. 
     DS_DISPLAY_NAME = 3, 

     // obsolete - see #define later 
     // DS_DOMAIN_SIMPLE_NAME = 4, 

     // obsolete - see #define later 
     // DS_ENTERPRISE_SIMPLE_NAME = 5, 

     // String-ized GUID as returned by IIDFromString(). 
     // eg: {4fa050f0-f561-11cf-bdd9-00aa003a77b6} 
     DS_UNIQUE_ID_NAME = 6, 

     // eg: example.microsoft.com/software/user name 
     // Domain-only version includes trailing '/'. 
     DS_CANONICAL_NAME = 7, 

     // eg: [email protected] 
     DS_USER_PRINCIPAL_NAME = 8, 

     // Same as DS_CANONICAL_NAME except that rightmost '/' is 
     // replaced with '\n' - even in domain-only case. 
     // eg: example.microsoft.com/software\nuser name 
     DS_CANONICAL_NAME_EX = 9, 

     // eg: www/[email protected] - generalized service principal 
     // names. 
     DS_SERVICE_PRINCIPAL_NAME = 10, 

     // This is the string representation of a SID. Invalid for formatDesired. 
     // See sddl.h for SID binary <--> text conversion routines. 
     // eg: S-1-5-21-397955417-626881126-188441444-501 
     DS_SID_OR_SID_HISTORY_NAME = 11, 

     // Pseudo-name format so GetUserNameEx can return the DNS domain name to 
     // a caller. This level is not supported by the DS APIs. 
     DS_DNS_DOMAIN_NAME = 12 
    } 

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 
    public struct DS_NAME_RESULT_ITEM 
    { 
     public DS_NAME_ERROR status; 
     public string pDomain; 
     public string pName; 
    } 

    [DllImport("ntdsapi.dll", CharSet = CharSet.Auto)] 
    static public extern uint DsBind(
     string DomainControllerName,  // in, optional 
     string DnsDomainName,   // in, optional 
     out IntPtr phDS); 

    [DllImport("ntdsapi.dll", CharSet = CharSet.Auto)] 
    static public extern uint DsUnBind(ref IntPtr phDS); 

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 
    public struct DS_NAME_RESULT 
    { 
     public uint cItems; 
     public IntPtr rItems; // PDS_NAME_RESULT_ITEM 
    } 

    [STAThread] 
    static void Main(string[] args) 
    { 
     // Bind to default global catalog 
     IntPtr hDS; 
     uint err = DsBind(null, null, out hDS); 
     if (err != NO_ERROR) 
     { 
      Console.WriteLine("Error on DsBind : {0}", err); 
      return; 
     } 
     // Crack the currently logged on name 
     try 
     { 
      string[] names = new string[] { System.Security.Principal.WindowsIdentity.GetCurrent().Name }; 
      DS_NAME_RESULT_ITEM[] results = 
       HandleDsCrackNames(hDS, DS_NAME_FLAGS.DS_NAME_NO_FLAGS, DS_NAME_FORMAT.DS_NT4_ACCOUNT_NAME, DS_NAME_FORMAT.DS_USER_PRINCIPAL_NAME, names); 
      foreach (DS_NAME_RESULT_ITEM result in results) 
      { 
       Console.WriteLine("Result : {0}\r\nDomain : {1}\r\nName : {2}", result.status, result.pDomain, result.pName); 
      } 
     } 
     finally 
     { 
      DsUnBind(ref hDS); 
     } 
    } 

    /// <summary> 
    /// A wrapper function for the DsCrackNames OS call 
    /// </summary> 
    /// <param name="hDS">DsBind handle</param> 
    /// <param name="flags">Flags controlling the process</param> 
    /// <param name="formatOffered">Format of the names</param> 
    /// <param name="formatDesired">Desired format for the names</param> 
    /// <param name="names">The names to crack</param> 
    /// <returns>The crack result</returns> 
    public static DS_NAME_RESULT_ITEM[] HandleDsCrackNames(IntPtr hDS, DS_NAME_FLAGS flags, DS_NAME_FORMAT formatOffered, DS_NAME_FORMAT formatDesired, string[] names) 
    { 
     IntPtr pResult; 
     DS_NAME_RESULT_ITEM[] ResultArray; 
     uint err = DsCrackNames(
     hDS, 
     flags, 
     formatOffered, 
     formatDesired, 
     (uint)((names == null) ? 0 : names.Length), 
     names, 
     out pResult); 
     if (err != NO_ERROR) 
      throw new System.ComponentModel.Win32Exception((int)err); 
     try 
     { 
      // Next convert the returned structure to managed environment 
      DS_NAME_RESULT Result = new DS_NAME_RESULT(); 
      Result.cItems = (uint)Marshal.ReadInt32(pResult); 
      Result.rItems = Marshal.ReadIntPtr(pResult, Marshal.OffsetOf(typeof(DS_NAME_RESULT), "rItems").ToInt32()); 
      IntPtr curptr = Result.rItems; 
      ResultArray = new DS_NAME_RESULT_ITEM[Result.cItems]; 
      for (int index = 0; index < (int)Result.cItems; index++) 
      { 
       ResultArray[index] = (DS_NAME_RESULT_ITEM)Marshal.PtrToStructure(curptr, typeof(DS_NAME_RESULT_ITEM)); 
       curptr = (IntPtr)((int)curptr + Marshal.SizeOf(ResultArray[index])); 
      } 
     } 
     finally 
     { 
      DsFreeNameResult(pResult); 
     } 
     return ResultArray; 
    } 

} 
+1

Dziękuję za trud. Niestety, ponieważ wiąże się to z zapytaniem o AD, wydaje się, że używanie przestrzeni nazw 'System.DirectoryServices' byłoby lepszym pomysłem ... Chociaż - możliwe, że to podejście daje lepszą wydajność. –

3

Działa to dobrze, ale wiąże się zapytanie do usługi Active Directory/SAM sklepu (w zależności od kontekstu) ...

private WindowsIdentity GetWindowsIdentity(
    string userName) 
{ 
    using (var user = 
    UserPrincipal.FindByIdentity(
     UserPrincipal.Current.Context, 
     IdentityType.SamAccountName, 
     userName 
    ) ?? 
    UserPrincipal.FindByIdentity(
     UserPrincipal.Current.Context, 
     IdentityType.UserPrincipalName, 
     userName 
    )) 
    { 
    return user == null 
     ? null 
     : new WindowsIdentity(user.UserPrincipalName); 
    } 
} 
Powiązane problemy