Merge pull request #32 from lithnet/feature/bitmap-transparency

Add transparent bitmap support and COM ABI coverage
This commit is contained in:
Ryan Newington
2026-08-30 12:11:14 +10:00
committed by GitHub
87 changed files with 1785 additions and 393 deletions
+38 -4
View File
@@ -9,7 +9,7 @@ A library for creating secure Windows Credential Providers in .NET, without the
The Lithnet Credential Provider for Windows provides an easy way to create a credential provider, without having to implement the COM components. The COM components are still there, but abstracted away into a fully managed implementation.
## Getting started
* Create a new Class Library project. You can use .NET Framework 4.6.1 or higher, or you can use .NET 6.0 or higher) to create your provider. You must build as either an x64 or x86 binary. You cannot use AnyCPU.
* Create a new Class Library project. You can use .NET Framework 4.7.2 or later, or .NET 8.0, 9.0, or 10.0. You must build an x64 or x86 binary. You cannot use AnyCPU.
* Install the package from nuget `Install-Package Lithnet.CredentialProvider`
* Modify the `csproj` file and set `RegisterForComInterop` to `false`
@@ -21,11 +21,11 @@ The Lithnet Credential Provider for Windows provides an easy way to create a cre
</PropertyGroup>
```
* If you are using .NET 6 or higher, you must also set `EnableComHosting` to `true`
* If you are using .NET 8.0, 9.0, or 10.0, you must also set `EnableComHosting` to `true`.
```xml
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x64</Platform>
<EnableComHosting>true</EnableComHosting>
@@ -113,7 +113,7 @@ public override CredentialTile2 CreateUserTile(CredentialProviderUser user)
}
```
* Create your tile class. Inherit from `CredentialTile2` if you want to create personalized tiles supported by Windows 8 and later, or `CredentialTile1` if you only want to implement a generic tile. Grab the instances of your controls in the `Initialize` method, so you can attach to their properties to read and respond to value changes. Finally, override the `GetCredentials` method, which is called when the user clicks the submit button.
* Create your tile class. Inherit from `CredentialTile2` if you want to create personalized tiles supported by Windows 8 and later, or `CredentialTile` if you only want to implement a generic tile. Use `CredentialTile3` when an image must preserve transparency. Grab the instances of your controls in the `Initialize` method, so you can attach to their properties to read and respond to value changes. Finally, override the `GetCredentials` method, which is called when the user clicks the submit button.
```cs
public class MyTile : CredentialTile2
@@ -188,10 +188,44 @@ public class MyTile : CredentialTile2
};
}
}
```
* Build your project and you have a functional credential provider!
## Bitmap transparency
`CredentialProviderLogoControl` and `UserTileControl` both accept a `Bitmap`. The tile base class controls how Windows receives images that contain transparent or partially transparent pixels.
| Tile base class | Image behaviour |
|-----------------|-----------------|
| `CredentialTile` | Renders transparency against the control's `BackgroundColor`. |
| `CredentialTile2` | Renders transparency against the control's `BackgroundColor`. |
| `CredentialTile3` | Preserves the image's alpha channel and ignores `BackgroundColor`. |
Existing providers that inherit from `CredentialTile` or `CredentialTile2` keep their current behaviour. The default `BackgroundColor` is `#464646`.
To preserve transparency, inherit your tile class from `CredentialTile3` and provide a `Bitmap` with an alpha channel. The library selects the image representation required by Windows, so your provider does not need to handle that conversion.
```cs
public class MyTile : CredentialTile3
{
public MyTile(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
}
public MyTile(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider, user)
{
}
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
{
Bitmap image = LoadTransparentBitmap();
yield return new UserTileControl("UserTile", "User tile image", image);
yield return new CredentialProviderLogoControl("ProviderLogo", "Credential provider logo", image);
}
```
## Installing the credential provider
You can use the traditional methods of registering a credential provider (regasm, regsvr32, create registry keys etc), but we've provided a PowerShell module to automatically register your credential provider with a single command.
+220 -150
View File
@@ -1,3 +1,10 @@
resources:
repositories:
- repository: release_tools
type: git
name: release-tools/release-tools
ref: refs/heads/master
pool:
vmImage: 'windows-latest'
@@ -20,18 +27,152 @@ variables:
value: $(build.version.major).$(build.version.minor).$(build.version.revision)
- name: build.date
value: $[format('{0:yyyy}-{0:MM}-{0:dd}T{0:HH}:{0:mm}:{0:ss}', pipeline.startTime)]
- group: Azure KeyVault Code Signing
- name: azure.subscription
value: 'ProductionServices'
name: $(build.version.major).$(build.version.minor).$(build.version.revision)$(build.version.suffix)
# This pipeline is manual only. Repository pushes and pull requests must not start builds.
trigger: none
pr: none
stages:
- stage: test_provider
displayName: Test credential provider
dependsOn: []
jobs:
- job: test_windows
displayName: Test Windows
strategy:
matrix:
x64:
testArchitecture: x64
testImage: windows-latest
testProject: src/Lithnet.CredentialProvider.UnitTests.x64/Lithnet.CredentialProvider.UnitTests.x64.csproj
x86:
testArchitecture: x86
testImage: windows-latest
testProject: src/Lithnet.CredentialProvider.UnitTests.x86/Lithnet.CredentialProvider.UnitTests.x86.csproj
pool:
vmImage: $(testImage)
steps:
- task: UseDotNet@2
displayName: Install .NET 8 SDK
inputs:
packageType: sdk
version: 8.0.x
- task: UseDotNet@2
displayName: Install .NET 9 SDK
inputs:
packageType: sdk
version: 9.0.x
- task: UseDotNet@2
displayName: Install .NET 10 SDK
inputs:
packageType: sdk
version: 10.0.x
- task: PowerShell@2
displayName: Install x86 .NET runtimes
condition: eq(variables['testArchitecture'], 'x86')
inputs:
targetType: inline
script: |
$ErrorActionPreference = 'Stop'
$installScriptPath = "$(Agent.TempDirectory)\dotnet-install.ps1"
$runtimePath = "$(Agent.TempDirectory)\dotnet-x86"
Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile $installScriptPath
foreach ($channel in @('8.0', '9.0', '10.0'))
{
& $installScriptPath -Runtime dotnet -Channel $channel -Architecture x86 -InstallDir $runtimePath -NoPath
if (-not $?)
{
throw "Failed to install the .NET $channel x86 runtime"
}
}
- task: DotNetCoreCLI@2
displayName: Test $(testArchitecture)
env:
DOTNET_ROOT_X86: $(Agent.TempDirectory)\dotnet-x86
inputs:
command: test
projects: $(testProject)
arguments: '--configuration $(buildConfiguration) --arch $(testArchitecture)'
publishTestResults: true
testRunTitle: Credential Provider $(testArchitecture)
- template: templates/azure-vm-start.yaml@release_tools
parameters:
jobName: start_arm64_test_vm
vmNames: cpw1125a64-ci
deployEnvironment: CredentialProvider
distroType: win
resourceGroup: rg-ams-testhosts
azureSubscription: CredentialProviderAzureTestLab
agentMode: pool
targetPoolName: CredentialProvider
- job: test_windows_arm64
displayName: Test Windows ARM64
dependsOn: start_arm64_test_vm
pool:
name: CredentialProvider
demands:
- arch -equals arm64
- role -equals unittest
- product -equals credential-provider
steps:
- task: UseDotNet@2
displayName: Install .NET 8 SDK
inputs:
packageType: sdk
version: 8.0.x
- task: UseDotNet@2
displayName: Install .NET 9 SDK
inputs:
packageType: sdk
version: 9.0.x
- task: UseDotNet@2
displayName: Install .NET 10 SDK
inputs:
packageType: sdk
version: 10.0.x
- task: DotNetCoreCLI@2
displayName: Test ARM64
inputs:
command: test
projects: src/Lithnet.CredentialProvider.UnitTests.arm64/Lithnet.CredentialProvider.UnitTests.arm64.csproj
arguments: '--configuration $(buildConfiguration) --arch arm64'
publishTestResults: true
testRunTitle: Credential Provider ARM64
- template: templates/azure-vm-stop.yaml@release_tools
parameters:
jobName: stop_arm64_test_vm
vmNames: cpw1125a64-ci
deployEnvironment: CredentialProvider
distroType: win
resourceGroup: rg-ams-testhosts
azureSubscription: CredentialProviderAzureTestLab
dependsOn: test_windows_arm64
condition: always()
- stage: build_provider
displayName: Build credential provider
dependsOn: []
dependsOn: test_provider
jobs:
- job: "build_provider_job"
steps:
- template: templates/checkout.yaml@release_tools
- task: DotNetCoreCLI@2
displayName: dotnet build
inputs:
@@ -39,41 +180,13 @@ stages:
arguments: '-c $(buildConfiguration) -p:Version=$(build.version) -p:GeneratePackageOnBuild=false'
projects: 'src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj'
- task: DotNetCoreCLI@2
inputs:
command: 'custom'
custom: 'tool'
arguments: 'update --global azuresigntool'
displayName: Install AzureSignTool
- task: PowerShell@2
displayName: 'Sign files with AzureSignTool'
inputs:
targetType: 'inline'
script: |
$files = @()
$files += (Get-ChildItem -Recurse -Path "$(Build.SourcesDirectory)\Lithnet*.dll").FullName
write-host "Signing $($files.Length) files:"
write-output $files
$cmdargs = @(
"sign",
"-d", "Lithnet Windows Credential Provider",
"-kvu", "$(akv.url)",
"-kvi", "$(akv.applicationID)",
"-kvs", "$(akv.secret)",
"-kvt", "$(akv.tenantId)",
"-kvc", "$(akv.certificateName)",
"-tr", "http://timestamp.digicert.com",
"-td", "sha256"
)
$cmdargs += $files
& AzureSignTool $cmdargs
failOnStderr: true
showWarnings: true
- template: templates/codesign.yaml@release_tools
parameters:
path:
- '$(Build.SourcesDirectory)/**/Lithnet.CredentialProvider.dll'
environment: 'prod'
azureSubscription: '$(azure.subscription)'
description: 'Lithnet Windows Credential Provider'
- task: DotNetCoreCLI@2
displayName: dotnet pack
@@ -86,70 +199,14 @@ stages:
versioningScheme: 'byEnvVar'
versionEnvVar: 'build.version'
- task: DotNetCoreCLI@2
inputs:
command: 'custom'
custom: 'tool'
arguments: 'update --global NuGetKeyVaultSignTool'
displayName: Install NugetKeyVaultSignTool
- task: PowerShell@2
displayName: 'Sign Nuget package'
inputs:
targetType: 'inline'
script: |
$cmdargs = @(
"sign", "$(Build.ArtifactStagingDirectory)\cp\Lithnet.CredentialProvider.$(build.version).nupkg"
"-fd", "sha256",
"-kvu", "$(akv.url)",
"-kvi", "$(akv.applicationID)",
"-kvs", "$(akv.secret)",
"-kvt", "$(akv.tenantId)",
"-kvc", "$(akv.certificateName)",
"-tr", "http://timestamp.digicert.com",
"-td", "sha256"
)
& NuGetKeyVaultSignTool $cmdargs
failOnStderr: true
showWarnings: true
- task: PowerShell@2
displayName: 'Sign Nuget symbols package'
inputs:
targetType: 'inline'
script: |
$cmdargs = @(
"sign", "$(Build.ArtifactStagingDirectory)\cp\Lithnet.CredentialProvider.$(build.version).snupkg"
"-fd", "sha256",
"-kvu", "$(akv.url)",
"-kvi", "$(akv.applicationID)",
"-kvs", "$(akv.secret)",
"-kvt", "$(akv.tenantId)",
"-kvc", "$(akv.certificateName)",
"-tr", "http://timestamp.digicert.com",
"-td", "sha256"
)
& NuGetKeyVaultSignTool $cmdargs
failOnStderr: true
showWarnings: true
- task: DotNetCoreCLI@2
displayName: Publish package to internal feed
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/cp/*.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: '91a552bc-359d-4f28-bdbd-f36f71cfdf81'
- task: DotNetCoreCLI@2
displayName: Publish symbols to internal feed
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/cp/*.snupkg'
nuGetFeedType: 'internal'
publishVstsFeed: '91a552bc-359d-4f28-bdbd-f36f71cfdf81'
- template: templates/codesign.yaml@release_tools
parameters:
path:
- '$(Build.ArtifactStagingDirectory)/cp/*.nupkg'
- '$(Build.ArtifactStagingDirectory)/cp/*.snupkg'
environment: 'prod'
azureSubscription: '$(azure.subscription)'
description: 'Lithnet Windows Credential Provider'
- task: PublishPipelineArtifact@1
displayName: Publish nuget artifact
@@ -158,38 +215,35 @@ stages:
publishLocation: 'pipeline'
artifact: cp
- task: GitHubRelease@1
inputs:
gitHubConnection: github.com_lithnet # string. Required. GitHub connection (OAuth or PAT).
repositoryName: '$(Build.Repository.Name)' # string. Required. Repository. Default: $(Build.Repository.Name).
action: 'create' # 'create' | 'edit' | 'delete'. Required. Action. Default: create.
#target: '$(Build.SourceVersion)' # string. Required when action = create || action = edit. Target. Default: $(Build.SourceVersion).
tagSource: 'userSpecifiedTag' # 'gitTag' | 'userSpecifiedTag'. Required when action = create. Tag source. Default: gitTag.
#tagPattern: # string. Optional. Use when tagSource = gitTag. Tag Pattern.
tag: v$(build.version) # string. Required when action = edit || action = delete || tagSource = userSpecifiedTag. Tag.
title: v$(build.version) # string. Optional. Use when action = create || action = edit. Release title.
#releaseNotesSource: 'filePath' # 'filePath' | 'inline'. Optional. Use when action = create || action = edit. Release notes source. Default: filePath.
#releaseNotesFilePath: # string. Optional. Use when releaseNotesSource = filePath. Release notes file path.
#releaseNotesInline: # string. Optional. Use when releaseNotesSource = inline. Release notes.
assets: | # string. Optional. Use when action = create || action = edit. Assets. Default: $(Build.ArtifactStagingDirectory)/*.
$(Build.ArtifactStagingDirectory)/cp/*.nupkg
#assetUploadMode: 'delete' # 'delete' | 'replace'. Optional. Use when action = edit. Asset upload mode. Default: delete.
#isDraft: false # boolean. Optional. Use when action = create || action = edit. Draft release. Default: false.
isPreRelease: true # boolean. Optional. Use when action = create || action = edit. Pre-release. Default: false.
addChangeLog: true # boolean. Optional. Use when action = create || action = edit. Add changelog. Default: true.
# Changelog configuration
changeLogCompareToRelease: 'lastFullRelease' # 'lastFullRelease' | 'lastNonDraftRelease' | 'lastNonDraftReleaseByTag'. Required when addChangeLog = true. Compare to. Default: lastFullRelease.
#changeLogCompareToReleaseTag: # string. Required when changeLogCompareToRelease = lastNonDraftReleaseByTag && addChangeLog = true. Release Tag.
changeLogType: 'commitBased' # 'commitBased' | 'issueBased'. Required when addChangeLog = true. Changelog type. Default: commitBased.
#changeLogLabels: '[{ "label" : "bug", "displayName" : "Bugs", "state" : "closed" }]' # string. Optional. Use when changeLogType = issueBased && addChangeLog = true. Categories. Default: [{ "label" : "bug", "displayName" : "Bugs", "state" : "closed" }].
- stage: publish_nuget
displayName: Publish CredProvider to nuget.org
dependsOn: "build_provider"
# Publish signed packages to Azure Artifacts only after the test and build stages succeed.
- stage: publish_internal
displayName: Publish to internal feed
dependsOn: build_provider
jobs:
- deployment: 'PublishPackages'
- job: publish_internal_job
displayName: Publish packages to internal feed
steps:
- checkout: none
- download: current
artifact: cp
- template: templates/publish-nuget.yaml@release_tools
parameters:
nugetPackagePath: '$(Pipeline.Workspace)/cp/*.nupkg'
symbolPackagePath: '$(Pipeline.Workspace)/cp/*.snupkg'
publishInternal: true
publishExternal: false
# Public release is limited to main and waits for approval on the Public nuget feed environment.
- stage: publish_prod
displayName: Publish to nuget.org and GitHub
dependsOn: publish_internal
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: publish_nuget
displayName: Publish package to public nuget feed
environment: 'Public nuget feed'
displayName: Publish packages to public nuget feed
pool:
vmImage: windows-2022
strategy:
@@ -197,26 +251,42 @@ stages:
deploy:
steps:
- checkout: none
- download: current
artifact: cp
- task: NuGetToolInstaller@1
inputs:
versionSpec: '>=4.9.0-0'
- task: NuGetCommand@2
displayName: 'Publish nuget package to public feed'
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/cp/*.nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'WindowsCredentialProviderNuget'
- template: templates/publish-nuget.yaml@release_tools
parameters:
nugetPackagePath: '$(Pipeline.Workspace)/cp/*.nupkg'
publishInternal: false
publishExternal: true
externalFeedCredentials: 'WindowsCredentialProviderNuget'
# Create the GitHub release only after nuget.org accepts the package.
- job: publish_github
displayName: Publish package to GitHub releases
dependsOn: publish_nuget
condition: succeeded()
pool:
vmImage: windows-2022
steps:
- checkout: none
- download: current
artifact: cp
- task: GitHubRelease@1
displayName: Create GitHub release (v$(build.version))
inputs:
gitHubConnection: github.com_lithnet # string. Required. GitHub connection (OAuth or PAT).
repositoryName: '$(Build.Repository.Name)' # string. Required. Repository. Default: $(Build.Repository.Name).
action: 'edit' # 'create' | 'edit' | 'delete'. Required. Action. Default: create.
target: '$(Build.SourceVersion)' # string. Required when action = create || action = edit. Target. Default: $(Build.SourceVersion).
tagSource: 'userSpecifiedTag' # 'gitTag' | 'userSpecifiedTag'. Required when action = create. Tag source. Default: gitTag.
#tagPattern: # string. Optional. Use when tagSource = gitTag. Tag Pattern.
tag: v$(build.version) # string. Required when action = edit || action = delete || tagSource = userSpecifiedTag. Tag.
isPreRelease: false # boolean. Optional. Use when action = create || action = edit. Pre-release. Default: false.
addChangeLog: false # boolean. Optional. Use when action = create || action = edit. Add changelog. Default: true.
gitHubConnection: github.com_lithnet
repositoryName: '$(Build.Repository.Name)'
action: create
target: '$(Build.SourceVersion)'
tagSource: userSpecifiedTag
tag: v$(build.version)
title: v$(build.version)
assets: '$(Pipeline.Workspace)/cp/*.nupkg'
isPreRelease: false
addChangeLog: true
changeLogCompareToRelease: lastFullRelease
changeLogType: commitBased
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows;net9.0-windows;net10.0-windows;net472;net48</TargetFrameworks>
<IsPackable>false</IsPackable>
<PlatformTarget>ARM64</PlatformTarget>
<DefineConstants>$(DefineConstants);TEST_ARM64</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
<Compile Include="..\Lithnet.CredentialProvider.UnitTests.x64\BitmapControlTests.cs" Link="BitmapControlTests.cs" />
<Compile Include="..\Lithnet.CredentialProvider.UnitTests.x64\ComInterop\**\*.cs" Link="ComInterop\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Project>
@@ -0,0 +1,97 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using NUnit.Framework;
namespace Lithnet.CredentialProvider.UnitTests
{
public class BitmapControlTests
{
[Test]
public void TransparentBufferPreservesAlphaChannel()
{
using (Bitmap source = new Bitmap(2, 1, PixelFormat.Format32bppArgb))
{
source.SetPixel(0, 0, Color.FromArgb(0, 10, 20, 30));
source.SetPixel(1, 0, Color.FromArgb(128, 40, 50, 60));
var control = new UserTileControl("image", "Image", source);
byte[] bytes = GetBitmapBuffer(control);
Assert.That(bytes, Has.Length.GreaterThan(8));
Assert.That(bytes[0], Is.EqualTo(0x89));
Assert.That(bytes[1], Is.EqualTo(0x50));
Assert.That(bytes[2], Is.EqualTo(0x4e));
Assert.That(bytes[3], Is.EqualTo(0x47));
using (MemoryStream stream = new MemoryStream(bytes))
using (Bitmap decoded = new Bitmap(stream))
{
Assert.That(decoded.GetPixel(0, 0).A, Is.EqualTo(0));
Assert.That(decoded.GetPixel(1, 0).A, Is.EqualTo(128));
Assert.That(decoded.GetPixel(1, 0).R, Is.EqualTo(40));
Assert.That(decoded.GetPixel(1, 0).G, Is.EqualTo(50));
Assert.That(decoded.GetPixel(1, 0).B, Is.EqualTo(60));
}
}
}
[Test]
public void BitmapBufferDoesNotApplyConfiguredBackgroundColor()
{
using (Bitmap source = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
{
source.SetPixel(0, 0, Color.Transparent);
var control = new UserTileControl("image", "Image", source)
{
BackgroundColor = Color.FromArgb(12, 34, 56)
};
byte[] bytes = GetBitmapBuffer(control);
using (MemoryStream stream = new MemoryStream(bytes))
using (Bitmap decoded = new Bitmap(stream))
{
Assert.That(decoded.GetPixel(0, 0).A, Is.EqualTo(0));
}
}
}
[Test]
public void CloneCopiesBitmapAndBackgroundColor()
{
using (Bitmap source = new Bitmap(1, 1))
{
var control = new UserTileControl("image", "Image", source)
{
BackgroundColor = Color.CornflowerBlue
};
var clone = (UserTileControl)control.Clone();
Assert.That(clone.Bitmap, Is.SameAs(source));
Assert.That(clone.BackgroundColor, Is.EqualTo(Color.CornflowerBlue));
}
}
private static byte[] GetBitmapBuffer(BitmapControl control)
{
IntPtr buffer = control.GetBitmapBuffer(out uint size);
try
{
byte[] bytes = new byte[size];
Marshal.Copy(buffer, bytes, 0, checked((int)size));
return bytes;
}
finally
{
Marshal.FreeCoTaskMem(buffer);
}
}
}
}
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("2A83F3F8-A46C-4104-8FFB-BD9979279450")]
internal sealed class AbiTestCredentialProvider : CredentialProviderBase
{
public AbiTestCredentialProvider()
{
this.Field = new SmallLabelControl("message", "COM ABI test");
}
public SmallLabelControl Field { get; }
public AbiTestCredentialTile2 Tile { get; private set; }
public override bool IsUsageScenarioSupported(UsageScenario cpus, CredUIWinFlags dwFlags)
{
return cpus == UsageScenario.CredUI;
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
{
return new ControlBase[] { this.Field };
}
public override bool ShouldIncludeUserTile(CredentialProviderUser user)
{
return false;
}
public override bool ShouldIncludeGenericTile()
{
return true;
}
public override CredentialTile CreateGenericTile()
{
this.Tile = new AbiTestCredentialTile2(this);
return this.Tile;
}
public override CredentialTile2 CreateUserTile(CredentialProviderUser user)
{
return null;
}
}
}
@@ -0,0 +1,14 @@
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal sealed class AbiTestCredentialTile2 : CredentialTile2
{
public AbiTestCredentialTile2(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
}
protected override CredentialResponseBase GetCredentials()
{
return null;
}
}
}
@@ -0,0 +1,79 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal sealed class ComInterfacePointer : IDisposable
{
private IntPtr value;
private ComInterfacePointer(IntPtr value)
{
this.value = value;
}
public IntPtr Value
{
get
{
if (this.value == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(ComInterfacePointer));
}
return this.value;
}
}
public static ComInterfacePointer Create(object instance, Guid interfaceId)
{
IntPtr unknown = Marshal.GetIUnknownForObject(instance);
try
{
int hresult = ComMarshal.QueryInterface(unknown, interfaceId, out IntPtr interfacePointer);
if (hresult != CredentialProviderAbi.S_OK)
{
if (interfacePointer != IntPtr.Zero)
{
Marshal.Release(interfacePointer);
}
throw new COMException("The COM interface was not available", hresult);
}
return new ComInterfacePointer(interfacePointer);
}
finally
{
Marshal.Release(unknown);
}
}
public static ComInterfacePointer TakeOwnership(IntPtr value)
{
if (value == IntPtr.Zero)
{
throw new ArgumentException("The COM interface pointer cannot be zero", nameof(value));
}
return new ComInterfacePointer(value);
}
public TDelegate GetMethod<TDelegate>(int slot) where TDelegate : class
{
IntPtr vtable = Marshal.ReadIntPtr(this.Value);
IntPtr method = Marshal.ReadIntPtr(vtable, checked(slot * IntPtr.Size));
return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(method, typeof(TDelegate));
}
public void Dispose()
{
if (this.value != IntPtr.Zero)
{
Marshal.Release(this.value);
this.value = IntPtr.Zero;
}
}
}
}
@@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal static class ComMarshal
{
public static int QueryInterface(IntPtr unknown, Guid interfaceId, out IntPtr interfacePointer)
{
#if NET9_0_OR_GREATER
return Marshal.QueryInterface(unknown, in interfaceId, out interfacePointer);
#else
return Marshal.QueryInterface(unknown, ref interfaceId, out interfacePointer);
#endif
}
}
}
@@ -0,0 +1,59 @@
using System;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal static class CredentialProviderAbi
{
public static readonly Guid ICredentialProvider = new Guid("D27C3481-5A1C-45B2-8AAA-C20EBBE8229E");
public static readonly Guid ICredentialProviderCredential = new Guid("63913A93-40C1-481A-818D-4072FF8C70CC");
public static readonly Guid ICredentialProviderCredential2 = new Guid("FD672C54-40EA-4D6E-9B49-CFB1A7507BD7");
public static readonly Guid ICredentialProviderSetUserArray = new Guid("095C1484-1C0C-4388-9C6D-500E61BF84BD");
public static readonly Guid ICredentialProviderUserArray = new Guid("90C119AE-0F18-4520-A1F1-114366A40FE8");
public const int SetUsageScenarioSlot = 3;
public const int GetFieldDescriptorCountSlot = 7;
public const int GetFieldDescriptorAtSlot = 8;
public const int GetCredentialCountSlot = 9;
public const int GetCredentialAtSlot = 10;
public const int SetUserArraySlot = 3;
public const int UserArrayGetCountSlot = 5;
public const int SetSelectedSlot = 5;
public const int SetDeselectedSlot = 6;
public const int GetFieldStateSlot = 7;
public const int GetStringValueSlot = 8;
public const int GetUserSidSlot = 20;
public const int SmallTextFieldType = 2;
public const int DisplayInSelectedTileFieldState = 1;
public const int NoInteractiveFieldState = 0;
public const uint NoDefaultCredential = 0xFFFFFFFF;
public const int S_OK = 0;
public const int S_FALSE = 1;
public const int E_FAIL = unchecked((int)0x80004005);
public const int E_INVALIDARG = unchecked((int)0x80070057);
public const int E_NOTIMPL = unchecked((int)0x80004001);
}
}
@@ -0,0 +1,450 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using NUnit.Framework;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[TestFixture]
[NonParallelizable]
[Apartment(ApartmentState.STA)]
public class CredentialProviderAbiTests
{
[Test]
public void CredentialProviderInterfaceCanBeQueried()
{
var provider = new AbiTestCredentialProvider();
IntPtr unknown = Marshal.GetIUnknownForObject(provider);
IntPtr providerInterface = IntPtr.Zero;
try
{
int hresult = ComMarshal.QueryInterface(unknown, CredentialProviderAbi.ICredentialProvider, out providerInterface);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(providerInterface, Is.Not.EqualTo(IntPtr.Zero));
}
finally
{
if (providerInterface != IntPtr.Zero)
{
Marshal.Release(providerInterface);
}
Marshal.Release(unknown);
GC.KeepAlive(provider);
}
}
[Test]
public void SetUsageScenarioPreservesHResultAndArguments()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
SetUsageScenarioDelegate setUsageScenario = providerInterface.GetMethod<SetUsageScenarioDelegate>(CredentialProviderAbi.SetUsageScenarioSlot);
int hresult = setUsageScenario(providerInterface.Value, (int)UsageScenario.CredUI, (uint)CredUIWinFlags.CREDUIWIN_SECURE_PROMPT);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(provider.UsageScenario, Is.EqualTo(UsageScenario.CredUI));
Assert.That(provider.CredUIFlags, Is.EqualTo(CredUIWinFlags.CREDUIWIN_SECURE_PROMPT));
hresult = setUsageScenario(providerInterface.Value, (int)UsageScenario.Logon, 0);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.E_NOTIMPL));
Assert.That(provider.UsageScenario, Is.EqualTo(UsageScenario.Logon));
Assert.That(provider.CredUIFlags, Is.EqualTo((CredUIWinFlags)0));
}
GC.KeepAlive(provider);
}
[Test]
public void GetFieldDescriptorCountReturnsProviderControlCount()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetFieldDescriptorCountDelegate getFieldDescriptorCount = providerInterface.GetMethod<GetFieldDescriptorCountDelegate>(CredentialProviderAbi.GetFieldDescriptorCountSlot);
int hresult = getFieldDescriptorCount(providerInterface.Value, out uint count);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
}
GC.KeepAlive(provider);
}
[Test]
public void GetFieldDescriptorAtReturnsWindowsSdkLayout()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetFieldDescriptorCountDelegate getFieldDescriptorCount = providerInterface.GetMethod<GetFieldDescriptorCountDelegate>(CredentialProviderAbi.GetFieldDescriptorCountSlot);
GetFieldDescriptorAtDelegate getFieldDescriptorAt = providerInterface.GetMethod<GetFieldDescriptorAtDelegate>(CredentialProviderAbi.GetFieldDescriptorAtSlot);
Assert.That(getFieldDescriptorCount(providerInterface.Value, out uint count), Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
IntPtr descriptorPointer = IntPtr.Zero;
IntPtr labelPointer = IntPtr.Zero;
try
{
int hresult = getFieldDescriptorAt(providerInterface.Value, 0, out descriptorPointer);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(descriptorPointer, Is.Not.EqualTo(IntPtr.Zero));
NativeCredentialProviderFieldDescriptor descriptor = Marshal.PtrToStructure<NativeCredentialProviderFieldDescriptor>(descriptorPointer);
labelPointer = descriptor.Label;
Assert.That(descriptor.FieldId, Is.EqualTo(provider.Field.Id));
Assert.That(descriptor.FieldType, Is.EqualTo(CredentialProviderAbi.SmallTextFieldType));
Assert.That(Marshal.PtrToStringUni(labelPointer), Is.EqualTo("COM ABI test"));
Assert.That(descriptor.FieldTypeGuid, Is.EqualTo(Guid.Empty));
}
finally
{
if (labelPointer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(labelPointer);
}
if (descriptorPointer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(descriptorPointer);
}
}
}
GC.KeepAlive(provider);
}
[Test]
public void GetFieldDescriptorAtRejectsInvalidIndex()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetFieldDescriptorCountDelegate getFieldDescriptorCount = providerInterface.GetMethod<GetFieldDescriptorCountDelegate>(CredentialProviderAbi.GetFieldDescriptorCountSlot);
GetFieldDescriptorAtDelegate getFieldDescriptorAt = providerInterface.GetMethod<GetFieldDescriptorAtDelegate>(CredentialProviderAbi.GetFieldDescriptorAtSlot);
Assert.That(getFieldDescriptorCount(providerInterface.Value, out uint count), Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
int hresult = getFieldDescriptorAt(providerInterface.Value, count, out IntPtr descriptorPointer);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.E_INVALIDARG));
Assert.That(descriptorPointer, Is.EqualTo(IntPtr.Zero));
}
GC.KeepAlive(provider);
}
[Test]
public void CredentialProviderSetUserArrayInterfaceCanBeQueried()
{
var provider = new AbiTestCredentialProvider();
IntPtr unknown = Marshal.GetIUnknownForObject(provider);
IntPtr setUserArrayInterface = IntPtr.Zero;
try
{
int hresult = ComMarshal.QueryInterface(unknown, CredentialProviderAbi.ICredentialProviderSetUserArray, out setUserArrayInterface);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(setUserArrayInterface, Is.Not.EqualTo(IntPtr.Zero));
}
finally
{
if (setUserArrayInterface != IntPtr.Zero)
{
Marshal.Release(setUserArrayInterface);
}
Marshal.Release(unknown);
GC.KeepAlive(provider);
}
}
[Test]
public void TestUserArrayUsesWindowsSdkGetCountSlot()
{
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer userArrayInterface = ComInterfacePointer.Create(users, CredentialProviderAbi.ICredentialProviderUserArray))
{
GetUserCountDelegate getCount = userArrayInterface.GetMethod<GetUserCountDelegate>(CredentialProviderAbi.UserArrayGetCountSlot);
int hresult = getCount(userArrayInterface.Value, out uint count);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(0));
Assert.That(users.GetCountCallCount, Is.EqualTo(1));
}
GC.KeepAlive(users);
}
[Test]
public void SetUserArrayInvokesWindowsSdkUserArrayGetCount()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
SetEmptyUserArray(provider, users);
Assert.That(users.GetCountCallCount, Is.EqualTo(1));
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void GetCredentialCountReturnsGenericTileWithoutDefault()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
SetEmptyUserArray(provider, users);
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetCredentialCountDelegate getCredentialCount = providerInterface.GetMethod<GetCredentialCountDelegate>(CredentialProviderAbi.GetCredentialCountSlot);
int hresult = getCredentialCount(providerInterface.Value, out uint count, out uint defaultCredential, out int autoLogonWithDefault);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
Assert.That(defaultCredential, Is.EqualTo(CredentialProviderAbi.NoDefaultCredential));
Assert.That(autoLogonWithDefault, Is.EqualTo(0));
Assert.That(users.GetCountCallCount, Is.EqualTo(2));
Assert.That(provider.Tile, Is.Not.Null);
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void GetCredentialAtRejectsInvalidIndex()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
SetEmptyUserArray(provider, users);
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetCredentialCountDelegate getCredentialCount = providerInterface.GetMethod<GetCredentialCountDelegate>(CredentialProviderAbi.GetCredentialCountSlot);
GetCredentialAtDelegate getCredentialAt = providerInterface.GetMethod<GetCredentialAtDelegate>(CredentialProviderAbi.GetCredentialAtSlot);
Assert.That(getCredentialCount(providerInterface.Value, out uint count, out uint defaultCredential, out int autoLogonWithDefault), Is.EqualTo(CredentialProviderAbi.S_OK));
int hresult = getCredentialAt(providerInterface.Value, count, out IntPtr credential);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.E_FAIL));
Assert.That(credential, Is.EqualTo(IntPtr.Zero));
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV1SelectionMethodsPreserveStateAndHResults()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
SetSelectedDelegate setSelected = credential.GetMethod<SetSelectedDelegate>(CredentialProviderAbi.SetSelectedSlot);
SetDeselectedDelegate setDeselected = credential.GetMethod<SetDeselectedDelegate>(CredentialProviderAbi.SetDeselectedSlot);
int hresult = setSelected(credential.Value, out int autoLogon);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(autoLogon, Is.EqualTo(0));
Assert.That(provider.Tile.IsSelected, Is.True);
hresult = setDeselected(credential.Value);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(provider.Tile.IsSelected, Is.False);
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV1GetFieldStateUsesWindowsSdkEnumValues()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
GetFieldStateDelegate getFieldState = credential.GetMethod<GetFieldStateDelegate>(CredentialProviderAbi.GetFieldStateSlot);
int hresult = getFieldState(credential.Value, provider.Field.Id, out int fieldState, out int interactiveState);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(fieldState, Is.EqualTo(CredentialProviderAbi.DisplayInSelectedTileFieldState));
Assert.That(interactiveState, Is.EqualTo(CredentialProviderAbi.NoInteractiveFieldState));
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV1GetStringValueReturnsComTaskMemory()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
GetStringValueDelegate getStringValue = credential.GetMethod<GetStringValueDelegate>(CredentialProviderAbi.GetStringValueSlot);
IntPtr value = IntPtr.Zero;
try
{
int hresult = getStringValue(credential.Value, provider.Field.Id, out value);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(value, Is.Not.EqualTo(IntPtr.Zero));
Assert.That(Marshal.PtrToStringUni(value), Is.EqualTo("COM ABI test"));
}
finally
{
if (value != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(value);
}
}
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV2GetUserSidUsesInheritedVtableOrder()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
IntPtr credential2Pointer = IntPtr.Zero;
try
{
int hresult = ComMarshal.QueryInterface(credential.Value, CredentialProviderAbi.ICredentialProviderCredential2, out credential2Pointer);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(credential2Pointer, Is.Not.EqualTo(IntPtr.Zero));
using (ComInterfacePointer credential2 = ComInterfacePointer.TakeOwnership(credential2Pointer))
{
credential2Pointer = IntPtr.Zero;
GetUserSidDelegate getUserSid = credential2.GetMethod<GetUserSidDelegate>(CredentialProviderAbi.GetUserSidSlot);
IntPtr sid = IntPtr.Zero;
try
{
hresult = getUserSid(credential2.Value, out sid);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_FALSE));
Assert.That(sid, Is.EqualTo(IntPtr.Zero));
}
finally
{
if (sid != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(sid);
}
}
}
}
finally
{
if (credential2Pointer != IntPtr.Zero)
{
Marshal.Release(credential2Pointer);
}
}
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void FieldDescriptorDeclarationMatchesWindowsSdkSize()
{
int expectedSize = IntPtr.Size == 4 ? 28 : 32;
Assert.That(Marshal.SizeOf<NativeCredentialProviderFieldDescriptor>(), Is.EqualTo(expectedSize));
}
private static void SetEmptyUserArray(AbiTestCredentialProvider provider, TestCredentialProviderUserArray users)
{
using (ComInterfacePointer setUserArrayInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProviderSetUserArray))
using (ComInterfacePointer userArrayInterface = ComInterfacePointer.Create(users, CredentialProviderAbi.ICredentialProviderUserArray))
{
SetUserArrayDelegate setUserArray = setUserArrayInterface.GetMethod<SetUserArrayDelegate>(CredentialProviderAbi.SetUserArraySlot);
int hresult = setUserArray(setUserArrayInterface.Value, userArrayInterface.Value);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
}
}
private static ComInterfacePointer CreateCredentialInterface(AbiTestCredentialProvider provider, TestCredentialProviderUserArray users)
{
SetEmptyUserArray(provider, users);
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetCredentialCountDelegate getCredentialCount = providerInterface.GetMethod<GetCredentialCountDelegate>(CredentialProviderAbi.GetCredentialCountSlot);
GetCredentialAtDelegate getCredentialAt = providerInterface.GetMethod<GetCredentialAtDelegate>(CredentialProviderAbi.GetCredentialAtSlot);
Assert.That(getCredentialCount(providerInterface.Value, out uint count, out uint defaultCredential, out int autoLogonWithDefault), Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
IntPtr credential = IntPtr.Zero;
try
{
int hresult = getCredentialAt(providerInterface.Value, 0, out credential);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(credential, Is.Not.EqualTo(IntPtr.Zero));
ComInterfacePointer result = ComInterfacePointer.TakeOwnership(credential);
credential = IntPtr.Zero;
return result;
}
finally
{
if (credential != IntPtr.Zero)
{
Marshal.Release(credential);
}
}
}
}
}
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetCredentialAtDelegate(IntPtr instance, uint index, out IntPtr credential);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetCredentialCountDelegate(IntPtr instance, out uint count, out uint defaultCredential, out int autoLogonWithDefault);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetFieldDescriptorAtDelegate(IntPtr instance, uint index, out IntPtr descriptor);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetFieldDescriptorCountDelegate(IntPtr instance, out uint count);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetFieldStateDelegate(IntPtr instance, uint fieldId, out int fieldState, out int interactiveState);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetStringValueDelegate(IntPtr instance, uint fieldId, out IntPtr value);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetUserCountDelegate(IntPtr instance, out uint count);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetUserSidDelegate(IntPtr instance, out IntPtr sid);
}
@@ -0,0 +1,27 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[ComVisible(true)]
[Guid("90C119AE-0F18-4520-A1F1-114366A40FE8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestCredentialProviderUserArray
{
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int SetProviderFilter(ref Guid providerToFilterTo);
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int GetAccountOptions(out int accountOptions);
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int GetCount(out uint userCount);
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int GetAt(uint userIndex, out IntPtr user);
}
}
@@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[StructLayout(LayoutKind.Sequential)]
internal struct NativeCredentialProviderFieldDescriptor
{
public uint FieldId;
public int FieldType;
public IntPtr Label;
public Guid FieldTypeGuid;
}
}
@@ -0,0 +1,27 @@
using System;
using NUnit.Framework;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[TestFixture]
public class ProcessArchitectureTests
{
[Test]
public void TestHostUsesRequestedArchitecture()
{
#if TEST_X86
const string expectedArchitecture = "x86";
#elif TEST_X64
const string expectedArchitecture = "AMD64";
#elif TEST_ARM64
const string expectedArchitecture = "ARM64";
#else
#error A test process architecture must be defined by the project.
#endif
string actualArchitecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
Assert.That(actualArchitecture, Is.EqualTo(expectedArchitecture).IgnoreCase);
}
}
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetDeselectedDelegate(IntPtr instance);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetSelectedDelegate(IntPtr instance, out int autoLogon);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetUsageScenarioDelegate(IntPtr instance, int usageScenario, uint flags);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetUserArrayDelegate(IntPtr instance, IntPtr users);
}
@@ -0,0 +1,51 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
// The in-process call also requires the callback to implement the parameter's managed interface type.
// ITestCredentialProviderUserArray keeps the COM contract under test independent of that adapter.
internal sealed class TestCredentialProviderUserArray : ITestCredentialProviderUserArray, ICredentialProviderUserArray
{
public int GetCountCallCount { get; private set; }
public int SetProviderFilter(ref Guid providerToFilterTo)
{
return CredentialProviderAbi.E_NOTIMPL;
}
public int GetAccountOptions(out int accountOptions)
{
accountOptions = 0;
return CredentialProviderAbi.S_OK;
}
public int GetCount(out uint userCount)
{
this.GetCountCallCount++;
userCount = 0;
return CredentialProviderAbi.S_OK;
}
public int GetAt(uint userIndex, out IntPtr user)
{
user = IntPtr.Zero;
return CredentialProviderAbi.E_INVALIDARG;
}
int ICredentialProviderUserArray.GetAccountOptions(out AccountOptions accountOptions)
{
accountOptions = AccountOptions.None;
return CredentialProviderAbi.S_OK;
}
int ICredentialProviderUserArray.GetAt(uint userIndex, out ICredentialProviderUser user)
{
user = null;
return CredentialProviderAbi.E_INVALIDARG;
}
}
}
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0-windows;net7.0-windows;net8.0-windows;net461;net48</TargetFrameworks>
<TargetFrameworks>net8.0-windows;net9.0-windows;net10.0-windows;net472;net48</TargetFrameworks>
<IsPackable>false</IsPackable>
<PlatformTargets>x64</PlatformTargets>
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>$(DefineConstants);TEST_X64</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
</ItemGroup>
<ItemGroup>
@@ -1,19 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0-windows;net7.0-windows;net8.0-windows;net461;net48</TargetFrameworks>
<TargetFrameworks>net8.0-windows;net9.0-windows;net10.0-windows;net472;net48</TargetFrameworks>
<IsPackable>false</IsPackable>
<PlatformTarget>x86</PlatformTarget>
<DefineConstants>$(DefineConstants);TEST_X86</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
<Compile Include="..\Lithnet.CredentialProvider.UnitTests.x64\BitmapControlTests.cs" Link="BitmapControlTests.cs" />
<Compile Include="..\Lithnet.CredentialProvider.UnitTests.x64\ComInterop\**\*.cs" Link="ComInterop\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
+10 -4
View File
@@ -11,13 +11,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\azure-pipelines.yml = ..\azure-pipelines.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.net6.0.x64", "samples\Lithnet.CredentialProvider.Sample.net6.0.x64\Lithnet.CredentialProvider.Sample.net6.0.x64.csproj", "{163E16D0-9FF3-40D3-AE96-3F221C922AA3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.Core.x64", "samples\Lithnet.CredentialProvider.Sample.Core.x64\Lithnet.CredentialProvider.Sample.Core.x64.csproj", "{163E16D0-9FF3-40D3-AE96-3F221C922AA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.net6.0.x86", "samples\Lithnet.CredentialProvider.Sample.net6.0.x86\Lithnet.CredentialProvider.Sample.net6.0.x86.csproj", "{7A8A10F9-7D79-4755-BBDD-77C80FDC58ED}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.Core.x86", "samples\Lithnet.CredentialProvider.Sample.Core.x86\Lithnet.CredentialProvider.Sample.Core.x86.csproj", "{7A8A10F9-7D79-4755-BBDD-77C80FDC58ED}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.net472.x64", "samples\Lithnet.CredentialProvider.Sample.net472.x64\Lithnet.CredentialProvider.Sample.net472.x64.csproj", "{F60AC10A-337C-46D9-A2DE-1ED48B4AB301}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.Framework.x64", "samples\Lithnet.CredentialProvider.Sample.Framework.x64\Lithnet.CredentialProvider.Sample.Framework.x64.csproj", "{F60AC10A-337C-46D9-A2DE-1ED48B4AB301}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.net472.x86", "samples\Lithnet.CredentialProvider.Sample.net472.x86\Lithnet.CredentialProvider.Sample.net472.x86.csproj", "{57F2780E-58F1-4A7B-BCB4-A218733273EA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.Sample.Framework.x86", "samples\Lithnet.CredentialProvider.Sample.Framework.x86\Lithnet.CredentialProvider.Sample.Framework.x86.csproj", "{57F2780E-58F1-4A7B-BCB4-A218733273EA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lithnet.CredentialProvider.TestApp.x64", "samples\Lithnet.CredentialProvider.TestApp.x64\Lithnet.CredentialProvider.TestApp.x64.csproj", "{5019CCEB-AD78-4688-8C11-89A85C4289CD}"
EndProject
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lithnet.CredentialProvider.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lithnet.CredentialProvider.UnitTests.x86", "Lithnet.CredentialProvider.UnitTests.x86\Lithnet.CredentialProvider.UnitTests.x86.csproj", "{DA23151B-B7B6-4942-B7A4-48E1918EF145}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lithnet.CredentialProvider.UnitTests.arm64", "Lithnet.CredentialProvider.UnitTests.arm64\Lithnet.CredentialProvider.UnitTests.arm64.csproj", "{07D19CE5-8419-4E06-B39D-CC90EAA3FF9A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -69,6 +71,10 @@ Global
{DA23151B-B7B6-4942-B7A4-48E1918EF145}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA23151B-B7B6-4942-B7A4-48E1918EF145}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA23151B-B7B6-4942-B7A4-48E1918EF145}.Release|Any CPU.Build.0 = Release|Any CPU
{07D19CE5-8419-4E06-B39D-CC90EAA3FF9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07D19CE5-8419-4E06-B39D-CC90EAA3FF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07D19CE5-8419-4E06-B39D-CC90EAA3FF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07D19CE5-8419-4E06-B39D-CC90EAA3FF9A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1,7 +1,7 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// The <c ref="ChangePasswordResponse"/> object is used to communicate the results of a password change operation to LogonUI
/// The <see cref="ChangePasswordResponse"/> object communicates the result of a password change operation to LogonUI.
/// </summary>
public class ChangePasswordResponse
{
@@ -17,7 +17,7 @@ namespace Lithnet.CredentialProvider
/// ConsentUIData is an abstract base class that represents all the different types of data structures that can be passed to the ConsentUI process for a UAC elevation prompt.
/// The static members of the class can be used to retrieve the data structure passed to the ConsentUI process, or to determine if the current process is the ConsentUI process.
/// The caller will be provided with one of the concrete implementations of this class, depending on the type of data structure that was passed to the ConsentUI process.
/// Use the <see cref="ConsentUIData.ConsentUIType"/> property to determine the type of data structure and cast it to one of the concrete implementations.
/// Use the <see cref="Type"/> property to determine the type of data structure and cast it to one of the concrete implementations.
/// </summary>
public abstract class ConsentUIData
{
@@ -55,7 +55,7 @@ namespace Lithnet.CredentialProvider
/// <summary>
/// A series of flags that AppInfo passes to ConsentUI to signify actions that need to
/// take place on the UI side.
/// This includes specifics around the UI that should be presented & signature verification settings.
/// This includes details about the UI that should be presented and the signature verification settings.
/// </summary>
public ConsentUIFlags Flags => this.header.Flags;
@@ -8,13 +8,20 @@ using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// The base class of image-based controls
/// Provides common image behaviour for credential provider logo and user tile controls.
/// </summary>
public abstract class BitmapControl : ControlBase
{
private Bitmap bitmap;
private Color backgroundColor;
/// <summary>
/// Initializes an image control.
/// </summary>
/// <param name="key">The unique key for the control.</param>
/// <param name="label">The label associated with the control.</param>
/// <param name="isProviderLogo"><see langword="true"/> to identify the image as the credential provider logo; otherwise, <see langword="false"/>.</param>
/// <param name="bitmap">The initial image displayed by the control.</param>
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)
{
@@ -22,11 +29,20 @@ namespace Lithnet.CredentialProvider
this.backgroundColor = Color.FromArgb(70, 70, 70);
}
protected BitmapControl(BitmapControl source) : base(source) { }
/// <summary>
/// Initializes an image control by copying an existing image control.
/// </summary>
/// <param name="source">The image control to copy.</param>
protected BitmapControl(BitmapControl source) : base(source)
{
this.bitmap = source.bitmap;
this.backgroundColor = source.backgroundColor;
}
/// <summary>
/// Specifies the background color that should replace any transparent elements of the image. This defaults to #707070
/// Gets or sets the background color used when an image that contains transparency is displayed by a <see cref="CredentialTile"/> or <see cref="CredentialTile2"/>.
/// </summary>
/// <remarks>The default color is #464646. A <see cref="CredentialTile3"/> preserves the image's alpha channel and does not use this property.</remarks>
public Color BackgroundColor
{
get { return this.backgroundColor; }
@@ -41,7 +57,7 @@ namespace Lithnet.CredentialProvider
}
/// <summary>
/// The image to be displayed
/// Gets or sets the image displayed by the control.
/// </summary>
public Bitmap Bitmap
{
@@ -53,10 +69,7 @@ namespace Lithnet.CredentialProvider
{
this.bitmap = value;
if (this.Events is ICredentialProviderCredentialEvents2 e)
{
e.SetFieldBitmap(this.Credential, this.Id, this.GetHBitmap());
}
this.UpdateBitmap();
this.RaisePropertyChanged();
}
@@ -76,26 +89,48 @@ namespace Lithnet.CredentialProvider
internal IntPtr GetBitmapBuffer(out uint size)
{
size = 0;
var hbitmap = this.GetHBitmap();
if (hbitmap == IntPtr.Zero)
if (this.bitmap == null)
{
return IntPtr.Zero;
}
var image = Bitmap.FromHbitmap(hbitmap);
IntPtr buffer = IntPtr.Zero;
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, ImageFormat.Bmp);
this.bitmap.Save(ms, ImageFormat.Png);
var bitmapBytes = ms.ToArray();
size = (uint)bitmapBytes.Length;
buffer = Marshal.AllocCoTaskMem(bitmapBytes.Length);
size = checked((uint)bitmapBytes.Length);
IntPtr buffer = Marshal.AllocCoTaskMem(bitmapBytes.Length);
Marshal.Copy(bitmapBytes, 0, buffer, bitmapBytes.Length);
}
return buffer;
}
}
private void UpdateBitmap()
{
if (this.Credential is ICredentialProviderCredential3 && this.Events is ICredentialProviderCredentialEvents3 events3)
{
IntPtr buffer = this.GetBitmapBuffer(out uint size);
try
{
events3.SetFieldBitmapBuffer(this.Credential, this.Id, size, buffer);
}
finally
{
if (buffer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(buffer);
}
}
return;
}
if (this.Events is ICredentialProviderCredentialEvents2 events2)
{
events2.SetFieldBitmap(this.Credential, this.Id, this.GetHBitmap());
}
}
}
}
@@ -10,13 +10,13 @@ namespace Lithnet.CredentialProvider
private bool isChecked;
/// <summary>
/// Creates a new <c ref="CheckboxControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="CheckboxControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -10,13 +10,13 @@ namespace Lithnet.CredentialProvider
private int selectedItemIndex;
/// <summary>
/// Creates a new <c ref="ComboboxControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="ComboboxControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -9,13 +9,13 @@ namespace Lithnet.CredentialProvider
public class CommandLinkControl : ControlBase
{
/// <summary>
/// Creates a new <c ref="CommandLinkControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="CommandLinkControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -18,6 +18,10 @@ namespace Lithnet.CredentialProvider
private string label;
private FieldOptions options;
private protected ICredentialProviderLogger logger;
/// <summary>
/// Occurs when the value of a public property changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private protected ControlBase(ControlBase source)
@@ -1,22 +1,21 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a control that provides the credential UI with the name of this credential provider
/// 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
/// Creates a new <see cref="CredentialProviderLabelControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
/// <param name="key">The unique key for this control.</param>
public CredentialProviderLabelControl(string key) : this(key, null) { }
/// <summary>
/// Creates a new <c ref="CredentialProviderLabelControl"/> control
/// Creates a new <see cref="CredentialProviderLabelControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
/// <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;
@@ -3,29 +3,30 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a control that provides the credential UI with the logo of this credential provider
/// Represents a control that provides the credential UI with the logo of this credential provider.
/// </summary>
/// <remarks>See <see cref="BitmapControl.BackgroundColor"/> for the image transparency behaviour of each credential tile version.</remarks>
public class CredentialProviderLogoControl : BitmapControl
{
/// <summary>
/// Creates a new <c ref="CredentialProviderLogoControl"/> control
/// Creates a new <see cref="CredentialProviderLogoControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <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
/// Creates a new <see cref="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>
/// <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
/// Creates a new <see cref="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>
/// <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;
@@ -35,10 +36,7 @@ namespace Lithnet.CredentialProvider
internal override ControlBase Clone()
{
var clone = new CredentialProviderLogoControl(this);
clone.Bitmap = this.Bitmap;
clone.BackgroundColor = this.BackgroundColor;
return clone;
return new CredentialProviderLogoControl(this);
}
}
}
@@ -12,13 +12,13 @@ namespace Lithnet.CredentialProvider
private string password;
/// <summary>
/// Creates a new <c ref="InsecurePasswordTextboxControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="InsecurePasswordTextboxControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -8,13 +8,13 @@ namespace Lithnet.CredentialProvider
public class LargeLabelControl : ControlBase
{
/// <summary>
/// Creates a new <c ref="LargeLabelControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="LargeLabelControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -13,13 +13,13 @@ namespace Lithnet.CredentialProvider
private SecureString password;
/// <summary>
/// Creates a new <c ref="SecurePasswordTextboxControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="SecurePasswordTextboxControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -11,7 +11,14 @@ namespace Lithnet.CredentialProvider
{
private readonly List<string> backingList = new List<string>();
/// <summary>
/// Occurs after an item is added to the list.
/// </summary>
public event EventHandler<string> ItemAdded;
/// <summary>
/// Occurs after an item is removed from the list. The event value is the former zero-based index of the item.
/// </summary>
public event EventHandler<int> ItemRemoved;
/// <summary>
@@ -9,13 +9,13 @@ namespace Lithnet.CredentialProvider
public class SmallLabelControl : ControlBase
{
/// <summary>
/// Creates a new <c ref="SmallLabelControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="SmallLabelControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -10,14 +10,14 @@ namespace Lithnet.CredentialProvider
private ControlBase adjacentToControl;
/// <summary>
/// Creates a new <c ref="SubmitButtonControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="SubmitButtonControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -13,13 +13,13 @@ namespace Lithnet.CredentialProvider
private TextboxControl(TextboxControl source) : base(source) { }
/// <summary>
/// Creates a new <c ref="TextboxControl"/> control
/// Creates a new <see cref="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
/// Creates a new <see cref="TextboxControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <param name="label">The label associated with the control</param>
@@ -3,40 +3,37 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// A control that displays the user's tile image
/// Represents a control that displays the user's tile image.
/// </summary>
/// <remarks>See <see cref="BitmapControl.BackgroundColor"/> for the image transparency behaviour of each credential tile version.</remarks>
public class UserTileControl : BitmapControl
{
/// <summary>
/// Creates a new <c ref="UserTileControl"/> control
/// Creates a new <see cref="UserTileControl"/> control.
/// </summary>
/// <param name="key">The unique key for this control</param>
/// <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
/// Creates a new <see cref="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="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
/// Creates a new <see cref="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>
/// <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;
return new UserTileControl(this);
}
}
}
@@ -67,6 +67,10 @@ namespace Lithnet.CredentialProvider
/// </summary>
public CredentialSerialization InboundSerialization { get; private set; }
/// <summary>
/// Initializes a credential provider and obtains its identifier from the <see cref="GuidAttribute"/> on the derived class.
/// </summary>
/// <exception cref="InvalidOperationException">The derived credential provider class does not have a <see cref="GuidAttribute"/>.</exception>
protected CredentialProviderBase()
{
this.LoggerFactory = this.GetLoggerFactory();
@@ -84,13 +88,13 @@ namespace Lithnet.CredentialProvider
}
/// <summary>
/// Gets a logger factory. Override this method and provide an implementation of <c ref="ILoggerFactory"/> to enable credential provider logging
/// Gets a logger factory. Override this method and provide an implementation of <see cref="ICredentialProviderLoggerFactory"/> to enable credential provider logging.
/// </summary>
/// <returns>An ILoggerFactory instance</returns>
/// <returns>An <see cref="ICredentialProviderLoggerFactory"/> instance.</returns>
protected virtual ICredentialProviderLoggerFactory GetLoggerFactory() { return TraceLoggerFactory.Instance; }
/// <summary>
/// Gets a value indicating if the credential provider supports the <c ref="UsageScenario"/> provided by LogonUI or CredUI
/// Gets a value indicating whether the credential provider supports the <see cref="UsageScenario"/> provided by LogonUI or CredUI.
/// </summary>
/// <param name="cpus">The usage scenario</param>
/// <param name="dwFlags">Additional flags provided by CredUI</param>
@@ -116,10 +120,22 @@ namespace Lithnet.CredentialProvider
/// </summary>
public abstract bool ShouldIncludeGenericTile();
/// <summary>
/// Gets or sets the tile that the credential provider reports as the default credential.
/// </summary>
protected internal CredentialTile DefaultTile { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether Logon UI or Credential UI should immediately request serialization from the default tile.
/// </summary>
protected internal bool DefaultTileAutoLogon { get; set; }
/// <summary>
/// Sets the default tile, configures its automatic logon request, and notifies the credential UI to enumerate the tiles again.
/// </summary>
/// <param name="tile">A tile in the current <see cref="Tiles"/> collection.</param>
/// <param name="autoLogon"><see langword="true"/> to make Logon UI or Credential UI immediately request serialization from the default tile; otherwise, <see langword="false"/>.</param>
/// <exception cref="InvalidOperationException"><paramref name="tile"/> is not in the current <see cref="Tiles"/> collection.</exception>
public void SetDefaultTile(CredentialTile tile, bool autoLogon)
{
if (this.DefaultTile == tile && this.DefaultTileAutoLogon == autoLogon)
@@ -188,7 +204,7 @@ namespace Lithnet.CredentialProvider
}
/// <summary>
/// This method is used to generate the generic tile for this credential provider. This is called when <c ref="ShouldIncludeGenericTile"/> return true
/// Creates the generic tile for this credential provider. This method is called when <see cref="ShouldIncludeGenericTile"/> returns <see langword="true"/>.
/// </summary>
public abstract CredentialTile CreateGenericTile();
@@ -15,6 +15,10 @@ namespace Lithnet.CredentialProvider
private protected ICredentialProviderCredentialEvents2 events2;
private protected ControlCollection controls;
/// <summary>
/// Initializes a version 1 credential tile.
/// </summary>
/// <param name="credentialProvider">The credential provider that owns this tile.</param>
protected CredentialTile(CredentialProviderBase credentialProvider)
{
this.CredentialProvider = credentialProvider;
@@ -34,7 +38,7 @@ namespace Lithnet.CredentialProvider
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.
/// Gets a value that indicates whether Logon UI or Credential UI should immediately request serialization from this tile. The tile must also be the default tile.
/// </summary>
public bool IsDefaultTileAutoLogon
{
@@ -111,7 +115,7 @@ namespace Lithnet.CredentialProvider
}
/// <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/>
/// Indicates to the host that multiple fields will be updated and that it should delay updating the UI until <see cref="EndBulkFieldUpdate"/> is called.
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
public void BeginBulkFieldUpdate()
@@ -176,7 +180,7 @@ namespace Lithnet.CredentialProvider
protected virtual void OnDeselected() { }
/// <summary>
/// Called just before credentials are serialized and returned to the host
/// Called immediately before credentials are serialized and returned to the host
/// </summary>
protected virtual void OnBeforeSerialize() { }
@@ -23,8 +23,17 @@
/// <remarks>This does not apply in scenarios where a personalized tile is provided</remarks>
public GenericTileDisplayMode GenericTileDisplayMode { get; set; }
/// <summary>
/// Initializes a generic version 2 credential tile.
/// </summary>
/// <param name="credentialProvider">The credential provider that owns this tile.</param>
protected CredentialTile2(CredentialProviderBase credentialProvider) : this(credentialProvider, null) { }
/// <summary>
/// Initializes a version 2 credential tile for a user.
/// </summary>
/// <param name="credentialProvider">The credential provider that owns this tile.</param>
/// <param name="user">The user represented by this tile, or <see langword="null"/> for a generic tile.</param>
protected CredentialTile2(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider)
{
this.User = user;
@@ -3,7 +3,7 @@ using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
internal abstract partial class CredentialTile3 : ICredentialProviderCredential3
public abstract partial class CredentialTile3 : ICredentialProviderCredential3
{
int ICredentialProviderCredential3.GetBitmapBufferValue(uint dwFieldID, out uint pImageBufferSize, out IntPtr ppImageBuffer)
{
@@ -18,7 +18,7 @@ namespace Lithnet.CredentialProvider
if (this.Controls.TryGetControl<BitmapControl>(dwFieldID, FieldType.TileImage, out var instance))
{
var hbitmap = instance.GetBitmapBuffer(out pImageBufferSize);
ppImageBuffer = instance.GetBitmapBuffer(out pImageBufferSize);
return HRESULT.S_OK;
}
@@ -4,13 +4,22 @@ using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents a user credential tile that implements the functionality of <see cref="CredentialTile"/> and <see cref="CredentialTile2"/>, but includes support for dynamically updating bitmap images.
/// Represents a version 3 credential tile that preserves transparency in bitmap controls.
/// </summary>
/// <remarks>This interface is public, but undocumented by Microsoft. It is recommended to use <see cref="CredentialTile2"/> tiles unless this specific functionality is needed</remarks>
internal abstract partial class CredentialTile3 : CredentialTile2
/// <remarks>Inherit from this class when a <see cref="CredentialProviderLogoControl"/> or <see cref="UserTileControl"/> must preserve the image's alpha channel. The <see cref="BitmapControl.BackgroundColor"/> property does not apply to this tile type. Microsoft does not publish documentation for the underlying version 3 credential interfaces, so use <see cref="CredentialTile2"/> unless you need image transparency.</remarks>
public abstract partial class CredentialTile3 : CredentialTile2
{
/// <summary>
/// Initializes a generic version 3 credential tile.
/// </summary>
/// <param name="credentialProvider">The credential provider that owns this tile.</param>
protected CredentialTile3(CredentialProviderBase credentialProvider) : this(credentialProvider, null) { }
/// <summary>
/// Initializes a version 3 credential tile for a user.
/// </summary>
/// <param name="credentialProvider">The credential provider that owns this tile.</param>
/// <param name="user">The user represented by this tile, or <see langword="null"/> for a generic tile.</param>
protected CredentialTile3(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider, user) { }
}
}
@@ -1,5 +1,8 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies the reason that Consent UI received an elevation request.
/// </summary>
public enum ConsentUIElevationReason
{
/// <summary>
@@ -2,9 +2,15 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies how Consent UI should display and verify an elevation request.
/// </summary>
[Flags]
public enum ConsentUIFlags
{
/// <summary>
/// The purpose of the 0x01 flag is not documented.
/// </summary>
SkipSignatureVerification = 0x01,
/// <summary>
@@ -12,8 +18,19 @@ namespace Lithnet.CredentialProvider
/// </summary>
SecureDesktop = 0x02,
/// <summary>
/// The purpose of the 0x04 flag is not documented.
/// </summary>
Unknown1 = 0x04,
/// <summary>
/// The purpose of the 0x08 flag is not documented.
/// </summary>
Unknown2 = 0x08,
/// <summary>
/// The purpose of the 0x10 flag is not documented.
/// </summary>
Unknown3 = 0x10,
/// <summary>
@@ -42,6 +59,9 @@ namespace Lithnet.CredentialProvider
/// </summary>
AutoElevationOther = 0x100,
/// <summary>
/// The purpose of the 0x200 flag is not documented.
/// </summary>
Unknown4 = 0x200,
/// <summary>
@@ -2,10 +2,24 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies the Windows Installer action described by Consent UI data.
/// </summary>
public enum ConsentUIMsiAction : uint
{
/// <summary>
/// Installs a Windows Installer package.
/// </summary>
Install = 0,
/// <summary>
/// Uninstalls a Windows Installer package.
/// </summary>
Uninstall = 1,
/// <summary>
/// Updates or repairs an installed Windows Installer package.
/// </summary>
Update = 2
}
}
@@ -1,10 +1,28 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies how Consent UI obtains approval for an elevation request.
/// </summary>
public enum ConsentUIPromptType
{
/// <summary>
/// The prompt type is not known.
/// </summary>
Unknown = 0,
/// <summary>
/// Uses the Consent UI automatic administrator mode.
/// </summary>
AutomaticAdmin = 1,
/// <summary>
/// Requests consent from an administrator.
/// </summary>
Consent = 2,
/// <summary>
/// Requests administrator credentials.
/// </summary>
Credentials = 3
}
}
@@ -1,12 +1,38 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Identifies the type of data supplied to Consent UI for an elevation request.
/// </summary>
public enum ConsentUIType
{
/// <summary>
/// The data describes an executable file.
/// </summary>
Exe = 0,
/// <summary>
/// The data describes an elevated COM object.
/// </summary>
Com = 1,
/// <summary>
/// The data describes a Windows Installer package.
/// </summary>
Msi = 2,
/// <summary>
/// The data describes an ActiveX installation.
/// </summary>
ActiveX = 3,
/// <summary>
/// The data uses the CredCollect structure. The purpose of this structure is not documented.
/// </summary>
CredCollect = 4,
/// <summary>
/// The data describes a packaged application.
/// </summary>
Msix = 5
}
}
@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies options that control the Windows credential user interface.
/// </summary>
[Flags]
public enum CredUIWinFlags
{
@@ -1,5 +1,8 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Specifies how the credential UI should continue after a credential provider handles a serialization request.
/// </summary>
public enum SerializationResponse
{
/// <summary>
@@ -18,7 +18,7 @@
/// <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.
/// <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. This is a subset of the possible factors.
/// - 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.
@@ -31,7 +31,7 @@
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.
/// Credential UI. This scenario enables you to use credentials serialized by the credential provider 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 <see cref="Logon"/>, <see cref="UnlockWorkstation"/>, and <see cref="ChangePassword"/>, so the state of the credential provider cannot be maintained across the different scenarios.
/// </summary>
CredUI,
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0-windows;net7.0-windows;net8.0-windows;net461</TargetFrameworks>
<TargetFrameworks>net8.0-windows;net9.0-windows;net10.0-windows;net472;net48</TargetFrameworks>
<RegisterForComInterop>false</RegisterForComInterop>
<OutputType>Library</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -10,6 +10,7 @@
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<LangVersion>9</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
@@ -25,6 +26,7 @@
<IsPackable>true</IsPackable>
<PackageId>Lithnet.CredentialProvider</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/lithnet/windows-credential-provider</RepositoryUrl>
<SupportUrl>https://github.com/lithnet/windows-credential-provider</SupportUrl>
<PackageOutputPath>D:\dev\nuget\packages</PackageOutputPath>
@@ -45,6 +47,13 @@
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Lithnet.CredentialProvider.UnitTests.x86</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Lithnet.CredentialProvider.UnitTests.arm64</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
@@ -2,16 +2,40 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Receives log messages from a credential provider and its credential tiles.
/// </summary>
public interface ICredentialProviderLogger
{
/// <summary>
/// Logs an error message and its associated exception.
/// </summary>
/// <param name="ex">The exception associated with the error.</param>
/// <param name="message">The error message.</param>
void LogError(Exception ex, string message);
/// <summary>
/// Logs an error message.
/// </summary>
/// <param name="message">The error message.</param>
void LogError(string message);
/// <summary>
/// Logs a trace message.
/// </summary>
/// <param name="message">The trace message.</param>
void LogTrace(string message);
/// <summary>
/// Logs an informational message.
/// </summary>
/// <param name="message">The informational message.</param>
void LogInformation(string message);
/// <summary>
/// Logs a warning message.
/// </summary>
/// <param name="message">The warning message.</param>
void LogWarning(string message);
}
}
@@ -2,10 +2,23 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Creates loggers for credential provider components.
/// </summary>
public interface ICredentialProviderLoggerFactory
{
/// <summary>
/// Creates a logger for the specified component type.
/// </summary>
/// <param name="type">The type that will write log messages.</param>
/// <returns>A logger for the specified type.</returns>
ICredentialProviderLogger CreateLogger(Type type);
/// <summary>
/// Creates a logger for the specified component type.
/// </summary>
/// <typeparam name="T">The type that will write log messages.</typeparam>
/// <returns>A logger for the specified type.</returns>
ICredentialProviderLogger CreateLogger<T>();
}
}
@@ -3,28 +3,52 @@ using System.Diagnostics;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Writes credential provider log messages to <see cref="Trace"/>.
/// </summary>
public class TraceLogger : ICredentialProviderLogger
{
/// <summary>
/// Writes an error message and its associated exception to <see cref="Trace"/>.
/// </summary>
/// <param name="ex">The exception associated with the error.</param>
/// <param name="v">The error message.</param>
public void LogError(Exception ex, string v)
{
Trace.WriteLine($"{v}\r\n\r\n{ex?.ToString()}");
}
/// <summary>
/// Writes an error message to <see cref="Trace"/>.
/// </summary>
/// <param name="v">The error message.</param>
public void LogError(string v)
{
Trace.WriteLine(v);
}
/// <summary>
/// Writes an informational message to <see cref="Trace"/>.
/// </summary>
/// <param name="message">The informational message.</param>
public void LogInformation(string message)
{
Trace.WriteLine(message);
}
/// <summary>
/// Writes a trace message to <see cref="Trace"/>.
/// </summary>
/// <param name="v">The trace message.</param>
public void LogTrace(string v)
{
Trace.WriteLine(v);
}
/// <summary>
/// Writes a warning message to <see cref="Trace"/>.
/// </summary>
/// <param name="message">The warning message.</param>
public void LogWarning(string message)
{
Trace.WriteLine(message);
@@ -2,13 +2,26 @@
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Creates <see cref="TraceLogger"/> instances.
/// </summary>
public class TraceLoggerFactory : ICredentialProviderLoggerFactory
{
/// <summary>
/// Creates a trace logger for the specified component type.
/// </summary>
/// <param name="type">The type that will write log messages.</param>
/// <returns>A trace logger for the specified type.</returns>
public ICredentialProviderLogger CreateLogger(Type type)
{
return new TraceLogger();
}
/// <summary>
/// Creates a trace logger for the specified component type.
/// </summary>
/// <typeparam name="T">The type that will write log messages.</typeparam>
/// <returns>A trace logger for the specified type.</returns>
public ICredentialProviderLogger CreateLogger<T>()
{
return new TraceLogger();
@@ -16,6 +29,9 @@ namespace Lithnet.CredentialProvider
private static readonly TraceLoggerFactory loggerFactory = new TraceLoggerFactory();
/// <summary>
/// Gets the shared trace logger factory.
/// </summary>
public static ICredentialProviderLoggerFactory Instance => loggerFactory;
}
}
@@ -7,10 +7,10 @@ In order to install and run the sample app, you have to register the COM compone
Build the EXE, and from an elevated command prompt, change to the bin folder, and run the following commands
```
regsvr32 "Lithnet.CredentialProvider.Sample.net6.0.x64.comhost.dll"
regsvr32 "Lithnet.CredentialProvider.Sample.Core.x64.comhost.dll"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{4cd12d80-9259-4f38-94dc-1828080ad9ff}" /f
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{4cd12d80-9259-4f38-94dc-1828080ad9ff}" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.net6.0.x64"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{4cd12d80-9259-4f38-94dc-1828080ad9ff}" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.Core.x64"
```
## Disable the sample
@@ -31,6 +31,6 @@ REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credentia
To remove the credential provider, run the following command.
```
regsvr32 /u "Lithnet.CredentialProvider.Sample.net6.0.x64.comhost.dll"
regsvr32 /u "Lithnet.CredentialProvider.Sample.Core.x64.comhost.dll"
REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{4cd12d80-9259-4f38-94dc-1828080ad9ff}" /f
```
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x64</Platform>
<EnableComHosting>true</EnableComHosting>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\InternalLogger.cs" Link="InternalLogger.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\ControlKeys.cs" Link="ControlKeys.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\TestCredentialProviderTile.cs" Link="TestCredentialProviderTile.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
<PackageReference Include="NLog" Version="5.1.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.1" />
</ItemGroup>
</Project>
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
@@ -9,11 +11,11 @@ namespace Lithnet.CredentialProvider.Samples
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Lithnet.CredentialProvider.Sample.net6.0.x64")]
[ProgId("Lithnet.CredentialProvider.Sample.Core.x64")]
[Guid("4cd12d80-9259-4f38-94dc-1828080ad9ff")]
public class TestCredentialProviderNet60x64 : CredentialProviderBase
public class TestCredentialProviderCoreX64 : CredentialProviderBase
{
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderNet60x64>();
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderCoreX64>();
protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
@@ -36,10 +38,11 @@ namespace Lithnet.CredentialProvider.Samples
{
yield return new CredentialProviderLabelControl(ControlKeys.LabelCredentialProvider, "Login with showcase credential provider");
var image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.net6.0.x64.Resources.TileIcon.png"));
var providerLogo = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.Core.x64.Resources.TileIcon.png"));
var transparentUserTile = CreateTransparentUserTile();
yield return new CredentialProviderLogoControl(ControlKeys.ImageCredentialProvider, "Credential provider logo", image);
yield return new CredentialProviderLogoControl(ControlKeys.ImageUserTile, "User tile image", image);
yield return new CredentialProviderLogoControl(ControlKeys.ImageCredentialProvider, "Credential provider logo", providerLogo);
yield return new UserTileControl(ControlKeys.ImageUserTile, "Transparent user tile image", transparentUserTile);
yield return new LargeLabelControl(ControlKeys.LabelLargeHeading, "The is our showcase credential provider");
yield return new SmallLabelControl(ControlKeys.LabelSmallHeading, "Let's see what we can do");
@@ -95,5 +98,25 @@ namespace Lithnet.CredentialProvider.Samples
{
return new TestCredentialProviderTile(this, user);
}
private static Bitmap CreateTransparentUserTile()
{
// CredentialTile3 preserves the alpha channel in this image. CredentialTile and CredentialTile2 render it against the control's BackgroundColor.
Bitmap image = new Bitmap(128, 128, PixelFormat.Format32bppArgb);
using (Graphics graphics = Graphics.FromImage(image))
using (SolidBrush shadow = new SolidBrush(Color.FromArgb(96, 0, 0, 0)))
using (SolidBrush foreground = new SolidBrush(Color.FromArgb(255, 38, 132, 255)))
using (Pen outline = new Pen(Color.White, 5))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.Clear(Color.Transparent);
graphics.FillEllipse(shadow, 28, 30, 88, 88);
graphics.FillEllipse(foreground, 12, 12, 88, 88);
graphics.DrawEllipse(outline, 12, 12, 88, 88);
}
return image;
}
}
}
@@ -7,10 +7,10 @@ In order to install and run the sample app, you have to register the COM compone
Build the EXE, and from an elevated command prompt, change to the bin folder, and run the following commands
```
regsvr32 "Lithnet.CredentialProvider.Sample.net6.0.x86.comhost.dll"
regsvr32 "Lithnet.CredentialProvider.Sample.Core.x86.comhost.dll"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{90592593-f4d3-4f62-aa83-9cf1f7b590e0}" /f
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{90592593-f4d3-4f62-aa83-9cf1f7b590e0}" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.net6.0.x86"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{90592593-f4d3-4f62-aa83-9cf1f7b590e0}" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.Core.x86"
```
## Disable the sample
@@ -31,6 +31,6 @@ REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credentia
To remove the credential provider, run the following command.
```
regsvr32 /u "Lithnet.CredentialProvider.Sample.net6.0.x86.comhost.dll"
regsvr32 /u "Lithnet.CredentialProvider.Sample.Core.x86.comhost.dll"
REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{90592593-f4d3-4f62-aa83-9cf1f7b590e0}" /f
```
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x86</Platform>
<EnableComHosting>true</EnableComHosting>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\InternalLogger.cs" Link="InternalLogger.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\ControlKeys.cs" Link="ControlKeys.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\TestCredentialProviderTile.cs" Link="TestCredentialProviderTile.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
<PackageReference Include="NLog" Version="5.1.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.1" />
</ItemGroup>
</Project>
@@ -9,11 +9,11 @@ namespace Lithnet.CredentialProvider.Samples
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Lithnet.CredentialProvider.Sample.net6.0.x86")]
[ProgId("Lithnet.CredentialProvider.Sample.Core.x86")]
[Guid("90592593-f4d3-4f62-aa83-9cf1f7b590e0")]
public class TestCredentialProviderNet60x86 : CredentialProviderBase
public class TestCredentialProviderCoreX86 : CredentialProviderBase
{
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderNet60x86>();
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderCoreX86>();
protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
@@ -34,7 +34,7 @@ namespace Lithnet.CredentialProvider.Samples
}
else
{
var image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.net6.0.x86.Resources.TileIcon.png"));
var image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.Core.x86.Resources.TileIcon.png"));
yield return new CredentialProviderLabelControl(ControlKeys.LabelCredentialProvider, "Login with showcase credential provider");
yield return new CredentialProviderLogoControl(ControlKeys.ImageCredentialProvider, "Credential provider logo", image);
@@ -9,8 +9,8 @@ Build the EXE, and from an elevated command prompt, change to the bin folder, an
```
SETLOCAL
SET CLSID={4EB911FA-CA18-40EA-86DF-19AFF5D1DA58}
SET BinaryPath=D:\dev\git\lithnet\windows-credential-provider\src\samples\Lithnet.CredentialProvider.Sample.net472.x64\bin\Debug\net472\Lithnet.CredentialProvider.Sample.net472.x64.dll
REM %windir%\Microsoft.NET\Framework64\v4.0.30319\regasm /codebase "Lithnet.CredentialProvider.Sample.net472.x64.dll"
SET BinaryPath=D:\dev\git\lithnet\windows-credential-provider\src\samples\Lithnet.CredentialProvider.Sample.Framework.x64\bin\Debug\net472\Lithnet.CredentialProvider.Sample.Framework.x64.dll
REM %windir%\Microsoft.NET\Framework64\v4.0.30319\regasm /codebase "Lithnet.CredentialProvider.Sample.Framework.x64.dll"
REG ADD "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Samples.TestCredentialProvider"
REG ADD "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"
@@ -20,11 +20,11 @@ REM REG ADD "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\InprocServer32" /v "Class" /t R
REG ADD "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\InprocServer32" /v "RuntimeVersion" /t REG_SZ /f /d "v4.0.30319"
REG ADD "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\InprocServer32" /v "CodeBase" /t REG_SZ /f /d "%BinaryPath%"
REG ADD "HKLM\SOFTWARE\Classes\Lithnet.CredentialProvider.Sample.net472.x64" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Samples.TestCredentialProvider"
REG ADD "HKLM\SOFTWARE\Classes\Lithnet.CredentialProvider.Sample.net472.x64\CLSID" /ve /t REG_SZ /f /d "%CLSID%"
REG ADD "HKLM\SOFTWARE\Classes\Lithnet.CredentialProvider.Sample.Framework.x64" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Samples.TestCredentialProvider"
REG ADD "HKLM\SOFTWARE\Classes\Lithnet.CredentialProvider.Sample.Framework.x64\CLSID" /ve /t REG_SZ /f /d "%CLSID%"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\%CLSID%" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.net472.x64"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\%CLSID%" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.Framework.x64"
```
## Disable the sample
@@ -45,6 +45,6 @@ REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credentia
To remove the credential provider, run the following command.
```
%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm /u "Lithnet.CredentialProvider.Sample.net472.x64.dll"
%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm /u "Lithnet.CredentialProvider.Sample.Framework.x64.dll"
REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{4eb911fa-ca18-40ea-86df-19aff5d1da58"}" /f
```
@@ -15,7 +15,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lithnet.CredentialProvider" Version="1.0.*-beta1*" />
<ProjectReference Include="..\..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
<PackageReference Include="NLog" Version="5.1.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.1" />
</ItemGroup>
@@ -10,9 +10,9 @@ namespace Lithnet.CredentialProvider.Samples
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Lithnet.CredentialProvider.Sample.net472.x64")]
[ProgId("Lithnet.CredentialProvider.Sample.Framework.x64")]
[Guid("4eb911fa-ca18-40ea-86df-19aff5d1da58")]
public class TestCredentialProviderNet472x64 : CredentialProviderBase
public class TestCredentialProviderFrameworkX64 : CredentialProviderBase
{
protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
@@ -33,7 +33,7 @@ namespace Lithnet.CredentialProvider.Samples
}
else
{
var image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.net472.x64.Resources.TileIcon.png"));
var image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.Framework.x64.Resources.TileIcon.png"));
yield return new CredentialProviderLabelControl(ControlKeys.LabelCredentialProvider, "Login with showcase credential provider");
yield return new CredentialProviderLogoControl(ControlKeys.ImageCredentialProvider, "Credential provider logo", image);
@@ -5,7 +5,10 @@ using Microsoft.Extensions.Logging;
namespace Lithnet.CredentialProvider.Samples
{
public class TestCredentialProviderTile : CredentialTile2
/// <summary>
/// Demonstrates a version 3 credential tile that preserves image transparency.
/// </summary>
public class TestCredentialProviderTile : CredentialTile3
{
private TextboxControl UsernameControl;
private SecurePasswordTextboxControl PasswordControl;
@@ -7,10 +7,10 @@ In order to install and run the sample app, you have to register the COM compone
Build the EXE, and from an elevated command prompt, change to the bin folder, and run the following commands
```
%windir%\Microsoft.NET\Framework\v4.0.30319\regasm /codebase "Lithnet.CredentialProvider.Sample.net472.x86.dll"
%windir%\Microsoft.NET\Framework\v4.0.30319\regasm /codebase "Lithnet.CredentialProvider.Sample.Framework.x86.dll"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{c9055c88-03f9-4a12-8e33-1ee75826a4a6}" /f
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{c9055c88-03f9-4a12-8e33-1ee75826a4a6}" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.net472.x86"
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{c9055c88-03f9-4a12-8e33-1ee75826a4a6}" /ve /t REG_SZ /f /d "Lithnet.CredentialProvider.Sample.Framework.x86"
```
## Disable the sample
@@ -31,6 +31,6 @@ REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credentia
To remove the credential provider, run the following command.
```
%windir%\Microsoft.NET\Framework\v4.0.30319\regasm /u "Lithnet.CredentialProvider.Sample.net472.x86.dll"
%windir%\Microsoft.NET\Framework\v4.0.30319\regasm /u "Lithnet.CredentialProvider.Sample.Framework.x86.dll"
REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{c9055c88-03f9-4a12-8e33-1ee75826a4a6}" /f
```
@@ -11,9 +11,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\InternalLogger.cs" Link="InternalLogger.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\ControlKeys.cs" Link="ControlKeys.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\TestCredentialProviderTile.cs" Link="TestCredentialProviderTile.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\InternalLogger.cs" Link="InternalLogger.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\ControlKeys.cs" Link="ControlKeys.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.Framework.x64\TestCredentialProviderTile.cs" Link="TestCredentialProviderTile.cs" />
</ItemGroup>
<ItemGroup>
@@ -21,7 +21,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lithnet.CredentialProvider" Version="1.0.*-beta1*" />
<ProjectReference Include="..\..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
<PackageReference Include="NLog" Version="5.1.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.1" />
@@ -9,11 +9,11 @@ namespace Lithnet.CredentialProvider.Samples
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Lithnet.CredentialProvider.Sample.net472.x86")]
[ProgId("Lithnet.CredentialProvider.Sample.Framework.x86")]
[Guid("c9055c88-03f9-4a12-8e33-1ee75826a4a6")]
public class TestCredentialProviderNet472x86 : CredentialProviderBase
public class TestCredentialProviderFrameworkX86 : CredentialProviderBase
{
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderNet472x86>();
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderFrameworkX86>();
protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
@@ -34,7 +34,7 @@ namespace Lithnet.CredentialProvider.Samples
}
else
{
var image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.net472.x86.Resources.TileIcon.png"));
var image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("Lithnet.CredentialProvider.Sample.Framework.x86.Resources.TileIcon.png"));
yield return new CredentialProviderLabelControl(ControlKeys.LabelCredentialProvider, "Login with showcase credential provider");
yield return new CredentialProviderLogoControl(ControlKeys.ImageCredentialProvider, "Credential provider logo", image);
@@ -1,32 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x64</Platform>
<EnableComHosting>true</EnableComHosting>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\InternalLogger.cs" Link="InternalLogger.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\ControlKeys.cs" Link="ControlKeys.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\TestCredentialProviderTile.cs" Link="TestCredentialProviderTile.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lithnet.CredentialProvider" Version="1.0.*-beta1*" />
<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>
</Project>
@@ -1,32 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x86</Platform>
<EnableComHosting>true</EnableComHosting>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\InternalLogger.cs" Link="InternalLogger.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\ControlKeys.cs" Link="ControlKeys.cs" />
<Compile Include="..\Lithnet.CredentialProvider.Sample.net472.x64\TestCredentialProviderTile.cs" Link="TestCredentialProviderTile.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\TileIcon.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lithnet.CredentialProvider" Version="1.0.*-beta1*" />
<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>
</Project>
@@ -8,8 +8,6 @@
</PropertyGroup>
<ItemGroup>
<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" />
@@ -2,14 +2,12 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x86</Platform>
</PropertyGroup>
<ItemGroup>
<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>