Skip to content

Commit

Permalink
Improve code (#35)
Browse files Browse the repository at this point in the history
* Add Directory.Build.props as single source of truth for package metadata
* Switch to first party BouncyCastle package
* Improve HttpClientExtensions for fetching access token
  • Loading branch information
ChristopherMann authored Nov 20, 2024
1 parent 8492935 commit 84504d4
Show file tree
Hide file tree
Showing 19 changed files with 572 additions and 509 deletions.
16 changes: 8 additions & 8 deletions IdentityModel.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30320.27
# Visual Studio Version 17
VisualStudioVersion = 17.11.35327.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Eryph.IdentityModel", "src\Eryph.IdentityModel\Eryph.IdentityModel.csproj", "{5438E138-B865-48B0-85D5-FA8736CE1D48}"
EndProject
Expand All @@ -13,10 +13,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
GitVersion.yml = GitVersion.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityModel.Clients.Tests", "test\IdentityModel.Clients.Tests\IdentityModel.Clients.Tests.csproj", "{46EA05B2-36EB-442C-9C66-AB1C8F83FF41}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Eryph.IdentityModel.Tests", "test\Eryph.IdentityModel.Tests\Eryph.IdentityModel.Tests.csproj", "{FC61422C-260F-413C-8A42-FA5140CE480F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Eryph.IdentityModel.Clients.Tests", "test\Eryph.IdentityModel.Clients.Tests\Eryph.IdentityModel.Clients.Tests.csproj", "{AAC70B24-4DF6-43AB-B0CA-6F8BC5705E89}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -31,14 +31,14 @@ Global
{2D1C55E6-67A1-4173-BF53-EE87457CF9B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D1C55E6-67A1-4173-BF53-EE87457CF9B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D1C55E6-67A1-4173-BF53-EE87457CF9B0}.Release|Any CPU.Build.0 = Release|Any CPU
{46EA05B2-36EB-442C-9C66-AB1C8F83FF41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46EA05B2-36EB-442C-9C66-AB1C8F83FF41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46EA05B2-36EB-442C-9C66-AB1C8F83FF41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46EA05B2-36EB-442C-9C66-AB1C8F83FF41}.Release|Any CPU.Build.0 = Release|Any CPU
{FC61422C-260F-413C-8A42-FA5140CE480F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC61422C-260F-413C-8A42-FA5140CE480F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC61422C-260F-413C-8A42-FA5140CE480F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC61422C-260F-413C-8A42-FA5140CE480F}.Release|Any CPU.Build.0 = Release|Any CPU
{AAC70B24-4DF6-43AB-B0CA-6F8BC5705E89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAC70B24-4DF6-43AB-B0CA-6F8BC5705E89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAC70B24-4DF6-43AB-B0CA-6F8BC5705E89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAC70B24-4DF6-43AB-B0CA-6F8BC5705E89}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
37 changes: 37 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project>
<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://www.eryph.io</PackageProjectUrl>
<PackageReleaseNotes>https://github.com/eryph-org/dotnet-identitymodel/releases</PackageReleaseNotes>
<Authors>dbosoft GmbH and Eryph contributors</Authors>
<Company>dbosoft GmbH</Company>
<Product>Eryph</Product>
<Copyright>dbosoft GmbH. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/eryph-org/dotnet-identitymodel</RepositoryUrl>
<!-- Declare that the Repository URL can be published to NuSpec -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- Embed source files that are not tracked by the source control manager to the PDB -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup>
<LangVersion>12</LangVersion>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>

<PropertyGroup>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">True</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">True</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="5.12.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
</ItemGroup>
</Project>
97 changes: 48 additions & 49 deletions src/Eryph.IdentityModel.Clients/ClientCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,66 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace Eryph.IdentityModel.Clients
namespace Eryph.IdentityModel.Clients;

[PublicAPI]
public sealed class ClientCredentials
{
[PublicAPI]
public sealed class ClientCredentials
private readonly Uri _tokenUrl;

public ClientCredentials(string id,
SecureString keyPairData,
Uri identityProvider,
string configuration)
{
[DataMember]
public string Id { get; }
Id = id;
KeyPairData = keyPairData;
IdentityProvider = identityProvider;
Configuration = configuration;
var uriBuilder = new UriBuilder(IdentityProvider);
uriBuilder.Path += uriBuilder.Path.EndsWith("/") ? "connect/token" : "/connect/token";
_tokenUrl = uriBuilder.Uri;
}

public SecureString KeyPairData { get; }
public string Id { get; }

public Uri IdentityProvider { get; }
public SecureString KeyPairData { get; }

public string Configuration { get; }

public ClientCredentials(string id, SecureString keyPairData, Uri identityProvider, string configuration)
{
Id = id;
KeyPairData = keyPairData;
IdentityProvider = identityProvider;
Configuration = configuration;
}
public Uri IdentityProvider { get; }

public Task<AccessTokenResponse> GetAccessToken(HttpClient httpClient = null)
{
return GetAccessToken(null, httpClient);
}
public string Configuration { get; }

public async Task<AccessTokenResponse> GetAccessToken(IEnumerable<string> scopes, HttpClient httpClient = null)
public Task<AccessTokenResponse> GetAccessToken(
HttpClient httpClient = null)
{
return GetAccessToken(null, httpClient);
}

public async Task<AccessTokenResponse> GetAccessToken(
IReadOnlyList<string> scopes,
HttpClient httpClient = null)
{
var disposeHttpClient = httpClient == null;
var keyPairPtr = Marshal.SecureStringToGlobalAllocUnicode(KeyPairData);

try
{
var disposeHttpClient = httpClient == null;
httpClient ??= new HttpClient();

httpClient.BaseAddress = IdentityProvider;

var keyPairPtr = Marshal.SecureStringToGlobalAllocUnicode(KeyPairData);
try
{
var keyPair = Internal.PrivateKey.ReadString(Marshal.PtrToStringUni(keyPairPtr));
if (!disposeHttpClient)
return await httpClient.GetClientAccessToken(
Id,
keyPair.ToRSAParameters(), scopes).ConfigureAwait(false);

using (httpClient)
{
var result = await httpClient.GetClientAccessToken(
Id,
keyPair.ToRSAParameters(), scopes).ConfigureAwait(false);
return result;
}
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(keyPairPtr);
}
var keyPair = Internal.PrivateKey.ReadString(Marshal.PtrToStringUni(keyPairPtr));
return await httpClient.GetClientAccessToken(
_tokenUrl, Id, keyPair.ToRSAParameters(), scopes)
.ConfigureAwait(false);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(keyPairPtr);
if (disposeHttpClient)
httpClient?.Dispose();

}

}
}
}
38 changes: 8 additions & 30 deletions src/Eryph.IdentityModel.Clients/ClientData.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using JetBrains.Annotations;

namespace Eryph.IdentityModel.Clients
{
[PublicAPI]
public sealed class ClientData
{

public ClientData(string id, string name)
{
Id = id;
Name = name;
}

/// <summary>
/// constructor for deserialization
/// </summary>
[ExcludeFromCodeCoverage]
internal ClientData()
{}
using JetBrains.Annotations;

namespace Eryph.IdentityModel.Clients;

[DataMember]
public string Id { get; }


[DataMember]
public string Name { get; }
[PublicAPI]
public sealed class ClientData(string id, string name)
{
public string Id { get; } = id;

}
}
public string Name { get; } = name;
}
18 changes: 3 additions & 15 deletions src/Eryph.IdentityModel.Clients/Eryph.IdentityModel.Clients.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,13 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>dbosoft GmbH and Eryph contributors</Authors>
<Company>dbosoft GmbH</Company>
<Product>Eryph</Product>
<Description>Eryph .NET library for authentication with eryph clients.</Description>
<Copyright>dbosoft GmbH. All rights reserved.</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/eryph-org/dotnet-identitymodel</RepositoryUrl>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="5.10.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="6.0.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />
</ItemGroup>

Expand Down
1 change: 1 addition & 0 deletions src/Eryph.IdentityModel.Clients/Internal/PrivateKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public static AsymmetricCipherKeyPair ReadFile(string filepath, IFileSystem file
using var decryptedData = new MemoryStream(
ProtectedData.Unprotect(protectedDataStream.GetBuffer(), entropy, scope));
using var reader = new StreamReader(decryptedData);

return (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
}
catch (Exception)
Expand Down
19 changes: 19 additions & 0 deletions src/Eryph.IdentityModel/Clients/AccessTokenException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Eryph.IdentityModel.Clients;

/// <summary>
/// This exception is thrown when an access token cannot be retrieved.
/// </summary>
public class AccessTokenException : Exception
{
public AccessTokenException(string message)
: base(message)
{
}

public AccessTokenException(string message, Exception innerException)
: base(message, innerException)
{
}
}
42 changes: 21 additions & 21 deletions src/Eryph.IdentityModel/Clients/AccessTokenResponse.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Eryph.IdentityModel.Clients
namespace Eryph.IdentityModel.Clients;

/// <summary>
/// Contains the requested access token and some metadata about it.
/// </summary>
public sealed class AccessTokenResponse
{
public sealed class AccessTokenResponse
{
/// <summary>Gets the Access Token requested.</summary>
[DataMember]
public string AccessToken { get; internal set; }
/// <summary>
/// The requested access token.
/// </summary>
public string AccessToken { get; internal set; }

/// <summary>
/// Gets the point in time in which the Access Token returned in the AccessToken property ceases to be valid.
/// This value is calculated based on current UTC time measured locally and the value expiresIn received from the
/// service.
/// </summary>
[DataMember]
public DateTimeOffset? ExpiresOn { get; internal set; }
/// <summary>
/// Gets the point in time when the access token ceases to be valid.
/// This value is calculated based on current UTC time measured locally and the
/// value <c>expiresIn</c> received from the service.
/// </summary>
public DateTimeOffset? ExpiresOn { get; internal set; }

/// <summary>
/// The scopes for this access token
/// </summary>
[DataMember]
public IEnumerable<string> Scopes { get; internal set; }
}
}
/// <summary>
/// The scopes which have been granted to this access token.
/// </summary>
public IReadOnlyList<string> Scopes { get; internal set; }
}
Loading

0 comments on commit 84504d4

Please sign in to comment.