Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ version: 1
analysis:
# Paths to exclude from license/security analysis (relative to repo root)
ignorePaths:
- "NETCore.Keycloak.Client.Tests/**"

- "NETCore.Keycloak.Client.Tests/**"
# Specific package locators to ignore (use exact locator shown by FOSSA)
ignorePackages:
- "pip+ansible$13.2.0"
- "pip+ansible-core$2.17.7"
- "nuget+Microsoft.NET.Test.Sdk$17.6.0"
- "pip+ansible$13.2.0"
- "pip+ansible-core$2.17.7"
- "nuget+Microsoft.NET.Test.Sdk$17.6.0"

# Notes:
# - Commit and push this file and then trigger a new FOSSA scan so the server-side
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/branch_naming_policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
- reopened
jobs:
branch-naming-policy:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
40 changes: 23 additions & 17 deletions .github/workflows/build_test_analyze.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Build test and analyze
on:
push:
push:
branches:
- master
pull_request:
Expand All @@ -15,48 +15,54 @@ on:
jobs:
# Build test and analyze source code
build_test_analyze:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
strategy:
matrix:
java-version: [ 21 ]
java-version: [21]
steps:
- name: Checkout Repository
uses: actions/checkout@v4

# Setup OpenJDK
- name: Setup OpenJDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
distribution: "adopt"
java-version: ${{ matrix.java-version }}

# Setup Python
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

# Install all required .NET SDK versions
- name: Install .NET SDKs (6.0, 7.0, 8.0)
- name: Install .NET SDKs (8.0, 9.0 and 10.0)
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x

9.0.x
10.0.x

# Install dependencies
- name: Install dependencies
run: |
sudo apt install -y make python3-pip python3-rpm python3-psycopg2
pip install 'python-keycloak==3.3.0' --user
sudo apt install -y make python3-rpm
dotnet tool install --global dotnet-sonarscanner
dotnet tool install --global Cake.Tool
dotnet tool install --global JetBrains.dotCover.GlobalTool
dotnet tool install --global JetBrains.dotCover.CommandLineTools

# Build test and analyze the project
- name: Build test and analyze the project
if: success()
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
VIRTUAL_ENV_DIR: keycloak.venv
run: |
# Copy Licence
cp LICENSE NETCore.Keycloak.Client/
# Build, test and analyze project with keycloak version 20
cd NETCore.Keycloak.Client.Tests
dotnet cake build_test_analyse.cake --kc_major_version=20 --sonar_token=${SONAR_TOKEN}

# Build, test and analyze project with keycloak version 26
cd NETCore.Keycloak.Client.Tests
dotnet cake build_test_analyse.cake --kc_major_version=26 --sonar_token=${SONAR_TOKEN}
13 changes: 6 additions & 7 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: [8.0.x]
dotnet-version: [10.0.x]
steps:
- name: Checkout Repository
uses: actions/checkout@v4

# Setup .NET SDK
- name: Set up .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ matrix.dotnet-version }}

# Setup cake tool
- name: Setup cake tool
run: dotnet tool install --global Cake.Tool
Expand All @@ -32,10 +32,10 @@ jobs:
run: |
# Copy Licence
cp LICENSE NETCore.Keycloak.Client/

# Build project
dotnet cake build.cake --target=build

# Deploy nuget package
- name: Deploy nuget package
if: success()
Expand All @@ -44,7 +44,6 @@ jobs:
run: |
# Extract nuget package version
NUGET_PKG_VERSION=$(cat NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj | grep "PackageVersion" | awk -F '>' '{print $2}' | awk -F '<' '{print $1}')

# Deploy package
dotnet nuget push NETCore.Keycloak.Client/bin/Release/Keycloak.NETCore.Client.${NUGET_PKG_VERSION}.nupkg --api-key $NUGET_API_KEY --source https://api.nuget.org/v3/index.json

5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ persister
Test
/persister
.vscode
.claude
Scripting
docker
kubernetes
Expand Down Expand Up @@ -37,6 +38,9 @@ out
**/out
**/containers/**
**/Assets/*.json
**/*.dcvr
**/dotCover.Output.xml
**/dotCover.Output.html

**/NETCore.Keycloak.Client/LICENSE

Expand Down Expand Up @@ -198,7 +202,6 @@ nCrunchTemp_*
*.mm.*
AutoTest.Net/


# local files
compose.yml

