Skip to content

Commit 8534f77

Browse files
authored
Merge pull request #21 from ghaith100994/feature/add_net9_net10_support
Feature/add net9 net10 support
2 parents 57d1553 + 73c1274 commit 8534f77

File tree

12 files changed

+543
-38
lines changed

12 files changed

+543
-38
lines changed

.fossa.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 1
2+
3+
analysis:
4+
# Paths to exclude from license/security analysis (relative to repo root)
5+
ignorePaths:
6+
- "NETCore.Keycloak.Client.Tests/**"
7+
8+
# Specific package locators to ignore (use exact locator shown by FOSSA)
9+
ignorePackages:
10+
- "pip+ansible$13.2.0"
11+
- "pip+ansible-core$2.17.7"
12+
- "nuget+Microsoft.NET.Test.Sdk$17.6.0"
13+
14+
# Notes:
15+
# - Commit and push this file and then trigger a new FOSSA scan so the server-side
16+
# analysis picks up the repo-level exclusions. Local changes alone won't modify
17+
# the existing project findings until FOSSA re-analyzes the repository.
18+
# - If your organization manages FOSSA centrally, you may need to update project
19+
# settings in the FOSSA UI instead of relying on this file.

NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
5-
<LangVersion>latest</LangVersion>
4+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
5+
<LangVersion>latest</LangVersion>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>disable</Nullable>
88
<IsPackable>false</IsPackable>
@@ -15,23 +15,22 @@
1515
</PropertyGroup>
1616

1717
<ItemGroup>
18-
<PackageReference Include="AutoFixture" Version="4.18.1" />
19-
<PackageReference Include="Bogus" Version="35.6.1" />
20-
<PackageReference Include="Cake" Version="1.3.0"/>
21-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
22-
<PackageReference Include="Moq" Version="4.20.72" />
23-
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4"/>
24-
<PackageReference Include="MSTest.TestFramework" Version="3.0.4"/>
25-
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
26-
<PackageReference Include="PasswordGenerator" Version="2.1.0" />
18+
<PackageReference Include="AutoFixture" Version="4.18.1" PrivateAssets="all" />
19+
<PackageReference Include="Bogus" Version="35.6.5" PrivateAssets="all" />
20+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" PrivateAssets="all" />
21+
<PackageReference Include="Moq" Version="4.20.72" PrivateAssets="all" />
22+
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4" PrivateAssets="all" />
23+
<PackageReference Include="MSTest.TestFramework" Version="3.0.4" PrivateAssets="all" />
24+
<PackageReference Include="coverlet.collector" Version="6.0.4" PrivateAssets="all" />
25+
<PackageReference Include="PasswordGenerator" Version="2.1.0" PrivateAssets="all" />
2726
</ItemGroup>
2827

2928
<ItemGroup>
30-
<ProjectReference Include="..\NETCore.Keycloak.Client\NETCore.Keycloak.Client.csproj"/>
29+
<ProjectReference Include="..\NETCore.Keycloak.Client\NETCore.Keycloak.Client.csproj" />
3130
</ItemGroup>
3231

3332
<ItemGroup>
34-
<Compile Remove="**/*.venv/**/*.cs"/>
33+
<Compile Remove="**/*.venv/**/*.cs" />
3534
</ItemGroup>
3635

