using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using Lithnet.CredentialProvider.Interop; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Lithnet.CredentialProvider { /// /// This class represents the base of a credential provider. Inherit from this class to create a new credential provider. /// public abstract partial class CredentialProviderBase { private readonly ILogger logger; private ICredentialProviderEvents CredentialProviderEvents; private ICredentialProviderUserArray credentialProviderUsers; private IntPtr credentialProviderEventsAdviseContext; private bool notifyOnTileCollectionChange; private List tiles; internal ILoggerFactory LoggerFactory { get; } /// /// Gets the GUID of the credential provider /// public Guid CredentialProviderId { get; private set; } /// /// Gets the list of users that were supplied to the credential provider from LogonUI /// public IReadOnlyList SuppliedUsers { get; private set; } /// /// Gets the usage scenario communicated by LogonUI or CreDUI /// public UsageScenario UsageScenario { get; private set; } /// /// Gets the list of controls used by this credential provider /// public ControlCollection Controls { get; private set; } /// /// Gets a list of the tiles created for this credential provider /// public IReadOnlyList Tiles { get; private set; } /// /// Provides access to the serialized input data provided by CredUI /// public CredentialSerialization InboundSerialization { get; private set; } protected CredentialProviderBase() { this.LoggerFactory = this.GetLoggerFactory(); this.logger = this.LoggerFactory.CreateLogger(this.GetType()); var guidAttribute = (GuidAttribute)(this.GetType().GetCustomAttribute(typeof(GuidAttribute))); if (guidAttribute == null) { throw new InvalidOperationException("The Credential Provider must have a [Guid(\"xxx\")] attribute assigned to its class"); } this.CredentialProviderId = Guid.Parse(guidAttribute.Value); } /// /// Gets a logger factory. Override this method and provide an implementation of to enable credential provider logging /// /// An ILoggerFactory instance protected virtual ILoggerFactory GetLoggerFactory() { return NullLoggerFactory.Instance; } /// /// Gets a value indicating if the credential provider supports the provided by LogonUI or CredUI /// /// The usage scenario /// Additional flags provided by CredUI /// True, if the credential provider can handle the specified usage scenario, or false if it cannot public abstract bool IsUsageScenarioSupported(UsageScenario cpus, CredUIWinFlags dwFlags); /// /// Gets the set of controls used by this provider to render the UI /// /// The usage scenario to obtain the controls for /// A collection of ControlBase objects public abstract IEnumerable GetControls(UsageScenario cpus); /// /// Gets a value that indicates if the specified user should have a tile rendered for them by this UI /// /// Details of the user provided by LogonUI or CredUI /// A value indicating if this credential provider should show a tile for this user public abstract bool ShouldIncludeUserTile(CredentialProviderUser user); /// /// Gets a value that indicates if the credential provider should show a generic tile. That is, a tile that is not associated with a specific user. /// public abstract bool ShouldIncludeGenericTile(); /// /// Adds additional user tiles to the collection, and notifies LogonUI that new tiles are available /// /// One or more credential tiles to add public void AddAdditionalUserTiles(params CredentialProviderCredential1Tile[] tiles) { if (tiles == null) { return; } foreach (var tile in tiles) { if (!this.tiles.Contains(tile)) { this.tiles.Add(tile); tile.Initialize(); } } this.NotifyHostOfTileCollectionChange(); } /// /// Removes one or more user tiles, and notifies LogonUI that tiles have been removed /// /// The crendential tiles to remove public void RemoveUserTiles(params CredentialProviderCredential1Tile[] tiles) { if (tiles == null) { return; } foreach (var tile in tiles) { this.tiles.Remove(tile); } this.NotifyHostOfTileCollectionChange(); } /// /// This method is used to generate the generic tile for this credential provider. This is called when return true /// public abstract CredentialProviderCredential1Tile CreateGenericTile(); /// /// Creates a credential tile for the specified user /// /// The user to create the tile for public abstract CredentialProviderCredential1Tile CreateUserTile(CredentialProviderUser user); /// /// This method is called when the LogonUI or CredUI provides inbound credential data. Override this method to respond to the incoming data. /// /// The inbound serialized credential public virtual void OnSetSerialization(CredentialSerialization inboundSerialization) { } private void BuildControls() { if (this.Controls == null) { this.Controls = new ControlCollection(); foreach (var control in this.GetControls(this.UsageScenario)) { control.SetLogger(this.LoggerFactory); this.Controls.Add(control); } this.Controls.Lock(); } } private List GenerateSuppliedUserTiles() { this.BuildControls(); var tiles = new List(); var users = new List(); this.credentialProviderUsers.GetCount(out var count); for (uint i = 0; i < count; i++) { var result = this.credentialProviderUsers.GetAt(i, out var user); if (result != HRESULT.S_OK) { this.logger.LogError($"Could not get user at index {i}"); continue; } user.GetSid(out var sid); this.logger.LogTrace($"Got supplied user {i}: with name {user.GetQualifiedUserName()} and SID {sid}"); var credentialProviderUser = new CredentialProviderUser(this.LoggerFactory, user); users.Add(credentialProviderUser); if (this.ShouldIncludeUserTile(credentialProviderUser)) { var userTile = this.CreateUserTile(credentialProviderUser); if (userTile != null) { tiles.Add(userTile); userTile.Initialize(); } } } if (this.ShouldIncludeGenericTile()) { var genericTile = this.CreateGenericTile(); if (genericTile != null) { tiles.Add(genericTile); genericTile.Initialize(); } } this.SuppliedUsers = users.AsReadOnly(); return tiles; } private void SetupTiles() { this.tiles = new List(this.GenerateSuppliedUserTiles()); this.Tiles = this.tiles.AsReadOnly(); } private void NotifyHostOfTileCollectionChange() { if (this.notifyOnTileCollectionChange) { this.CredentialProviderEvents?.CredentialsChanged(this.credentialProviderEventsAdviseContext); } } } }