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
{
internal static ILoggerFactory LoggerFactory { get; set; } = NullLoggerFactory.Instance;
private readonly ILogger logger;
private readonly ILoggerFactory loggerFactory;
private ICredentialProviderEvents CredentialProviderEvents;
private ICredentialProviderUserArray credentialProviderUsers;
private IntPtr credentialProviderEventsAdviseContext;
private bool notifyOnTileCollectionChange;
private List tiles;
///
/// 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();
if (CredentialProviderBase.LoggerFactory != this.loggerFactory)
{
CredentialProviderBase.LoggerFactory = this.loggerFactory;
}
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
public 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))
{
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(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);
}
}
}
}