3736
<ItemGroup>
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
ansible==8.7.0
2-
ansible-core==2.15.13
1+
ansible>=13.2.0
2+
ansible-core>=2.17.7
33
certifi==2024.12.14
44
cffi==1.17.1
55
charset-normalizer==3.4.1
6-
cryptography==44.0.0
6+
cryptography==44.0.1
77
deprecation==2.1.0
88
ecdsa==0.19.0
99
idna==3.10
1010
importlib-resources==5.0.7
1111
Jinja2==3.1.5
1212
MarkupSafe==3.0.2
1313
packaging==24.2
14-
pyasn1==0.6.1
14+
pyasn1==0.6.2
1515
pycparser==2.22
16-
python-jose==3.3.0
17-
python-keycloak==3.3.0
16+
python-jose==3.4.0
17+
python-keycloak==7.0.2
1818
PyYAML==6.0.2
19-
requests==2.32.3
19+
requests==2.32.4
2020
requests-toolbelt==1.0.0
2121
resolvelib==1.0.1
2222
rsa==4.9
2323
six==1.17.0
24-
urllib3==2.3.0
24+
urllib3==2.6.3
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using NETCore.Keycloak.Client.Exceptions;
2+
using NETCore.Keycloak.Client.Models;
3+
using NETCore.Keycloak.Client.Models.Organizations;
4+
5+
namespace NETCore.Keycloak.Client.HttpClients.Abstraction;
6+
7+
/// <summary>
8+
/// Keycloak organizations REST client.
9+
/// <see href="https://www.keycloak.org/docs-api/latest/rest-api/index.html#_organizations"/>
10+
/// </summary>
11+
public interface IKcOrganizations
12+
{
13+
/// <summary>
14+
/// Creates a new organization in a specified Keycloak realm.
15+
///
16+
/// POST /{realm}/organizations
17+
/// </summary>
18+
/// <param name="realm">The Keycloak realm where the organization will be created.</param>
19+
/// <param name="accessToken">The access token used for authentication.</param>
20+
/// <param name="organization">The organization representation to create.</param>
21+
/// <param name="cancellationToken">Optional cancellation token.</param>
22+
/// <returns>
23+
/// A <see cref="KcResponse{T}"/> indicating the result of the operation.
24+
/// </returns>
25+
/// <exception cref="KcException">Thrown if any required parameter is null, empty, or invalid.</exception>
26+
Task<KcResponse<object>> CreateAsync(
27+
string realm,
28+
string accessToken,
29+
KcOrganization organization,
30+
CancellationToken cancellationToken = default);
31+
32+
/// <summary>
33+
/// Updates an existing organization in a specified Keycloak realm.
34+
///
35+
/// PUT /{realm}/organizations/{organizationId}
36+
/// </summary>
37+
/// <param name="realm">The Keycloak realm where the organization exists.</param>
38+
/// <param name="accessToken">The access token used for authentication.</param>
39+
/// <param name="organizationId">The ID of the organization to update.</param>
40+
/// <param name="organization">The updated organization representation.</param>
41+
/// <param name="cancellationToken">Optional cancellation token.</param>
42+
/// <returns>
43+
/// A <see cref="KcResponse{T}"/> indicating the result of the operation.
44+
/// </returns>
45+
/// <exception cref="KcException">Thrown if any required parameter is null, empty, or invalid.</exception>
46+
Task<KcResponse<object>> UpdateAsync(
47+
string realm,
48+
string accessToken,
49+
string organizationId,
50+
KcOrganization organization,
51+
CancellationToken cancellationToken = default);
52+
53+
/// <summary>
54+
/// Deletes an organization from a specified Keycloak realm.
55+
///
56+
/// DELETE /{realm}/organizations/{organizationId}
57+
/// </summary>
58+
/// <param name="realm">The Keycloak realm where the organization exists.</param>
59+
/// <param name="accessToken">The access token used for authentication.</param>
60+
/// <param name="organizationId">The ID of the organization to delete.</param>
61+
/// <param name="cancellationToken">Optional cancellation token.</param>
62+
/// <returns>
63+
/// A <see cref="KcResponse{T}"/> indicating the result of the operation.
64+
/// </returns>
65+
/// <exception cref="KcException">Thrown if any required parameter is null, empty, or invalid.</exception>
66+
Task<KcResponse<object>> DeleteAsync(
67+
string realm,
68+
string accessToken,
69+
string organizationId,
70+
CancellationToken cancellationToken = default);
71+
72+
/// <summary>
73+
/// Retrieves a specific organization by its ID from a specified Keycloak realm.
74+
///
75+
/// GET /{realm}/organizations/{organizationId}
76+
/// </summary>
77+
/// <param name="realm">The Keycloak realm to query.</param>
78+
/// <param name="accessToken">The access token used for authentication.</param>
79+
/// <param name="organizationId">The ID of the organization to retrieve.</param>
80+
/// <param name="cancellationToken">Optional cancellation token.</param>
81+
/// <returns>
82+
/// A <see cref="KcResponse{T}"/> containing the <see cref="KcOrganization"/> details.
83+
/// </returns>
84+
/// <exception cref="KcException">Thrown if any required parameter is null, empty, or invalid.</exception>
85+
Task<KcResponse<KcOrganization>> GetAsync(
86+
string realm,
87+
string accessToken,
88+
string organizationId,
89+
CancellationToken cancellationToken = default);
90+
91+
/// <summary>
92+
/// Retrieves a list of organizations from a specified Keycloak realm, optionally filtered by criteria.
93+
///
94+
/// GET /{realm}/organizations
95+
/// </summary>
96+
/// <param name="realm">The Keycloak realm from which organizations will be listed.</param>
97+
/// <param name="accessToken">The access token used for authentication.</param>
98+
/// <param name="filter">Optional filter criteria.</param>
99+
/// <param name="cancellationToken">Optional cancellation token.</param>
100+
/// <returns>
101+
/// A <see cref="KcResponse{T}"/> containing an enumerable of <see cref="KcOrganization"/> objects.
102+
/// </returns>
103+
/// <exception cref="KcException">Thrown if any required parameter is null, empty, or invalid.</exception>
104+
Task<KcResponse<IEnumerable<KcOrganization>>> ListAsync(
105+
string realm,
106+
string accessToken,
107+
KcOrganizationFilter filter = null,
108+
CancellationToken cancellationToken = default);
109+
110+
/// <summary>
111+
/// Retrieves the count of organizations in a specified Keycloak realm, optionally filtered.
112+
///
113+
/// GET /{realm}/organizations/count
114+
/// </summary>
115+
/// <param name="realm">The Keycloak realm to query.</param>
116+
/// <param name="accessToken">The access token used for authentication.</param>
117+
/// <param name="filter">Optional filter criteria.</param>
118+
/// <param name="cancellationToken">Optional cancellation token.</param>
119+
/// <returns>
120+
/// A <see cref="KcResponse{T}"/> with the count of organizations.
121+
/// </returns>
122+
/// <exception cref="KcException">Thrown if any required parameter is null, empty, or invalid.</exception>
123+
Task<KcResponse<long>> CountAsync(
124+
string realm,
125+
string accessToken,
126+
KcOrganizationFilter filter = null,
127+
CancellationToken cancellationToken = default);
128+
}

