Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added JumpCloud support #797

Merged
merged 10 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
martincostello marked this conversation as resolved.
Show resolved Hide resolved
</PropertyGroup>

<!-- This property group is required until the new provider is first published to NuGet.org -->
<PropertyGroup>
<DisablePackageBaselineValidation>true</DisablePackageBaselineValidation>
</PropertyGroup>

<PropertyGroup>
<Description>ASP.NET Core security middleware enabling JumpCloud authentication.</Description>
<Authors>AaronSadlerUK</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ public static class JumpCloudAuthenticationDefaults
/// </summary>
public static readonly string Issuer = "JumpCloud";

/// <summary>
/// The name of the default JumpCloud custom Authorization Server.
/// </summary>
public static readonly string DefaultAuthorizationServer = "default";

/// <summary>
/// Default value for <see cref="RemoteAuthenticationOptions.CallbackPath"/>.
/// </summary>
Expand All @@ -56,15 +51,15 @@ public static class JumpCloudAuthenticationDefaults
/// <summary>
/// Default path to use for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
/// </summary>
public static readonly string AuthorizationEndpointPath = string.Format(CultureInfo.InvariantCulture, AuthorizationEndpointPathFormat, DefaultAuthorizationServer);
public static readonly string AuthorizationEndpointPath = string.Format(CultureInfo.InvariantCulture, AuthorizationEndpointPathFormat);

/// <summary>
/// Default path to use for <see cref="OAuthOptions.TokenEndpoint"/>.
/// </summary>
public static readonly string TokenEndpointPath = string.Format(CultureInfo.InvariantCulture, TokenEndpointPathFormat, DefaultAuthorizationServer);
public static readonly string TokenEndpointPath = string.Format(CultureInfo.InvariantCulture, TokenEndpointPathFormat);

/// <summary>
/// Default path to use for <see cref="OAuthOptions.UserInformationEndpoint"/>.
/// </summary>
public static readonly string UserInformationEndpointPath = string.Format(CultureInfo.InvariantCulture, UserInformationEndpointPathFormat, DefaultAuthorizationServer);
public static readonly string UserInformationEndpointPath = string.Format(CultureInfo.InvariantCulture, UserInformationEndpointPathFormat);
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public static AuthenticationBuilder AddJumpCloud([NotNull] this AuthenticationBu
public static AuthenticationBuilder AddJumpCloud(
[NotNull] this AuthenticationBuilder builder,
[NotNull] string scheme,
string? caption,
[CanBeNull] string caption,
martincostello marked this conversation as resolved.
Show resolved Hide resolved
[NotNull] Action<JumpCloudAuthenticationOptions> configuration)
{
builder.Services.TryAddSingleton<IPostConfigureOptions<JumpCloudAuthenticationOptions>, JumpCloudPostConfigureOptions>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ public JumpCloudAuthenticationOptions()
ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name");
}

/// <summary>
/// Gets or sets the JumpCloud custom authorization server to use for authentication.
/// </summary>
/// <remarks>
/// The default value is <c>default</c>.
/// </remarks>
public string AuthorizationServer { get; set; } = JumpCloudAuthenticationDefaults.DefaultAuthorizationServer;

/// <summary>
/// Gets or sets the JumpCloud domain (Org URL) to use for authentication.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,9 @@ public class JumpCloudPostConfigureOptions : IPostConfigureOptions<JumpCloudAuth
throw new ArgumentException("No JumpCloud domain configured.", nameof(options));
}

if (string.IsNullOrWhiteSpace(options.AuthorizationServer))
{
throw new ArgumentException("No JumpCloud authorization server configured.", nameof(options));
}

options.AuthorizationEndpoint = CreateUrl(options.Domain, JumpCloudAuthenticationDefaults.AuthorizationEndpointPathFormat, options.AuthorizationServer);
options.TokenEndpoint = CreateUrl(options.Domain, JumpCloudAuthenticationDefaults.TokenEndpointPathFormat, options.AuthorizationServer);
options.UserInformationEndpoint = CreateUrl(options.Domain, JumpCloudAuthenticationDefaults.UserInformationEndpointPathFormat, options.AuthorizationServer);
options.AuthorizationEndpoint = CreateUrl(options.Domain, JumpCloudAuthenticationDefaults.AuthorizationEndpointPathFormat);
options.TokenEndpoint = CreateUrl(options.Domain, JumpCloudAuthenticationDefaults.TokenEndpointPathFormat);
options.UserInformationEndpoint = CreateUrl(options.Domain, JumpCloudAuthenticationDefaults.UserInformationEndpointPathFormat);
}

private static string CreateUrl(string domain, string pathFormat, params object[] args)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using AspNet.Security.OAuth.Okta;
martincostello marked this conversation as resolved.
Show resolved Hide resolved

namespace AspNet.Security.OAuth.JumpCloud;

