File: System\Security\Principal\NTAccount.cs
Web Access
Project: src\src\runtime\src\libraries\System.Security.Principal.Windows\src\System.Security.Principal.Windows.csproj (System.Security.Principal.Windows)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;

namespace System.Security.Principal
{
    public sealed class NTAccount : IdentityReference
    {
        #region Private members

        private readonly string _name;

        //
        // Limit for nt account names for users is 20 while that for groups is 256
        //
        internal const int MaximumAccountNameLength = 256;

        //
        // Limit for dns domain names is 255
        //
        internal const int MaximumDomainNameLength = 255;

        #endregion

        #region Constructors

        public NTAccount(string domainName, string accountName)
        {
            ArgumentException.ThrowIfNullOrEmpty(accountName);

            if (accountName.Length > MaximumAccountNameLength)
            {
                throw new ArgumentException(SR.IdentityReference_AccountNameTooLong, nameof(accountName));
            }

            if (domainName != null && domainName.Length > MaximumDomainNameLength)
            {
                throw new ArgumentException(SR.IdentityReference_DomainNameTooLong, nameof(domainName));
            }

            if (string.IsNullOrEmpty(domainName))
            {
                _name = accountName;
            }
            else
            {
                _name = domainName + "\\" + accountName;
            }
        }

        public NTAccount(string name)
        {
            ArgumentException.ThrowIfNullOrEmpty(name);

            if (name.Length > (MaximumDomainNameLength + 1 /* '\' */ + MaximumAccountNameLength))
            {
                throw new ArgumentException(SR.IdentityReference_AccountNameTooLong, nameof(name));
            }

            _name = name;
        }

        #endregion

        #region Inherited properties and methods
        public override string Value
        {
            get
            {
                return ToString();
            }
        }

