Skip to content

Commit

Permalink
Merge pull request #17 from claboran/feature/Net60
Browse files Browse the repository at this point in the history
Feature/net60
  • Loading branch information
claboran committed Jan 18, 2023
2 parents c355751 + 5667848 commit 2bd3b7d
Show file tree
Hide file tree
Showing 32 changed files with 801 additions and 695 deletions.
2 changes: 1 addition & 1 deletion .idea/.idea.ForceDotNetJwtCompanion/.idea/indexLayout.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/.idea.ForceDotNetJwtCompanion/.idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
Empty file.
Binary file added .vs/ForceDotNetJwtCompanion/v17/.suo
Binary file not shown.
19 changes: 2 additions & 17 deletions ForceDotNetJwtCompanion.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=b06ee029_002D60e0_002D49ea_002D8edd_002Dc60a53227d03/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="Authenticate_WithUnencryptedKey_Success" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96::net5.0::ForceDotNetJwtCompanionTest.Tests.AuthenticationClientTests.Authenticate_WithUnencryptedKey_Success&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=b0ef948d_002Db53c_002D45dd_002Da72c_002Dde125cf79071/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="ReadKeyFile" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Or&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96::net5.0::ForceDotNetJwtCompanionTest.KeyHelperTests&lt;/TestId&gt;
&lt;TestId&gt;xUnit::2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96::net5.0::ForceDotNetJwtCompanionTest.JwtTests.JwtPayloadTest&lt;/TestId&gt;
&lt;TestId&gt;xUnit::2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96::net5.0::ForceDotNetJwtCompanionTest.JwtTests.CreateExpTimeAsStringTest&lt;/TestId&gt;
&lt;TestId&gt;xUnit::2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96::net5.0::ForceDotNetJwtCompanionTest.JwtTests.JwtPayloadToBase64Test&lt;/TestId&gt;
&lt;TestId&gt;xUnit::2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96::net5.0::ForceDotNetJwtCompanionTest.JwtTests&lt;/TestId&gt;
&lt;TestId&gt;xUnit::2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96::net5.0::ForceDotNetJwtCompanionTest.Tests.ValidatorsTests&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;ProjectFolder&gt;2CADC6E3-1FCE-4BF7-A27B-7CE49CFE0F96/d:Tests&lt;/ProjectFolder&gt;
&lt;/Or&gt;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=e39ae419_002D0ef4_002D4086_002D8fda_002Debf0bcde3426/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Solution /&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
15 changes: 7 additions & 8 deletions ForceDotNetJwtCompanion/ForceAuthenticationException.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using System;
using System.Net;

namespace ForceDotNetJwtCompanion
namespace ForceDotNetJwtCompanion;

