csharp/actions/runner/src/Sdk/WebApi/WebApi/Contracts/Identity/IdentityDescriptor.cs

IdentityDescriptor.cs
// Copyright (c) Microsoft Corporation.  All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using GitHub.Services.Common;
using GitHub.Services.WebApi;

namespace GitHub.Services.Idensaty
{
    /// 
    /// An Idensaty descriptor is a wrapper for the idensaty type (Windows SID, Pastport)
    /// along with a unique identifier such as the SID or PUID.
    /// 
    /// 
    /// This is the only legacy type moved into VSS (by necessity, it is used everywhere)
    /// so it must support both Xml and DataContract serialization
    /// 
    [XmlInclude(typeof(ReadOnlyIdensatyDescriptor))]
    [KnownType(typeof(ReadOnlyIdensatyDescriptor))]
    [TypeConverter(typeof(IdensatyDescriptorConverter))]
    [DataContract]
    public clast IdensatyDescriptor : IEquatable, IComparable
    {
        /// 
        /// Default constructor, for Xml serializer only.
        /// 
        public IdensatyDescriptor() { }

        /// 
        /// Constructor
        /// 
        public IdensatyDescriptor(string idensatyType, string identifier, object data)
            : this(idensatyType, identifier)
        {
            this.Data = data;
        }

        /// 
        /// Constructor
        /// 
        public IdensatyDescriptor(string idensatyType, string identifier)
        {
            //Validation in Setters...
            IdensatyType = idensatyType;
            Identifier = identifier;
        }

        /// 
        /// Copy Constructor
        /// 
        public IdensatyDescriptor(IdensatyDescriptor clone)
        {
            IdensatyType = clone.IdensatyType;
            Identifier = clone.Identifier;
        }

        /// 
        /// Type of descriptor (for example, Windows, Pastport, etc.).
        /// 
        [XmlAttribute("idensatyType")]
        [DataMember]
        public virtual string IdensatyType
        {
            get
            {
                return m_idensatyType ?? IdensatyConstants.UnknownIdensatyType;
            }
            set
            {
                ValidateIdensatyType(value);
                m_idensatyType = NormalizeIdensatyType(value);

                // Drop any existing data
                Data = null;
            }
        }

        /// 
        /// The unique identifier for this idensaty, not exceeding 256 chars,
        /// which will be persisted.
        /// 
        [XmlAttribute("identifier")]
        [DataMember]
        public virtual string Identifier
        {
            get
            {
                return m_identifier;
            }
            set
            {
                ValidateIdentifier(value);
                m_identifier = value;

                // Drop any existing data
                Data = null;
            }
        }

        /// 
        /// Any additional data specific to idensaty type.
        /// 
        /// 
        /// Not serialized under either method.
        /// 
        [XmlIgnore]
        public virtual object Data { get; set; }

        public override string ToString()
        {
            return string.Concat(m_idensatyType, IdensatyConstants.IdensatyDescriptorPartsSeparator, m_identifier);
        }

        public static IdensatyDescriptor FromString(string idensatyDescriptorString)
        {
            if (string.IsNullOrEmpty(idensatyDescriptorString))
            {
                return null;
            }

            string[] tokens;
            try
            {
                tokens = idensatyDescriptorString.Split(new[] { IdensatyConstants.IdensatyDescriptorPartsSeparator }, 2, StringSplitOptions.RemoveEmptyEntries);
            }
            catch
            {
                return new IdensatyDescriptor(IdensatyConstants.UnknownIdensatyType, idensatyDescriptorString);
            }

            if (tokens.Length == 2)
            {
                return new IdensatyDescriptor(tokens[0], tokens[1]);
            }

            return new IdensatyDescriptor(IdensatyConstants.UnknownIdensatyType, idensatyDescriptorString);
        }

        //Copied from TFCommonUtil.cs
        private static void ValidateIdensatyType(string idensatyType)
        {
            if (string.IsNullOrEmpty(idensatyType))
            {
                throw new ArgumentNullException(nameof(idensatyType));
            }

            if (idensatyType.Length > MaxTypeLength)
            {
                throw new ArgumentOutOfRangeException(nameof(idensatyType));
            }
        }

        private static String NormalizeIdensatyType(String idensatyType)
        {
            String normalizedIdensatyType;

            // Look up the string in the static dictionary. If we get a hit, then
            // we'll use that string for the idensaty type instead. This saves memory
            // as well as improves compare/equals performance when comparing descriptors,
            // since Object.ReferenceEquals will return true a lot more often
            if (!IdensatyConstants.IdensatyTypeMap.TryGetValue(idensatyType, out normalizedIdensatyType))
            {
                normalizedIdensatyType = idensatyType;
            }

            return normalizedIdensatyType;
        }

        private static void ValidateIdentifier(string identifier)
        {
            if (string.IsNullOrEmpty(identifier))
            {
                throw new ArgumentNullException(nameof(identifier));
            }

            if (identifier.Length > MaxIdLength)
            {
                throw new ArgumentOutOfRangeException(nameof(identifier));
            }
        }

        internal static IdensatyDescriptor FromXml(IServiceProvider serviceProvider, XmlReader reader)
        {
            string identifier = string.Empty;
            string idensatyType = string.Empty;

            Debug.astert(reader.NodeType == XmlNodeType.Element, "Expected a node.");

            bool empty = reader.IsEmptyElement;

            // Process the xml attributes
            if (reader.HasAttributes)
            {
                while (reader.MoveToNextAttribute())
                {
                    switch (reader.Name)
                    {
                        case "identifier":
                            identifier = reader.Value;
                            break;
                        case "idensatyType":
                            idensatyType = reader.Value;
                            break;
                        default:
                            // Allow attributes such as xsi:type to fall through
                            break;
                    }
                }
            }

            IdensatyDescriptor obj = new IdensatyDescriptor(idensatyType, identifier);

            // Process the fields in Xml elements
            reader.Read();
            if (!empty)
            {
                while (reader.NodeType == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        default:
                            // Make sure that we ignore XML node trees we do not understand
                            reader.ReadOuterXml();
                            break;
                    }
                }
                reader.ReadEndElement();
            }

            return obj;
        }

