Files
SGU-CredentialProvider/src/Lithnet.CredentialProvider/CredentialProviderBase.cs
T
Ryan Newington 1205f217b3 Cleans up static loggers
Adds test projects for different frameworks and architectures
Adds registration tool assembly
2023-01-29 17:30:37 +11:00

246 lines
9.3 KiB
C#

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
{
/// <summary>
/// This class represents the base of a credential provider. Inherit from this class to create a new credential provider.
/// </summary>
public abstract partial class CredentialProviderBase
{
private readonly ILogger logger;
private ICredentialProviderEvents CredentialProviderEvents;
private ICredentialProviderUserArray credentialProviderUsers;
private IntPtr credentialProviderEventsAdviseContext;
private bool notifyOnTileCollectionChange;
private List<CredentialProviderCredential1Tile> tiles;
internal ILoggerFactory LoggerFactory { get; }
/// <summary>
/// Gets the GUID of the credential provider
/// </summary>
public Guid CredentialProviderId { get; private set; }
/// <summary>
/// Gets the list of users that were supplied to the credential provider from LogonUI
/// </summary>
public IReadOnlyList<CredentialProviderUser> SuppliedUsers { get; private set; }
/// <summary>
/// Gets the usage scenario communicated by LogonUI or CreDUI
/// </summary>
public UsageScenario UsageScenario { get; private set; }
/// <summary>
/// Gets the list of controls used by this credential provider
/// </summary>
public ControlCollection Controls { get; private set; }
/// <summary>
/// Gets a list of the tiles created for this credential provider
/// </summary>
public IReadOnlyList<CredentialProviderCredential1Tile> Tiles { get; private set; }
/// <summary>
/// Provides access to the serialized input data provided by CredUI
/// </summary>
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);
}
/// <summary>
/// Gets a logger factory. Override this method and provide an implementation of <c ref="ILoggerFactory"/> to enable credential provider logging
/// </summary>
/// <returns>An ILoggerFactory instance</returns>
protected virtual ILoggerFactory GetLoggerFactory() { return NullLoggerFactory.Instance; }
/// <summary>
/// Gets a value indicating if the credential provider supports the <c ref="UsageScenario"/> provided by LogonUI or CredUI
/// </summary>
/// <param name="cpus">The usage scenario</param>
/// <param name="dwFlags">Additional flags provided by CredUI</param>
/// <returns>True, if the credential provider can handle the specified usage scenario, or false if it cannot</returns>
public abstract bool IsUsageScenarioSupported(UsageScenario cpus, CredUIWinFlags dwFlags);
/// <summary>
/// Gets the set of controls used by this provider to render the UI
/// </summary>
/// <param name="cpus">The usage scenario to obtain the controls for</param>
/// <returns>A collection of ControlBase objects</returns>
public abstract IEnumerable<ControlBase> GetControls(UsageScenario cpus);
/// <summary>
/// Gets a value that indicates if the specified user should have a tile rendered for them by this UI
/// </summary>
/// <param name="user">Details of the user provided by LogonUI or CredUI</param>
/// <returns>A value indicating if this credential provider should show a tile for this user</returns>
public abstract bool ShouldIncludeUserTile(CredentialProviderUser user);
/// <summary>
/// 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.
/// </summary>
public abstract bool ShouldIncludeGenericTile();
/// <summary>
/// Adds additional user tiles to the collection, and notifies LogonUI that new tiles are available
/// </summary>
/// <param name="tiles">One or more credential tiles to add</param>
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();
}
/// <summary>
/// Removes one or more user tiles, and notifies LogonUI that tiles have been removed
/// </summary>
/// <param name="tiles">The crendential tiles to remove</param>
public void RemoveUserTiles(params CredentialProviderCredential1Tile[] tiles)
{
if (tiles == null)
{
return;
}
foreach (var tile in tiles)
{
this.tiles.Remove(tile);
}
this.NotifyHostOfTileCollectionChange();
}
/// <summary>
/// This method is used to generate the generic tile for this credential provider. This is called when <c ref="ShouldIncludeGenericTile"/> return true
/// </summary>
public abstract CredentialProviderCredential1Tile CreateGenericTile();
/// <summary>
/// Creates a credential tile for the specified user
/// </summary>
/// <param name="user">The user to create the tile for</param>
public abstract CredentialProviderCredential1Tile CreateUserTile(CredentialProviderUser user);
/// <summary>
/// This method is called when the LogonUI or CredUI provides inbound credential data. Override this method to respond to the incoming data.
/// </summary>
/// <param name="inboundSerialization">The inbound serialized credential </param>
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<CredentialProviderCredential1Tile> GenerateSuppliedUserTiles()
{
this.BuildControls();
var tiles = new List<CredentialProviderCredential1Tile>();
var users = new List<CredentialProviderUser>();
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<CredentialProviderCredential1Tile>(this.GenerateSuppliedUserTiles());
this.Tiles = this.tiles.AsReadOnly();
}
private void NotifyHostOfTileCollectionChange()
{
if (this.notifyOnTileCollectionChange)
{
this.CredentialProviderEvents?.CredentialsChanged(this.credentialProviderEventsAdviseContext);
}
}
}
}