Initial commit

This commit is contained in:
Ryan Newington
2023-01-28 21:15:17 +11:00
parent eb7b6fa4eb
commit 52e75c1748
90 changed files with 7306 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
## Third-party notices
This software contains portions or derivative works of the following products
### CredProvider.NET
https://github.com/SteveSyfuhs/CredProvider.NET
MIT License
Copyright (c) 2017 Steve Syfuhs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
### CredNet library
https://github.com/Sajidur78/CredNet
MIT License
Copyright 2020 Sajid
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+226
View File
@@ -0,0 +1,226 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = true:suggestion
dotnet_style_qualification_for_field = true
dotnet_style_qualification_for_method = true:suggestion
dotnet_style_qualification_for_property = true:suggestion
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = 0
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied members
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
csharp_style_prefer_readonly_struct = true
# Code-block preferences
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_top_level_statements = false
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
@@ -0,0 +1,60 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Samples
{
public static class CredUI
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CREDUI_INFO
{
public int cbSize;
public IntPtr hwndParent;
public string pszMessageText;
public string pszCaptionText;
public IntPtr hbmBanner;
}
[DllImport("credui.dll", CharSet = CharSet.Auto)]
public static extern int CredUIPromptForWindowsCredentials(
ref CREDUI_INFO uiInfo,
int authError,
ref uint authPackage,
IntPtr InAuthBuffer,
uint InAuthBufferSize,
out IntPtr refOutAuthBuffer,
out uint refOutAuthBufferSize,
ref bool fSave,
uint flags
);
public static void Prompt(string caption, string message)
{
var uiInfo = new CREDUI_INFO()
{
pszCaptionText = caption,
pszMessageText = message
};
uiInfo.cbSize = Marshal.SizeOf(uiInfo);
uint authPackage = 0;
var save = false;
CredUIPromptForWindowsCredentials(
ref uiInfo,
0,
ref authPackage,
IntPtr.Zero,
0,
out IntPtr outCredBuffer,
out uint outCredSize,
ref save,
0
);
Marshal.FreeCoTaskMem(outCredBuffer);
}
}
}
@@ -0,0 +1,36 @@
# Using the sample credential provider
## Installing the sample
In order to install and run the sample app, you have to register the COM component
Build the EXE, and from an elevated command prompt, change to the bin folder, and run the following commands
```
%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm /codebase "Lithnet.CredentialProvider.Samples.exe"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{1A3993B6-EB2B-44BB-A788-7AB1711DFF16}" /f
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{1A3993B6-EB2B-44BB-A788-7AB1711DFF16}" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Samples.TestCredentialProvider"
```
## Disable the sample
To disable the credential provider, run the following command.
```
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{1A3993B6-EB2B-44BB-A788-7AB1711DFF16}" /v "Disabled" /t REG_DWORD /f /d 1
```
## Re-enable the sample
To enable the provider again after disabling it, run the following command.
```
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{1A3993B6-EB2B-44BB-A788-7AB1711DFF16}" /v "Disabled" /t REG_DWORD /f /d 0
```
## Uninstalling the sample
To remove the credential provider, run the following command.
```
%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm /u "Lithnet.CredentialProvider.Samples.exe"
REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{1A3993B6-EB2B-44BB-A788-7AB1711DFF16}" /f
```
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Lithnet.CredentialProvider" Version="1.0.0-beta.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="NLog" Version="5.1.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -0,0 +1,44 @@
using System;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Extensions.Logging;
namespace Lithnet.CredentialProvider.Samples
{
internal static class Program
{
internal static ILoggerFactory LoggerFactory { get; }
static Program()
{
/*
This sample uses NLog to capture trace events from the provider, but you can use any
logging system compatible with Microsoft.Extensions.Logging;
*/
var config = new NLog.Config.LoggingConfiguration();
var logconsole = new NLog.Targets.ConsoleTarget("logconsole");
/*
Add file based logging if required
*/
// var logfile = new NLog.Targets.FileTarget("logfile") { FileName = "c:\\file.txt" };
//config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile);
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logconsole);
LogManager.Configuration = config;
Program.LoggerFactory = new NLogLoggerFactory(new NLogLoggerProvider(new NLogProviderOptions() { ReplaceLoggerFactory = true }, LogManager.LogFactory));
}
static void Main(string[] args)
{
CredUI.Prompt("Login with Cred UI", "Select your favorite credential provider");
Console.WriteLine("Done! Press any key to exit");
Console.ReadKey();
}
}
}
@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Lithnet.CredentialProvider.Samples {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Lithnet.CredentialProvider.Samples.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap TileIcon {
get {
object obj = ResourceManager.GetObject("TileIcon", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="TileIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\TileIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>
Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider.Samples
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Lithnet.CredentialProvider.Samples.TestCredentialProvider")]
[Guid("1A3993B6-EB2B-44BB-A788-7AB1711DFF16")]
public class TestCredentialProvider : CredentialProviderBase
{
private static readonly ILogger logger = Program.LoggerFactory.CreateLogger<TestCredentialProvider>();
public TestCredentialProvider()
{
}
public override ILoggerFactory GetLoggerFactory()
{
return Program.LoggerFactory;
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
{
var password = new SecurePasswordTextboxControl(TestCredentialProviderControlKeys.Password, "Password");
if (cpus == UsageScenario.ChangePassword)
{
var confirmPassword = new SecurePasswordTextboxControl(TestCredentialProviderControlKeys.ConfirmPassword, "Confirm password");
yield return new TextboxControl(TestCredentialProviderControlKeys.Username, "Username");
yield return password;
yield return confirmPassword;
yield return new SubmitButtonControl(TestCredentialProviderControlKeys.ButtonSubmit, "Submit", confirmPassword);
}
else
{
yield return new CredentialProviderLabelControl(TestCredentialProviderControlKeys.LabelCredentialProvider, "Login with showcase credential provider");
yield return new CredentialProviderLogoControl(TestCredentialProviderControlKeys.ImageCredentialProvider, "Credential provider logo", Resources.TileIcon);
yield return new CredentialProviderLogoControl(TestCredentialProviderControlKeys.ImageUserTile, "User tile image", Resources.TileIcon);
yield return new LargeLabelControl(TestCredentialProviderControlKeys.LabelLargeHeading, "The is our showcase credential provider");
yield return new SmallLabelControl(TestCredentialProviderControlKeys.LabelSmallHeading, "Let's see what we can do");
yield return new CheckboxControl(TestCredentialProviderControlKeys.Checkbox, "A checkbox");
yield return new SmallLabelControl(TestCredentialProviderControlKeys.LabelCheckboxValue, "The check box is currently unchecked");
yield return new CommandLinkControl(TestCredentialProviderControlKeys.CommandLinkCheckboxValue, "Click this link to change the check box value in code behind");
yield return new ComboboxControl(TestCredentialProviderControlKeys.Combobox, "Items to choose from:");
yield return new SmallLabelControl(TestCredentialProviderControlKeys.LabelComboboxSelectedItem, "This is the currently selected item: <none>");
yield return new CommandLinkControl(TestCredentialProviderControlKeys.CommandLinkComboboxAdd, "Add a random item to the combo box");
yield return new CommandLinkControl(TestCredentialProviderControlKeys.CommandLinkComboboxRemove, "Remove the last item from the combo box");
yield return new TextboxControl(TestCredentialProviderControlKeys.Username, "Username");
yield return new CommandLinkControl(TestCredentialProviderControlKeys.CommandLinkUsername, "Click this link to generate a random username");
yield return password;
yield return new SubmitButtonControl(TestCredentialProviderControlKeys.ButtonSubmit, "Submit", password);
}
}
public override bool IsUsageScenarioSupported(UsageScenario cpus, CredUIWinFlags dwFlags)
{
switch (cpus)
{
case UsageScenario.Logon:
case UsageScenario.UnlockWorkstation:
case UsageScenario.CredUI:
case UsageScenario.ChangePassword:
return true;
default:
return false;
}
}
public override bool ShouldIncludeUserTile(CredentialProviderUser user)
{
return true;
}
public override bool ShouldIncludeGenericTile()
{
return true;
}
public override CredentialProviderCredential1Tile CreateGenericTile()
{
return new TestCredentialProviderTile(this);
}
public override CredentialProviderCredential1Tile CreateUserTile(CredentialProviderUser user)
{
return new TestCredentialProviderTile(this, user);
}
}
}
@@ -0,0 +1,23 @@
namespace Lithnet.CredentialProvider.Samples
{
internal static class TestCredentialProviderControlKeys
{
internal const string LabelCredentialProvider = "LabelCredentialProvider";
internal const string ImageCredentialProvider = "ImageCredentialProvider";
internal const string LabelLargeHeading = "LabelHeadingLarge";
internal const string LabelSmallHeading = "LabelHeadingSmall";
internal const string Username = "TextUsername";
internal const string CommandLinkUsername = "CommandLinkUsername";
internal const string Password = "TextPassword";
internal const string ConfirmPassword = "TextPasswordConfirm";
internal const string Checkbox = "Checkbox";
internal const string LabelCheckboxValue = "LabelCheckbox";
internal const string CommandLinkCheckboxValue = "CommandLinkCheckbox";
internal const string ImageUserTile = "UerTileImage";
internal const string Combobox = "Combobox";
internal const string LabelComboboxSelectedItem = "ComboSelectedItemLabel";
internal const string CommandLinkComboboxAdd = "CommandLinkComboboxAdd";
internal const string CommandLinkComboboxRemove = "CommandLinkComboboxRemove";
internal const string ButtonSubmit = "SubmitButton";
}
}
@@ -0,0 +1,192 @@
using System;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider.Samples
{
public class TestCredentialProviderTile : CredentialProviderCredential2Tile
{
private TextboxControl UsernameControl;
private SecurePasswordTextboxControl PasswordControl;
private SecurePasswordTextboxControl PasswordConfirmControl;
private ComboboxControl ComboboxControl;
private CheckboxControl CheckboxControl;
private SmallLabelControl CheckboxStateControl;
private SmallLabelControl ComboboxStateControl;
private ILogger logger = Program.LoggerFactory.CreateLogger<TestCredentialProviderTile>();
public TestCredentialProviderTile(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
}
public TestCredentialProviderTile(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider, user)
{
}
public string Username
{
get => UsernameControl.Text;
set => UsernameControl.Text = value;
}
public SecureString Password
{
get => PasswordControl.Password;
set => PasswordControl.Password = value;
}
public SecureString ConfirmPassword
{
get => PasswordConfirmControl.Password;
set => PasswordConfirmControl.Password = value;
}
public string ComboboxSelectedItemLabel
{
get => ComboboxStateControl.Label;
set => ComboboxStateControl.Label = value;
}
public bool IsChecked
{
get => CheckboxControl.IsChecked;
set => CheckboxControl.IsChecked = value;
}
public string CheckboxStateLabel
{
get => CheckboxStateControl.Label;
set => CheckboxStateControl.Label = value;
}
public override void Initialize()
{
if (UsageScenario == UsageScenario.ChangePassword)
{
PasswordConfirmControl = Controls.GetControl<SecurePasswordTextboxControl>(TestCredentialProviderControlKeys.ConfirmPassword);
}
PasswordControl = Controls.GetControl<SecurePasswordTextboxControl>(TestCredentialProviderControlKeys.Password);
CheckboxControl = Controls.GetControl<CheckboxControl>(TestCredentialProviderControlKeys.Checkbox);
CheckboxStateControl = Controls.GetControl<SmallLabelControl>(TestCredentialProviderControlKeys.LabelCheckboxValue);
Controls.GetControl<CommandLinkControl>(TestCredentialProviderControlKeys.CommandLinkCheckboxValue).OnClick = () =>
{
CheckboxControl.IsChecked = !CheckboxControl.IsChecked;
};
CheckboxControl.PropertyChanged += CheckboxControl_PropertyChanged;
UsernameControl = Controls.GetControl<TextboxControl>(TestCredentialProviderControlKeys.Username);
Controls.GetControl<CommandLinkControl>(TestCredentialProviderControlKeys.CommandLinkUsername).OnClick = () =>
{
Username = Guid.NewGuid().ToString();
};
ComboboxStateControl = Controls.GetControl<SmallLabelControl>(TestCredentialProviderControlKeys.LabelComboboxSelectedItem);
ComboboxControl = Controls.GetControl<ComboboxControl>(TestCredentialProviderControlKeys.Combobox);
ComboboxControl.PropertyChanged += ComboboxControl_PropertyChanged;
ComboboxControl.ComboBoxItems.Add("Item 1");
ComboboxControl.ComboBoxItems.Add("Item 2");
ComboboxControl.ComboBoxItems.Add("Item 3");
ComboboxControl.SelectedItemIndex = 0;
int count = 3;
Controls.GetControl<CommandLinkControl>(TestCredentialProviderControlKeys.CommandLinkComboboxAdd).OnClick = () =>
{
ComboboxControl.ComboBoxItems.Add($"Item {++count}");
};
Controls.GetControl<CommandLinkControl>(TestCredentialProviderControlKeys.CommandLinkComboboxRemove).OnClick = () =>
{
if (ComboboxControl.ComboBoxItems.Count > 0)
{
ComboboxControl.ComboBoxItems.Remove(ComboboxControl.ComboBoxItems[ComboboxControl.ComboBoxItems.Count - 1]);
}
};
Username = User?.QualifiedUserName;
}
private void ComboboxControl_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ComboboxControl.SelectedItemIndex))
{
ComboboxSelectedItemLabel = $"Selected item: {(ComboboxControl.SelectedItem ?? "(none)")}";
}
}
private void CheckboxControl_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
CheckboxStateControl.Label = $"The check box is {(CheckboxControl.IsChecked ? "checked" : "not checked")}";
}
protected override CredentialResponseBase GetCredentials()
{
string username;
string domain;
if (Username.Contains("\\"))
{
domain = Username.Split('\\')[0];
username = Username.Split('\\')[1];
}
else
{
username = Username;
domain = Environment.MachineName;
}
var spassword = Controls.GetControl<SecurePasswordTextboxControl>(TestCredentialProviderControlKeys.Password).Password;
if (!IsChecked)
{
logger.LogTrace("Performing secure login");
return new CredentialResponseSecure()
{
IsSuccess = true,
Password = spassword,
Domain = domain,
Username = username
};
}
else
{
logger.LogTrace("Performing insecure login");
string plainTextPassword = GetPlainTextPasswordFromSecureString(spassword);
return new CredentialResponseInsecure()
{
IsSuccess = true,
Password = plainTextPassword,
Domain = domain,
Username = username
};
}
}
private string GetPlainTextPasswordFromSecureString(SecureString s)
{
string plainTextPassword;
IntPtr pPassword = IntPtr.Zero;
try
{
pPassword = Marshal.SecureStringToGlobalAllocUnicode(s);
plainTextPassword = Marshal.PtrToStringUni(pPassword);
}
finally
{
if (pPassword != IntPtr.Zero)
{
Marshal.FreeHGlobal(pPassword);
}
}
return plainTextPassword;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33205.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider", "Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj", "{9C07477C-ABB8-4DE5-B69C-3F8719D952C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Samples", "Lithnet.CredentialProvider.Samples\Lithnet.CredentialProvider.Samples.csproj", "{967DF0EB-F85A-4006-A3BF-0C558753A9DC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9E97082E-4E8D-4E8B-A320-40E2A1494C74}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9C07477C-ABB8-4DE5-B69C-3F8719D952C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C07477C-ABB8-4DE5-B69C-3F8719D952C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C07477C-ABB8-4DE5-B69C-3F8719D952C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C07477C-ABB8-4DE5-B69C-3F8719D952C6}.Release|Any CPU.Build.0 = Release|Any CPU
{967DF0EB-F85A-4006-A3BF-0C558753A9DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{967DF0EB-F85A-4006-A3BF-0C558753A9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{967DF0EB-F85A-4006-A3BF-0C558753A9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{967DF0EB-F85A-4006-A3BF-0C558753A9DC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0563C71A-762F-4784-887B-D51D14C92903}
EndGlobalSection
EndGlobal
@@ -0,0 +1,23 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// The <c ref="ChangePasswordResponse"/> object is used to communicate the results of a password change operation to LogonUI
/// </summary>
public class ChangePasswordResponse
{
/// <summary>
/// Gets or sets a value indicating if the password was successfully changed
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// Optional. Gets or sets the icon to use when displaying the status text
/// </summary>
public StatusIcon StatusIcon { get; set; }
/// <summary>
/// Options. Details about the result of the password change operation
/// </summary>
public string StatusText { get; set; }
}
}
@@ -0,0 +1,102 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// The base class of image-based controls
/// </summary>
public abstract class BitmapControl : ControlBase
{
private Bitmap bitmap;
private Color backgroundColor;
protected BitmapControl(string key, string label, bool isProviderLogo, Bitmap bitmap) :
base(key, label, FieldType.TileImage, isProviderLogo ? Guid.Parse(CredProviderConstants.CPFG_CREDENTIAL_PROVIDER_LOGO) : Guid.Empty)
{
this.bitmap = bitmap;
this.backgroundColor = Color.FromArgb(70, 70, 70);
}
protected BitmapControl(BitmapControl source) : base(source) { }
/// <summary>
/// Specifies the background color that should replace any transparent elements of the image. This defaults to #707070
/// </summary>
public Color BackgroundColor
{
get { return this.backgroundColor; }
set
{
if (this.backgroundColor != value)
{
this.backgroundColor = value;
this.RaisePropertyChanged();
}
}
}
/// <summary>
/// The image to be displayed
/// </summary>
public Bitmap Bitmap
{
get { return this.bitmap; }
set
{
if (this.bitmap != value)
{
this.bitmap = value;
if (this.Events is ICredentialProviderCredentialEvents3 e)
{
var buffer = this.GetBitmapBuffer(out uint size);
e.SetFieldBitmapBuffer(this.Credential, this.Id, size, buffer);
}
this.RaisePropertyChanged();
}
}
}
internal IntPtr GetHBitmap()
{
if (this.bitmap == null)
{
return IntPtr.Zero;
}
return this.Bitmap.GetHbitmap(this.BackgroundColor);
}
internal IntPtr GetBitmapBuffer(out uint size)
{
size = 0;
var hbitmap = this.GetHBitmap();
if (hbitmap == IntPtr.Zero)
{
return IntPtr.Zero;
}
var image = Bitmap.FromHbitmap(hbitmap);
IntPtr buffer = IntPtr.Zero;
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, ImageFormat.Bmp);
var bitmapBytes = ms.ToArray();
size = (uint)bitmapBytes.Length;
buffer = Marshal.AllocCoTaskMem(bitmapBytes.Length);
Marshal.Copy(bitmapBytes, 0, buffer, bitmapBytes.Length);
}
return buffer;
}
}
}
@@ -0,0 +1,57 @@
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A control that renders a check box in the credential UI
/// </summary>
public class CheckboxControl : ControlBase
{
private bool isChecked;
/// <summary>
/// Creates a new <c ref="CheckboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public CheckboxControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="CheckboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public CheckboxControl(string key, string label) : base(key, label, FieldType.CheckBox) { }
/// <summary>
/// Gets or sets a value indicating if the checkbox is currently checked
/// </summary>
public bool IsChecked
{
get { return this.isChecked; }
set
{
if (this.isChecked != value)
{
this.isChecked = value;
this.Events?.SetFieldCheckbox(this.Credential, this.Id, value ? 1 : 0, this.Label);
this.RaisePropertyChanged();
}
}
}
internal void SetIsCheckedInternal(bool value)
{
this.isChecked = value;
this.RaisePropertyChanged(nameof(this.IsChecked));
}
private CheckboxControl(CheckboxControl source) : base(source) { }
internal override ControlBase Clone()
{
var clone = new CheckboxControl(this);
clone.IsChecked = this.IsChecked;
return clone;
}
}
}
@@ -0,0 +1,110 @@
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A control that renders a comboxbox in the credential UI
/// </summary>
public class ComboboxControl : ControlBase
{
private int selectedItemIndex;
/// <summary>
/// Creates a new <c ref="ComboboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public ComboboxControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="ComboboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public ComboboxControl(string key, string label) : base(key, label, FieldType.ComboBox)
{
this.ComboBoxItems = new SimpleList();
this.ComboBoxItems.ItemAdded += this.ComboBoxItems_ItemAdded;
this.ComboBoxItems.ItemRemoved += this.ComboBoxItems_ItemRemoved;
}
private void ComboBoxItems_ItemRemoved(object sender, int e)
{
if (e == this.selectedItemIndex)
{
this.SelectedItemIndex = e - 1;
}
this.Events?.DeleteFieldComboBoxItem(this.Credential, this.Id, (uint)e);
}
private void ComboBoxItems_ItemAdded(object sender, string item)
{
this.Events?.AppendFieldComboBoxItem(this.Credential, this.Id, item);
if (this.selectedItemIndex == -1)
{
this.SelectedItemIndex = 0;
}
}
/// <summary>
/// Get the list of items in the combobox
/// </summary>
public SimpleList ComboBoxItems { get; }
/// <summary>
/// Gets or sets the index of the currently selected item in the combobox
/// </summary>
public int SelectedItemIndex
{
get { return this.selectedItemIndex; }
set
{
if (this.selectedItemIndex != value)
{
this.selectedItemIndex = value;
this.Events?.SetFieldComboBoxSelectedItem(this.Credential, this.Id, (uint)this.selectedItemIndex);
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(this.SelectedItem));
}
}
}
/// <summary>
/// Get the value of the currently selected item in the combobox
/// </summary>
public string SelectedItem
{
get
{
if (this.selectedItemIndex >= 0 && this.selectedItemIndex < this.ComboBoxItems.Count)
{
return this.ComboBoxItems[this.selectedItemIndex];
}
return null;
}
}
internal void SetComboboxSelectedItemIndexInternal(int index)
{
this.selectedItemIndex = index;
this.RaisePropertyChanged(nameof(this.SelectedItemIndex));
this.RaisePropertyChanged(nameof(this.SelectedItem));
}
private ComboboxControl(ComboboxControl source) : base(source)
{
this.ComboBoxItems = new SimpleList();
this.ComboBoxItems.ItemAdded += this.ComboBoxItems_ItemAdded;
this.ComboBoxItems.ItemRemoved += this.ComboBoxItems_ItemRemoved;
}
internal override ControlBase Clone()
{
var clone = new ComboboxControl(this);
clone.ComboBoxItems.AddRange(this.ComboBoxItems);
clone.SelectedItemIndex = this.SelectedItemIndex;
return clone;
}
}
}
@@ -0,0 +1,38 @@
using System;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a control that renders as a clickable text link in the credential UI
/// </summary>
public class CommandLinkControl : ControlBase
{
/// <summary>
/// Creates a new <c ref="CommandLinkControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public CommandLinkControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="CommandLinkControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public CommandLinkControl(string key, string label) : base(key, label, FieldType.CommandLink) { }
private CommandLinkControl(CommandLinkControl source) : base(source) { }
/// <summary>
/// Gets or sets an action to take when the link is clicked by the user
/// </summary>
public Action OnClick { get; set; }
internal override ControlBase Clone()
{
var clone = new CommandLinkControl(this);
clone.OnClick = this.OnClick;
return clone;
}
}
}
@@ -0,0 +1,202 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents the base of all control types
/// </summary>
public abstract class ControlBase : INotifyPropertyChanged
{
private static uint fieldIdCounter;
private FieldState state;
private FieldInteractiveState interactiveState;
private string label;
private FieldOptions options;
private protected ILogger logger = NullLogger.Instance;
public event PropertyChangedEventHandler PropertyChanged;
private protected ControlBase(ControlBase source)
{
this.Id = source.Id;
this.Key = source.Key;
this.Type = source.Type;
this.FieldTypeGuid = source.FieldTypeGuid;
this.state = source.State;
this.interactiveState = source.InteractiveState;
this.label = source.Label;
this.options = source.options;
}
private protected ControlBase(string key, FieldType type)
: this(key, null, type, Guid.Empty) { }
private protected ControlBase(string key, string label, FieldType type)
: this(key, label, type, Guid.Empty) { }
private protected ControlBase(string key, string label, FieldType type, Guid guidType)
{
this.Id = fieldIdCounter++;
this.Type = type;
this.FieldTypeGuid = guidType;
this.label = label;
this.state = FieldState.DisplayInSelectedTile;
this.interactiveState = FieldInteractiveState.None;
this.options = FieldOptions.None;
this.Key = key;
this.logger = CredentialProviderBase.LoggerFactory.CreateLogger(this.GetType());
}
internal void AssignCredential(ICredentialProviderCredential credential)
{
this.Credential = credential;
}
internal void AssignEvents(ICredentialProviderCredentialEvents events)
{
this.Events = events;
}
internal void UnassignEvents()
{
this.Events = null;
}
internal ICredentialProviderCredentialEvents Events { get; private set; }
internal ICredentialProviderCredential Credential { get; private set; }
/// <summary>
/// Gets the unique ID associated with this control
/// </summary>
public string Key { get; }
/// <summary>
/// Gets the internal numerical ID associated with this control
/// </summary>
public uint Id { get; }
/// <summary>
/// Gets or sets the label text for the control
/// </summary>
public string Label
{
get { return this.label; }
set
{
if (this.label != value)
{
this.label = value;
if (this.Type != FieldType.PasswordText &&
this.Type != FieldType.EditText)
{
this.Events?.SetFieldString(this.Credential, this.Id, Marshal.StringToCoTaskMemUni(this.label));
}
this.RaisePropertyChanged();
}
}
}
internal FieldType Type { get; }
internal Guid FieldTypeGuid { get; }
/// <summary>
/// Gets or sets the options that control the rendering of this field
/// </summary>
public FieldOptions Options
{
get
{
return this.options;
}
set
{
if (this.options != value)
{
this.options = value;
if (this.Events is ICredentialProviderCredentialEvents2 e)
{
e.SetFieldOptions(this.Credential, this.Id, this.options);
}
this.RaisePropertyChanged();
}
}
}
/// <summary>
/// Gets or sets the states in which this control should be displayed
/// </summary>
public FieldState State
{
get
{
return this.state;
}
set
{
if (this.state != value)
{
this.state = value;
this.Events?.SetFieldState(this.Credential, this.Id, this.state);
this.RaisePropertyChanged();
}
}
}
/// <summary>
/// Gets or sets the interactivity status of the control
/// </summary>
public FieldInteractiveState InteractiveState
{
get { return this.interactiveState; }
set
{
if (this.interactiveState != value)
{
this.interactiveState = value;
this.Events?.SetFieldInteractiveState(this.Credential, this.Id, this.interactiveState);
this.RaisePropertyChanged();
}
}
}
internal FieldDescriptor GetDescriptor()
{
return new FieldDescriptor
{
FieldType = this.Type,
FieldID = this.Id,
FieldTypeGuid = this.FieldTypeGuid,
Label = this.Label
};
}
/// <inheritdoc/>
public override string ToString()
{
return $"{this.Id}:{this.Type}:{this.Label}";
}
internal void RaisePropertyChanged([CallerMemberName] string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
internal abstract ControlBase Clone();
}
}
@@ -0,0 +1,197 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A collection of control objects
/// </summary>
public class ControlCollection : IEnumerable<ControlBase>
{
private readonly Dictionary<uint, ControlBase> idControls = new Dictionary<uint, ControlBase>();
private readonly Dictionary<string, ControlBase> keyedControls = new Dictionary<string, ControlBase>(StringComparer.OrdinalIgnoreCase);
private readonly List<ControlBase> controls = new List<ControlBase>();
private ICredentialProviderCredentialEvents events;
private ICredentialProviderCredential credential;
private bool locked;
internal ControlCollection() { }
internal ControlCollection(CredentialProviderCredential1Tile credential)
{
this.credential = credential;
}
internal void SetCredential(ICredentialProviderCredential credential)
{
if (this.credential != null && this.credential != credential)
{
throw new InvalidOperationException("Cannot set credential more than once");
}
this.credential = credential;
}
internal void Add(ControlBase control)
{
if (this.locked)
{
throw new InvalidOperationException("Cannot add controls after the provider has had GetFieldDescriptorCount called");
}
if (this.credential != null)
{
control.AssignCredential(this.credential);
}
if (this.events != null)
{
control.AssignEvents(this.events);
}
if (this.keyedControls.ContainsKey(control.Key))
{
throw new ArgumentException("An control with the same key already exists");
}
this.idControls.Add(control.Id, control);
this.keyedControls.Add(control.Key, control);
this.controls.Add(control);
}
internal void AssignEvents(ICredentialProviderCredentialEvents events)
{
if (this.credential == null)
{
throw new InvalidOperationException("Unable to assign events until a credential has been set");
}
this.events = events;
foreach (var control in this.idControls.Values)
{
control.AssignEvents(events);
}
}
internal void UnassignEvents()
{
this.events = null;
foreach (var control in this.idControls.Values)
{
control.UnassignEvents();
}
}
/// <summary>
/// Gets a control from the collection
/// </summary>
/// <typeparam name="T">The type of control</typeparam>
/// <param name="key">The unique ID of the control</param>
/// <returns></returns>
public T GetControl<T>(string key) where T : ControlBase
{
return (T)this.keyedControls[key];
}
internal T GetControl<T>(uint id) where T : ControlBase
{
return (T)this.idControls[id];
}
internal bool TryGetControl<T>(uint id, FieldType type, out T control) where T : ControlBase
{
if (this.idControls.ContainsKey(id))
{
control = this.idControls[id] as T;
if (control != null)
{
if (control.Type == type)
{
return true;
}
}
}
control = null;
return false;
}
internal bool TryGetControl(uint id, out ControlBase control)
{
control = null;
if (this.idControls.ContainsKey(id))
{
control = this.idControls[id];
return true;
}
return false;
}
/// <summary>
/// Gets the total number of controls in the collection
/// </summary>
public int Count => this.idControls.Count;
internal ControlBase this[int index]
{
get
{
return this.controls[index];
}
}
internal void Lock()
{
this.locked = true;
}
/// <summary>
/// Gets a control by its unique ID
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public ControlBase this[string key]
{
get
{
return this.keyedControls[key];
}
}
/// <summary>
/// Get all controls of the specified type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
internal IEnumerable<ControlBase> GetByType(FieldType type)
{
foreach (var item in this.idControls)
{
if (item.Value.Type == type)
{
yield return item.Value;
}
}
}
/// <inheritdoc/>
public IEnumerator<ControlBase> GetEnumerator()
{
return this.idControls.Values.GetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return this.idControls.Values.GetEnumerator();
}
}
}
@@ -0,0 +1,32 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a control that provides the credential UI with the name of this credential provider
/// </summary>
public class CredentialProviderLabelControl : SmallLabelControl
{
/// <summary>
/// Creates a new <c ref="CredentialProviderLabelControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public CredentialProviderLabelControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="CredentialProviderLabelControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public CredentialProviderLabelControl(string key, string label) : base(key, label, true)
{
this.State = FieldState.DisplayInDeselectedTile;
}
private CredentialProviderLabelControl(CredentialProviderLabelControl source) : base(source) { }
internal override ControlBase Clone()
{
return new CredentialProviderLabelControl(this);
}
}
}
@@ -0,0 +1,44 @@
using System.Drawing;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a control that provides the credential UI with the logo of this credential provider
/// </summary>
public class CredentialProviderLogoControl : BitmapControl
{
/// <summary>
/// Creates a new <c ref="CredentialProviderLogoControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public CredentialProviderLogoControl(string key) : this(key, null, null) { }
/// <summary>
/// Creates a new <c ref="CredentialProviderLogoControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control. This value is not displayed to the user</param>
public CredentialProviderLogoControl(string key, string label) : this(key, label, null) { }
/// <summary>
/// Creates a new <c ref="CredentialProviderLogoControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
/// <param name="bitmap">The bitmap to use as the logo</param>
public CredentialProviderLogoControl(string key, string label, Bitmap bitmap) : base(key, label, true, bitmap)
{
this.State = FieldState.DisplayInDeselectedTile;
}
private CredentialProviderLogoControl(CredentialProviderLogoControl source) : base(source) { }
internal override ControlBase Clone()
{
var clone = new CredentialProviderLogoControl(this);
clone.Bitmap = this.Bitmap;
clone.BackgroundColor = this.BackgroundColor;
return clone;
}
}
}
@@ -0,0 +1,69 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A masked password text box, with a Password property that provides the password in plain text
/// </summary>
public class InsecurePasswordTextboxControl : ControlBase
{
private string password;
/// <summary>
/// Creates a new <c ref="InsecurePasswordTextboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public InsecurePasswordTextboxControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="InsecurePasswordTextboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public InsecurePasswordTextboxControl(string key, string label) : base(key, label, FieldType.PasswordText) { }
private InsecurePasswordTextboxControl(InsecurePasswordTextboxControl source) : base(source) { }
/// <summary>
/// Gets or sets the password value in plain text
/// </summary>
public string Password
{
get { return this.password; }
set
{
if (this.password != value)
{
this.password = value;
if (this.password?.Length > 0)
{
var ptr = Marshal.StringToCoTaskMemUni(this.password);
this.logger.LogTrace($"0x:{ptr.ToString("X16")} - Created ptr for outgoing SetFieldString");
this.Events?.SetFieldString(this.Credential, this.Id, ptr);
}
else
{
this.Events?.SetFieldString(this.Credential, this.Id, IntPtr.Zero);
}
this.RaisePropertyChanged(nameof(this.Password));
}
}
}
internal void SetPasswordInternal(string s)
{
this.password = s;
this.RaisePropertyChanged(nameof(this.Password));
}
internal override ControlBase Clone()
{
var clone = new InsecurePasswordTextboxControl(this);
clone.password = this.password;
return clone;
}
}
}
@@ -0,0 +1,30 @@
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A text label that appears in a larger font size
/// </summary>
public class LargeLabelControl : ControlBase
{
/// <summary>
/// Creates a new <c ref="LargeLabelControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public LargeLabelControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="LargeLabelControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public LargeLabelControl(string key, string label) : base(key, label, FieldType.LargeText) { }
private LargeLabelControl(LargeLabelControl source) : base(source) { }
internal override ControlBase Clone()
{
return new LargeLabelControl(this);
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A masked password text box, with a Password property that provides the password as a secure string
/// </summary>
public class SecurePasswordTextboxControl : ControlBase
{
private SecureString password;
/// <summary>
/// Creates a new <c ref="SecurePasswordTextboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public SecurePasswordTextboxControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="SecurePasswordTextboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public SecurePasswordTextboxControl(string key, string label) : base(key, label, FieldType.PasswordText) { }
/// <summary>
/// Gets or sets the password value as a secure string
/// </summary>
public SecureString Password
{
get { return this.password; }
set
{
if (this.password != value)
{
this.password = value;
if (this.password?.Length > 0)
{
var ptr = Marshal.SecureStringToCoTaskMemUnicode(this.password);
Trace.WriteLine($"0x:{ptr.ToString("X16")} - CONTROL: Created ptr for outgoing SetFieldString");
this.Events?.SetFieldString(this.Credential, this.Id, ptr);
}
else
{
this.Events?.SetFieldString(this.Credential, this.Id, IntPtr.Zero);
}
this.RaisePropertyChanged(nameof(this.Password));
}
}
}
internal void SetPasswordInternal(SecureString s)
{
this.password = s;
this.RaisePropertyChanged(nameof(this.Password));
}
private SecurePasswordTextboxControl(SecurePasswordTextboxControl source) : base(source) { }
internal override ControlBase Clone()
{
var clone = new SecurePasswordTextboxControl(this);
clone.password = this.password;
return clone;
}
}
}
@@ -0,0 +1,83 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a simple list of strings with events
/// </summary>
public class SimpleList : IEnumerable<string>
{
private readonly List<string> backingList = new List<string>();
public event EventHandler<string> ItemAdded;
public event EventHandler<int> ItemRemoved;
/// <summary>
/// Adds the specified value to the list
/// </summary>
/// <param name="value">A string value to add to the list</param>
public void Add(string value)
{
this.backingList.Add(value);
this.ItemAdded?.Invoke(this, value);
}
/// <summary>
/// Removes the specified value to the list, if found
/// </summary>
/// <param name="value">A string value to remove from the list</param>
public void Remove(string value)
{
var index = this.backingList.IndexOf(value);
if (index >= 0)
{
this.backingList.RemoveAt(index);
this.ItemRemoved?.Invoke(this, index);
}
}
/// <summary>
/// Adds the specified values to the list
/// </summary>
/// <param name="items">Items to add to the list</param>
public void AddRange(IEnumerable<string> items)
{
foreach (var item in items)
{
this.Add(item);
}
}
/// <summary>
/// Gets the total number of items in the list
/// </summary>
public int Count => this.backingList.Count;
/// <inheritdoc/>
public IEnumerator<string> GetEnumerator()
{
return this.backingList.GetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return this.backingList.GetEnumerator();
}
/// <summary>
/// Gets an item from the list based on its index
/// </summary>
/// <param name="index">The index of the item to get</param>
/// <returns>The item matching the index provided</returns>
public string this[int index]
{
get
{
return this.backingList[index];
}
}
}
}
@@ -0,0 +1,40 @@
using System;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A text label that appears in a smaller font size
/// </summary>
public class SmallLabelControl : ControlBase
{
/// <summary>
/// Creates a new <c ref="SmallLabelControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public SmallLabelControl(string key) : this(key, null, false) { }
/// <summary>
/// Creates a new <c ref="SmallLabelControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public SmallLabelControl(string key, string label) : this(key, label, false) { }
private protected SmallLabelControl(SmallLabelControl source) : base(source) { }
private protected SmallLabelControl(string key, string label, bool isProviderLabel) :
base(key, label, FieldType.SmallText, isProviderLabel ? Guid.Parse(CredProviderConstants.CPFG_CREDENTIAL_PROVIDER_LABEL) : Guid.Empty)
{
if (isProviderLabel)
{
this.State = FieldState.DisplayInDeselectedTile;
}
}
internal override ControlBase Clone()
{
return new SmallLabelControl(this);
}
}
}
@@ -0,0 +1,47 @@
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// The submit button for the login form. This button is ignored when the credential provider is invoked via CredUI. Note, there can only be one submit button.
/// </summary>
public class SubmitButtonControl : ControlBase
{
/// <summary>
/// Creates a new <c ref="SubmitButtonControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="adjacentToControl">The control that the submit button should appear adjacent to</param>
public SubmitButtonControl(string key, ControlBase adjacentToControl) : this(key, null, adjacentToControl) { }
/// <summary>
/// Creates a new <c ref="SubmitButtonControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
/// <param name="adjacentToControl">The control that the submit button should appear adjacent to</param>
public SubmitButtonControl(string key, string label, ControlBase adjacentToControl) : base(key, label, FieldType.Submit)
{
this.AdjacentToId = adjacentToControl.Id;
this.AdjacentToKey = adjacentToControl.Key;
this.State = FieldState.DisplayInSelectedTile;
}
/// <summary>
/// Gets the unique ID of the control that the button is adjacent to
/// </summary>
public string AdjacentToKey { get; }
internal uint AdjacentToId { get; private set; }
private SubmitButtonControl(SubmitButtonControl source) : base(source) { }
internal override ControlBase Clone()
{
var clone = new SubmitButtonControl(this);
clone.AdjacentToId = this.AdjacentToId;
return clone;
}
}
}
@@ -0,0 +1,61 @@
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A text box control used to capture use input
/// </summary>
public class TextboxControl : ControlBase
{
private string text;
private TextboxControl(TextboxControl source) : base(source) { }
/// <summary>
/// Creates a new <c ref="TextboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public TextboxControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="TextboxControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public TextboxControl(string key, string label) : base(key, label, FieldType.EditText)
{
}
/// <summary>
/// Gets or sets the text value of the text box
/// </summary>
public string Text
{
get { return this.text; }
set
{
if (this.text != value)
{
this.text = value;
this.Events?.SetFieldString(this.Credential, this.Id, Marshal.StringToCoTaskMemUni(value));
this.RaisePropertyChanged();
}
}
}
internal void SetTextInternal(string text)
{
this.text = text;
this.RaisePropertyChanged(nameof(this.Text));
}
internal override ControlBase Clone()
{
var clone = new TextboxControl(this);
clone.Text = this.Text;
return clone;
}
}
}
@@ -0,0 +1,42 @@
using System.Drawing;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A control that displays the user's tile image
/// </summary>
public class UserTileControl : BitmapControl
{
/// <summary>
/// Creates a new <c ref="UserTileControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
public UserTileControl(string key) : this(key, null, null) { }
/// <summary>
/// Creates a new <c ref="UserTileControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
public UserTileControl(string key, string label) : this(key, label, null) { }
/// <summary>
/// Creates a new <c ref="UserTileControl"/> control
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
/// <param name="bitmap">The bitmap to use as the user's tile image</param>
public UserTileControl(string key, string label, Bitmap bitmap) : base(key, label, false, bitmap) { }
private UserTileControl(UserTileControl source) : base(source) { }
internal override ControlBase Clone()
{
var clone = new UserTileControl(this);
clone.Bitmap = this.Bitmap;
clone.BackgroundColor = this.BackgroundColor;
return clone;
}
}
}
@@ -0,0 +1,214 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
public abstract partial class CredentialProviderBase : ICredentialProvider
{
int ICredentialProvider.SetUsageScenario(UsageScenario cpus, CredUIWinFlags dwFlags)
{
try
{
this.logger.LogTrace($"SetUsageScenario: Usage: {cpus} flags: {dwFlags}");
this.UsageScenario = cpus;
if (this.IsUsageScenarioSupported(cpus, (CredUIWinFlags)dwFlags))
{
return HRESULT.S_OK;
}
else
{
return HRESULT.E_NOTIMPL;
}
}
catch (Exception ex)
{
this.logger.LogError(ex, "SetUsageScenario failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProvider.SetSerialization(ref CredentialSerialization pcpcs)
{
try
{
this.InboundSerialization = pcpcs;
this.OnSetSerialization(pcpcs);
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "UnAdvSetSerializationise failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProvider.Advise(ICredentialProviderEvents pcpe, IntPtr upAdviseContext)
{
try
{
this.logger.LogTrace($"Advise: {upAdviseContext}");
if (pcpe != null)
{
this.credentialProviderEventsAdviseContext = upAdviseContext;
this.CredentialProviderEvents = pcpe;
var intPtr = Marshal.GetIUnknownForObject(pcpe);
Marshal.AddRef(intPtr);
}
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Advise failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProvider.UnAdvise()
{
try
{
this.logger.LogTrace($"Unadvise");
if (this.CredentialProviderEvents != null)
{
var intPtr = Marshal.GetIUnknownForObject(this.CredentialProviderEvents);
Marshal.Release(intPtr);
this.CredentialProviderEvents = null;
this.credentialProviderEventsAdviseContext = IntPtr.Zero;
}
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "UnAdvise failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProvider.GetFieldDescriptorCount(out uint pdwCount)
{
pdwCount = 0;
try
{
this.logger.LogTrace("GetFieldDescriptorCount");
this.BuildControls();
this.Controls.Lock();
pdwCount = (uint)this.Controls.Count;
this.logger.LogTrace($"GetFieldDescriptorCount return {pdwCount}");
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetFieldDescriptorCount failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProvider.GetFieldDescriptorAt(uint dwIndex, out IntPtr ppcpfd)
{
ppcpfd = IntPtr.Zero;
try
{
this.logger.LogTrace($"GetFieldDescriptorAt {dwIndex}");
if (dwIndex >= this.Controls.Count)
{
return HRESULT.E_INVALIDARG;
}
var item = this.Controls[(int)dwIndex].GetDescriptor();
var size = Marshal.SizeOf<FieldDescriptor>();
var ptr = Marshal.AllocCoTaskMem(size);
Marshal.StructureToPtr<FieldDescriptor>(item, ptr, false);
ppcpfd = ptr;
this.logger.LogTrace($"GetFieldDescriptorAt {dwIndex}: returning {item}");
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetFieldDescriptorAt failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProvider.GetCredentialCount(out uint pdwCount, out uint pdwDefault, out int pbAutoLogonWithDefault)
{
pdwCount = 0;
pdwDefault = 0;
pbAutoLogonWithDefault = 0;
try
{
this.logger.LogTrace($"GetCredentialCount");
if (this.Tiles == null)
{
this.logger.LogTrace($"GetCredentialCount is enumerating tiles");
this.SetupTiles();
this.logger.LogTrace($"{this.Tiles.Count} tiles enumerated. {this.Tiles.Count(t => t.IsGenericTile)} generic and {this.tiles.Count(t => !t.IsGenericTile)} personalized");
}
this.notifyOnTileCollectionChange = true;
var autoLogonTile = this.Tiles.FirstOrDefault(t => t.IsAutoLogon);
var defaultTile = this.Tiles.FirstOrDefault(t => t.IsDefault);
int defaultIndex = 0;
if (autoLogonTile != null)
{
defaultIndex = this.tiles.IndexOf(autoLogonTile);
}
else if (defaultTile != null)
{
defaultIndex = this.tiles.IndexOf(defaultTile);
}
pdwCount = (uint)this.Tiles.Count;
pdwDefault = (uint)defaultIndex;
pbAutoLogonWithDefault = autoLogonTile == null ? 0 : 1;
this.logger.LogTrace($"GetCredentialCount returning pdwCount: {pdwCount}, pdwDefault: {pdwDefault}, pbAutoLogonWithDefault: {pbAutoLogonWithDefault}");
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetCredentialCount failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProvider.GetCredentialAt(uint dwIndex, out ICredentialProviderCredential ppcpc)
{
ppcpc = null;
try
{
this.logger.LogTrace($"GetCredentialAt {dwIndex}");
var tile = this.Tiles[(int)dwIndex];
ppcpc = (ICredentialProviderCredential)tile;
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetCredentialAt failed");
return HRESULT.E_FAIL;
}
}
}
}
@@ -0,0 +1,33 @@
using System;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
public abstract partial class CredentialProviderBase : ICredentialProviderSetUserArray
{
int ICredentialProviderSetUserArray.SetUserArray(ICredentialProviderUserArray users)
{
try
{
this.logger.LogTrace($"SetUserArray");
if (users.GetCount(out uint count) != HRESULT.S_OK)
{
this.logger.LogTrace($"ICredentialProviderUserArray.GetCount failed");
return HRESULT.S_FALSE;
}
this.logger.LogTrace($"SetUserArray called with {count} users");
this.credentialProviderUsers = users;
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "SetUserArray failed");
return HRESULT.E_FAIL;
}
}
}
}
@@ -0,0 +1,251 @@
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
{
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<CredentialProviderCredential1Tile> tiles;
/// <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();
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);
}
/// <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>
public 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))
{
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(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);
}
}
}
}
@@ -0,0 +1,481 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
public partial class CredentialProviderCredential1Tile : ICredentialProviderCredential
{
int ICredentialProviderCredential.Advise(ICredentialProviderCredentialEvents pcpce)
{
try
{
this.logger.LogTrace("Advise");
if (pcpce != null)
{
this.Controls.AssignEvents(pcpce);
this.events = pcpce;
this.events2 = pcpce as ICredentialProviderCredentialEvents2;
var intPtr = Marshal.GetIUnknownForObject(pcpce);
Marshal.AddRef(intPtr);
}
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Advise failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.UnAdvise()
{
try
{
this.logger.LogTrace("Unadvise");
if (this.events != null)
{
this.Controls.UnassignEvents();
var intPtr = Marshal.GetIUnknownForObject(this.events);
Marshal.Release(intPtr);
this.events = null;
}
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "UnAdvise failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.SetSelected(out int pbAutoLogon)
{
pbAutoLogon = 0;
try
{
this.logger.LogTrace("SetSelected");
this.IsSelected = true;
pbAutoLogon = this.IsAutoLogon ? 1 : 0;
this.OnSelected();
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "SetSelected failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.SetDeselected()
{
try
{
this.logger.LogTrace("SetDeselected");
this.IsSelected = false;
this.OnDeselected();
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "SetDeselected failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetFieldState(uint dwFieldID, out FieldState pcpfs, out FieldInteractiveState pcpfis)
{
pcpfis = FieldInteractiveState.None;
pcpfs = FieldState.Hidden;
try
{
this.logger.LogTrace($"GetFieldState: field {dwFieldID}");
if (this.Controls.TryGetControl(dwFieldID, out var instance))
{
pcpfs = instance.State;
pcpfis = instance.InteractiveState;
this.logger.LogTrace($"GetFieldState on [{instance}] returning state {instance.State} and interactive state {instance.InteractiveState}");
return HRESULT.S_OK;
}
this.logger.LogError($"GetFieldState failed to find a field match for {dwFieldID}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetFieldState failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetStringValue(uint dwFieldID, out IntPtr ppsz)
{
ppsz = IntPtr.Zero;
try
{
this.logger.LogTrace($"GetStringValue: field {dwFieldID}");
if (this.Controls.TryGetControl(dwFieldID, out var instance))
{
if (instance is TextboxControl t)
{
ppsz = Marshal.StringToCoTaskMemUni(t.Text);
return HRESULT.S_OK;
}
if (instance is SecurePasswordTextboxControl p)
{
if (p.Password == null || p.Password.Length == 0)
{
ppsz = IntPtr.Zero;
}
else
{
ppsz = Marshal.SecureStringToCoTaskMemUnicode(p.Password);
this.logger.LogCritical($"0x{ppsz.ToString("X16")} - Put password for outbound GetStringValue");
}
return HRESULT.S_OK;
}
if (instance is InsecurePasswordTextboxControl i)
{
if (i.Password == null || i.Password.Length == 0)
{
ppsz = IntPtr.Zero;
}
else
{
ppsz = Marshal.StringToCoTaskMemUni(i.Password);
this.logger.LogCritical($"0x{ppsz.ToString("X16")} - Put password for outbound GetStringValue");
}
return HRESULT.S_OK;
}
ppsz = Marshal.StringToCoTaskMemUni(instance.Label);
return HRESULT.S_OK;
}
this.logger.LogError($"GetStringValue failed to find a field match for {dwFieldID}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetStringValue failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetBitmapValue(uint dwFieldID, out IntPtr phbmp)
{
phbmp = IntPtr.Zero;
try
{
this.logger.LogTrace($"GetBitmapValue: field {dwFieldID}");
if (this.Controls.TryGetControl<BitmapControl>(dwFieldID, FieldType.TileImage, out var instance))
{
phbmp = instance.GetHBitmap();
return HRESULT.S_OK;
}
this.logger.LogError($"GetBitmapValue was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetBitmapValue failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetCheckboxValue(uint dwFieldID, out int pbChecked, out string ppszLabel)
{
pbChecked = 0;
ppszLabel = null;
try
{
this.logger.LogTrace($"GetCheckboxValue: field {dwFieldID}");
if (this.Controls.TryGetControl<CheckboxControl>(dwFieldID, FieldType.CheckBox, out var instance))
{
ppszLabel = instance.Label;
pbChecked = instance.IsChecked ? 1 : 0;
return HRESULT.S_OK;
}
this.logger.LogError($"GetCheckboxValue was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetCheckboxValue failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetSubmitButtonValue(uint dwFieldID, out uint pdwAdjacentTo)
{
pdwAdjacentTo = 0;
try
{
this.logger.LogTrace($"GetSubmitButtonValue: field {dwFieldID}");
if (this.Controls.TryGetControl<SubmitButtonControl>(dwFieldID, FieldType.Submit, out var instance))
{
pdwAdjacentTo = instance.AdjacentToId;
return HRESULT.S_OK;
}
this.logger.LogError($"GetSubmitButtonValue was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetSubmitButtonValue failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetComboBoxValueCount(uint dwFieldID, out uint pcItems, out uint pdwSelectedItem)
{
pcItems = 0;
pdwSelectedItem = 0;
try
{
this.logger.LogTrace($"GetComboBoxValueCount: field {dwFieldID}");
if (this.Controls.TryGetControl<ComboboxControl>(dwFieldID, FieldType.ComboBox, out var instance))
{
pcItems = (uint)instance.ComboBoxItems.Count;
pdwSelectedItem = (uint)instance.SelectedItemIndex;
return HRESULT.S_OK;
}
this.logger.LogError($"GetComboBoxValueCount was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetComboBoxValueCount failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetComboBoxValueAt(uint dwFieldID, uint dwItem, out string ppszItem)
{
ppszItem = null;
try
{
this.logger.LogTrace($"GetComboBoxValueAt: field {dwFieldID}");
if (this.Controls.TryGetControl<ComboboxControl>(dwFieldID, FieldType.ComboBox, out var instance))
{
if (dwItem < instance.ComboBoxItems.Count)
{
ppszItem = instance.ComboBoxItems[(int)dwItem];
return HRESULT.S_OK;
}
else
{
return HRESULT.E_FAIL;
}
}
this.logger.LogError($"GetComboBoxValueAt was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetComboBoxValueAt failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.SetStringValue(uint dwFieldID, IntPtr psz)
{
try
{
this.logger.LogTrace($"0x{psz.ToString("X16")} - Incoming SetStringValue: field {dwFieldID}");
if (this.Controls.TryGetControl(dwFieldID, out var instance))
{
if (instance.Type == FieldType.EditText && instance is TextboxControl b)
{
b.SetTextInternal(Marshal.PtrToStringUni(psz));
return HRESULT.S_OK;
}
if (instance.Type == FieldType.PasswordText && instance is SecurePasswordTextboxControl p)
{
this.logger.LogCritical($"0x{psz.ToString("X16")} - Incoming password in SetStringValue");
p.SetPasswordInternal(psz.IntPtrToSecureString());
PInvoke.SecureZeroMemory(psz, (uint)(psz.Wcslen() * 2));
return HRESULT.S_OK;
}
if (instance.Type == FieldType.PasswordText && instance is InsecurePasswordTextboxControl i)
{
this.logger.LogCritical($"0x{psz.ToString("X16")} - Incoming password in SetStringValue");
i.SetPasswordInternal(Marshal.PtrToStringUni(psz));
PInvoke.SecureZeroMemory(psz, (uint)(psz.Wcslen() * 2));
return HRESULT.S_OK;
}
}
this.logger.LogError($"SetStringValue was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "SetStringValue failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.SetCheckboxValue(uint dwFieldID, int bChecked)
{
try
{
this.logger.LogTrace($"SetCheckboxValue: field {dwFieldID}");
if (this.Controls.TryGetControl<CheckboxControl>(dwFieldID, FieldType.CheckBox, out var instance))
{
instance.SetIsCheckedInternal(bChecked != 0);
return HRESULT.S_OK;
}
this.logger.LogError($"SetCheckboxValue was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "SetCheckboxValue failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.SetComboBoxSelectedValue(uint dwFieldID, uint dwSelectedItem)
{
try
{
this.logger.LogTrace($"SetComboBoxSelectedValue: field {dwFieldID}");
if (this.Controls.TryGetControl<ComboboxControl>(dwFieldID, FieldType.ComboBox, out var instance))
{
instance.SetComboboxSelectedItemIndexInternal((unchecked((int)dwSelectedItem)));
return HRESULT.S_OK;
}
this.logger.LogError($"SetComboBoxSelectedValue was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "SetComboBoxSelectedValue failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.CommandLinkClicked(uint dwFieldID)
{
try
{
this.logger.LogTrace($"CommandLinkClicked: field {dwFieldID}");
if (this.Controls.TryGetControl<CommandLinkControl>(dwFieldID, FieldType.CommandLink, out var instance))
{
instance.OnClick?.Invoke();
return HRESULT.S_OK;
}
this.logger.LogError($"CommandLinkClicked was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "CommandLinkClicked failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.GetSerialization(out SerializationResponse pcpgsr, out CredentialSerialization pcpcs, out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon)
{
pcpgsr = SerializationResponse.NoCredentialNotFinished;
pcpcs = default;
ppszOptionalStatusText = null;
pcpsiOptionalStatusIcon = StatusIcon.None;
try
{
this.logger.LogTrace($"GetSerialization called");
NativeSerializationResponse response = this.OnGetSerialization();
pcpgsr = response.SerializationResponse;
pcpcs = response.SerializedCredentials;
ppszOptionalStatusText = response.OptionalStatusText;
pcpsiOptionalStatusIcon = response.OptionalStatusIcon;
this.logger.LogTrace($"GetSerialization is returning {pcpgsr}, with auth provider {pcpcs.AuthenticationPackage}, status {pcpsiOptionalStatusIcon}: {ppszOptionalStatusText}");
return response.HResult;
}
catch (Exception ex)
{
ppszOptionalStatusText = "Unexpected error";
pcpsiOptionalStatusIcon = StatusIcon.Error;
this.logger.LogError(ex, "GetSerialization failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential.ReportResult(int ntsStatus, int ntsSubstatus, out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon)
{
ppszOptionalStatusText = null;
pcpsiOptionalStatusIcon = StatusIcon.None;
try
{
this.logger.LogTrace($"ReportResult");
this.OnLogonStatusReported(ntsStatus, ntsSubstatus, out ppszOptionalStatusText, out pcpsiOptionalStatusIcon);
if (ppszOptionalStatusText == null && pcpsiOptionalStatusIcon == StatusIcon.None)
{
pcpsiOptionalStatusIcon = 0;
return HRESULT.E_NOTIMPL;
}
return HRESULT.S_OK;
}
catch (Exception ex)
{
this.logger.LogError(ex, "ReportResult failed");
return HRESULT.E_FAIL;
}
}
}
}
@@ -0,0 +1,36 @@
using System;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
public abstract partial class CredentialProviderCredential1Tile : ICredentialProviderCredentialWithFieldOptions
{
int ICredentialProviderCredentialWithFieldOptions.GetFieldOptions(uint dwFieldID, out FieldOptions options)
{
options = FieldOptions.None;
try
{
this.logger.LogTrace($"GetFieldOptions: field {dwFieldID}");
if (this.Controls.TryGetControl(dwFieldID, out var instance))
{
options = instance.Options;
this.logger.LogTrace($"GetFieldOptions on [{instance}] returning options {instance.Options}");
return HRESULT.S_OK;
}
this.logger.LogError($"GetFieldOptions failed to find a field match for {dwFieldID}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetFieldOptions failed");
return HRESULT.E_FAIL;
}
}
}
}
@@ -0,0 +1,254 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a 'v1' user credential tile that implements the minimum functionality required by the credential provider framework
/// </summary>
/// <remarks>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</remarks>
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 = CredentialProviderBase.LoggerFactory.CreateLogger(this.GetType());
}
internal CredentialProviderBase CredentialProvider { get; }
/// <summary>
/// Gets a value indicating if this is a generic, as opposed to a personalized tile
/// </summary>
public virtual bool IsGenericTile => true;
/// <summary>
/// Gets a value that indicates if this tile is currently selected by the user
/// </summary>
public bool IsSelected { get; private set; }
/// <summary>
/// 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.
/// </summary>
public bool IsAutoLogon { get; set; }
/// <summary>
/// Gets a value indicating if this should be the default time
/// </summary>
public bool IsDefault { get; set; }
/// <summary>
/// Gets the current usage scenario
/// </summary>
public UsageScenario UsageScenario => this.CredentialProvider.UsageScenario;
/// <summary>
/// Gets a list of controls assigned to this tile
/// </summary>
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;
}
/// <summary>
/// Gets the HWND of the parent of the credential provider, and notifies LogonUI or CredUI that we need to create a Window
/// </summary>
/// <returns>A HWND to the parentobject</returns>
/// <exception cref="InvalidOperationException">The method was called before the host has advised that is ready to rpovide events</exception>
/// <exception cref="COMException">The request to obtain the parent window HWND failed</exception>
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);
}
/// <summary>
/// Indicates to the host that multiple updates need to be made to the fields, and that it should delay updating the UI until <see cref="EndBulkFieldUpdate" is called/>
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
public void BeginBulkFieldUpdate()
{
if (this.events2 == null)
{
throw new InvalidOperationException("The credential provider has not provided an ICredentialProviderCredentialEvents2 interface");
}
this.events2.BeginFieldUpdates();
}
/// <summary>
/// Indicates to the host that the bulk update to fields has completed, and it can now update the UI according to the new values
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
public void EndBulkFieldUpdate()
{
if (this.events2 == null)
{
throw new InvalidOperationException("The credential provider has not provided an ICredentialProviderCredentialEvents2 interface");
}
this.events2.EndFieldUpdates();
}
/// <summary>
/// Called after the tile has been initialized. Override this method to perform post-initialization actions.
/// </summary>
public virtual void Initialize() { }
/// <summary>
/// Called when the user selects this tile
/// </summary>
protected virtual void OnSelected() { }
/// <summary>
/// Called when a user deselects this tile
/// </summary>
protected virtual void OnDeselected() { }
/// <summary>
/// Called just before credentials are serialized and returned to the host
/// </summary>
protected virtual void OnBeforeSerialize() { }
/// <summary>
/// Constructs the credential set from tile for serialization
/// </summary>
/// <remarks>Override this method, and provide either a <see cref="CredentialResponseSecure"/> or <see cref="CredentialResponseInsecure"/> response.
/// </remarks>
/// <returns>The credential set ready to be serialized and returned to LogonUI/CredUI</returns>
protected abstract CredentialResponseBase GetCredentials();
/// <summary>
/// This method is called when the <see cref="UsageScenario"/> is set to <see cref="UsageScenario.ChangePassword"/> 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 <see cref="ChangePasswordResponse"/> object to indicate if the operation was successful or not.
/// </summary>
/// <returns>A <see cref="ChangePasswordResponse"/> object</returns>
protected virtual ChangePasswordResponse ChangePassword()
{
return null;
}
/// <summary>
/// Called by LogonUI to translates a received error status code into the appropriate user-readable message. The Credential UI does not call this method.
/// </summary>
/// <param name="ntStatusCode">The NTSTATUS value that reflects the return value of the Winlogon call to LsaLogonUser.</param>
/// <param name="ntSubstatusCode">The NTSTATUS value that reflects the value pointed to by the SubStatus parameter of LsaLogonUser when that function returns after being called by Winlogon.</param>
/// <param name="optionalStatusText">Optional. The error message that will be displayed to the user.</param>
/// <param name="optionalStatusIcon">Optional. An icon that will shown on the credential</param>
protected virtual void OnLogonStatusReported(int ntStatusCode, int ntSubstatusCode, out string optionalStatusText, out StatusIcon optionalStatusIcon)
{
optionalStatusText = null;
optionalStatusIcon = StatusIcon.None;
}
/// <summary>
/// Performs serialization of the credentials by first calling <see cref="GetCredentials"/> and then serializing the response for return to LogonUI/CredUI. You may override this method to perform the serialization yourself. In that case, <see cref="GetCredentials"/> will not be called and does not need to be implemented.
/// </summary>
/// <exception cref="InvalidOperationException"><see cref="GetCredentials"/> returned an invalid response</exception>
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;
}
CredentialSerializer.Logger = this.logger;
if (credentials is CredentialResponseSecure s)
{
response.SerializedCredentials = CredentialSerializer.GenerateCredentialSerialization(credentials.Domain, credentials.Username, s.Password, this.UsageScenario == UsageScenario.UnlockWorkstation, this.CredentialProvider.CredentialProviderId);
}
else if (credentials is CredentialResponseInsecure i)
{
response.SerializedCredentials = CredentialSerializer.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;
}
}
}
@@ -0,0 +1,135 @@
using System;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a 'v2' user credential tile that implements the functionality of <see cref="CredentialProviderCredential1Tile"/>, and includes support for personalized tile, where a single user tile is shown, with multiple logon options grouped within it.
/// </summary>
/// <remarks>Inheriting from this class enables you to provide a v2 credential tile. V2 credential tiles were introduced in Windows 8. See the Microsoft documentation on ICredentialProviderCredential2 for more information</remarks>
/// <inheritdoc/>
public abstract class CredentialProviderCredential2Tile : CredentialProviderCredential1Tile, ICredentialProviderCredential2
{
public CredentialProviderUser User { get; }
public override bool IsGenericTile => this.User == null;
public GenericTileDisplayMode GenericTileDisplayMode { get; set; }
protected CredentialProviderCredential2Tile(CredentialProviderBase credentialProvider) : this(credentialProvider, null) { }
protected CredentialProviderCredential2Tile(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider)
{
this.User = user;
}
int ICredentialProviderCredential2.GetUserSid(out string sid)
{
sid = null;
try
{
this.logger.LogTrace("GetUserSid");
if (this.IsGenericTile)
{
this.logger.LogTrace("GetUserSid: Tile is generic so returning no SID");
return this.GenericTileDisplayMode == GenericTileDisplayMode.DisplayUnderOtherUser ? HRESULT.S_FALSE : HRESULT.E_NOTIMPL;
}
return this.User.User.GetSid(out sid);
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetUserSid failed");
return HRESULT.E_FAIL;
}
}
int ICredentialProviderCredential2.SetSelected(out int pbAutoLogon)
{
return ((ICredentialProviderCredential)this).SetSelected(out pbAutoLogon);
}
int ICredentialProviderCredential2.SetDeselected()
{
return ((ICredentialProviderCredential)this).SetDeselected();
}
int ICredentialProviderCredential2.GetFieldState(uint dwFieldID, out FieldState pcpfs, out FieldInteractiveState pcpfis)
{
return ((ICredentialProviderCredential)this).GetFieldState(dwFieldID, out pcpfs, out pcpfis);
}
int ICredentialProviderCredential2.GetStringValue(uint dwFieldID, out IntPtr ppsz)
{
return ((ICredentialProviderCredential)this).GetStringValue(dwFieldID, out ppsz);
}
int ICredentialProviderCredential2.GetBitmapValue(uint dwFieldID, out IntPtr phbmp)
{
return ((ICredentialProviderCredential)this).GetBitmapValue(dwFieldID, out phbmp);
}
int ICredentialProviderCredential2.GetCheckboxValue(uint dwFieldID, out int pbChecked, out string ppszLabel)
{
return ((ICredentialProviderCredential)this).GetCheckboxValue(dwFieldID, out pbChecked, out ppszLabel);
}
int ICredentialProviderCredential2.GetSubmitButtonValue(uint dwFieldID, out uint pdwAdjacentTo)
{
return ((ICredentialProviderCredential)this).GetSubmitButtonValue(dwFieldID, out pdwAdjacentTo);
}
int ICredentialProviderCredential2.GetComboBoxValueCount(uint dwFieldID, out uint pcItems, out uint pdwSelectedItem)
{
return ((ICredentialProviderCredential)this).GetComboBoxValueCount(dwFieldID, out pcItems, out pdwSelectedItem);
}
int ICredentialProviderCredential2.GetComboBoxValueAt(uint dwFieldID, uint dwItem, out string ppszItem)
{
return ((ICredentialProviderCredential)this).GetComboBoxValueAt(dwFieldID, dwItem, out ppszItem);
}
int ICredentialProviderCredential2.SetCheckboxValue(uint dwFieldID, int bChecked)
{
return ((ICredentialProviderCredential)this).SetCheckboxValue(dwFieldID, bChecked);
}
int ICredentialProviderCredential2.SetStringValue(uint dwFieldID, IntPtr psz)
{
return ((ICredentialProviderCredential)this).SetStringValue(dwFieldID, psz);
}
int ICredentialProviderCredential2.CommandLinkClicked(uint dwFieldID)
{
return ((ICredentialProviderCredential)this).CommandLinkClicked(dwFieldID);
}
int ICredentialProviderCredential2.SetComboBoxSelectedValue(uint dwFieldID, uint dwSelectedItem)
{
return ((ICredentialProviderCredential)this).SetComboBoxSelectedValue(dwFieldID, dwSelectedItem);
}
int ICredentialProviderCredential2.GetSerialization(out SerializationResponse pcpgsr, out CredentialSerialization pcpcs, out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon)
{
return ((ICredentialProviderCredential)this).GetSerialization(out pcpgsr, out pcpcs, out ppszOptionalStatusText, out pcpsiOptionalStatusIcon);
}
int ICredentialProviderCredential2.UnAdvise()
{
return ((ICredentialProviderCredential)this).UnAdvise();
}
int ICredentialProviderCredential2.Advise(ICredentialProviderCredentialEvents pcpce)
{
return ((ICredentialProviderCredential)this).Advise(pcpce);
}
int ICredentialProviderCredential2.ReportResult(int ntsStatus, int ntsSubstatus, out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon)
{
return ((ICredentialProviderCredential)this).ReportResult(ntsStatus, ntsSubstatus, out ppszOptionalStatusText, out pcpsiOptionalStatusIcon);
}
}
}
@@ -0,0 +1,135 @@
using System;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a user credential tile that implements the functionality of <see cref="CredentialProviderCredential1Tile"/> and <see cref="CredentialProviderCredential2Tile"/>, but includes support for dynamically updating bitmap images.
/// </summary>
/// <remarks>This interface is public, but undocumented by Microsoft. It is recommended to use <see cref="CredentialProviderCredential2Tile"/> tiles unless this specific functionality is needed</remarks>
public abstract class CredentialProviderCredential3Tile : CredentialProviderCredential2Tile, ICredentialProviderCredential3
{
protected CredentialProviderCredential3Tile(CredentialProviderBase credentialProvider) : this(credentialProvider, null) { }
protected CredentialProviderCredential3Tile(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider, user) { }
int ICredentialProviderCredential3.GetBitmapBufferValue(uint dwFieldID, out uint pImageBufferSize, out IntPtr ppImageBuffer)
{
this.logger.LogTrace($"Called GetBitmapBufferValue {dwFieldID}");
ppImageBuffer = IntPtr.Zero;
pImageBufferSize = 0;
try
{
this.logger.LogTrace($"GetBitmapBufferValue: field {dwFieldID}");
if (this.Controls.TryGetControl<BitmapControl>(dwFieldID, FieldType.TileImage, out var instance))
{
var hbitmap = instance.GetBitmapBuffer(out pImageBufferSize);
return HRESULT.S_OK;
}
this.logger.LogError($"GetBitmapValue was incorrectly called on field {instance}");
return HRESULT.E_FAIL;
}
catch (Exception ex)
{
this.logger.LogError(ex, "GetBitmapValue failed");
return HRESULT.E_FAIL;
}
}
///<inheritdoc cref="ICredentialProviderCredential2.GetUserSid(out string)"/>
int ICredentialProviderCredential3.GetUserSid(out string sid)
{
return ((ICredentialProviderCredential2)this).GetUserSid(out sid);
}
///<inheritdoc cref="CredentialProviderCredential1Tile"/>
int ICredentialProviderCredential3.SetSelected(out int pbAutoLogon)
{
return ((ICredentialProviderCredential)this).SetSelected(out pbAutoLogon);
}
///<inheritdoc cref="CredentialProviderCredential1Tile"/>
int ICredentialProviderCredential3.SetDeselected()
{
return ((ICredentialProviderCredential)this).SetDeselected();
}
int ICredentialProviderCredential3.GetFieldState(uint dwFieldID, out FieldState pcpfs, out FieldInteractiveState pcpfis)
{
return ((ICredentialProviderCredential)this).GetFieldState(dwFieldID, out pcpfs, out pcpfis);
}
int ICredentialProviderCredential3.GetStringValue(uint dwFieldID, out IntPtr ppsz)
{
return ((ICredentialProviderCredential)this).GetStringValue(dwFieldID, out ppsz);
}
int ICredentialProviderCredential3.GetBitmapValue(uint dwFieldID, out IntPtr phbmp)
{
return ((ICredentialProviderCredential)this).GetBitmapValue(dwFieldID, out phbmp);
}
int ICredentialProviderCredential3.GetCheckboxValue(uint dwFieldID, out int pbChecked, out string ppszLabel)
{
return ((ICredentialProviderCredential)this).GetCheckboxValue(dwFieldID, out pbChecked, out ppszLabel);
}
int ICredentialProviderCredential3.GetSubmitButtonValue(uint dwFieldID, out uint pdwAdjacentTo)
{
return ((ICredentialProviderCredential)this).GetSubmitButtonValue(dwFieldID, out pdwAdjacentTo);
}
int ICredentialProviderCredential3.GetComboBoxValueCount(uint dwFieldID, out uint pcItems, out uint pdwSelectedItem)
{
return ((ICredentialProviderCredential)this).GetComboBoxValueCount(dwFieldID, out pcItems, out pdwSelectedItem);
}
int ICredentialProviderCredential3.GetComboBoxValueAt(uint dwFieldID, uint dwItem, out string ppszItem)
{
return ((ICredentialProviderCredential)this).GetComboBoxValueAt(dwFieldID, dwItem, out ppszItem);
}
int ICredentialProviderCredential3.SetCheckboxValue(uint dwFieldID, int bChecked)
{
return ((ICredentialProviderCredential)this).SetCheckboxValue(dwFieldID, bChecked);
}
int ICredentialProviderCredential3.SetStringValue(uint dwFieldID, IntPtr psz)
{
return ((ICredentialProviderCredential)this).SetStringValue(dwFieldID, psz);
}
int ICredentialProviderCredential3.CommandLinkClicked(uint dwFieldID)
{
return ((ICredentialProviderCredential)this).CommandLinkClicked(dwFieldID);
}
int ICredentialProviderCredential3.SetComboBoxSelectedValue(uint dwFieldID, uint dwSelectedItem)
{
return ((ICredentialProviderCredential)this).SetComboBoxSelectedValue(dwFieldID, dwSelectedItem);
}
int ICredentialProviderCredential3.GetSerialization(out SerializationResponse pcpgsr, out CredentialSerialization pcpcs, out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon)
{
return ((ICredentialProviderCredential)this).GetSerialization(out pcpgsr, out pcpcs, out ppszOptionalStatusText, out pcpsiOptionalStatusIcon);
}
int ICredentialProviderCredential3.UnAdvise()
{
return ((ICredentialProviderCredential)this).UnAdvise();
}
int ICredentialProviderCredential3.Advise(ICredentialProviderCredentialEvents pcpce)
{
return ((ICredentialProviderCredential)this).Advise(pcpce);
}
int ICredentialProviderCredential3.ReportResult(int ntsStatus, int ntsSubstatus, out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon)
{
return ((ICredentialProviderCredential)this).ReportResult(ntsStatus, ntsSubstatus, out ppszOptionalStatusText, out pcpsiOptionalStatusIcon);
}
}
}
@@ -0,0 +1,151 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a user provided by the credential framework
/// </summary>
public class CredentialProviderUser
{
internal readonly ICredentialProviderUser User;
private readonly ILogger logger;
private string qualifiedUserName;
private string sid;
private string userName;
private string displayName;
private string logonStatus;
private string providerId;
internal CredentialProviderUser(ICredentialProviderUser user)
{
this.User = user;
this.logger = CredentialProviderBase.LoggerFactory.CreateLogger<CredentialProviderUser>();
}
/// <summary>
/// Gets the qualified username of the user. This name is used to pack an authentication buffer
/// </summary>
public string QualifiedUserName
{
get
{
if (this.qualifiedUserName == null)
{
this.qualifiedUserName = this.TryGetValueOrDefault(PropertyKeys.PKEY_Identity_QualifiedUserName);
}
return this.qualifiedUserName;
}
}
/// <summary>
/// Gets the SID of the user
/// </summary>
public string Sid
{
get
{
if (this.sid == null)
{
this.sid = this.TryGetValueOrDefault(PropertyKeys.PKEY_Identity_PrimarySid);
}
return this.sid;
}
}
/// <summary>
/// Gets the username of the user
/// </summary>
public string UserName
{
get
{
if (this.userName == null)
{
this.userName = this.TryGetValueOrDefault(PropertyKeys.PKEY_Identity_UserName);
}
return this.userName;
}
}
/// <summary>
/// Gets the display name of the user
/// </summary>
public string DisplayName
{
get
{
if (this.displayName == null)
{
this.displayName = this.TryGetValueOrDefault(PropertyKeys.PKEY_Identity_DisplayName);
}
return this.displayName;
}
}
/// <summary>
/// Gets the logon status of the user. Does not apply to the CredUI scenario.
/// </summary>
/// <remarks>For example, "Signed-in", "Locked"</remarks>
public string LogonStatus
{
get
{
if (this.logonStatus == null)
{
this.logonStatus = this.TryGetValueOrDefault(PropertyKeys.PKEY_Identity_LogonStatusString);
}
return this.logonStatus;
}
}
/// <summary>
/// The user's provider ID
/// </summary>
public string ProviderID
{
get
{
if (this.providerId == null)
{
this.providerId = this.TryGetValueOrDefault(PropertyKeys.PKEY_Identity_ProviderID);
}
return this.providerId;
}
}
private string GetValue(PropertyKey key)
{
var result = this.User.GetStringValue(key, out var value);
if (result != HRESULT.S_OK)
{
throw new COMException("Could not get user SID", result);
}
return value;
}
private string TryGetValueOrDefault(PropertyKey key)
{
try
{
return this.GetValue(key);
}
catch (Exception ex)
{
this.logger.LogError(ex, $"Unable to get property value for key {key.PropertyID}");
}
return null;
}
}
}
@@ -0,0 +1,33 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A base class used to provide credentials for serialization
/// </summary>
public abstract class CredentialResponseBase
{
/// <summary>
/// Gets or sets a value indicating if the credentials were sucessfully obtained
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// An optional status icon
/// </summary>
public StatusIcon StatusIcon { get; set; }
/// <summary>
/// An optional status message describing any errors that occurred
/// </summary>
public string StatusText { get; set; }
/// <summary>
/// The domain of the user who's credentials are to be serialized
/// </summary>
public string Domain { get; set; }
/// <summary>
/// The username of the user who's credentials are to be serialized
/// </summary>
public string Username { get; set; }
}
}
@@ -0,0 +1,13 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A representation of a set of credentials to serialize back to LogonUI/CredUI. Consider using the <see cref="CredentialResponseSecure"/> class, which uses a SecureString for the password.
/// </summary>
public class CredentialResponseInsecure : CredentialResponseBase
{
/// <summary>
/// The plain-text password of the user who's credentials are to be serialized
/// </summary>
public string Password { get; set; }
}
}
@@ -0,0 +1,15 @@
using System.Security;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A representation of a set of credentials to serialize back to LogonUI/CredUI.
/// </summary>
public class CredentialResponseSecure : CredentialResponseBase
{
/// <summary>
/// The password of the user who's credentials are to be serialized
/// </summary>
public SecureString Password { get; set; }
}
}
@@ -0,0 +1,32 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A native credential serialization structure used to pass logon information back to LogonUI/CredUI
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct CredentialSerialization
{
/// <summary>
/// The unique identifier of the authentication package. This parameter is required when calling LsaLogonUser. In a Credential UI scenario, this value is set before a serialization is sent through SetSerialization. This is the same as the authentication package value returned by LsaLookupAuthenticationPackage. Content providers can use this parameter to determine if they are able to return credentials for this authentication package. Developers who write their own authentication package may supply their own value.
/// </summary>
public uint AuthenticationPackage;
/// <summary>
/// The CLSID of the credential provider. Credential providers assign their own CLSID to this member during serialization. Credential UI ignores this member.
/// </summary>
public Guid ProviderClassGuid;
/// <summary>
/// The size, in bytes, of the credential pointed to by <see cref="SerializationData"/>.
/// </summary>
public uint SerializationSize;
/// <summary>
/// A pointer to an array of bytes containing serialized credential information. The exact format of this data depends on the authentication package targeted by a credential provider.
/// </summary>
public IntPtr SerializationData;
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lithnet.CredentialProvider
{
[Flags]
public enum CredUIWinFlags
{
/// <summary>
/// The caller is requesting that the credential provider return the user name and password in plain text.
/// This value cannot be combined with SECURE_PROMPT.
/// </summary>
CREDUIWIN_GENERIC = 0x1,
/// <summary>
/// The Save check box is displayed in the dialog box.
/// </summary>
CREDUIWIN_CHECKBOX = 0x2,
/// <summary>
/// Only credential providers that support the authentication package specified by the authPackage parameter should be enumerated.
/// This value cannot be combined with CREDUIWIN_IN_CRED_ONLY.
/// </summary>
CREDUIWIN_AUTHPACKAGE_ONLY = 0x10,
/// <summary>
/// Only the credentials specified by the InAuthBuffer parameter for the authentication package specified by the authPackage parameter should be enumerated.
/// If this flag is set, and the InAuthBuffer parameter is NULL, the function fails.
/// This value cannot be combined with CREDUIWIN_AUTHPACKAGE_ONLY.
/// </summary>
CREDUIWIN_IN_CRED_ONLY = 0x20,
/// <summary>
/// Credential providers should enumerate only administrators. This value is intended for User Account Control (UAC) purposes only. We recommend that external callers not set this flag.
/// </summary>
CREDUIWIN_ENUMERATE_ADMINS = 0x100,
/// <summary>
/// Only the incoming credentials for the authentication package specified by the authPackage parameter should be enumerated.
/// </summary>
CREDUIWIN_ENUMERATE_CURRENT_USER = 0x200,
/// <summary>
/// The credential dialog box should be displayed on the secure desktop. This value cannot be combined with CREDUIWIN_GENERIC.
/// Windows Vista: This value is not supported until Windows Vista with SP1.
/// </summary>
CREDUIWIN_SECURE_PROMPT = 0x1000,
/// <summary>
/// The credential provider should align the credential BLOB pointed to by the refOutAuthBuffer parameter to a 32-bit boundary, even if the provider is running on a 64-bit system.
/// </summary>
CREDUIWIN_PACK_32_WOW = 0x10000000,
}
}
@@ -0,0 +1,28 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Describes the state of a field and how it a user can interact with it. Fields can be displayed by a credential provider in a variety of different interactive states.
/// </summary>
public enum FieldInteractiveState
{
/// <summary>
/// The field can be edited if the field type supports editing. It also contains none of the other available interactive states.
/// </summary>
None,
/// <summary>
/// Reserved and not used.
/// </summary>
ReadOnly,
/// <summary>
/// The field is disabled. The user can see it but not interact with it. This support was added starting with Windows 10.
/// </summary>
Disabled,
/// <summary>
/// Credential providers use this field interactive state to indicate that the field should receive initial keyboard focus. This interactive state may not be specified for field types that the user cannot edit. If several editable fields specify this state, the last of them based on dwIndex order receives focus. On systems before Windows 10, it was the first of editable fields based on dwIndex order. This field interactive state is obeyed only during initial enumeration.
/// </summary>
Focused
}
}
@@ -0,0 +1,41 @@
using System;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Provides customization options for a single field in a logon or credential UI
/// </summary>
[Flags]
public enum FieldOptions
{
/// <summary>
/// Default. Don't show the "password reveal" glyph, and use the standard on-screen keyboard layout.
/// </summary>
None = 0,
/// <summary>
/// Display the "password reveal" glyph in a password entry box. When this glyph is held down by the user, the entry in the password box is shown in plain text. The glyph is shown here:
/// </summary>
PasswordReveal = 1,
/// <summary>
/// The field will contain an e-mail address. The on-screen keyboard should be optimized for that input (showing the .com and @ keys on the primary keyboard layout). This option is used with Microsoft account credentials.
/// </summary>
Email = 2,
/// <summary>
/// When enabled, the touch keyboard will be automatically invoked. This should be set only on the CPFG_CREDENTIAL_PROVIDER_LOGO field.
/// </summary>
TouchKeyboardAutoInvoke = 4,
/// <summary>
/// The field will only allow numerals to be entered. The on-screen keyboard should be optimized for that input (showing only a number keypad on the primary keyboard layout). This should be set only on the CPFT_PASSWORD_TEXT field
/// </summary>
NumbersOnly = 8,
/// <summary>
/// Show the English keyboard.
/// </summary>
ShowEnglishKeyboard = 16
}
}
@@ -0,0 +1,28 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies the state of a single field in the Credential UI
/// </summary>
public enum FieldState
{
/// <summary>
/// Do not show the field in any state. One example of this would be a password edit control that should not be displayed until the user authenticates a thumb print. Until the thumb print has been authenticated, the state of the password field would be CPFS_HIDDEN.
/// </summary>
Hidden,
/// <summary>
/// Show the field when in the selected state.
/// </summary>
DisplayInSelectedTile,
/// <summary>
/// Show the field when in the deselected state. This value is only valid for a UsageScenario set to CredUI.
/// </summary>
DisplayInDeselectedTile,
/// <summary>
/// Show the field both when the credential tile is selected and when it is not selected.
/// </summary>
DisplayInBoth
}
}
@@ -0,0 +1,26 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies how a generic tile should be dispalyed in LogonUI
/// </summary>
public enum GenericTileDisplayMode
{
/// <summary>
/// Specifies that the generic tile should be displayed as a logon provider under the generic 'Other User' tile.
/// </summary>
/// <remarks>
/// This applies only to scenarios involving LogonUI. This value is ignored for CredUI.
/// This scenario is only supported in Windows 8 and above.
/// </remarks>
DisplayUnderOtherUser = 0,
/// <summary>
/// Specifies that the generic tile should be displayed as it's own dedicated credential tile. Note this applies to LogonUI scenarios only.
/// </summary>
/// <remarks>
/// This applies only to scenarios involving LogonUI. This value is ignored for CredUI.
/// Selecting this option emulates the behaviour of a pre-Windows 8 credential provider.
/// </remarks>
DisplayAsDedicatedTile = 1
}
}
@@ -0,0 +1,25 @@
namespace Lithnet.CredentialProvider
{
public enum SerializationResponse
{
/// <summary>
///No credential was serialized because more information is needed. One example of this would be if a credential requires both a PIN and an answer to a secret question, but the user has only provided the PIN. This signals the caller should be given a chance to alter its response.
/// </summary>
NoCredentialNotFinished,
/// <summary>
/// The credential provider has not serialized a credential but has completed its work. This response has multiple meanings. It can mean that no credential was serialized and that the user should not try again. This response can also mean that no credential was submitted but the credential's work is complete. For example, in the Change Password scenario, this response implies success.
/// </summary>
NoCredentialFinished,
/// <summary>
/// A credential was serialized. This response implies that a serialization structure was passed back.
/// </summary>
ReturnCredentialFinished,
/// <summary>
/// The credential provider has not serialized a credential, but has completed its work. The difference between this value and <see cref="NoCredentialNotFinished"/> is that this flag will force the logon UI to return, which will call UnAdvise for all the credential providers.
/// </summary>
ReturnNoCredentialFinished
}
}
@@ -0,0 +1,32 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Indicates which status icon should be displayed.
/// </summary>
/// <remarks>
/// CREDENTIAL_PROVIDER_STATUS_ICON is not used starting in Windows 10.
/// As part of ReportResult, a credential provider may specify a status icon to display.It is important to not that only Logon UI calls ReportResult. Credential UI does not.
/// </remarks>
public enum StatusIcon
{
/// <summary>
/// No icon indicated.
/// </summary>
None,
/// <summary>
/// Display the error icon.
/// </summary>
Error,
/// <summary>
/// Display the warning icon.
/// </summary>
Warning,
/// <summary>
/// Reserved
/// </summary>
Success
}
}
@@ -0,0 +1,43 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Declares the scenarios in which a credential provider is supported. A credential provider usage scenario (CPUS) enables the credential provider to provide distinct enumeration behavior and UI field setup across scenarios.
/// </summary>
public enum UsageScenario
{
/// <summary>
/// No usage scenario has been set for the Credential Provider
/// </summary>
Invalid,
/// <summary>
/// Workstation logon or unlock. Credential providers that implement this scenario should be prepared to serialize credentials to the local authority for authentication. Starting in Windows 10, the CPUS_LOGON and CPUS_UNLOCK_WORKSTATION user scenarios have been combined.
/// </summary>
Logon,
/// <summary>
/// Workstation unlock. Credential providers that implement this scenario should be prepared to serialize credentials to the local authority for authentication. These credential providers also need to enumerate the currently logged-in user as the default tile.
/// </summary>
/// <remarks> Starting in Windows 10, the CPUS_LOGON and CPUS_UNLOCK_WORKSTATION user scenarios have been combined. This enables the system to support multiple users logging into a machine without creating and switching sessions unnecessarily. Any user on the machine can log into it once it has been locked without needing to back out of a current session and create a new one. Because of this, CPUS_LOGON can be used both for logging onto a system or when a workstation is unlocked. However, CPUS_LOGON cannot be used in all cases. Because of policy restrictions imposed by various systems, sometimes it is necessary for the user scenario to be CPUS_UNLOCK_WORKSTATION. Your credential provider should be robust enough to create the appropriate credential structure based on the scenario given to it. Windows will request the appropriate user scenario based on the situation. Some of the factors that impact whether or not a CPUS_UNLOCK_WORKSTATION scenario must be used include the following. Note that this is just a subset of possibilities.
/// - The operating system of the device.
/// - Whether this is a console or remote session.
/// - Group policies such as hiding entry points for fast user switching, or interactive logon that does not display the user's last name.
/// </remarks>
UnlockWorkstation,
/// <summary>
/// Password change. This enables a credential provider to enumerate tiles in response to a user's request to change the password. Do not implement this scenario if you do not require some secret information from the user such as a password or PIN. These credential providers also need to enumerate the currently logged-in user as the default tile.
/// </summary>
ChangePassword,
/// <summary>
/// Credential UI. This scenario enables you to use credentials serialized by the credential provider to be used as authentication on remote machines. This is also the scenario used for over-the-shoulder prompting in User Access Control. This scenario uses a different instance of the credential provider than the one used for <c ref="Logon"/>, <c ref="UnlockWorkstation"/>, and <c ref="ChangePassword"/>, so the state of the credential provider cannot be maintained across the different scenarios.
/// </summary>
CredUI,
/// <summary>
/// Pre-Logon-Access Provider.
/// </summary>
PLAP
}
}
@@ -0,0 +1,9 @@
namespace Lithnet.CredentialProvider.Interop
{
internal enum AccountOptions
{
None,
EmptyLocal,
EmptyConnected
}
}
@@ -0,0 +1,11 @@
namespace Lithnet.CredentialProvider.Interop
{
internal static class CredProviderConstants
{
public const string CPFG_CREDENTIAL_PROVIDER_LOGO = "2d837775-f6cd-464e-a745-482fd0b47493";
public const string CPFG_CREDENTIAL_PROVIDER_LABEL = "286bbff3-bad4-438f-b007-79b7267c3d48";
public const string MICROSOFT_KERBEROS_NAME_A = "Kerberos";
public const string NEGOSSP_NAME_A = "Negotiate";
public const string MSV1_0_PACKAGE_NAME = "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0";
}
}
@@ -0,0 +1,152 @@
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Lithnet.CredentialProvider.Interop
{
internal static class CredentialSerializer
{
internal static ILogger Logger { get; set; } = NullLogger.Instance;
public static CredentialSerialization GenerateCredentialSerialization(string domain, string username, SecureString password, bool isWorkstationUnlock, Guid providerId)
{
var authPackage = PInvoke.LookupAuthenticationPackage(CredProviderConstants.NEGOSSP_NAME_A);
var pData = SerializeKerbLogon(domain, username, password, isWorkstationUnlock ? KerbLogonSubmitType.WorkstationUnlockLogon : KerbLogonSubmitType.InteractiveLogon, out int size);
Logger.LogTrace($"0x{pData.ToString("X16")} - Serializer: Password got packed into ");
return new CredentialSerialization()
{
AuthenticationPackage = authPackage,
SerializationData = pData,
ProviderClassGuid = providerId,
SerializationSize = (uint)size
};
}
public static CredentialSerialization GenerateCredentialSerialization(string domain, string username, string password, bool isWorkstationUnlock, Guid providerId)
{
var authPackage = PInvoke.LookupAuthenticationPackage(CredProviderConstants.NEGOSSP_NAME_A);
var pData = SerializeKerbLogon(domain, username, password, isWorkstationUnlock ? KerbLogonSubmitType.WorkstationUnlockLogon : KerbLogonSubmitType.InteractiveLogon, out int size);
Logger.LogTrace($"0x{pData.ToString("X16")}: Password got packed");
return new CredentialSerialization()
{
AuthenticationPackage = authPackage,
SerializationData = pData,
ProviderClassGuid = providerId,
SerializationSize = (uint)size
};
}
private static unsafe IntPtr SerializeKerbLogon(string domain, string username, string password, KerbLogonSubmitType type, out int size)
{
size = sizeof(KerberosInteractiveUnlockLogon) +
Encoding.Unicode.GetMaxByteCount(domain.Length) +
Encoding.Unicode.GetMaxByteCount(username.Length) +
Encoding.Unicode.GetMaxByteCount(password.Length);
IntPtr pBuffer = Marshal.AllocCoTaskMem(size);
byte* buffer = (byte*)pBuffer;
var logon = (KerberosInteractiveUnlockLogon*)buffer;
logon->SubmitType = type;
logon->LogonDomainName.MaxLength = (ushort)Encoding.Unicode.GetMaxByteCount(domain.Length);
logon->LogonDomainName.Length = (ushort)(domain.Length * sizeof(char));
logon->LogonDomainName.Buffer = (IntPtr)sizeof(KerberosInteractiveUnlockLogon);
fixed (char* domainBuffer = domain)
{
Encoding.Unicode.GetBytes(domainBuffer, domain.Length,
buffer + logon->LogonDomainName.Buffer.ToInt64(), logon->LogonDomainName.Length);
}
logon->Username.Length = (ushort)(username.Length * sizeof(char));
logon->Username.MaxLength = (ushort)Encoding.Unicode.GetMaxByteCount(username.Length);
logon->Username.Buffer = (IntPtr)(sizeof(KerberosInteractiveUnlockLogon) + logon->LogonDomainName.MaxLength);
fixed (char* usernameBuffer = username)
{
Encoding.Unicode.GetBytes(usernameBuffer, username.Length,
buffer + logon->Username.Buffer.ToInt64(), logon->Username.Length);
}
logon->Password.MaxLength = (ushort)Encoding.Unicode.GetMaxByteCount(password.Length);
logon->Password.Length = (ushort)(password.Length * sizeof(char));
logon->Password.Buffer = (IntPtr)(sizeof(KerberosInteractiveUnlockLogon) + logon->LogonDomainName.MaxLength + logon->Username.MaxLength);
fixed (char* passwordBuffer = password)
{
Encoding.Unicode.GetBytes(passwordBuffer, password.Length,
buffer + logon->Password.Buffer.ToInt64(), logon->Password.Length);
}
return pBuffer;
}
private static unsafe IntPtr SerializeKerbLogon(string domain, string username, SecureString password, KerbLogonSubmitType type, out int size)
{
size = sizeof(KerberosInteractiveUnlockLogon) +
Encoding.Unicode.GetMaxByteCount(domain.Length) +
Encoding.Unicode.GetMaxByteCount(username.Length) +
Encoding.Unicode.GetMaxByteCount(password.Length);
IntPtr pBuffer = Marshal.AllocCoTaskMem(size);
byte* buffer = (byte*)pBuffer;
var logon = (KerberosInteractiveUnlockLogon*)buffer;
logon->SubmitType = type;
logon->LogonDomainName.MaxLength = (ushort)Encoding.Unicode.GetMaxByteCount(domain.Length);
logon->LogonDomainName.Length = (ushort)(domain.Length * sizeof(char));
logon->LogonDomainName.Buffer = (IntPtr)sizeof(KerberosInteractiveUnlockLogon);
fixed (char* domainBuffer = domain)
{
Encoding.Unicode.GetBytes(domainBuffer, domain.Length,
buffer + logon->LogonDomainName.Buffer.ToInt64(), logon->LogonDomainName.Length);
}
logon->Username.Length = (ushort)(username.Length * sizeof(char));
logon->Username.MaxLength = (ushort)Encoding.Unicode.GetMaxByteCount(username.Length);
logon->Username.Buffer = (IntPtr)(sizeof(KerberosInteractiveUnlockLogon) + logon->LogonDomainName.MaxLength);
fixed (char* usernameBuffer = username)
{
Encoding.Unicode.GetBytes(usernameBuffer, username.Length,
buffer + logon->Username.Buffer.ToInt64(), logon->Username.Length);
}
logon->Password.MaxLength = (ushort)Encoding.Unicode.GetMaxByteCount(password.Length);
logon->Password.Length = (ushort)(password.Length * sizeof(char));
logon->Password.Buffer = (IntPtr)(sizeof(KerberosInteractiveUnlockLogon) + logon->LogonDomainName.MaxLength + logon->Username.MaxLength);
IntPtr buff = IntPtr.Zero;
try
{
buff = Marshal.SecureStringToCoTaskMemUnicode(password);
Logger.LogTrace($"0x{buff.ToString("X16")} - Serializer: Unprotected password");
IntPtr targetPositionToCopyTo = (IntPtr)(buffer + logon->Password.Buffer.ToInt64());
Buffer.MemoryCopy(buff.ToPointer(), targetPositionToCopyTo.ToPointer(), logon->Password.Length, password.Length * sizeof(char));
Logger.LogTrace($"0x{targetPositionToCopyTo.ToString("X16")} - Serializer: Copied unprotected password into LSA string buffer");
}
finally
{
if (buff != IntPtr.Zero)
{
Marshal.ZeroFreeCoTaskMemUnicode(buff);
Logger.LogTrace($"0x{buff.ToString("X16")} - Serializer: Freed Unprotected password");
}
}
Logger.LogTrace($"0x{((IntPtr)buffer).ToString("X16")} - Serializer: Put password");
return pBuffer;
}
}
}
@@ -0,0 +1,18 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 8)]
internal struct FieldDescriptor
{
public uint FieldID;
public FieldType FieldType;
[MarshalAs(UnmanagedType.LPWStr)]
public string Label;
public Guid FieldTypeGuid;
}
}
@@ -0,0 +1,16 @@
namespace Lithnet.CredentialProvider.Interop
{
internal enum FieldType
{
Invalid,
LargeText,
SmallText,
CommandLink,
EditText,
PasswordText,
TileImage,
CheckBox,
ComboBox,
Submit
}
}
@@ -0,0 +1,21 @@
namespace Lithnet.CredentialProvider.Interop
{
internal static class HRESULT
{
public const int S_OK = 0x00000000;
public const int S_FALSE = 0x00000001;
public const int E_ACCESSDENIED = unchecked((int)0x80070005);
public const int E_FAIL = unchecked((int)0x80004005);
public const int E_INVALIDARG = unchecked((int)0x80070057);
public const int E_OUTOFMEMORY = unchecked((int)0x8007000E);
public const int E_POINTER = unchecked((int)0x80004003);
public const int E_UNEXPECTED = unchecked((int)0x8000FFFF);
public const int E_ABORT = unchecked((int)0x80004004);
public const int E_HANDLE = unchecked((int)0x80070006);
public const int E_NOINTERFACE = unchecked((int)0x80004002);
public const int E_NOTIMPL = unchecked((int)0x80004001);
}
}
@@ -0,0 +1,149 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Explicit, Pack = 8, Size = 8)]
internal struct InnerPropertyValue
{
[FieldOffset(0)]
public sbyte cVal;
[FieldOffset(0)]
public byte bVal;
[FieldOffset(0)]
public short iVal;
[FieldOffset(0)]
public ushort uiVal;
[FieldOffset(0)]
public int lVal;
[FieldOffset(0)]
public uint ulVal;
[FieldOffset(0)]
public int intVal;
[FieldOffset(0)]
public uint uintVal;
[FieldOffset(0)]
public LargeInteger hVal;
[FieldOffset(0)]
public ULargeInteger uhVal;
[FieldOffset(0)]
public float fltVal;
[FieldOffset(0)]
public double dblVal;
[FieldOffset(0)]
public short boolVal;
[FieldOffset(0)]
public short __OBSOLETE__VARIANT_BOOL;
[FieldOffset(0)]
[MarshalAs(UnmanagedType.Error)]
public int scode;
[FieldOffset(0)]
[MarshalAs(UnmanagedType.Currency)]
public decimal cyVal;
[FieldOffset(0)]
public DateTime date;
[FieldOffset(0)]
public System.Runtime.InteropServices.ComTypes.FILETIME filetime;
[FieldOffset(0)]
public UnmanagedBlob bstrblobVal;
[FieldOffset(0)]
public UnmanagedBlob blob;
[FieldOffset(0)]
public UnmanagedArray cac;
[FieldOffset(0)]
public UnmanagedArray caub;
[FieldOffset(0)]
public UnmanagedArray cai;
[FieldOffset(0)]
public UnmanagedArray caui;
[FieldOffset(0)]
public UnmanagedArray cal;
[FieldOffset(0)]
public UnmanagedArray caul;
[FieldOffset(0)]
public UnmanagedArray caflt;
[FieldOffset(0)]
public UnmanagedArray cadbl;
[FieldOffset(0)]
public UnmanagedArray cabool;
[FieldOffset(0)]
public UnmanagedArray cascode;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pcVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pbVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr piVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr puiVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr plVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pulVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pintVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr puintVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pfltVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pdblVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pboolVal;
[ComConversionLoss]
[FieldOffset(0)]
public IntPtr pscode;
}
}
@@ -0,0 +1,89 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("9387928B-AC75-4BF9-8AB2-2B93C4A55290")]
[ComImport]
internal interface IConnectableCredentialProviderCredential : ICredentialProviderCredential
{
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int Advise([MarshalAs(UnmanagedType.Interface)] [In] ICredentialProviderCredentialEvents pcpce);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int UnAdvise();
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetSelected(out int pbAutoLogon);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetDeselected();
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetFieldState([In] uint dwFieldID, out FieldState pcpfs, out FieldInteractiveState pcpfis);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetStringValue([In] uint dwFieldID, out IntPtr ppsz);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetBitmapValue([In] uint dwFieldID, out IntPtr phbmp);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetCheckboxValue([In] uint dwFieldID, out int pbChecked, [MarshalAs(UnmanagedType.LPWStr)] out string ppszLabel);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetSubmitButtonValue([In] uint dwFieldID, out uint pdwAdjacentTo);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetComboBoxValueCount([In] uint dwFieldID, out uint pcItems, out uint pdwSelectedItem);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetComboBoxValueAt([In] uint dwFieldID, uint dwItem, [MarshalAs(UnmanagedType.LPWStr)] out string ppszItem);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetStringValue([In] uint dwFieldID, [In] IntPtr psz);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetCheckboxValue([In] uint dwFieldID, [In] int bChecked);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetComboBoxSelectedValue([In] uint dwFieldID, [In] uint dwSelectedItem);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int CommandLinkClicked([In] uint dwFieldID);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetSerialization(out SerializationResponse pcpgsr, out CredentialSerialization pcpcs, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int ReportResult([In] int ntsStatus, [In] int ntsSubstatus, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int Connect([MarshalAs(UnmanagedType.Interface)] [In] IQueryContinueWithStatus pqcws);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int Disconnect();
}
}
@@ -0,0 +1,93 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("D27C3481-5A1C-45B2-8AAA-C20EBBE8229E")]
[ComImport]
internal interface ICredentialProvider
{
/// <summary>
/// Defines the scenarios for which the credential provider is valid. Called whenever the credential provider is initialized.
/// </summary>
/// <param name="cpus">The scenario the credential provider has been created in. This is the usage scenario that needs to be supported. See the Remarks for more information.</param>
/// <param name="dwFlags">A value that affects the behavior of the credential provider. This value can be a bitwise-OR combination of one or more of the following values defined in Wincred.h. See CredUIPromptForWindowsCredentials for more information.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetUsageScenario([In] UsageScenario cpus, [In] CredUIWinFlags dwFlags);
/// <summary>
/// Sets the serialization characteristics of the credential provider.
/// </summary>
/// <param name="pcpcs">A CredentialSerialization structure that stores the serialization characteristics of the credential provider</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetSerialization([In] ref CredentialSerialization pcpcs);
/// <summary>
/// Allows a credential provider to initiate events in the Logon UI or Credential UI through a callback interface.
/// </summary>
/// <param name="pcpe">An ICredentialProviderEvents callback interface to be used as the notification mechanism.</param>
/// <param name="upAdviseContext">A pointer to an integer that uniquely identifies which credential provider has requested re-enumeration.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int Advise([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderEvents pcpe, IntPtr upAdviseContext);
/// <summary>
/// Used by the Logon UI or Credential UI to advise the credential provider that event callbacks are no longer accepted.
/// </summary>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int UnAdvise();
/// <summary>
/// Retrieves the count of fields in the needed to display this provider's credentials.
/// </summary>
/// <param name="pdwCount">The field count.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetFieldDescriptorCount(out uint pdwCount);
/// <summary>
/// Gets metadata that describes a specified field.
/// </summary>
/// <param name="dwIndex">The zero-based index of the field for which the information should be retrieved.</param>
/// <param name="ppcpfd">A pointer to a CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR structure which receives the information about the field.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetFieldDescriptorAt([In] uint dwIndex, out IntPtr ppcpfd);
/// <summary>
/// Gets the number of available credentials under this credential provider.
/// </summary>
/// <param name="pdwCount">A DWORD value that receives the count of credentials.</param>
/// <param name="pdwDefault">A DWORD value that receives the index of the credential to be used as the default. If no default value has been set, this value should be set to CREDENTIAL_PROVIDER_NO_DEFAULT.</param>
/// <param name="pbAutoLogonWithDefault">A BOOL value indicating if the default credential identified by pdwDefault should be used for an auto logon attempt. An auto logon attempt means the Logon UI or Credential UI will immediately call GetSerialization on the provider's default tile.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetCredentialCount(out uint pdwCount, out uint pdwDefault, out int pbAutoLogonWithDefault);
/// <summary>
/// Gets a specific credential.
/// </summary>
/// <param name="dwIndex">The zero-based index of the credential within the set of credentials enumerated for this credential provider.</param>
/// <param name="ppcpc">An ICredentialProviderCredential instance representing the credential.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetCredentialAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out ICredentialProviderCredential ppcpc);
}
}
@@ -0,0 +1,190 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Exposes methods that enable the handling of a credential.
/// </summary>
/// <remarks>
/// ICredentialProviderCredential is implemented by outside parties providing a Logon UI or Credential UI prompting for user credentials. Enumeration of user tiles cannot be done without an implementation of this interface.
/// </remarks>
[Guid("63913A93-40C1-481A-818D-4072FF8C70CC")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderCredential
{
/// <summary>
/// Enables a credential to initiate events in the Logon UI or Credential UI through a callback interface. This method should be called before other methods in <see cref="ICredentialProviderCredential"/> interface.
/// </summary>
/// <param name="pcpce">An <see cref="ICredentialProviderCredentialEvents"/> callback interface to be used as the notification mechanism.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int Advise([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredentialEvents pcpce);
/// <summary>
/// Used by the Logon UI or Credential UI to advise the credential that event callbacks are no longer accepted.
/// </summary>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int UnAdvise();
/// <summary>
/// Called when a credential is selected. Enables the implementer to set logon characteristics.
/// </summary>
/// <param name="pbAutoLogon">When this method returns, contains TRUE if selection of the credential indicates that it should attempt to logon immediately and automatically, otherwise FALSE. For example, a credential provider that enumerates an account without a password may want to return this as true.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetSelected(out int pbAutoLogon);
/// <summary>
/// Called when a credential loses selection.
/// </summary>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetDeselected();
/// <summary>
/// Retrieves the field state. The Logon UI and Credential UI use this to gain information about a field of a credential to display this information in the user tile.
/// </summary>
/// <param name="dwFieldID">The identifier for the field.</param>
/// <param name="pcpfs">The credential provider field state. This indicates when the field should be displayed on the user tile.</param>
/// <param name="pcpfis">The credential provider field interactive state. This indicates when the user can interact with the field.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetFieldState([In] uint dwFieldID, out FieldState pcpfs, out FieldInteractiveState pcpfis);
/// <summary>
/// Enables retrieval of text from a credential with a text field.
/// </summary>
/// <param name="dwFieldID">The identifier for the field.</param>
/// <param name="ppsz">A pointer to a null-terminated Unicode string to return to the Logon UI or Credential UI.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetStringValue([In] uint dwFieldID, out IntPtr ppsz);
/// <summary>
/// Enables retrieval of bitmap data from a credential with a bitmap field.
/// </summary>
/// <param name="dwFieldID">The identifier for the field.</param>
/// <param name="phbmp">Contains a pointer to the handle of the bitmap.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetBitmapValue([In] uint dwFieldID, out IntPtr phbmp);
/// <summary>
/// Retrieves the checkbox value.
/// </summary>
/// <param name="dwFieldID">The identifier for the field.</param>
/// <param name="pbChecked">Indicates the state of the checkbox. TRUE indicates the checkbox is checked, otherwise FALSE.</param>
/// <param name="ppszLabel">Points to the label on the checkbox.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetCheckboxValue([In] uint dwFieldID, out int pbChecked, [MarshalAs(UnmanagedType.LPWStr)] out string ppszLabel);
/// <summary>
/// Retrieves the identifier of a field that the submit button should be placed next to in the Logon UI. The Credential UI does not call this method.
/// </summary>
/// <param name="dwFieldID">The identifier for the field a submit button value is needed for.</param>
/// <param name="pdwAdjacentTo">The field ID of the field that the submit button should be placed next to.</param>
/// <remarks>Note to implementers: Do not return the field ID of a bitmap in this parameter. It is not good UI design to place the submit button next to a bitmap, and doing so can cause a failure in the Logon UI.</remarks>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetSubmitButtonValue([In] uint dwFieldID, out uint pdwAdjacentTo);
/// <summary>
/// Gets a count of the items in the specified combo box and designates which item should have initial selection.
/// </summary>
/// <param name="dwFieldID">The identifier for the combo box to gather information about.</param>
/// <param name="pcItems">A pointer to the number of items in the given combo box.</param>
/// <param name="pdwSelectedItem">Contains a pointer to the item that receives initial selection.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetComboBoxValueCount([In] uint dwFieldID, out uint pcItems, out uint pdwSelectedItem);
/// <summary>
/// Gets the string label for a combo box entry at the given index.
/// </summary>
/// <param name="dwFieldID">The identifier for the combo box to query.</param>
/// <param name="dwItem">The index of the desired item.</param>
/// <param name="ppszItem">A string value that receives the combo box label.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetComboBoxValueAt([In] uint dwFieldID, uint dwItem, [MarshalAs(UnmanagedType.LPWStr)] out string ppszItem);
/// <summary>
/// Enables a Logon UI or Credential UI to update the text for a CPFT_EDIT_TEXT fields as the user types in them.
/// </summary>
/// <param name="dwFieldID">The identifier for the field that needs to be updated.</param>
/// <param name="psz">A pointer to a buffer containing the new text.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetStringValue([In] uint dwFieldID, [In] IntPtr psz);
/// <summary>
/// Enables a Logon UI and Credential UI to indicate that a checkbox value has changed.
/// </summary>
/// <param name="dwFieldID">The identifier for the field to update.</param>
/// <param name="bChecked">Indicates the new value for the checkbox. TRUE means the checkbox should be checked, FALSE means unchecked.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetCheckboxValue([In] uint dwFieldID, [In] int bChecked);
/// <summary>
/// Enables a Logon UI and Credential UI to indicate that a combo box value has been selected.
/// </summary>
/// <param name="dwFieldID">The identifier for the combo box that is affected.</param>
/// <param name="dwSelectedItem">The specific item selected.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetComboBoxSelectedValue([In] uint dwFieldID, [In] uint dwSelectedItem);
/// <summary>
/// Enables the Logon UI and Credential UI to indicate that a link was clicked.
/// </summary>
/// <param name="dwFieldID">The identifier for the field clicked on.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int CommandLinkClicked([In] uint dwFieldID);
/// <summary>
/// Called in response to an attempt to submit this credential to the underlying authentication engine.
/// </summary>
/// <param name="pcpgsr">Indicates the success or failure of the attempt to serialize credentials.</param>
/// <param name="pcpcs">A pointer to the credential. Depending on the result, there may be no valid credential.</param>
/// <param name="ppszOptionalStatusText">A pointer to a Unicode string value that will be displayed by the Logon UI after serialization. May be NULL.</param>
/// <param name="pcpsiOptionalStatusIcon">A pointer to an icon that will be displayed by the credential after the call to GetSerialization returns. This value can be NULL.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetSerialization(out SerializationResponse pcpgsr, out CredentialSerialization pcpcs, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
/// <summary>
/// Translates a received error status code into the appropriate user-readable message. The Credential UI does not call this method.
/// </summary>
/// <param name="ntsStatus">The NTSTATUS value that reflects the return value of the Winlogon call to LsaLogonUser.</param>
/// <param name="ntsSubstatus">The NTSTATUS value that reflects the value pointed to by the SubStatus parameter of LsaLogonUser when that function returns after being called by Winlogon.</param>
/// <param name="ppszOptionalStatusText">A pointer to the error message that will be displayed to the user. May be NULL.</param>
/// <param name="pcpsiOptionalStatusIcon">A pointer to an icon that will shown on the credential. May be NULL.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int ReportResult([In] int ntsStatus, [In] int ntsSubstatus, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
}
}
@@ -0,0 +1,116 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Extends the ICredentialProviderCredential interface by adding a method that retrieves the security identifier (SID) of a user. The credential is associated with that user and can be grouped under the user's tile.
/// </summary>
/// <remarks><para>This class is required for creating a V2 credential provider. V2 credential providers provide a personalized log on experience for the user. This occurs by the credential provider telling the Logon UI what sign in options are available for a user. It is recommended that new credential providers should be V2 credential providers.</para>
/// <para>In order to create an ICredentialProviderCredential2 instance, a valid SID needs to be returned by the GetUserSid function.Valid is defined by the returned SID being for one of the users currently enumerated by the Logon UI.</para>
/// <para>A useful tool for getting the available users and determining which ones you want to associate with is the ICredentialProviderUserArray object. This object contains a list of ICredentialProviderUser objects that can be queried to gain information about the users that will be enumerated.For example you could gain the user's SID or username using GetStringValue with a passed in parameter of PKEY_Identity_PrimarySid or PKEY_Identity_USerName respectively. You can even filter the results using SetProviderFilter to only display a subset of available users.</para>
/// <para>Using the ICredentialProviderUserArray is optional, but it is a convenient way to get the necessary information to make valid SID values.In order to get a list of users that will be enumerated by the Logon UI, implement the ICredentialProviderSetUserArray interface to get the ICredentialProviderUserArray object from SetUserArray.Logon UI calls SetUserArray before GetCredentialCount, so the ICredentialProviderUserArray object is ready when a credential provider is about to return credentials.</para>
/// <para>A V2 credential provider is represented by an icon displayed underneath the "Sign-in options" link.In order to provide an icon for your credential provider, define a CREDENTIAL_PROVIDER_FIELD_TYPE of CPFT_TILE_IMAGE in the credential itself.Then make sure the guidFieldType of the CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR is set to CPFG_CREDENTIAL_PROVIDER_LOGO.The recommended size for an icon is 72 by 72 pixels.</para>
/// <para>Similar to specifying an icon for your credential provider, you can also specify a text string to identify your credential provider.This string appears in a pop-up window when a user hovers over the icon.To do this, define a CREDENTIAL_PROVIDER_FIELD_TYPE of CPFT_SMALL_TEXT in the credential itself.Then make sure the guidFieldType of the CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR is set to CPFG_CREDENTIAL_PROVIDER_LABEL.This string should supplement the credential provider icon described above and be descriptive enough that users understand what it is. For example, the picture password provider's description is "Picture Password".</para></remarks>
[Guid("FD672C54-40EA-4D6E-9B49-CFB1A7507BD7")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderCredential2 : ICredentialProviderCredential
{
/// <inheritdoc cref="ICredentialProviderCredential.Advise(ICredentialProviderCredentialEvents)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int Advise([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredentialEvents pcpce);
/// <inheritdoc cref="ICredentialProviderCredential.UnAdvise"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int UnAdvise();
/// <inheritdoc cref="ICredentialProviderCredential.SetSelected(out int)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetSelected(out int pbAutoLogon);
/// <inheritdoc cref="ICredentialProviderCredential.SetDeselected"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetDeselected();
/// <inheritdoc cref="ICredentialProviderCredential.GetFieldState(uint, out FieldState, out FieldInteractiveState)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetFieldState([In] uint dwFieldID, out FieldState pcpfs, out FieldInteractiveState pcpfis);
/// <inheritdoc cref="ICredentialProviderCredential.GetStringValue(uint, out IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetStringValue([In] uint dwFieldID, out IntPtr ppsz);
/// <inheritdoc cref="ICredentialProviderCredential.GetBitmapValue(uint, out IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetBitmapValue([In] uint dwFieldID, out IntPtr phbmp);
/// <inheritdoc cref="ICredentialProviderCredential.GetCheckboxValue(uint, out int, out string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetCheckboxValue([In] uint dwFieldID, out int pbChecked, [MarshalAs(UnmanagedType.LPWStr)] out string ppszLabel);
/// <inheritdoc cref="ICredentialProviderCredential.GetSubmitButtonValue(uint, out uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetSubmitButtonValue([In] uint dwFieldID, out uint pdwAdjacentTo);
/// <inheritdoc cref="ICredentialProviderCredential.GetComboBoxValueCount(uint, out uint, out uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetComboBoxValueCount([In] uint dwFieldID, out uint pcItems, out uint pdwSelectedItem);
/// <inheritdoc cref="ICredentialProviderCredential.GetComboBoxValueAt(uint, uint, out string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetComboBoxValueAt([In] uint dwFieldID, uint dwItem, [MarshalAs(UnmanagedType.LPWStr)] out string ppszItem);
/// <inheritdoc cref="ICredentialProviderCredential.SetStringValue(uint, IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetStringValue([In] uint dwFieldID, [In] IntPtr psz);
/// <inheritdoc cref="ICredentialProviderCredential.SetCheckboxValue(uint, int)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetCheckboxValue([In] uint dwFieldID, [In] int bChecked);
/// <inheritdoc cref="ICredentialProviderCredential.SetComboBoxSelectedValue(uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetComboBoxSelectedValue([In] uint dwFieldID, [In] uint dwSelectedItem);
/// <inheritdoc cref="ICredentialProviderCredential.CommandLinkClicked(uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int CommandLinkClicked([In] uint dwFieldID);
/// <inheritdoc cref="ICredentialProviderCredential.GetSerialization(out SerializationResponse, out CredentialSerialization, out string, out StatusIcon)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetSerialization(out SerializationResponse pcpgsr, out CredentialSerialization pcpcs, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
/// <inheritdoc cref="ICredentialProviderCredential.ReportResult(int, int, out string, out StatusIcon)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int ReportResult([In] int ntsStatus, [In] int ntsSubstatus, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
/// <summary>
/// Retrieves the security identifier (SID) of the user that is associated with this credential.
/// </summary>
/// <param name="sid">The address of a pointer to a buffer that, when this method returns successfully, receives the user's SID.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
/// <remarks>The Logon UI will use the returned SID from this method to associate the credential tile with a user tile. To associate the credential with the "Other user" user tile in the Logon UI, this method should return S_FALSE and a null SID. The "Other user" tile is normally only valid when the PC is joined to a domain.</remarks>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetUserSid([MarshalAs(UnmanagedType.LPWStr)] out string sid);
}
}
@@ -0,0 +1,117 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Exposes methods that enable the handling of a credential.
/// </summary>
/// <remarks>ICredentialProviderCredential is implemented by outside parties providing a Logon UI or Credential UI prompting for user credentials. Enumeration of user tiles cannot be done without an implementation of this interface. </remarks>
[Guid("64A5010E-4363-41F8-9738-19045C20DABC")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderCredential3 : ICredentialProviderCredential2
{
/// <inheritdoc cref="ICredentialProviderCredential.Advise(ICredentialProviderCredentialEvents)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int Advise([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredentialEvents pcpce);
/// <inheritdoc cref="ICredentialProviderCredential.UnAdvise"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int UnAdvise();
/// <inheritdoc cref="ICredentialProviderCredential.SetSelected(out int)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetSelected(out int pbAutoLogon);
/// <inheritdoc cref="ICredentialProviderCredential.SetDeselected"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetDeselected();
/// <inheritdoc cref="ICredentialProviderCredential.GetFieldState(uint, out FieldState, out FieldInteractiveState)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetFieldState([In] uint dwFieldID, out FieldState pcpfs, out FieldInteractiveState pcpfis);
/// <inheritdoc cref="ICredentialProviderCredential.GetStringValue(uint, out IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetStringValue([In] uint dwFieldID, out IntPtr ppsz);
/// <inheritdoc cref="ICredentialProviderCredential.GetBitmapValue(uint, out IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetBitmapValue([In] uint dwFieldID, out IntPtr phbmp);
/// <inheritdoc cref="ICredentialProviderCredential.GetCheckboxValue(uint, out int, out string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetCheckboxValue([In] uint dwFieldID, out int pbChecked, [MarshalAs(UnmanagedType.LPWStr)] out string ppszLabel);
/// <inheritdoc cref="ICredentialProviderCredential.GetSubmitButtonValue(uint, out uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetSubmitButtonValue([In] uint dwFieldID, out uint pdwAdjacentTo);
/// <inheritdoc cref="ICredentialProviderCredential.GetComboBoxValueCount(uint, out uint, out uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetComboBoxValueCount([In] uint dwFieldID, out uint pcItems, out uint pdwSelectedItem);
/// <inheritdoc cref="ICredentialProviderCredential.GetComboBoxValueAt(uint, uint, out string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetComboBoxValueAt([In] uint dwFieldID, uint dwItem, [MarshalAs(UnmanagedType.LPWStr)] out string ppszItem);
/// <inheritdoc cref="ICredentialProviderCredential.SetStringValue(uint, IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetStringValue([In] uint dwFieldID, [In] IntPtr psz);
/// <inheritdoc cref="ICredentialProviderCredential.SetCheckboxValue(uint, int)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetCheckboxValue([In] uint dwFieldID, [In] int bChecked);
/// <inheritdoc cref="ICredentialProviderCredential.SetComboBoxSelectedValue(uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetComboBoxSelectedValue([In] uint dwFieldID, [In] uint dwSelectedItem);
/// <inheritdoc cref="ICredentialProviderCredential.CommandLinkClicked(uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int CommandLinkClicked([In] uint dwFieldID);
/// <inheritdoc cref="ICredentialProviderCredential.GetSerialization(out SerializationResponse, out CredentialSerialization, out string, out StatusIcon)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetSerialization(out SerializationResponse pcpgsr, out CredentialSerialization pcpcs, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
/// <inheritdoc cref="ICredentialProviderCredential.ReportResult(int, int, out string, out StatusIcon)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int ReportResult([In] int ntsStatus, [In] int ntsSubstatus, [MarshalAs(UnmanagedType.LPWStr)] out string ppszOptionalStatusText, out StatusIcon pcpsiOptionalStatusIcon);
/// <inheritdoc cref="ICredentialProviderCredential2.GetUserSid(out string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int GetUserSid([MarshalAs(UnmanagedType.LPWStr)] out string sid);
/// <summary>
/// Gets a buffer containing the image data
/// </summary>
/// <param name="fieldID">The field ID to get</param>
/// <param name="pImageBufferSize">The size of the bitmap buffer</param>
/// <param name="ppImageBuffer">A pointer to the bitmap buffer</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetBitmapBufferValue([In] uint fieldID, [Out] out uint pImageBufferSize, [Out] out IntPtr ppImageBuffer);
}
}
@@ -0,0 +1,124 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Provides an asynchronous callback mechanism used by a credential to notify it of state or text change events in the Logon UI or Credential UI.
/// </summary>
[Guid("FA6FA76B-66B7-4B11-95F1-86171118E816")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderCredentialEvents
{
/// <summary>
/// Communicates to the Logon UI or Credential UI that a field state has changed and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing a field whose interactivity state is being set. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the field.</param>
/// <param name="cpfs">The value from the CREDENTIAL_PROVIDER_FIELD_STATE enumeration that specifies the new field state.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldState([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] FieldState cpfs);
/// <summary>
/// Communicates to the Logon UI or Credential UI that the interactivity state of a field has changed and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing a field whose interactivity state is being set. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the field.</param>
/// <param name="cpfis"></param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldInteractiveState([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] FieldInteractiveState cpfis);
/// <summary>
/// Communicates to the Logon UI or Credential UI that the string associated with a field has changed and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing a field whose interactivity state is being set. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the field.</param>
/// <param name="psz">A pointer to the new string for the field.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldString([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] IntPtr psz);
/// <summary>
/// Communicates to the Logon UI or Credential UI that a checkbox field has changed and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing the checkbox field that is being set. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique field ID for the checkbox.</param>
/// <param name="bChecked">The new state of the checkbox. TRUE indicates the checkbox should be checked, FALSE indicates it should not.</param>
/// <param name="pszLabel">The new string for the checkbox label.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldCheckbox([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] int bChecked, [MarshalAs(UnmanagedType.LPWStr)][In] string pszLabel);
/// <summary>
/// Communicates to the Logon UI or Credential UI that a tile image field has changed and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing the tile image field that is being set. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the tile image field.</param>
/// <param name="hbmp">The new tile image.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldBitmap([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] IntPtr hbmp);
/// <summary>
/// Communicates to the Logon UI or Credential UI that the selected item in a combo box has changed and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing the combo box being set. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the combo box.</param>
/// <param name="dwSelectedItem">The index of the item to select in the combo box.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldComboBoxSelectedItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwSelectedItem);
/// <summary>
/// Communicates to the Logon UI or Credential UI that an item should be deleted from a combo box and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing the combo box that needs to be updated. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the combo box.</param>
/// <param name="dwItem">The index of the item that is deleted.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int DeleteFieldComboBoxItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwItem);
/// <summary>
/// Communicates to the Logon UI or Credential UI that a combo box needs an item appended and that the UI should be updated.
/// </summary>
/// <param name="pcpc">The credential containing the combo box that needs an item added. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the combo box.</param>
/// <param name="pszItem">The string that will be appended to the combo box as a new option.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int AppendFieldComboBoxItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [MarshalAs(UnmanagedType.LPWStr)][In] string pszItem);
/// <summary>
/// Enables credentials to set the field that the submit button appears adjacent to.
/// </summary>
/// <param name="pcpc">The credential containing a field whose interactivity state is being set. This value should be set to this. See ICredentialProviderCredentialEvents for more information.</param>
/// <param name="dwFieldID">The unique ID of the field.</param>
/// <param name="dwAdjacentTo">The unique field ID of the field that the submit button should be adjacent to when this method completes.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldSubmitButton([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwAdjacentTo);
/// <summary>
/// Called when the window is created. Enables credentials to retrieve the HWND of the parent window after Advise is called.
/// </summary>
/// <param name="phwndOwner">A pointer to the handle of the parent window.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int OnCreatingWindow(out IntPtr phwndOwner);
}
}
@@ -0,0 +1,101 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Extends the ICredentialProviderCredentialEvents interface by adding methods that enable batch updating of fields in theLogon UI or Credential UI.
/// </summary>
/// <remarks>
/// <para>In Windows 7 and Windows Vista, many credential providers used ICredentialProviderEvents::CredentialsChanged to update UI. While this works, it causes a re-enumeration of all the credentials from the calling credential provider. The processing of this event can, under some circumstances, lead to flashing or focus changes in the UI due to this re-enumeration. Therefore, using ICredentialProviderEvents::CredentialsChanged solely for UI updates is discouraged. The new recommendation is as follows:</para>
/// <para>
/// Use ICredentialProviderEvents::CredentialsChanged only if a credential provider needs to do automatically logon a user or change the number of credentials it is enumerating.
///Use ICredentialProviderCredentialEvents2 to update a credential provider's UI.
/// </para>
/// <para>ICredentialProviderCredentialEvents2 includes all of the methods inherited from ICredentialProviderCredentialEvents.This includes all of the inherited methods except OnCreatingWindow.</para>
/// <para>When interacting with a background thread, the use of ICredentialProviderCredentialEvents2 is similar to the use of ICredentialProviderCredentialEvents, in that proper inter-thread communication methods must be used.</para>
/// </remarks>
[Guid("B53C00B6-9922-4B78-B1F4-DDFE774DC39B")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderCredentialEvents2 : ICredentialProviderCredentialEvents
{
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldState(ICredentialProviderCredential, uint, FieldState)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldState([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] FieldState cpfs);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldInteractiveState(ICredentialProviderCredential, uint, FieldInteractiveState)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldInteractiveState([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] FieldInteractiveState cpfis);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldString(ICredentialProviderCredential, uint, IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldString([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] IntPtr psz);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldCheckbox(ICredentialProviderCredential, uint, int, string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldCheckbox([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] int bChecked, [MarshalAs(UnmanagedType.LPWStr)][In] string pszLabel);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldBitmap(ICredentialProviderCredential, uint, IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldBitmap([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] IntPtr hbmp);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldComboBoxSelectedItem(ICredentialProviderCredential, uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldComboBoxSelectedItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwSelectedItem);
///<inheritdoc cref="ICredentialProviderCredentialEvents.DeleteFieldComboBoxItem(ICredentialProviderCredential, uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int DeleteFieldComboBoxItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwItem);
///<inheritdoc cref="ICredentialProviderCredentialEvents.AppendFieldComboBoxItem(ICredentialProviderCredential, uint, string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int AppendFieldComboBoxItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [MarshalAs(UnmanagedType.LPWStr)][In] string pszItem);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldSubmitButton(ICredentialProviderCredential, uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldSubmitButton([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwAdjacentTo);
///<inheritdoc cref="ICredentialProviderCredentialEvents.OnCreatingWindow(out IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int OnCreatingWindow(out IntPtr phwndOwner);
/// <summary>
/// Starts a batch update to fields in the logon or credential UI.
/// </summary>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int BeginFieldUpdates();
/// <summary>
/// Finishes and commits the batch updates started by BeginFieldUpdates.
/// </summary>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int EndFieldUpdates();
/// <summary>
/// Specifies whether a specified field in the logon or credential UI should display a "password reveal" glyph or is expected to receive an e-mail address.
/// </summary>
/// <param name="credential">An ICredentialProviderCredential interface pointer to the credential object.</param>
/// <param name="fieldID">The ID of the field in the logon or credential UI for which this option applies.</param>
/// <param name="options">One or more of the CREDENTIAL_PROVIDER_CREDENTIAL_FIELD_OPTIONS values, which specify the field options.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetFieldOptions([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential credential, [In] uint fieldID, [In] FieldOptions options);
}
}
@@ -0,0 +1,87 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[Guid("2D8DEEB8-1322-4973-8DF9-B282F2468290")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderCredentialEvents3 : ICredentialProviderCredentialEvents2
{
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldState(ICredentialProviderCredential, uint, FieldState)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldState([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] FieldState cpfs);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldInteractiveState(ICredentialProviderCredential, uint, FieldInteractiveState)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldInteractiveState([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] FieldInteractiveState cpfis);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldString(ICredentialProviderCredential, uint, IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldString([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] IntPtr psz);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldCheckbox(ICredentialProviderCredential, uint, int, string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldCheckbox([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] int bChecked, [MarshalAs(UnmanagedType.LPWStr)][In] string pszLabel);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldBitmap(ICredentialProviderCredential, uint, IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldBitmap([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] IntPtr hbmp);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldComboBoxSelectedItem(ICredentialProviderCredential, uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldComboBoxSelectedItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwSelectedItem);
///<inheritdoc cref="ICredentialProviderCredentialEvents.DeleteFieldComboBoxItem(ICredentialProviderCredential, uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int DeleteFieldComboBoxItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwItem);
///<inheritdoc cref="ICredentialProviderCredentialEvents.AppendFieldComboBoxItem(ICredentialProviderCredential, uint, string)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int AppendFieldComboBoxItem([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [MarshalAs(UnmanagedType.LPWStr)][In] string pszItem);
///<inheritdoc cref="ICredentialProviderCredentialEvents.SetFieldSubmitButton(ICredentialProviderCredential, uint, uint)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldSubmitButton([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, [In] uint dwFieldID, [In] uint dwAdjacentTo);
///<inheritdoc cref="ICredentialProviderCredentialEvents.OnCreatingWindow(out IntPtr)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int OnCreatingWindow(out IntPtr phwndOwner);
///<inheritdoc cref="ICredentialProviderCredentialEvents2.BeginFieldUpdates"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int BeginFieldUpdates();
///<inheritdoc cref="ICredentialProviderCredentialEvents2.EndFieldUpdates"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int EndFieldUpdates();
///<inheritdoc cref="ICredentialProviderCredentialEvents2.SetFieldOptions(ICredentialProviderCredential, uint, FieldOptions)"/>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int SetFieldOptions([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential credential, [In] uint fieldID, [In] FieldOptions options);
/// <summary>
/// Communicates to the Logon UI or Credential UI that a bitmap field should be updated
/// </summary>
/// <param name="pcpc">An ICredentialProviderCredential interface pointer to the credential object.</param>
/// <param name="fieldID">The ID of the field to update</param>
/// <param name="imageBufferSize">The image buffer size</param>
/// <param name="pImageBuffer">A pointer to the image buffer</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
int SetFieldBitmapBuffer([MarshalAs(UnmanagedType.Interface)][In] ICredentialProviderCredential pcpc, uint fieldID, uint imageBufferSize, IntPtr pImageBuffer);
}
}
@@ -0,0 +1,28 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Provides a method that enables the credential provider framework to determine whether you've made a customization to a field's option in a logon or credential UI.
/// </summary>
/// <remarks>
/// Implement this interface if your credential provider overrides the default field options through ICredentialProviderCredentialEvents2::SetFieldOptions. This enables the credential provider framework to determine the field options that you've specified .
/// </remarks>
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("DBC6FB30-C843-49E3-A645-573E6F39446A")]
[ComImport]
internal interface ICredentialProviderCredentialWithFieldOptions
{
/// <summary>
/// Retrieves the current option set for a specified field in a logon or credential UI. Called by the credential provider framework.
/// </summary>
/// <param name="fieldID">The ID of the field in the logon or credential UI.</param>
/// <param name="options">A pointer to an CREDENTIAL_PROVIDER_CREDENTIAL_FIELD_OPTIONS value that, when this method returns successfully, receives one or more flags that specify the current options for the field.</param>
/// <remarks>Provides a method that enables the credential provider framework to determine whether you've made a customization to a field's option in a logon or credential UI.</remarks>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetFieldOptions([In] uint fieldID, out FieldOptions options);
}
}
@@ -0,0 +1,27 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Provides an asynchronous callback mechanism used by a credential provider to notify it of changes in the list of credentials or their fields.
/// </summary>
/// <remarks>
/// An implementation of ICredentialProviderEvents is provided for use by outside parties implementing a credential provider.
/// </remarks>
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("34201E5A-A787-41A3-A5A4-BD6DCF2A854E")]
[ComImport]
internal interface ICredentialProviderEvents
{
/// <summary>
/// Signals the Logon UI or Credential UI that the enumerated list of credentials has changed. This happens when the number of credentials change, the individual credentials change, or the number of fields available change. This is an asynchronous method.
/// </summary>
/// <param name="upAdviseContext">A pointer to an integer that uniquely identifies which credential provider has requested re-enumeration. The credential provider should pass back the interface pointer it received from Advise in this parameter.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int CredentialsChanged(IntPtr upAdviseContext);
}
}
@@ -0,0 +1,20 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("A5DA53F9-D475-4080-A120-910C4A739880")]
[ComImport]
internal interface ICredentialProviderFilter
{
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int Filter([In] UsageScenario cpus, [In] CredUIWinFlags dwFlags, IntPtr rgclsidProviders, IntPtr rgbAllow, [In] uint cProviders);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int UpdateRemoteCredential([In] ref CredentialSerialization pcpcsIn, ref CredentialSerialization pcpcsOut);
}
}
@@ -0,0 +1,26 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Provides a method that enables a credential provider to receive the set of users that will be shown in the logon or credential UI.
/// </summary>
/// <remarks>
/// Implement this interface for credential providers that have a need to know which users will appear in the logon or credential UI.
/// </remarks>
[Guid("095C1484-1C0C-4388-9C6D-500E61BF84BD")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderSetUserArray
{
/// <summary>
/// Called by the system during the initialization of a logon or credential UI to retrieve the set of users to show in that UI.
/// </summary>
/// <param name="users">A pointer to an array object that contains a set of ICredentialProviderUser objects, each representing a user that will appear in the logon or credential UI. This array enables the credential provider to enumerate and query each of the user objects for their SID, their associated credential provider's ID, various forms of the user name, and their logon status string.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetUserArray([MarshalAs(UnmanagedType.Interface)] [In] ICredentialProviderUserArray users);
}
}
@@ -0,0 +1,48 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Provides methods used to retrieve certain properties of an individual user included in a logon or credential UI.
/// </summary>
/// <remarks>Windows 8 introduces the ability to group credential providers by user. The logon UI can display a set of users rather than a set of multiple credential providers for each user. Selecting a user then displays the individual credential provider options associated with that user.</remarks>
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("13793285-3EA6-40FD-B420-15F47DA41FBB")]
[ComImport]
internal interface ICredentialProviderUser
{
/// <summary>
/// Retrieves the user's security identifier (SID).
/// </summary>
/// <param name="sid">The address of a pointer to a buffer that, when this method returns successfully, receives the user's SID. It is the responsibility of the caller to free this resource by calling the CoTaskMemFree function.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetSid([MarshalAs(UnmanagedType.LPWStr)] out string sid);
/// <summary>
/// Retrieves string properties from the ICredentialProviderUser object based on the input value.
/// </summary>
/// <param name="providerID"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetProviderID(out Guid providerID);
/// <summary>
/// One of the following values that specify the property to retrieve.
/// </summary>
/// <param name="key">One of the following values that specify the property to retrieve - PKEY_Identity_DisplayName, PKEY_Identity_LogonStatusString, PKEY_Identity_PrimarySid, PKEY_Identity_ProviderID, PKEY_Identity_QualifiedUserName or KEY_Identity_UserName </param>
/// <param name="stringValue">The address of a pointer to a buffer that, when this method returns successfully, receives the requested string.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetStringValue([In] ref PropertyKey key, [MarshalAs(UnmanagedType.LPWStr)] out string stringValue);
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetValue([In] ref PropertyKey key, out PropertyValue value);
}
}
@@ -0,0 +1,53 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Represents the set of users that will appear in the logon or credential UI. This information enables the credential provider to enumerate the set to retrieve property information about each user to populate fields or filter the set.
/// </summary>
/// <remarks>This object is provided by the Windows credential provider framework to your credential provider through the ICredentialProviderSetUserArray::SetUserArray method. Ownership of this object remains with the credential provider framework.</remarks>
[Guid("90C119AE-0F18-4520-A1F1-114366A40FE8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface ICredentialProviderUserArray
{
/// <summary>
/// Limits the set of users in the array to either local accounts or Microsoft accounts.
/// </summary>
/// <param name="guidProviderToFilterTo">Set this parameter to Identity_LocalUserProvider for the local accounts credential provider; otherwise set it to the GUID of the Microsoft account provider.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetProviderFilter([In] ref Guid guidProviderToFilterTo);
/// <summary>
/// Retrieves a value that indicates whether the "Other user" tile for local or Microsoft accounts is shown in the logon or credential UI. This information can be used by a credential provider to show the same behavior as the password or Microsoft account provider.
/// </summary>
/// <param name="credentialProviderAccountOptions">A pointer to a value that, when this method returns successfully, receives one or more flags that specify which empty tiles are shown by the logon or credential UI.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetAccountOptions(out AccountOptions credentialProviderAccountOptions);
/// <summary>
/// Retrieves the number of ICredentialProviderUser objects in the user array.
/// </summary>
/// <param name="userCount">A pointer to a value that, when this method returns successfully, receives the number of users in the array.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetCount(out uint userCount);
/// <summary>
/// Retrieves a specified user from the array.
/// </summary>
/// <param name="userIndex">The 0-based array index of the user. The size of the array can be obtained through the GetCount method.</param>
/// <param name="user">The address of a pointer to an object that, when this method returns successfully, represents the specified user. It is the responsibility of the caller to free this object when it is no longer needed by calling its Release method.</param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int GetAt([In] uint userIndex, [MarshalAs(UnmanagedType.Interface)] out ICredentialProviderUser user);
}
}
@@ -0,0 +1,15 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[Guid("7307055C-B24A-486B-9F25-163E597A28A9")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface IQueryContinue
{
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int QueryContinue();
}
}
@@ -0,0 +1,19 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("9090BE5B-502B-41FB-BCCC-0049A6C7254B")]
[ComImport]
internal interface IQueryContinueWithStatus : IQueryContinue
{
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
new int QueryContinue();
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[return: MarshalAs(UnmanagedType.Error)]
int SetStatusMessage([MarshalAs(UnmanagedType.LPWStr)] [In] string psz);
}
}
@@ -0,0 +1,69 @@
using System;
using System.Security;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider.Interop
{
internal static class InternalExtensions
{
internal static unsafe int Wcslen(this IntPtr addr)
{
const int maxLength = int.MaxValue;
var c = (char*)(addr);
for (int i = 0; i < maxLength; i++)
{
if (c[i] == '\0')
{
return i;
}
}
throw new ArgumentException("End of string not found");
}
internal static unsafe SecureString IntPtrToSecureString(this IntPtr psz)
{
var length = psz.Wcslen();
var charArray = (char*)psz;
return new SecureString(charArray, length);
}
internal static string GetUserName(this ICredentialProviderUser user)
{
user.GetStringValue(PropertyKeys.PKEY_Identity_UserName, out var value);
return value;
}
internal static string GetQualifiedUserName(this ICredentialProviderUser user)
{
user.GetStringValue(PropertyKeys.PKEY_Identity_QualifiedUserName, out var value);
return value;
}
internal static string GetDisplayName(this ICredentialProviderUser user)
{
user.GetStringValue(PropertyKeys.PKEY_Identity_DisplayName, out var value);
return value;
}
internal static string GetLogonStatus(this ICredentialProviderUser user)
{
user.GetStringValue(PropertyKeys.PKEY_Identity_LogonStatusString, out var value);
return value;
}
internal static string GetPrimarySid(this ICredentialProviderUser user)
{
user.GetStringValue(PropertyKeys.PKEY_Identity_PrimarySid, out var value);
return value;
}
internal static string GetProviderID(this ICredentialProviderUser user)
{
user.GetStringValue(PropertyKeys.PKEY_Identity_ProviderID, out var value);
return value;
}
}
}
@@ -0,0 +1,19 @@
namespace Lithnet.CredentialProvider.Interop
{
internal enum KerbLogonSubmitType : uint
{
InteractiveLogon = 2,
SmartCardLogon = 6,
WorkstationUnlockLogon = 7,
SmartCardUnlockLogon = 8,
ProxyLogon = 9,
TicketLogon = 10,
TicketUnlockLogon = 11,
S4ULogon = 12,
CertificateLogon = 13,
CertificateS4ULogon = 14,
CertificateUnlockLogon = 15,
NoElevationLogon = 83,
LuidLogon = 84,
}
}
@@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential)]
internal struct KerberosInteractiveUnlockLogon
{
public KerbLogonSubmitType SubmitType;
public LsaStringUni LogonDomainName;
public LsaStringUni Username;
public LsaStringUni Password;
public long LoginId;
}
}
@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 8)]
internal struct LargeInteger
{
public long QuadPart;
}
}
@@ -0,0 +1,40 @@
using System;
using System.ComponentModel;
namespace Lithnet.CredentialProvider.Interop
{
internal struct LsaHandle : IDisposable
{
public IntPtr Handle;
public LsaHandle(IntPtr handle)
{
this.Handle = handle;
}
public static implicit operator LsaHandle(IntPtr handle) => new LsaHandle(handle);
public static implicit operator IntPtr(LsaHandle handle) => handle.Handle;
public static LsaHandle ConnectUntrusted()
{
var result = PInvoke.LsaConnectUntrusted(out var handle);
if (result != 0)
{
throw new Win32Exception((int)result);
}
return handle;
}
public bool IsValid()
{
return this.Handle != IntPtr.Zero;
}
public void Dispose()
{
PInvoke.LsaDeregisterLogonProcess(this.Handle);
}
}
}
@@ -0,0 +1,33 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential)]
internal struct LsaString : IDisposable
{
public ushort Length;
public ushort MaxLength;
public IntPtr Buffer;
public LsaString(string value)
{
this.Length = (ushort)value.Length;
this.MaxLength = this.Length;
this.Buffer = Marshal.StringToHGlobalAnsi(value);
}
public static implicit operator LsaString(string value) => new LsaString(value);
public static implicit operator string(LsaString value) => value.ToString();
public override string ToString()
{
return Marshal.PtrToStringAnsi(this.Buffer) ?? string.Empty;
}
public void Dispose()
{
Marshal.FreeHGlobal(this.Buffer);
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential)]
internal struct LsaStringUni : IDisposable
{
public ushort Length;
public ushort MaxLength;
public IntPtr Buffer;
public LsaStringUni(string value)
{
this.Length = (ushort)(value.Length * sizeof(ushort));
this.MaxLength = this.Length;
this.Buffer = Marshal.StringToHGlobalUni(value);
}
public static implicit operator LsaStringUni(string value) => new LsaStringUni(value);
public static implicit operator string(LsaStringUni value) => value.ToString();
public override string ToString()
{
return Marshal.PtrToStringUni(this.Buffer) ?? string.Empty;
}
public void Dispose()
{
Marshal.FreeHGlobal(this.Buffer);
}
}
}
@@ -0,0 +1,65 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Lithnet.CredentialProvider.Interop
{
internal static class PInvoke
{
[DllImport("secur32.dll", SetLastError = false)]
public static extern uint LsaConnectUntrusted(out IntPtr LsaHandle);
[DllImport("secur32.dll", SetLastError = false)]
public static extern IntPtr LsaDeregisterLogonProcess([In] IntPtr handle);
[DllImport("secur32.dll", SetLastError = false)]
public static extern uint LsaLookupAuthenticationPackage(IntPtr lsaHandle, ref LsaString packageName, out uint authenticationPackage);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern uint LsaNtStatusToWinError(uint status);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetComputerName(StringBuilder buffer, ref uint size);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
public static string GetComputerName(uint bufferSize = 25)
{
var buffer = new StringBuilder((int)bufferSize);
if (GetComputerName(buffer, ref bufferSize))
{
return buffer.ToString();
}
throw new Win32Exception(Marshal.GetLastWin32Error());
}
public static uint LookupAuthenticationPackage(LsaString packageName)
{
using (var handle = LsaHandle.ConnectUntrusted())
{
var result = LsaLookupAuthenticationPackage(handle, ref packageName, out var package);
if (result != 0)
{
throw new Win32Exception((int)result);
}
return package;
}
}
[MethodImplAttribute(MethodImplOptions.NoOptimization)]
public static unsafe void SecureZeroMemory(IntPtr ptr, uint length)
{
for (int i = 0; i < length; i++)
{
*((byte*)ptr + i) = 0;
}
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct PropertyKey
{
public Guid FormatID;
public uint PropertyID;
public PropertyKey(string format, uint property)
{
FormatID = new Guid(format);
PropertyID = property;
}
public PropertyKey(uint propertyId,
uint a,
ushort b,
ushort c,
byte d,
byte e,
byte f,
byte g,
byte h,
byte i,
byte j,
byte k)
{
PropertyID = propertyId;
FormatID = new Guid(a, b, c, d, e, f, g, h, i, j, k);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 8)]
internal struct PropertyValue
{
public ushort vt;
public byte wReserved1;
public byte wReserved2;
public uint wReserved3;
public InnerPropertyValue Value;
}
}
@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 8)]
internal struct ULargeInteger
{
public ulong QuadPart;
}
}
@@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct UnmanagedArray
{
public uint cElems;
[ComConversionLoss]
public IntPtr pElems;
}
}
@@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct UnmanagedBlob
{
public uint cbSize;
[ComConversionLoss]
public IntPtr pData;
}
}
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<OutputType>Library</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTargets>AnyCPU</PlatformTargets>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<Description>Lithnet Windows Credential Provider</Description>
<Company>Lithnet</Company>
<Copyright>Copyright 2023 Lithnet Pty Ltd</Copyright>
<ProductName>Lithnet Windows Credential Provider</ProductName>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>beta.3</VersionSuffix>
<Authors>Lithnet</Authors>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<AutoIncrementPackageRevision>true</AutoIncrementPackageRevision>
<IsPackable>true</IsPackable>
<PackageId>Lithnet.CredentialProvider</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/lithnet/windows-credential-provider</RepositoryUrl>
<SupportUrl>https://github.com/lithnet/windows-credential-provider</SupportUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" Condition="$(TargetFramework) == 'netstandard2.0'" />
</ItemGroup>
</Project>
@@ -0,0 +1,33 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// An object that represents a response to LogonUI/CredUI that includes serialized credentials, or the result of the attempt to obtain them.
/// </summary>
public class NativeSerializationResponse
{
/// <summary>
/// Gets or sets the final HRESULT of the serialization operation
/// </summary>
public int HResult { get; set; }
/// <summary>
/// Gets or sets the status result of the serialization operation
/// </summary>
public SerializationResponse SerializationResponse { get; set; }
/// <summary>
/// Gets or sets the serialzied credential blob
/// </summary>
public CredentialSerialization SerializedCredentials { get; set; }
/// <summary>
/// Gets or sets the optional status text to display to the user
/// </summary>
public string OptionalStatusText { get; set; }
/// <summary>
/// Gets or sets the optional status icon to display to the user
/// </summary>
public StatusIcon OptionalStatusIcon { get; set; }
}
}