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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

namespace System.DirectoryServices.AccountManagement
{
#if TESTHOOK
    public class AccountInfo
#else
    internal sealed class AccountInfo
#endif
    {
        //
        // Properties exposed to the public through AuthenticablePrincipal
        //

        // AccountLockoutTime
        private Nullable<DateTime> _accountLockoutTime;
        private LoadState _accountLockoutTimeLoaded = LoadState.NotSet;

        public Nullable<DateTime> AccountLockoutTime
        {
            get
            {
                return _owningPrincipal.HandleGet<Nullable<DateTime>>(ref _accountLockoutTime, PropertyNames.AcctInfoAcctLockoutTime, ref _accountLockoutTimeLoaded);
            }
        }

        // LastLogon
        private Nullable<DateTime> _lastLogon;
        private LoadState _lastLogonLoaded = LoadState.NotSet;

        public Nullable<DateTime> LastLogon
        {
            get
            {
                return _owningPrincipal.HandleGet<Nullable<DateTime>>(ref _lastLogon, PropertyNames.AcctInfoLastLogon, ref _lastLogonLoaded);
            }
        }

        // PermittedWorkstations
        private PrincipalValueCollection<string> _permittedWorkstations = new PrincipalValueCollection<string>();
        private LoadState _permittedWorkstationsLoaded = LoadState.NotSet;

        public PrincipalValueCollection<string> PermittedWorkstations
        {
            get
            {
                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoPermittedWorkstations))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                return _owningPrincipal.HandleGet<PrincipalValueCollection<string>>(ref _permittedWorkstations, PropertyNames.AcctInfoPermittedWorkstations, ref _permittedWorkstationsLoaded);
            }
        }

        // PermittedLogonTimes
        //  We have to handle the change-tracking for this differently than for the other properties, because
        //  a byte[] is mutable.  After calling the get accessor, the app can change the permittedLogonTimes,
        //  without needing to ever call the set accessor.  Therefore, rather than a simple "changed" flag set
        //  by the set accessor, we need to track the original value of the property, and flag it as changed
        //  if current value != original value.
        private byte[] _permittedLogonTimes;
        private byte[] _permittedLogonTimesOriginal;
        private LoadState _permittedLogonTimesLoaded = LoadState.NotSet;

        public byte[] PermittedLogonTimes
        {
            get
            {
                return _owningPrincipal.HandleGet<byte[]>(ref _permittedLogonTimes, PropertyNames.AcctInfoPermittedLogonTimes, ref _permittedLogonTimesLoaded);
            }

            set
            {
                // We don't use HandleSet<T> here because of the slightly non-standard implementation of the change-tracking
                // for this property.

                // Check that we actually support this property in our store
                //this.owningPrincipal.CheckSupportedProperty(PropertyNames.AcctInfoPermittedLogonTimes);

                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoPermittedLogonTimes))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                // If we get to this point we know that the value of the property has changed and we should not load it from the store.
                // If value is retrieved the state is set to loaded.  Even if user modifies the reference we will
                // not overwrite it because we mark it as loaded.
                // If the user sets it before reading it we mark it as changed.  When the users accesses it we just return the current
                // value.  All change tracking to the store is done off of an actual object comparison because users can change the value
                // either through property set or modifying the reference returned.
                _permittedLogonTimesLoaded = LoadState.Changed;

                _permittedLogonTimes = value;
            }
        }

        // AccountExpirationDate
        private Nullable<DateTime> _expirationDate;
        private LoadState _expirationDateChanged = LoadState.NotSet;

        public Nullable<DateTime> AccountExpirationDate
        {
            get
            {
                return _owningPrincipal.HandleGet<Nullable<DateTime>>(ref _expirationDate, PropertyNames.AcctInfoExpirationDate, ref _expirationDateChanged);
            }

            set
            {
                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoExpirationDate))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                _owningPrincipal.HandleSet<Nullable<DateTime>>(ref _expirationDate, value, ref _expirationDateChanged,
                                               PropertyNames.AcctInfoExpirationDate);
            }
        }

        // SmartcardLogonRequired
        private bool _smartcardLogonRequired;
        private LoadState _smartcardLogonRequiredChanged = LoadState.NotSet;

        public bool SmartcardLogonRequired
        {
            get
            {
                return _owningPrincipal.HandleGet<bool>(ref _smartcardLogonRequired, PropertyNames.AcctInfoSmartcardRequired, ref _smartcardLogonRequiredChanged);
            }

            set
            {
                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoSmartcardRequired))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                _owningPrincipal.HandleSet<bool>(ref _smartcardLogonRequired, value, ref _smartcardLogonRequiredChanged,
                                 PropertyNames.AcctInfoSmartcardRequired);
            }
        }

        // DelegationPermitted
        private bool _delegationPermitted;
        private LoadState _delegationPermittedChanged = LoadState.NotSet;

        public bool DelegationPermitted
        {
            get
            {
                return _owningPrincipal.HandleGet<bool>(ref _delegationPermitted, PropertyNames.AcctInfoDelegationPermitted, ref _delegationPermittedChanged);
            }

            set
            {
                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoDelegationPermitted))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                _owningPrincipal.HandleSet<bool>(ref _delegationPermitted, value, ref _delegationPermittedChanged,
                                 PropertyNames.AcctInfoDelegationPermitted);
            }
        }

        // BadLogonCount
        private int _badLogonCount;
        private LoadState _badLogonCountChanged = LoadState.NotSet;

        public int BadLogonCount
        {
            get
            {
                return _owningPrincipal.HandleGet<int>(ref _badLogonCount, PropertyNames.AcctInfoBadLogonCount, ref _badLogonCountChanged);
            }
        }

        // HomeDirectory
        private string _homeDirectory;
        private LoadState _homeDirectoryChanged = LoadState.NotSet;

        public string HomeDirectory
        {
            get
            {
                return _owningPrincipal.HandleGet<string>(ref _homeDirectory, PropertyNames.AcctInfoHomeDirectory, ref _homeDirectoryChanged);
            }

            set
            {
                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoHomeDirectory))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                _owningPrincipal.HandleSet<string>(ref _homeDirectory, value, ref _homeDirectoryChanged,
                                   PropertyNames.AcctInfoHomeDirectory);
            }
        }

        // HomeDrive
        private string _homeDrive;
        private LoadState _homeDriveChanged = LoadState.NotSet;

        public string HomeDrive
        {
            get
            {
                return _owningPrincipal.HandleGet<string>(ref _homeDrive, PropertyNames.AcctInfoHomeDrive, ref _homeDriveChanged);
            }

            set
            {
                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoHomeDrive))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                _owningPrincipal.HandleSet<string>(ref _homeDrive, value, ref _homeDriveChanged,
                                   PropertyNames.AcctInfoHomeDrive);
            }
        }

        // ScriptPath
        private string _scriptPath;
        private LoadState _scriptPathChanged = LoadState.NotSet;

        public string ScriptPath
        {
            get
            {
                return _owningPrincipal.HandleGet<string>(ref _scriptPath, PropertyNames.AcctInfoScriptPath, ref _scriptPathChanged);
            }

            set
            {
                if (!_owningPrincipal.GetStoreCtxToUse().IsValidProperty(_owningPrincipal, PropertyNames.AcctInfoScriptPath))
                    throw new InvalidOperationException(SR.InvalidPropertyForStore);

                _owningPrincipal.HandleSet<string>(ref _scriptPath, value, ref _scriptPathChanged,
                                   PropertyNames.AcctInfoScriptPath);
            }
        }

        //
        // Methods exposed to the public through AuthenticablePrincipal
        //
        public bool IsAccountLockedOut()
        {
            if (!_owningPrincipal.unpersisted)
            {
                GlobalDebug.WriteLineIf(GlobalDebug.Info, "AccountInfo", "IsAccountLockedOut: sending lockout query");

                Debug.Assert(_owningPrincipal.Context != null);

                return _owningPrincipal.GetStoreCtxToUse().IsLockedOut(_owningPrincipal);
            }
            else
            {
                // A Principal that hasn't even been persisted can't be locked out
                return false;
            }
        }

        public void UnlockAccount()
        {
            if (!_owningPrincipal.unpersisted)
            {
                GlobalDebug.WriteLineIf(GlobalDebug.Info, "AccountInfo", "UnlockAccount: sending unlock request");

                Debug.Assert(_owningPrincipal.Context != null);

                _owningPrincipal.GetStoreCtxToUse().UnlockAccount(_owningPrincipal);
            }

            // Since a Principal that's not persisted can't have been locked-out,
            // there's nothing to do in that case
        }

        //
        // Internal constructor
        //
        internal AccountInfo(AuthenticablePrincipal principal)
        {
            _owningPrincipal = principal;
        }

        //
        // Private implementation
        //
        private readonly AuthenticablePrincipal _owningPrincipal;

        //
        // Load/Store
        //

        //
        // Loading with query results
        //

        internal void LoadValueIntoProperty(string propertyName, object value)
        {
            //            GlobalDebug.WriteLineIf(GlobalDebug.Info, "AccountInfo", "LoadValueIntoProperty: name=" + propertyName + " value=" + value.ToString());

            switch (propertyName)
            {
                case (PropertyNames.AcctInfoAcctLockoutTime):
                    _accountLockoutTime = (Nullable<DateTime>)value;
                    _accountLockoutTimeLoaded = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoLastLogon):
                    _lastLogon = (Nullable<DateTime>)value;
                    _lastLogonLoaded = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoPermittedWorkstations):
                    _permittedWorkstations.Load((List<string>)value);
                    _permittedWorkstationsLoaded = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoPermittedLogonTimes):
                    _permittedLogonTimes = (byte[])value;
                    _permittedLogonTimesOriginal = (byte[])((byte[])value).Clone();
                    _permittedLogonTimesLoaded = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoExpirationDate):
                    _expirationDate = (Nullable<DateTime>)value;
                    _expirationDateChanged = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoSmartcardRequired):
                    _smartcardLogonRequired = (bool)value;
                    _smartcardLogonRequiredChanged = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoDelegationPermitted):
                    _delegationPermitted = (bool)value;
                    _delegationPermittedChanged = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoBadLogonCount):
                    _badLogonCount = (int)value;
                    _badLogonCountChanged = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoHomeDirectory):
                    _homeDirectory = (string)value;
                    _homeDirectoryChanged = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoHomeDrive):
                    _homeDrive = (string)value;
                    _homeDriveChanged = LoadState.Loaded;
                    break;

                case (PropertyNames.AcctInfoScriptPath):
                    _scriptPath = (string)value;
                    _scriptPathChanged = LoadState.Loaded;
                    break;

                default:
                    Debug.Fail($"AccountInfo.LoadValueIntoProperty: fell off end looking for {propertyName}");
                    break;
            }
        }

        //
        // Getting changes to persist (or to build a query from a QBE filter)
        //

        // Given a property name, returns true if that property has changed since it was loaded, false otherwise.
        internal bool GetChangeStatusForProperty(string propertyName)
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "AccountInfo", "GetChangeStatusForProperty: name=" + propertyName);

            switch (propertyName)
            {
                case (PropertyNames.AcctInfoPermittedWorkstations):
                    return _permittedWorkstations.Changed;

                case (PropertyNames.AcctInfoPermittedLogonTimes):
                    // If they're equal, they have _not_ changed
                    if ((_permittedLogonTimes == null) && (_permittedLogonTimesOriginal == null))
                        return false;

                    if ((_permittedLogonTimes == null) || (_permittedLogonTimesOriginal == null))
                        return true;

                    return !Utils.AreBytesEqual(_permittedLogonTimes, _permittedLogonTimesOriginal);

                case (PropertyNames.AcctInfoExpirationDate):
                    return _expirationDateChanged == LoadState.Changed;

                case (PropertyNames.AcctInfoSmartcardRequired):
                    return _smartcardLogonRequiredChanged == LoadState.Changed;

                case (PropertyNames.AcctInfoDelegationPermitted):
                    return _delegationPermittedChanged == LoadState.Changed;

                case (PropertyNames.AcctInfoHomeDirectory):
                    return _homeDirectoryChanged == LoadState.Changed;

                case (PropertyNames.AcctInfoHomeDrive):
                    return _homeDriveChanged == LoadState.Changed;

                case (PropertyNames.AcctInfoScriptPath):
                    return _scriptPathChanged == LoadState.Changed;

                default:
                    Debug.Fail($"AccountInfo.GetChangeStatusForProperty: fell off end looking for {propertyName}");
                    return false;
            }
        }

        // Given a property name, returns the current value for the property.
        internal object GetValueForProperty(string propertyName)
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "AccountInfo", "GetValueForProperty: name=" + propertyName);

            switch (propertyName)
            {
                case (PropertyNames.AcctInfoPermittedWorkstations):
                    return _permittedWorkstations;

                case (PropertyNames.AcctInfoPermittedLogonTimes):
                    return _permittedLogonTimes;

                case (PropertyNames.AcctInfoExpirationDate):
                    return _expirationDate;

                case (PropertyNames.AcctInfoSmartcardRequired):
                    return _smartcardLogonRequired;

                case (PropertyNames.AcctInfoDelegationPermitted):
                    return _delegationPermitted;

                case (PropertyNames.AcctInfoHomeDirectory):
                    return _homeDirectory;

                case (PropertyNames.AcctInfoHomeDrive):
                    return _homeDrive;

                case (PropertyNames.AcctInfoScriptPath):
                    return _scriptPath;

                default:
                    Debug.Fail($"AccountInfo.GetValueForProperty: fell off end looking for {propertyName}");
                    return null;
            }
        }

        // Reset all change-tracking status for all properties on the object to "unchanged".
        internal void ResetAllChangeStatus()
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "AccountInfo", "ResetAllChangeStatus");

            _permittedWorkstations.ResetTracking();

            _permittedLogonTimesOriginal = (_permittedLogonTimes != null) ?
                                                            (byte[])_permittedLogonTimes.Clone() :
                                                            null;
            _expirationDateChanged = (_expirationDateChanged == LoadState.Changed) ? LoadState.Loaded : LoadState.NotSet;
            _smartcardLogonRequiredChanged = (_smartcardLogonRequiredChanged == LoadState.Changed) ? LoadState.Loaded : LoadState.NotSet;
            _delegationPermittedChanged = (_delegationPermittedChanged == LoadState.Changed) ? LoadState.Loaded : LoadState.NotSet;
            _homeDirectoryChanged = (_homeDirectoryChanged == LoadState.Changed) ? LoadState.Loaded : LoadState.NotSet;
            _homeDriveChanged = (_homeDriveChanged == LoadState.Changed) ? LoadState.Loaded : LoadState.NotSet;
            _scriptPathChanged = (_scriptPathChanged == LoadState.Changed) ? LoadState.Loaded : LoadState.NotSet;
        }
    }
}