Expand Down
72 changes: 72 additions & 0 deletions NETCore.Keycloak.Client.Tests/Abstraction/KcTestingModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using NETCore.Keycloak.Client.Models.Common;
using NETCore.Keycloak.Client.Models.Groups;
using NETCore.Keycloak.Client.Models.KcEnum;
using NETCore.Keycloak.Client.Models.Organizations;
using NETCore.Keycloak.Client.Models.Roles;
using NETCore.Keycloak.Client.Models.Tokens;
using NETCore.Keycloak.Client.Models.Users;
Expand Down Expand Up @@ -495,6 +496,62 @@ protected async Task<KcGroup> CreateAndGetGroupAsync(string context)
return listGroupsResponse.Response.First();
}

/// <summary>
/// Creates a new organization in the Keycloak realm and retrieves its details.
/// This method generates a mock organization, creates it via the Keycloak Admin API,
/// then retrieves the created organization by listing organizations with a matching name filter.
/// </summary>
/// <param name="context">The context name used for managing environment variables.</param>
/// <returns>A task representing the asynchronous operation, with a result of <see cref="KcOrganization"/>.</returns>
protected async Task<KcOrganization> CreateAndGetOrganizationAsync(string context)
{
// Retrieve an access token for the realm admin to perform the organization creation.
var accessToken = await GetRealmAdminTokenAsync(context).ConfigureAwait(false);
Assert.IsNotNull(accessToken);

// Create a faker instance for generating random data.
var faker = new Faker();

// Generate a mock organization.
var kcOrganization = KcOrganizationMocks.Generate(faker);

// Execute the operation to create the organization.
var createOrganizationResponse = await KeycloakRestClient.Organizations.CreateAsync(
TestEnvironment.TestingRealm.Name,
accessToken.AccessToken,
kcOrganization).ConfigureAwait(false);

// Validate the response from the organization creation operation.
Assert.IsNotNull(createOrganizationResponse);
Assert.IsFalse(createOrganizationResponse.IsError);

// Validate the monitoring metrics for the successful organization creation request.
KcCommonAssertion.AssertResponseMonitoringMetrics(createOrganizationResponse.MonitoringMetrics,
HttpStatusCode.Created, HttpMethod.Post);

// Execute the operation to list organizations matching the specified filter criteria.
var listOrganizationsResponse = await KeycloakRestClient.Organizations
.ListAsync(TestEnvironment.TestingRealm.Name, accessToken.AccessToken, new KcOrganizationFilter
{
Exact = true,
Search = kcOrganization.Name
}).ConfigureAwait(false);

// Validate the response from the organization listing operation.
Assert.IsNotNull(listOrganizationsResponse);
Assert.IsFalse(listOrganizationsResponse.IsError);
Assert.IsNotNull(listOrganizationsResponse.Response);

// Ensure that the test organization is included in the results.
Assert.IsTrue(listOrganizationsResponse.Response.Any(org => org.Name == kcOrganization.Name));

// Validate the monitoring metrics for the successful organization listing request.
KcCommonAssertion.AssertResponseMonitoringMetrics(listOrganizationsResponse.MonitoringMetrics,
HttpStatusCode.OK, HttpMethod.Get);

return listOrganizationsResponse.Response.First();
}

/// <summary>
/// Loads the test environment configuration from the `Assets/testing_environment.json` file.
/// The loaded configuration is deserialized into the <see cref="KcTestEnvironment"/> object.
Expand All @@ -508,4 +565,19 @@ protected void LoadConfiguration()

Assert.IsNotNull(TestEnvironment, "The test environment configuration must not be null.");
}

/// <summary>
/// Gets the major version of the Keycloak server from the test environment configuration.
/// </summary>
/// <returns>The major version number, or 0 if the version is not set.</returns>
protected int GetKcMajorVersion()
{
if ( string.IsNullOrWhiteSpace(TestEnvironment?.KcVersion) )
{
return 0;
}

var parts = TestEnvironment.KcVersion.Split('.');
return int.TryParse(parts[0], out var major) ? major : 0;
}
}
5 changes: 3 additions & 2 deletions NETCore.Keycloak.Client.Tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ CURRENT_MK := $(lastword $(MAKEFILE_LIST))

# Configuration for the virtual environment
# Virtual environment directory name
VIRTUAL_ENV_DIR := "keycloak.venv"
VIRTUAL_ENV_DIR := keycloak.venv
export VIRTUAL_ENV_DIR

# Directory context for the project
CONF_DIR_CONTEXT := "."
Expand Down Expand Up @@ -81,7 +82,7 @@ prepare_keycloak_25_environment: check_virtual_env stop
@pushd ${CONF_DIR_CONTEXT}/ && source ${VIRTUAL_ENV_DIR}/bin/activate && ansible-playbook ansible/provision_keycloak.yml -e "stack_state=present" -e "kc_version=25.0.6"