public class ForceAuthenticationException : Exception
{
public class ForceAuthenticationException : Exception
{
public HttpStatusCode HttpStatusCode { get; }
public HttpStatusCode HttpStatusCode { get; }

public ForceAuthenticationException(HttpStatusCode statusCode, string description) : base(description)
{
HttpStatusCode = statusCode;
}
public ForceAuthenticationException(HttpStatusCode statusCode, string description) : base(description)
{
HttpStatusCode = statusCode;
}
}
12 changes: 8 additions & 4 deletions ForceDotNetJwtCompanion/ForceDotNetJwtCompanion.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Title>.Net Authorization of an Salesforce Org with OAuth 2.0 JWT Bearer Flow</Title>
<Authors>Christian Laboranowitsch</Authors>
<Authors>Christian Laboranowitsch, Stefan van Raaij</Authors>
<PackageProjectUrl>https://github.com/claboran/ForceDotNetJwtCompanion</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/claboran/ForceDotNetJwtCompanion/blob/master/LICENSE</PackageLicenseUrl>
<RepositoryUrl>https://github.com/claboran/ForceDotNetJwtCompanion.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>jwt salesforce .Net</PackageTags>
<PackageVersion>1.0.1</PackageVersion>
<PackageVersion>1.0.3</PackageVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>Authenticate a Salesforce Connected App with OAuth2 JWT Bearer Flow</Description>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<PackageReleaseNotes>update solution januari 2023</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.8" />
<PackageReference Include="Newtonsoft.Json" Version="[11.0.1,)" />
<PackageReference Include="BouncyCastle.NetCore" Version="1.9.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>

</Project>
66 changes: 66 additions & 0 deletions ForceDotNetJwtCompanion/IJwtAuthenticationClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Threading.Tasks;

namespace ForceDotNetJwtCompanion;

/// <summary>
/// IJwtAuthenticationClient
///
/// HTTP handling and orchestration of JWT OAuth Flow with Salesforce.
///
/// </summary>
public interface IJwtAuthenticationClient : IDisposable
{
string InstanceUrl { get; set; }
string AccessToken { get; set; }
string Id { get; set; }
string ApiVersion { get; set; }

/// <summary>
/// JwtUnencryptedPrivateKeyAsync
///
/// Obtain access token with unencrypted private key (not recommended)
/// Token Endpoint: https://login.salesforce.com/services/oauth2/token (production)
/// </summary>
/// <param name="clientId">ClientId of the Connected App aka Consumer Key</param>
/// <param name="key">Private key as string, it is not required to remove header and footer</param>
/// <param name="username">Salesforce username</param>
Task JwtUnencryptedPrivateKeyAsync(string clientId, string key, string username);

/// <summary>
/// JwtPrivateKeyAsync
///
/// Obtain access token with encrypted private key
/// Token Endpoint: https://login.salesforce.com/services/oauth2/token (production)
/// </summary>
/// <param name="clientId">ClientId of the Connected App aka Consumer Key</param>
/// <param name="key">Private key as string, it is not required to remove header and footer</param>
/// <param name="passphrase">Passphrase of the private key</param>
/// <param name="username">Salesforce username</param>
Task JwtPrivateKeyAsync(string clientId, string key, string passphrase, string username);

/// <summary>
/// JwtUnencryptedPrivateKeyAsync
///
/// Obtain access token with unencrypted private key (not recommended)
/// with token endpoint
/// </summary>
/// <param name="clientId">ClientId of the Connected App aka Consumer Key</param>
/// <param name="key">Private key as string, it is not required to remove header and footer</param>
/// <param name="username">Salesforce username</param>
/// <param name="tokenEndpoint">TokenEndpointUrl e.g. https://test.salesforce.com/services/oauth2/token</param>
Task JwtUnencryptedPrivateKeyAsync(string clientId, string key, string username, string tokenEndpoint);

/// <summary>
/// JwtPrivateKeyAsync
///
/// Obtain access token with encrypted private key
/// with token endpoint
/// </summary>
/// <param name="clientId">ClientId of the Connected App aka Consumer Key</param>
/// <param name="key">Private key as string, it is not required to remove header and footer</param>
/// <param name="passphrase">Passphrase of the private key</param>
/// <param name="username">Salesforce username</param>
/// <param name="tokenEndpoint">TokenEndpointUrl e.g. https://test.salesforce.com/services/oauth2/token</param>
Task JwtPrivateKeyAsync(string clientId, string key, string passphrase, string username, string tokenEndpoint);
}
147 changes: 74 additions & 73 deletions ForceDotNetJwtCompanion/Jwt/Jwt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,89 @@
using ForceDotNetJwtCompanion.Models;
using ForceDotNetJwtCompanion.Util;

namespace ForceDotNetJwtCompanion.Jwt
namespace ForceDotNetJwtCompanion.Jwt;

/// <summary>
/// Jwt
/// Main class responsible for Salesforce JWT creation
///
/// It is basically a port of the Java example provided by Salesforce.
/// Retrieving Modulus and Exponent from key files has been extracted to
/// helper functions.
///
/// <see>https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5</see>
///
/// </summary>
public class Jwt
{
/// <summary>
/// Jwt
/// Main class responsible for Salesforce JWT creation
///
/// It is basically a port of the Java example provided by Salesforce.
/// Retrieving Modulus and Exponent from key files has been extracted to
/// helper functions.
///
/// <see>https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5</see>
///
/// </summary>
public class Jwt
// Salesforce supports RS256 (no other algorithms are provided so far)
private const string Header = "{\"alg\": \"RS256\"}";
private JwtPayload _jwtPayload;
private string _consumerKey; // aka ClientId
private string _audience;
private string _subject; // Salesforce user
private string _expiration;
private readonly PrivateKeyWrapper _privateKeyWrapper;

private Jwt(PrivateKeyWrapper privateKeyWrapper)
{
// Salesforce supports RS256 (no other algorithms are provided so far)
private const string Header = "{\"alg\": \"RS256\"}";
private JwtPayload _jwtPayload;
private string _consumerKey; // aka ClientId
private string _audience;
private string _subject; // Salesforce user
private string _expiration;
private readonly PrivateKeyWrapper _privateKeyWrapper;
_privateKeyWrapper = privateKeyWrapper;
}

private Jwt(PrivateKeyWrapper privateKeyWrapper)
{
_privateKeyWrapper = privateKeyWrapper;
}
public static Jwt CreateJwt(PrivateKeyWrapper privateKeyWrapper) => new(privateKeyWrapper);

public static Jwt CreateJwt(PrivateKeyWrapper privateKeyWrapper) => new Jwt(privateKeyWrapper);
public Jwt AddConsumerKey(string consumerKey)
{
_consumerKey = consumerKey;
return this;
}

public Jwt AddConsumerKey(string consumerKey)
{
_consumerKey = consumerKey;
return this;
}
public Jwt AddAudience(string tokenEndpoint)
{
_audience = tokenEndpoint;
return this;
}

public Jwt AddAudience(string tokenEndpoint)
{
_audience = tokenEndpoint;
return this;
}
public Jwt AddSubject(string subject)
{
_subject = subject;
return this;
}

public Jwt AddExpiration(DateTime now)
{
_expiration = JwtHelpers.CreateExpTimeAsString(now);
return this;
}

public Jwt AddSubject(string subject)
public string Build()
{
if (
string.IsNullOrEmpty(_consumerKey) ||
string.IsNullOrEmpty(_expiration) ||
string.IsNullOrEmpty(_subject) ||
string.IsNullOrEmpty(_audience)
) throw new ArgumentException("Missing arguments for JWT!");

_jwtPayload = new JwtPayload
{
_subject = subject;
return this;
}
Aud = _audience,
Exp = _expiration,
Iss = _consumerKey,
Sub = _subject
};

public Jwt AddExpiration(DateTime now)
{
_expiration = JwtHelpers.CreateExpTimeAsString(now);
return this;
}
var bytesToSign = Encoding.UTF8.GetBytes(
string.Join(".",
CommonHelpers.UrlEncode(Encoding.UTF8.GetBytes(Header)),
_jwtPayload.ConvertToBase64())
);

public string Build()
{
if (
string.IsNullOrEmpty(_consumerKey) ||
string.IsNullOrEmpty(_expiration) ||
string.IsNullOrEmpty(_subject) ||
string.IsNullOrEmpty(_audience)
) throw new ArgumentException("Missing arguments for JWT!");

_jwtPayload = new JwtPayload
{
Aud = _audience,
Exp = _expiration,
Iss = _consumerKey,
Sub = _subject
};
var bytesToSign = Encoding.UTF8.GetBytes(
string.Join(".",
CommonHelpers.UrlEncode(Encoding.UTF8.GetBytes(Header)),
_jwtPayload.ConvertToBase64())
);
return string
.Join(".",
CommonHelpers.UrlEncode(Encoding.UTF8.GetBytes(Header)),
_jwtPayload.ConvertToBase64(),
CommonHelpers.UrlEncode(KeyHelpers.CreateSignature(_privateKeyWrapper, bytesToSign))
);
}
return string
.Join(".",
CommonHelpers.UrlEncode(Encoding.UTF8.GetBytes(Header)),
_jwtPayload.ConvertToBase64(),
CommonHelpers.UrlEncode(KeyHelpers.CreateSignature(_privateKeyWrapper, bytesToSign))
);
}
}
41 changes: 20 additions & 21 deletions ForceDotNetJwtCompanion/Jwt/JwtHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
using System;