NETCore.Keycloak.Client/HttpClients/Abstraction/IKeycloakClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,9 @@ public interface IKeycloakClient
7676
/// See <see cref="IKcScopeMappings"/> for detailed operations.
7777
/// </summary>
7878
public IKcScopeMappings ScopeMappings { get; }
79+
80+
/// <summary>
81+
/// Gets the organizations REST client for managing organizations.
82+
/// </summary>
83+
public IKcOrganizations Organizations { get; }
7984
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
using Microsoft.Extensions.Logging;
2+
using NETCore.Keycloak.Client.HttpClients.Abstraction;
3+
using NETCore.Keycloak.Client.Models;
4+
using NETCore.Keycloak.Client.Models.Organizations;
5+
6+
namespace NETCore.Keycloak.Client.HttpClients.Implementation;
7+
8+
/// <inheritdoc cref="IKcOrganizations"/>
9+
internal sealed class KcOrganizations(string baseUrl,
10+
ILogger logger) : KcHttpClientBase(logger, baseUrl), IKcOrganizations
11+
{
12+
// Primary constructor on the class declaration is used; no explicit ctor body required.
13+
14+
/// <inheritdoc cref="IKcOrganizations.CreateAsync"/>
15+
public Task<KcResponse<object>> CreateAsync(
16+
string realm,
17+
string accessToken,
18+
KcOrganization organization,
19+
CancellationToken cancellationToken = default)
20+
{
21+
ValidateAccess(realm, accessToken);
22+
ValidateNotNull(nameof(organization), organization);
23+
24+
var url = $"{BaseUrl}/{realm}/organizations";
25+
return ProcessRequestAsync<object>(
26+
url,
27+
HttpMethod.Post,
28+
accessToken,
29+
"Unable to create organization",
30+
organization,
31+
"application/json",
32+
cancellationToken);
33+
}
34+
35+
/// <inheritdoc cref="IKcOrganizations.UpdateAsync"/>
36+
public Task<KcResponse<object>> UpdateAsync(
37+
string realm,
38+
string accessToken,
39+
string organizationId,
40+
KcOrganization organization,
41+
CancellationToken cancellationToken = default)
42+
{
43+
ValidateAccess(realm, accessToken);
44+
ValidateRequiredString(nameof(organizationId), organizationId);
45+
ValidateNotNull(nameof(organization), organization);
46+
47+
var url = $"{BaseUrl}/{realm}/organizations/{organizationId}";
48+
return ProcessRequestAsync<object>(
49+
url,
50+
HttpMethod.Put,
51+
accessToken,
52+
"Unable to update organization",
53+
organization,
54+
"application/json",
55+
cancellationToken);
56+
}
57+
58+
/// <inheritdoc cref="IKcOrganizations.DeleteAsync"/>
59+
public Task<KcResponse<object>> DeleteAsync(
60+
string realm,
61+
string accessToken,
62+
string organizationId,
63+
CancellationToken cancellationToken = default)
64+
{
65+
ValidateAccess(realm, accessToken);
66+
ValidateRequiredString(nameof(organizationId), organizationId);
67+
68+
var url = $"{BaseUrl}/{realm}/organizations/{organizationId}";
69+
return ProcessRequestAsync<object>(
70+
url,
71+
HttpMethod.Delete,
72+
accessToken,
73+
"Unable to delete organization",
74+
null,
75+
"application/json",
76+
cancellationToken);
77+
}
78+
79+
/// <inheritdoc cref="IKcOrganizations.GetAsync"/>
80+
public Task<KcResponse<KcOrganization>> GetAsync(
81+
string realm,
82+
string accessToken,
83+
string organizationId,
84+
CancellationToken cancellationToken = default)
85+
{
86+
ValidateAccess(realm, accessToken);
87+
ValidateRequiredString(nameof(organizationId), organizationId);
88+
89+
var url = $"{BaseUrl}/{realm}/organizations/{organizationId}";
90+
return ProcessRequestAsync<KcOrganization>(
91+
url,
92+
HttpMethod.Get,
93+
accessToken,
94+
"Unable to get organization",
95+
null,
96+
"application/json",
97+
cancellationToken);
98+
}
99+
100+
/// <inheritdoc cref="IKcOrganizations.ListAsync"/>
101+
public Task<KcResponse<IEnumerable<KcOrganization>>> ListAsync(
102+
string realm,
103+
string accessToken,
104+
KcOrganizationFilter filter = null,
105+
CancellationToken cancellationToken = default)
106+
{
107+
ValidateAccess(realm, accessToken);
108+
filter ??= new KcOrganizationFilter();
109+
110+
var url = $"{BaseUrl}/{realm}/organizations{filter.BuildQuery()}";
111+
return ProcessRequestAsync<IEnumerable<KcOrganization>>(
112+
url,
113+
HttpMethod.Get,
114+
accessToken,
115+
"Unable to list organizations",
116+
null,
117+
"application/json",
118+
cancellationToken);
119+
}
120+
121+
/// <inheritdoc cref="IKcOrganizations.CountAsync"/>
122+
public Task<KcResponse<long>> CountAsync(
123+
string realm,
124+
string accessToken,
125+
KcOrganizationFilter filter = null,
126+
CancellationToken cancellationToken = default)
127+
{
128+
ValidateAccess(realm, accessToken);
129+
filter ??= new KcOrganizationFilter();
130+
131+
var url = $"{BaseUrl}/{realm}/organizations/count{filter.BuildQuery()}";
132+
return ProcessRequestAsync<long>(
133+
url,
134+
HttpMethod.Get,
135+
accessToken,
136+
"Unable to count organizations",
137+
null,
138+
"application/json",
139+
cancellationToken);
140+
}
141+
}

0 commit comments

Comments
 (0)