prepare_keycloak_26_environment: check_virtual_env stop
@pushd ${CONF_DIR_CONTEXT}/ && source ${VIRTUAL_ENV_DIR}/bin/activate && ansible-playbook ansible/provision_keycloak.yml -e "stack_state=present" -e "kc_version=26.0.8"
@pushd ${CONF_DIR_CONTEXT}/ && source ${VIRTUAL_ENV_DIR}/bin/activate && ansible-playbook ansible/provision_keycloak.yml -e "stack_state=present" -e "kc_version=26.5.6"

# -----------------------------------
# Target: stop
Expand Down
50 changes: 50 additions & 0 deletions NETCore.Keycloak.Client.Tests/MockData/KcOrganizationMocks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Bogus;
using NETCore.Keycloak.Client.Models.Organizations;

namespace NETCore.Keycloak.Client.Tests.MockData;

/// <summary>
/// Provides mock data generation for Keycloak organization objects used in testing scenarios.
/// This class leverages the Faker library to generate realistic data for testing Keycloak organization configurations.
/// </summary>
public static class KcOrganizationMocks
{
/// <summary>
/// Generates a mock <see cref="KcOrganization"/> instance with randomized test data.
/// </summary>
/// <param name="faker">The <see cref="Faker"/> instance used to generate randomized data.</param>
/// <returns>A new <see cref="KcOrganization"/> instance populated with test data.</returns>
/// <exception cref="AssertFailedException">
/// Thrown if the provided <see cref="Faker"/> instance is null.
/// </exception>
public static KcOrganization Generate(Faker faker)
{
// Ensure the Faker instance is not null before generating data.
Assert.IsNotNull(faker);

// Generate a unique name for the organization.
var orgName = Guid.NewGuid().ToString().Replace("-", string.Empty, StringComparison.Ordinal);

// Create an organization with randomized details.
return new KcOrganization
{
Name = orgName,
Alias = orgName,
Enabled = true,
Description = faker.Lorem.Sentence(),
RedirectUrl = faker.Internet.Url(),
Attributes = new Dictionary<string, List<string>>
{
{ "test_attr", ["value1"] }
},
Domains =
[
new KcOrganizationDomain
{
Name = $"{orgName}.{faker.Internet.DomainSuffix()}",
Verified = false
}
]
};
}
}
6 changes: 6 additions & 0 deletions NETCore.Keycloak.Client.Tests/Models/KcTestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ namespace NETCore.Keycloak.Client.Tests.Models;
/// </summary>
public class KcTestEnvironment
{
/// <summary>
/// Gets or sets the Keycloak server version.
/// </summary>
[JsonProperty("kc_version")]
public string KcVersion { get; set; }

/// <summary>
/// Gets or sets the base URL of the Keycloak server.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void ShouldRegisterClaimsTransformation()
Assert.IsNotNull(claimsTransformer, "IClaimsTransformation should be registered.");

// Verify that the registered IClaimsTransformation is of type KcRolesClaimsTransformer.
Assert.IsInstanceOfType(claimsTransformer, typeof(KcRolesClaimsTransformer),
Assert.IsInstanceOfType<KcRolesClaimsTransformer>(claimsTransformer,
"Claims transformer should be of type KcRolesClaimsTransformer.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void ShouldRegisterAuthorizationHandler()

// Verify that the authorization handler is registered and of the expected type.
Assert.IsNotNull(authorizationHandler, "IAuthorizationHandler should be registered.");
Assert.IsInstanceOfType(authorizationHandler, typeof(KcBearerAuthorizationHandler));
Assert.IsInstanceOfType<KcBearerAuthorizationHandler>(authorizationHandler);

// Verify that the HTTP context accessor is registered.
Assert.IsNotNull(httpContextAccessor, "IHttpContextAccessor should be registered.");
Expand Down Expand Up @@ -151,6 +151,6 @@ public void ShouldRegisterKeycloakProtectedResourcesPoliciesServices()
Assert.IsNotNull(policyProvider, "IAuthorizationPolicyProvider should be registered.");

// Verify that the IAuthorizationPolicyProvider is of the expected type.
Assert.IsInstanceOfType(policyProvider, typeof(KcProtectedResourcePolicyProvider));
Assert.IsInstanceOfType<KcProtectedResourcePolicyProvider>(policyProvider);
}
}
Loading
Loading