        public override bool IsValidTargetType(Type targetType)
        {
            if (targetType == typeof(SecurityIdentifier))
            {
                return true;
            }
            else if (targetType == typeof(NTAccount))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public override IdentityReference Translate(Type targetType)
        {
            ArgumentNullException.ThrowIfNull(targetType);

            if (targetType == typeof(NTAccount))
            {
                return this; // assumes that NTAccount objects are immutable
            }
            else if (targetType == typeof(SecurityIdentifier))
            {
                IdentityReferenceCollection irSource = new IdentityReferenceCollection(1);
                irSource.Add(this);
                IdentityReferenceCollection irTarget;

                irTarget = NTAccount.Translate(irSource, targetType, true);

                return irTarget[0];
            }
            else
            {
                throw new ArgumentException(SR.IdentityReference_MustBeIdentityReference, nameof(targetType));
            }
        }

        public override bool Equals([NotNullWhen(true)] object? o)
        {
            return (this == o as NTAccount); // invokes operator==
        }

        public override int GetHashCode()
        {
            return StringComparer.OrdinalIgnoreCase.GetHashCode(_name);
        }

        public override string ToString()
        {
            return _name;
        }

        internal static IdentityReferenceCollection Translate(IdentityReferenceCollection sourceAccounts, Type targetType, bool forceSuccess)
        {
            IdentityReferenceCollection result = Translate(sourceAccounts, targetType, out bool someFailed);

            if (forceSuccess && someFailed)
            {
                IdentityReferenceCollection UnmappedIdentities = new IdentityReferenceCollection();

                foreach (IdentityReference id in result)
                {
                    if (id.GetType() != targetType)
                    {
                        UnmappedIdentities.Add(id);
                    }
                }

                throw new IdentityNotMappedException(SR.IdentityReference_IdentityNotMapped, UnmappedIdentities);
            }

            return result;
        }

        internal static IdentityReferenceCollection Translate(IdentityReferenceCollection sourceAccounts, Type targetType, out bool someFailed)
        {
            ArgumentNullException.ThrowIfNull(sourceAccounts);

            if (targetType == typeof(SecurityIdentifier))
            {
                return TranslateToSids(sourceAccounts, out someFailed);
            }

            throw new ArgumentException(SR.IdentityReference_MustBeIdentityReference, nameof(targetType));
        }

        #endregion

        #region Operators

        public static bool operator ==(NTAccount? left, NTAccount? right)
        {
            object? l = left;
            object? r = right;

            if (l == r)
            {
                return true;
            }
            else if (l == null || r == null)
            {
                return false;
            }
            else
            {
                return (left!.ToString().Equals(right!.ToString(), StringComparison.OrdinalIgnoreCase));
            }
        }

        public static bool operator !=(NTAccount? left, NTAccount? right)
        {
            return !(left == right); // invoke operator==
        }

        #endregion

        #region Private methods


        private static unsafe IdentityReferenceCollection TranslateToSids(IdentityReferenceCollection sourceAccounts, out bool someFailed)
        {
            ArgumentNullException.ThrowIfNull(sourceAccounts);

            if (sourceAccounts.Count == 0)
            {
                throw new ArgumentException(SR.Arg_EmptyCollection, nameof(sourceAccounts));
            }

            SafeLsaPolicyHandle? LsaHandle = null;
            SafeLsaMemoryHandle? ReferencedDomainsPtr = null;
            SafeLsaMemoryHandle? SidsPtr = null;

            try
            {
                //
                // Construct an array of unicode strings
                //

                Interop.Advapi32.MARSHALLED_UNICODE_STRING[] Names = new Interop.Advapi32.MARSHALLED_UNICODE_STRING[sourceAccounts.Count];

                int currentName = 0;
                foreach (IdentityReference id in sourceAccounts)
                {
                    if (!(id is NTAccount nta))
                    {
                        throw new ArgumentException(SR.Argument_ImproperType, nameof(sourceAccounts));
                    }

                    Names[currentName].Buffer = nta.ToString();

                    if (Names[currentName].Buffer.Length * 2 + 2 > ushort.MaxValue)
                    {
                        // this should never happen since we are already validating account name length in constructor and
                        // it is less than this limit
                        Debug.Fail("NTAccount::TranslateToSids - source account name is too long.");
                        throw new InvalidOperationException();
                    }

                    Names[currentName].Length = (ushort)(Names[currentName].Buffer.Length * 2);
                    Names[currentName].MaximumLength = (ushort)(Names[currentName].Length + 2);
                    currentName++;
                }

                //
                // Open LSA policy (for lookup requires it)
                //

                LsaHandle = Win32.LsaOpenPolicy(null, Interop.Advapi32.PolicyRights.POLICY_LOOKUP_NAMES);

                //
                // Now perform the actual lookup
                //

                someFailed = false;
                uint ReturnCode;

                ReturnCode = Interop.Advapi32.LsaLookupNames2(LsaHandle, 0, sourceAccounts.Count, Names, out ReferencedDomainsPtr, out SidsPtr);

                //
                // Make a decision regarding whether it makes sense to proceed
                // based on the return code and the value of the forceSuccess argument
                //

                if (ReturnCode == Interop.StatusOptions.STATUS_NO_MEMORY ||
                    ReturnCode == Interop.StatusOptions.STATUS_INSUFFICIENT_RESOURCES)
                {
                    throw new OutOfMemoryException();
                }
                else if (ReturnCode == Interop.StatusOptions.STATUS_ACCESS_DENIED)
                {
                    throw new UnauthorizedAccessException();
                }
                else if (ReturnCode == Interop.StatusOptions.STATUS_NONE_MAPPED ||
                    ReturnCode == Interop.StatusOptions.STATUS_SOME_NOT_MAPPED)
                {
                    someFailed = true;
                }
                else if (ReturnCode != 0)
                {
                    uint win32ErrorCode = Interop.Advapi32.LsaNtStatusToWinError(ReturnCode);

                    if (unchecked((int)win32ErrorCode) != Interop.Errors.ERROR_TRUSTED_RELATIONSHIP_FAILURE)
                    {
                        Debug.Fail($"Interop.LsaLookupNames(2) returned unrecognized error {win32ErrorCode}");
                    }

                    throw new Win32Exception(unchecked((int)win32ErrorCode));
                }

                //
                // Interpret the results and generate SID objects
                //

                IdentityReferenceCollection Result = new IdentityReferenceCollection(sourceAccounts.Count);

                if (ReturnCode == 0 || ReturnCode == Interop.StatusOptions.STATUS_SOME_NOT_MAPPED)
                {
                    SidsPtr.Initialize((uint)sourceAccounts.Count, (uint)sizeof(Interop.LSA_TRANSLATED_SID2));
                    ReferencedDomainsPtr.InitializeReferencedDomainsList();
                    Interop.LSA_TRANSLATED_SID2[] translatedSids = new Interop.LSA_TRANSLATED_SID2[sourceAccounts.Count];
                    SidsPtr.ReadArray(0, translatedSids, 0, translatedSids.Length);

                    for (int i = 0; i < sourceAccounts.Count; i++)
                    {
                        Interop.LSA_TRANSLATED_SID2 Lts = translatedSids[i];

                        //
                        // Only some names are recognized as NTAccount objects
                        //

                        switch ((SidNameUse)Lts.Use)
                        {
                            case SidNameUse.User:
                            case SidNameUse.Group:
                            case SidNameUse.Alias:
                            case SidNameUse.Computer:
                            case SidNameUse.WellKnownGroup:
                                Result.Add(new SecurityIdentifier(Lts.Sid));
                                break;

                            default:
                                someFailed = true;
                                Result.Add(sourceAccounts[i]);
                                break;
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < sourceAccounts.Count; i++)
                    {
                        Result.Add(sourceAccounts[i]);
                    }
                }

                return Result;
            }
            finally
            {
                LsaHandle?.Dispose();
                ReferencedDomainsPtr?.Dispose();
                SidsPtr?.Dispose();
            }
        }
        #endregion
    }
}