HOW TO: 在 NT 格式的登入名稱和 AD 的 UPN 名稱間轉換:小朱的技術空間:Xuite日誌
  • 小朱
  • 一個畢業於國立屏東商技資管系,已有數年軟體發展經驗的毛頭小子,長期在網路各大論壇上游走,具有微軟MVP資格,並持有多張微軟認證,自詡為軟體技術玩家,以善用技術為本,以解決問題為目標,對企業應用系統、資料庫系統、分散式系統以及應用解決方案架構等皆有涉獵與研究,且不定期在 MSDN, RUN PC 與技術論壇中分享心得。



  • 文章分類
  • 搜尋文章
  • 關鍵字
  • 我的發燒文
  • 累積 | 今日
    loading......
  • 日曆
  • 最愛連結
  • 最新文章
  • 最新回應
  • 參觀人氣統計
  • 日誌使用資源






  • 如何使用RSS
    Powered by Xuite
    2008-08-06 18:00 HOW TO: 在 NT 格式的登入名稱和 AD 的 UPN 名稱間轉換
  • ?
  • Active Directory
  • 好文轉寄
  • 平均分數:0 顆星    投票人數:0
    我要評分:
    標籤 : 


    在 ASP.NET 上若要使用 Forms Authentication 和 Active Directory 連接時,我們通常都會直接使用 System.DirectoryServices (System.DirectoryServices.dll) 命名空間中的 DirectoryEntry 以及 DirectorySearcher 來做,不過這樣會有一個問題,尤其是你的應用程式要記錄在 AD 中的使用者資訊到資料庫時,就會有要用哪種名稱的問題。因為在預設的 Windows 登入行為中,允許 Domain\User (NT 格式) 和 UPN 兩種名稱,可能有人說在資料表中開兩個欄位就好啦,不過在儲存時,使用者通常只會給一種格式,那要如何知道另外一種格式 (例如 CORPDOMAIN\Steve 的 UPN 名稱是什麼,或是 steve@mycompany.com 的 NT 格式名稱又是什麼)?這個功能在 .NET 內建的 API 中沒有提供,所以只能針對 ADSI 本身提供的 API 來做。

    首先,提供名稱對應的 API 是 DsCrackNames(),必須要傳入 DS 的 Handle,設定來源名稱的格式以及要對應的新格式,傳入要對應的名稱陣列以及大小,最後再取出對應的結果。

    DWORD DsCrackNames(
      __in   HANDLE hDS,
      __in   DS_NAME_FLAGS flags,
      __in   DS_NAME_FORMAT formatOffered,
      __in   DS_NAME_FORMAT formatDesired,
      __in   DWORD cNames,
      __in   LPCTSTR* rpNames,
      __out  PDS_NAME_RESULT* ppResult
    );

    換成 .NET Framework 宣告就是:

    [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
      );

    不過因為還要處理回傳的結果 (ppResult,傳回來的是 DS_NAME_RESULT),所以還要額外宣告一些結構與相關函式 (像是 DsBind(), DsUnBind(), DsFreeNameResult() 等),其宣告如下:

    public const long NO_ERROR = 0;
    
    [DllImport("activeds.dll", CharSet = CharSet.Unicode)]
    public static extern int ADsEncodeBinaryData(byte[] data, int length, ref IntPtr result);
    
    [DllImport("activeds.dll")]
    public static extern bool FreeADsMem(IntPtr pVoid);
    
    [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: usern@example.microsoft.com
        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/www.microsoft.com@example.com - 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
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    

    (宣告的來源為 PInvoke.net:http://www.pinvoke.net/default.aspx/ntdsapi/DsCrackNames.html

    然後在程式中就可以這樣用:

    public struct DsCrackNamesResult
    {
        public DsNameErrorEnum Status;
        public string Domain;
        public string Name;
    }
    
    public enum DsNameErrorEnum
    {
        DS_NAME_NO_ERROR = 0,
        DS_NAME_ERROR_RESOLVING = 1,
        DS_NAME_ERROR_NOT_FOUND = 2,
        DS_NAME_ERROR_NOT_UNIQUE = 3,
        DS_NAME_ERROR_NO_MAPPING = 4,
        DS_NAME_ERROR_DOMAIN_ONLY = 5,
        DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING = 6,
        DS_NAME_ERROR_TRUST_REFERRAL = 7
    }
    
    public static DsCrackNamesResult[] ResolveNTFormatNamesToUPNs(string[] NTFormatNames)
    {
        IntPtr hDS;
        IntPtr hDsResult;
        NativeMethods.DS_NAME_RESULT_ITEM[] nameResultItems = null;
        DsCrackNamesResult[] results = null;
        long ret = 0;
    
        // bind to default GC.
        ret = NativeMethods.DsBind(null, null, out hDS);
    
        if (ret != NativeMethods.NO_ERROR)
            throw new InvalidOperationException("Cannot_Bind_To_Default_Global_Catalog_Server");
    
        // execute to crack names.
        ret = 0;
        ret = NativeMethods.DsCrackNames(
            hDS,
            NativeMethods.DS_NAME_FLAGS.DS_NAME_NO_FLAGS,
            NativeMethods.DS_NAME_FORMAT.DS_NT4_ACCOUNT_NAME,
            NativeMethods.DS_NAME_FORMAT.DS_USER_PRINCIPAL_NAME,
            (uint)((NTFormatNames == null) ? 0 : NTFormatNames.Length),
            NTFormatNames,
            out hDsResult);
    
        if (ret != NativeMethods.NO_ERROR)
        {
            NativeMethods.DsUnBind(ref hDS);
            throw new InvalidOperationException("Cannot_Resolve_Names_With_Error:" + ret.ToString());
        }
    
        // handling results.
        try
        {
            NativeMethods.DS_NAME_RESULT nameResult = new NativeMethods.DS_NAME_RESULT();
            nameResult.cItems = (uint)Marshal.ReadInt32(hDsResult);
            nameResult.rItems = (IntPtr)Marshal.ReadInt32(hDsResult, Marshal.SizeOf(nameResult.cItems));
            IntPtr pItem = nameResult.rItems;
            nameResultItems = new NativeMethods.DS_NAME_RESULT_ITEM[nameResult.cItems];
            results = new DsCrackNamesResult[nameResult.cItems];
    
            for (int i = 0; i < (int)nameResult.cItems; i++)
            {
                nameResultItems[i] =
                    ((NativeMethods.DS_NAME_RESULT_ITEM)Marshal.PtrToStructure(pItem, typeof(NativeMethods.DS_NAME_RESULT_ITEM)));
                pItem = (IntPtr)((int)pItem + Marshal.SizeOf(nameResultItems[i]));
            }
        }
        catch (Exception e)
        {
            NativeMethods.DsFreeNameResult(hDsResult);
            throw new InvalidOperationException("Handling_Result_Failed:" + e.Message);
        }
        finally
        {
            NativeMethods.DsFreeNameResult(hDsResult);
        }
    
        // free bind of DS.
        NativeMethods.DsUnBind(ref hDS);
    
        // create result set.
        for (int i = 0; i < nameResultItems.Length; i++)
        {
            results[i].Status = (DsNameErrorEnum)nameResultItems[i].status;
            results[i].Domain = nameResultItems[i].pDomain;
            results[i].Name = nameResultItems[i].pName;
        }
    
        nameResultItems = null;
        return results;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    

    最後,由程式中呼叫 ResolveNTNameToUPNs(),傳入 NT 格式的登入名稱,就可以得到在 AD 中對應的 UPN 名稱了。

    讀者可參考 MSDN,舉一反三,設計出像 ResolveUPNsToNTNames() 的工具函式,來解決 NT Names 和 AD UPNs 的對應問題。



    小朱 / Xuite日誌 / 回應(0) / 引用(0) / 好文轉寄
  • 回應