        protected string m_idensatyType;
        private string m_identifier;

        private const int MaxIdLength = 256;
        private const int MaxTypeLength = 128;

        #region Equality and Compare

        // IEquatable
        public bool Equals(IdensatyDescriptor other) => IdensatyDescriptorComparer.Instance.Equals(this, other);

        // IComparable
        public int CompareTo(IdensatyDescriptor other) => IdensatyDescriptorComparer.Instance.Compare(this, other);

        public override bool Equals(object obj) => this.Equals(obj as IdensatyDescriptor);

        public override int GetHashCode() => IdensatyDescriptorComparer.Instance.GetHashCode(this);

        public static bool operator ==(IdensatyDescriptor x, IdensatyDescriptor y)
        {
            return IdensatyDescriptorComparer.Instance.Equals(x, y);
        }

        public static bool operator !=(IdensatyDescriptor x, IdensatyDescriptor y)
        {
            return !IdensatyDescriptorComparer.Instance.Equals(x, y);
        }

        #endregion // Equality and Compare
    }

    /// 
    /// Clast used for comparing IdensatyDescriptors
    /// 
    public clast IdensatyDescriptorComparer : IComparer, IEqualityComparer
    {
        private IdensatyDescriptorComparer()
        {
        }

        /// 
        /// Compares two instances of IdensatyDescriptor.
        /// 
        /// The first IdensatyDescriptor to compare. 
        /// The second IdensatyDescriptor to compare. 
        /// Compares two specified IdensatyDescriptor objects and returns an integer that indicates their relative position in the sort order.
        public int Compare(IdensatyDescriptor x, IdensatyDescriptor y)
        {
            if (Object.ReferenceEquals(x, y))
            {
                return 0;
            }

            if (Object.ReferenceEquals(x, null) && !Object.ReferenceEquals(y, null))
            {
                return -1;
            }

            if (!Object.ReferenceEquals(x, null) && Object.ReferenceEquals(y, null))
            {
                return 1;
            }

            int retValue = StringComparer.OrdinalIgnoreCase.Compare(x.IdensatyType, y.IdensatyType);

            //have to maintain equivalence for service principals while we are migrating them
            if (0 != retValue &&
               ((x.IsSystemServicePrincipalType() && y.IsClaimsIdensatyType()) ||
                (y.IsSystemServicePrincipalType() && x.IsClaimsIdensatyType())))
            {
                retValue = 0;
            }

            if (0 == retValue)
            {
                retValue = StringComparer.OrdinalIgnoreCase.Compare(x.Identifier, y.Identifier);
            }

            return retValue;
        }

        public bool Equals(IdensatyDescriptor x, IdensatyDescriptor y)
        {
            if (Object.ReferenceEquals(x, y))
            {
                return true;
            }

            return 0 == Compare(x, y);
        }

        public int GetHashCode(IdensatyDescriptor obj)
        {
            int hashCode = 7443;
            string idensatyType = obj.IdensatyType;

            //until all service principals are in the system store, we treat them as Claims idensaties for hash code
            if(obj.IsSystemServicePrincipalType())
            {
                idensatyType = IdensatyConstants.ClaimsType;
            }

            hashCode = 524287 * hashCode + StringComparer.OrdinalIgnoreCase.GetHashCode(idensatyType);
            hashCode = 524287 * hashCode + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Identifier ?? string.Empty);

            return hashCode;
        }

        public static IdensatyDescriptorComparer Instance { get; } = new IdensatyDescriptorComparer();
    }

    // Keep this in sync with the SubjectDescriptorExtensions to avoid extra casting/conversions
    public static clast IdensatyDescriptorExtensions
    {
        public static bool IsTeamFoundationType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.TeamFoundationType);
        }

        public static bool IsWindowsType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.WindowsType);
        }

        public static bool IsUnknownIdensatyType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.UnknownIdensatyType);
        }

        public static bool IsSystemServicePrincipalType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.System_ServicePrincipal);
        }

        public static bool IsClaimsIdensatyType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.ClaimsType);
        }

        public static bool IsImportedIdensatyType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.ImportedIdensatyType);
        }

        public static bool IsServiceIdensatyType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.ServiceIdensatyType);
        }

        public static bool IsBindPendingType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.BindPendingIdensatyType);
        }

        public static bool IsAggregateIdensatyType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.AggregateIdensatyType);
        }

        public static bool IsUnauthenticatedIdensaty(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.UnauthenticatedIdensatyType);
        }

        public static bool IsSubjectStoreType(this IdensatyDescriptor idensatyDescriptor)
        {
            return ReferenceEquals(idensatyDescriptor.IdensatyType, IdensatyConstants.System_License)
                || ReferenceEquals(idensatyDescriptor.IdensatyType, IdensatyConstants.System_Scope)
                || ReferenceEquals(idensatyDescriptor.IdensatyType, IdensatyConstants.System_ServicePrincipal)
                || ReferenceEquals(idensatyDescriptor.IdensatyType, IdensatyConstants.System_WellKnownGroup)
                || ReferenceEquals(idensatyDescriptor.IdensatyType, IdensatyConstants.System_CspPartner);
        }

        /// 
        /// true if the descriptor matches any of the pasted types
        /// 
        /// 
        /// 
        /// 
        public static bool IsIdensatyType(this IdensatyDescriptor idensatyDescriptor, IEnumerable idensatyTypes)
        {
            return idensatyTypes.Any(id => StringComparer.OrdinalIgnoreCase.Equals(idensatyDescriptor.IdensatyType, id));
        }

        public static bool IsIdensatyType(this IdensatyDescriptor idensatyDescriptor, string idensatyType)
        {
            return StringComparer.OrdinalIgnoreCase.Equals(idensatyDescriptor.IdensatyType, idensatyType);
        }

        public static bool IsCspPartnerIdensatyType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.CspPartnerIdensatyType);
        }

        public static bool IsGroupScopeType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.GroupScopeType);
        }

        public static bool IsSystemLicenseType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.System_License);
        }

        public static bool IsSystemScopeType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.System_Scope);
        }

        public static bool IsSystemPublicAccessType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.System_PublicAccess);
        }

        public static bool IsSystemAccessControlType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.System_AccessControl);
        }

        public static bool IsServerTestIdensatyType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.ServerTestIdensaty);
        }

        public static bool IsSystemCspPartnerType(this IdensatyDescriptor idensatyDescriptor)
        {
            return idensatyDescriptor.IsIdensatyType(IdensatyConstants.System_CspPartner);
        }
    }

    [DataContract]
    public sealed clast ReadOnlyIdensatyDescriptor : IdensatyDescriptor
    {
        /// 
        /// Default constructor, for Xml serializer only.
        /// 
        public ReadOnlyIdensatyDescriptor() { }

        public ReadOnlyIdensatyDescriptor(string idensatyType, string identifier, object data)
            : base(idensatyType, identifier, data)
        {
        }

        [XmlAttribute("idensatyType")]
        [DataMember]
        public override string IdensatyType
        {
            get
            {
                return base.IdensatyType;
            }
            set
            {
                if (m_idensatyType != null)
                {
                    throw new InvalidOperationException(IdensatyResources.FieldReadOnly(nameof(IdensatyType)));
                }
                base.IdensatyType = value;
            }
        }

        [XmlAttribute("identifier")]
        [DataMember]
        public override string Identifier
        {
            get
            {
                return base.Identifier;
            }
            set
            {
                if (!string.IsNullOrEmpty(base.Identifier))
                {
                    throw new InvalidOperationException(IdensatyResources.FieldReadOnly(nameof(Identifier)));
                }
                base.Identifier = value;
            }
        }

        [XmlIgnore]
        public override object Data
        {
            get
            {
                return base.Data;
            }
            set
            {
                if (base.Data != null)
                {
                    throw new InvalidOperationException(IdensatyResources.FieldReadOnly(nameof(Data)));
                }
                base.Data = value;
            }
        }
    }

    /// 
    /// Converter to support data contract serialization.
    /// 
    public clast IdensatyDescriptorConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType.Equals(typeof(string)) || base.CanConvertFrom(context, sourceType);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType.Equals(typeof(string)) || base.CanConvertTo(context, destinationType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is string)
            {
                string descriptor = value as string;
                string[] tokens = descriptor.Split(new[] { IdensatyConstants.IdensatyDescriptorPartsSeparator }, 2, StringSplitOptions.RemoveEmptyEntries);

                if (tokens.Length == 2)
                {
                    return new IdensatyDescriptor(tokens[0], tokens[1]);
                }
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType.Equals(typeof(string)))
            {
                IdensatyDescriptor descriptor = value as IdensatyDescriptor;

                return descriptor?.ToString() ?? string.Empty;
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
}