using System; using System.Runtime.InteropServices; using Lithnet.CredentialProvider.Interop; using Microsoft.Extensions.Logging; namespace Lithnet.CredentialProvider { /// /// Represents a 'v1' user credential tile that implements the minimum functionality required by the credential provider framework /// /// Inheriting from this class enables you to provide a v1 credential tile. V1 credential tiles were introduced in Windows Vista. These tiles are not personalized. See the Microsoft documentation on ICredentialProviderCredential for more information public abstract partial class CredentialProviderCredential1Tile { private protected readonly ILogger logger; private protected ICredentialProviderCredentialEvents events; private protected ICredentialProviderCredentialEvents2 events2; private protected ControlCollection controls; protected CredentialProviderCredential1Tile(CredentialProviderBase credentialProvider) { this.CredentialProvider = credentialProvider; this.logger = credentialProvider.LoggerFactory.CreateLogger(this.GetType()); } internal CredentialProviderBase CredentialProvider { get; } /// /// Gets a value indicating if this is a generic, as opposed to a personalized tile /// public virtual bool IsGenericTile => true; /// /// Gets a value that indicates if this tile is currently selected by the user /// public bool IsSelected { get; private set; } /// /// Gets a value that indicates if the user should be automatically logged on when the tile is selected. The tile must also have IsDefault set to true. /// public bool IsAutoLogon { get; set; } /// /// Gets a value indicating if this should be the default time /// public bool IsDefault { get; set; } /// /// Gets the current usage scenario /// public UsageScenario UsageScenario => this.CredentialProvider.UsageScenario; /// /// Gets a list of controls assigned to this tile /// public ControlCollection Controls { get { if (this.controls == null) { this.controls = this.GenerateCredentialControls(); } return this.controls; } } private ControlCollection GenerateCredentialControls() { ControlCollection list = new ControlCollection(this); foreach (var control in this.CredentialProvider.Controls) { list.Add(control.Clone()); } list.Lock(); return list; } /// /// Gets the HWND of the parent of the credential provider, and notifies LogonUI or CredUI that we need to create a Window /// /// A HWND to the parentobject /// The method was called before the host has advised that is ready to rpovide events /// The request to obtain the parent window HWND failed public IntPtr CreateParentWindowHwnd() { if (this.events == null) { throw new InvalidOperationException("The Advise method has not yet been called by the host"); } var result = this.events.OnCreatingWindow(out IntPtr phwndOwner); if (result == HRESULT.S_OK) { return phwndOwner; } throw new COMException("Unable to obtain parent window handle", result); } /// /// Indicates to the host that multiple updates need to be made to the fields, and that it should delay updating the UI until /// /// public void BeginBulkFieldUpdate() { if (this.events2 == null) { throw new InvalidOperationException("The credential provider has not provided an ICredentialProviderCredentialEvents2 interface"); } this.events2.BeginFieldUpdates(); } /// /// Indicates to the host that the bulk update to fields has completed, and it can now update the UI according to the new values /// /// public void EndBulkFieldUpdate() { if (this.events2 == null) { throw new InvalidOperationException("The credential provider has not provided an ICredentialProviderCredentialEvents2 interface"); } this.events2.EndFieldUpdates(); } /// /// Called after the tile has been initialized. Override this method to perform post-initialization actions. /// public virtual void Initialize() { } /// /// Called when the user selects this tile /// protected virtual void OnSelected() { } /// /// Called when a user deselects this tile /// protected virtual void OnDeselected() { } /// /// Called just before credentials are serialized and returned to the host /// protected virtual void OnBeforeSerialize() { } /// /// Constructs the credential set from tile for serialization /// /// Override this method, and provide either a or response. /// /// The credential set ready to be serialized and returned to LogonUI/CredUI protected abstract CredentialResponseBase GetCredentials(); /// /// This method is called when the is set to and the user clicks the submit button. If you are supporting this scenario, you should override this method, and perform the password change operation using the information in the tile controls. Return a object to indicate if the operation was successful or not. /// /// A object protected virtual ChangePasswordResponse ChangePassword() { return null; } /// /// Called by LogonUI to translates a received error status code into the appropriate user-readable message. The Credential UI does not call this method. /// /// The NTSTATUS value that reflects the return value of the Winlogon call to LsaLogonUser. /// The NTSTATUS value that reflects the value pointed to by the SubStatus parameter of LsaLogonUser when that function returns after being called by Winlogon. /// Optional. The error message that will be displayed to the user. /// Optional. An icon that will shown on the credential protected virtual void OnLogonStatusReported(int ntStatusCode, int ntSubstatusCode, out string optionalStatusText, out StatusIcon optionalStatusIcon) { optionalStatusText = null; optionalStatusIcon = StatusIcon.None; } /// /// Performs serialization of the credentials by first calling and then serializing the response for return to LogonUI/CredUI. You may override this method to perform the serialization yourself. In that case, will not be called and does not need to be implemented. /// /// returned an invalid response protected virtual NativeSerializationResponse OnGetSerialization() { this.OnBeforeSerialize(); var response = new NativeSerializationResponse(); if (this.UsageScenario == UsageScenario.Logon || this.UsageScenario == UsageScenario.UnlockWorkstation || this.UsageScenario == UsageScenario.CredUI || this.UsageScenario == UsageScenario.PLAP) { var credentials = this.GetCredentials(); response.OptionalStatusText = credentials?.StatusText; response.OptionalStatusIcon = credentials?.StatusIcon ?? StatusIcon.None; if (credentials?.IsSuccess != true) { response.SerializationResponse = SerializationResponse.NoCredentialNotFinished; response.HResult = HRESULT.S_OK; return response; } var serializer = new CredentialSerializer(this.CredentialProvider.LoggerFactory); if (credentials is CredentialResponseSecure s) { response.SerializedCredentials = serializer.GenerateCredentialSerialization(credentials.Domain, credentials.Username, s.Password, this.UsageScenario == UsageScenario.UnlockWorkstation, this.CredentialProvider.CredentialProviderId); } else if (credentials is CredentialResponseInsecure i) { response.SerializedCredentials = serializer.GenerateCredentialSerialization(credentials.Domain, credentials.Username, i.Password, this.UsageScenario == UsageScenario.UnlockWorkstation, this.CredentialProvider.CredentialProviderId); } else { throw new InvalidOperationException($"Unknown response type from {nameof(GetCredentials)}"); } response.HResult = HRESULT.S_OK; response.SerializationResponse = SerializationResponse.ReturnCredentialFinished; return response; } else if (this.UsageScenario == UsageScenario.ChangePassword) { var result = this.ChangePassword(); if (result?.IsSuccess == true) { response.SerializationResponse = SerializationResponse.NoCredentialFinished; response.HResult = HRESULT.S_OK; } else { response.SerializationResponse = SerializationResponse.NoCredentialNotFinished; response.HResult = HRESULT.S_OK; } response.OptionalStatusText = result?.StatusText; response.OptionalStatusIcon = result?.StatusIcon ?? StatusIcon.None; return response; } response.HResult = HRESULT.E_NOTIMPL; return response; } } }