namespace ForceDotNetJwtCompanion.Jwt
namespace ForceDotNetJwtCompanion.Jwt;

/// <summary>
/// JwtHelpers
///
/// Helper functions for JWT creation
///
/// </summary>
public static class JwtHelpers
{
/// <summary>
/// JwtHelpers
///
/// Helper functions for JWT creation
/// CreateExpTimeAsString
/// Converts timestamp with TTL = 3 minutes
/// You should use UTC time
///
/// <see>https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5</see>
///
/// </summary>
public static class JwtHelpers
{
/// <summary>
/// CreateExpTimeAsString
/// Converts timestamp with TTL = 3 minutes
/// You should use UTC time
///
/// <see>https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5</see>
///
/// </summary>
/// <param name="now"></param>
/// <returns>epoc as string (seconds since 01.01.1970)</returns>
public static string CreateExpTimeAsString(DateTime now) =>
new DateTimeOffset(now.AddMinutes(3))
.ToUnixTimeSeconds()
.ToString();
}
/// <param name="now"></param>
/// <returns>epoc as string (seconds since 01.01.1970)</returns>
public static string CreateExpTimeAsString(DateTime now) =>
new DateTimeOffset(now.AddMinutes(3))
.ToUnixTimeSeconds()
.ToString();
}
Loading

0 comments on commit 2bd3b7d

Please sign in to comment.