public static class JumpCloudAuthenticationOptionsTests
{
[Fact]
public static void Validate_Throws_If_AuthorizationEndpoint_Not_Set()
{
// Arrange
var options = new OktaAuthenticationOptions()
{
ClientId = "ClientId",
ClientSecret = "ClientSecret",
TokenEndpoint = "https://jumpcloud.local",
UserInformationEndpoint = "https://jumpcloud.local",
};

// Act and Assert
Assert.Throws<ArgumentException>("AuthorizationEndpoint", () => options.Validate());
}

[Fact]
public static void Validate_Throws_If_TokenEndpoint_Not_Set()
{
// Arrange
var options = new OktaAuthenticationOptions()
{
AuthorizationEndpoint = "https://jumpcloud.local",
ClientId = "ClientId",
ClientSecret = "ClientSecret",
UserInformationEndpoint = "https://jumpcloud.local",
};

// Act and Assert
Assert.Throws<ArgumentException>("TokenEndpoint", () => options.Validate());
}

[Fact]
public static void Validate_Throws_If_UserInformationEndpoint_Not_Set()
{
// Arrange
var options = new OktaAuthenticationOptions()
{
AuthorizationEndpoint = "https://okta.local",
ClientId = "ClientId",
ClientSecret = "ClientSecret",
TokenEndpoint = "https://okta.local",
};

// Act and Assert
Assert.Throws<ArgumentException>("UserInformationEndpoint", () => options.Validate());
}

[Fact]
public static void Validate_Does_Not_Throw_If_Uris_Are_Valid()
{
// Arrange
var options = new OktaAuthenticationOptions()
{
AuthorizationEndpoint = "https://okta.local",
ClientId = "ClientId",
ClientSecret = "ClientSecret",
TokenEndpoint = "https://okta.local",
UserInformationEndpoint = "https://okta.local",
};

// Act (no Assert)
options.Validate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

namespace AspNet.Security.OAuth.JumpCloud;

public static class JumpCloudPostConfigureOptionsTests
{
[Theory]
[InlineData("jumpcloud.local")]
[InlineData("http://jumpcloud.local")]
[InlineData("http://jumpcloud.local/")]
[InlineData("https://jumpcloud.local")]
[InlineData("https://jumpcloud.local/")]
public static void PostConfigure_Configures_Valid_Endpoints(string domain)
{
// Arrange
string name = "JumpCloud";
var target = new JumpCloudPostConfigureOptions();

var options = new JumpCloudAuthenticationOptions()
{
Domain = domain,
};

// Act
target.PostConfigure(name, options);

// Assert
options.AuthorizationEndpoint.ShouldStartWith("https://jumpcloud.local/oauth2/auth");
Uri.TryCreate(options.AuthorizationEndpoint, UriKind.Absolute, out _).ShouldBeTrue();

options.TokenEndpoint.ShouldStartWith("https://jumpcloud.local/oauth2/token");
Uri.TryCreate(options.TokenEndpoint, UriKind.Absolute, out _).ShouldBeTrue();

options.UserInformationEndpoint.ShouldStartWith("https://jumpcloud.local/userinfo");
Uri.TryCreate(options.UserInformationEndpoint, UriKind.Absolute, out _).ShouldBeTrue();
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public static void PostConfigure_Throws_If_Domain_Is_Invalid(string value)
{
// Arrange
string name = "JumpCloud";
var target = new JumpCloudPostConfigureOptions();

var options = new JumpCloudAuthenticationOptions()
{
Domain = value,
};

// Act and Assert
Assert.Throws<ArgumentException>("options", () => target.PostConfigure(name, options));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

namespace AspNet.Security.OAuth.JumpCloud;

public class JumpCloudTests : OAuthTests<JumpCloudAuthenticationOptions>
{
public JumpCloudTests(ITestOutputHelper outputHelper)
{
OutputHelper = outputHelper;
}

public override string DefaultScheme => JumpCloudAuthenticationDefaults.AuthenticationScheme;

protected internal override void RegisterAuthentication(AuthenticationBuilder builder)
{
builder.AddJumpCloud(options =>
{
ConfigureDefaults(builder, options);
options.Domain = "jumpcloud.local";
});
}

[Theory]
[InlineData(ClaimTypes.Email, "john.doe@example.com")]
[InlineData(ClaimTypes.GivenName, "John")]
[InlineData(ClaimTypes.Name, "John Doe")]
[InlineData(ClaimTypes.NameIdentifier, "00uid4BxXw6I6TV4m0g3")]
[InlineData(ClaimTypes.Surname, "Doe")]
public async Task Can_Sign_In_Using_JumpCloud(string claimType, string claimValue)
{
// Arrange
using var server = CreateTestServer();

// Act
var claims = await AuthenticateUserAsync(server);

// Assert
AssertClaim(claims, claimType, claimValue);
}
}
46 changes: 46 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/JumpCloud/bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/master/src/HttpClientInterception/Bundles/http-request-bundle-schema.json",
"items": [
{
"comment": "https://jumpcloud.com/support/sso-with-oidc",
"uri": "https://jumpcloud.local/oauth2/token",
"method": "POST",
"contentFormat": "json",
"contentJson": {
"access_token": "secret-access-token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email",
"refresh_token": "secret-refresh-token",
"id_token": "secret-id-token"
}
},
{
"comment": "https://jumpcloud.com/support/sso-with-oidc",
"uri": "https://jumpcloud.local/userinfo",
"contentFormat": "json",
"contentJson": {
"sub": "00uid4BxXw6I6TV4m0g3",
"name": "John Doe",
"nickname": "Jimmy",
"given_name": "John",
"middle_name": "James",
"family_name": "Doe",
"profile": "https://example.com/john.doe",
"zoneinfo": "America/Los_Angeles",
"locale": "en-US",
"updated_at": 1311280970,
"email": "john.doe@example.com",
"email_verified": true,
"address": {
"street_address": "123 Hollywood Blvd.",
"locality": "Los Angeles",
"region": "CA",
"postal_code": "90210",
"country": "US"
},
"phone_number": "+1 (425) 555-1212"
}
}
]
}