From bd4dc32f51ffd87c0a04c5ce076d79a6a2b99d74 Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Mon, 12 Jan 2026 14:09:47 +0300 Subject: [PATCH 01/13] Add support for managing organizations in Keycloak Introduced a new `IKcOrganizations` interface and its implementation in `KcOrganizations` to enable CRUD operations, listing, and filtering of organizations in Keycloak. Updated the `IKeycloakClient` and `KeycloakClient` classes to expose the `Organizations` client. Added supporting models: - `KcOrganization` to represent organization resources. - `KcOrganizationDomain` to represent domain information. - `KcOrganizationFilter` to enable filtering options for queries. Updated `NETCore.Keycloak.Client.csproj` to clean up formatting and remove unnecessary metadata files. --- .../Abstraction/IKcOrganizations.cs | 108 ++++++++++++++ .../Abstraction/IKeycloakClient.cs | 5 + .../Implementation/KcOrganizations.cs | 136 ++++++++++++++++++ .../Implementation/KeycloakClient.cs | 4 + .../Models/Organizations/KcOrganization.cs | 59 ++++++++ .../Organizations/KcOrganizationDomain.cs | 21 +++ .../Organizations/KcOrganizationFilter.cs | 114 +++++++++++++++ .../NETCore.Keycloak.Client.csproj | 23 ++- 8 files changed, 458 insertions(+), 12 deletions(-) create mode 100644 NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs create mode 100644 NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs create mode 100644 NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs create mode 100644 NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs create mode 100644 NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs diff --git a/NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs b/NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs new file mode 100644 index 0000000..2911a4d --- /dev/null +++ b/NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs @@ -0,0 +1,108 @@ +using NETCore.Keycloak.Client.Models; +using NETCore.Keycloak.Client.Models.Organizations; + +namespace NETCore.Keycloak.Client.HttpClients.Abstraction; + +/// +/// Keycloak organizations REST client +/// +public interface IKcOrganizations +{ + /// + /// Creates a new organization in a specified Keycloak realm. + /// + /// POST /{realm}/organizations + /// + /// The Keycloak realm where the organization will be created. + /// The access token used for authentication. + /// The organization representation to create. + /// Optional cancellation token. + /// A indicating the result. + Task> CreateAsync( + string realm, + string accessToken, + KcOrganization organization, + CancellationToken cancellationToken = default); + + /// + /// Updates an existing organization in a specified Keycloak realm. + /// + /// PUT /{realm}/organizations/{organizationId} + /// + /// The Keycloak realm where the organization exists. + /// The access token used for authentication. + /// The ID of the organization to update. + /// The updated organization representation. + /// Optional cancellation token. + /// A indicating the result. + Task> UpdateAsync( + string realm, + string accessToken, + string organizationId, + KcOrganization organization, + CancellationToken cancellationToken = default); + + /// + /// Deletes an organization from a specified Keycloak realm. + /// + /// DELETE /{realm}/organizations/{organizationId} + /// + /// The Keycloak realm where the organization exists. + /// The access token used for authentication. + /// The ID of the organization to delete. + /// Optional cancellation token. + /// A indicating the result. + Task> DeleteAsync( + string realm, + string accessToken, + string organizationId, + CancellationToken cancellationToken = default); + + /// + /// Retrieves a specific organization by its ID from a specified Keycloak realm. + /// + /// GET /{realm}/organizations/{organizationId} + /// + /// The Keycloak realm to query. + /// The access token used for authentication. + /// The ID of the organization to retrieve. + /// Optional cancellation token. + /// A with the organization details. + Task> GetAsync( + string realm, + string accessToken, + string organizationId, + CancellationToken cancellationToken = default); + + /// + /// Retrieves a list of organizations from a specified Keycloak realm, optionally filtered by criteria. + /// + /// GET /{realm}/organizations + /// + /// The Keycloak realm from which organizations will be listed. + /// The access token used for authentication. + /// Optional filter criteria. + /// Optional cancellation token. + /// A containing an enumerable of organizations. + Task>> ListAsync( + string realm, + string accessToken, + KcOrganizationFilter filter = null, + CancellationToken cancellationToken = default); + + /// + /// Retrieves the count of organizations in a specified Keycloak realm, optionally filtered. + /// + /// GET /{realm}/organizations/count + /// + /// The Keycloak realm to query. + /// The access token used for authentication. + /// Optional filter criteria. + /// Optional cancellation token. + /// A with the count of organizations. + Task> CountAsync( + string realm, + string accessToken, + KcOrganizationFilter filter = null, + CancellationToken cancellationToken = default); +} diff --git a/NETCore.Keycloak.Client/HttpClients/Abstraction/IKeycloakClient.cs b/NETCore.Keycloak.Client/HttpClients/Abstraction/IKeycloakClient.cs index 70409ea..d5edde5 100644 --- a/NETCore.Keycloak.Client/HttpClients/Abstraction/IKeycloakClient.cs +++ b/NETCore.Keycloak.Client/HttpClients/Abstraction/IKeycloakClient.cs @@ -76,4 +76,9 @@ public interface IKeycloakClient /// See for detailed operations. /// public IKcScopeMappings ScopeMappings { get; } + + /// + /// Gets the organizations REST client for managing organizations. + /// + public IKcOrganizations Organizations { get; } } diff --git a/NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs b/NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs new file mode 100644 index 0000000..036ed76 --- /dev/null +++ b/NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs @@ -0,0 +1,136 @@ +using Microsoft.Extensions.Logging; +using NETCore.Keycloak.Client.HttpClients.Abstraction; +using NETCore.Keycloak.Client.Models; +using NETCore.Keycloak.Client.Models.Organizations; + +namespace NETCore.Keycloak.Client.HttpClients.Implementation; + +/// +/// Organization service API for managing organizations in Keycloak. +/// +internal sealed class KcOrganizations(string baseUrl, ILogger logger) : KcHttpClientBase(logger, baseUrl), IKcOrganizations +{ + // Primary constructor on the class declaration is used; no explicit ctor body required. + + public Task> CreateAsync( + string realm, + string accessToken, + KcOrganization organization, + CancellationToken cancellationToken = default) + { + ValidateAccess(realm, accessToken); + ValidateNotNull(nameof(organization), organization); + + var url = $"{BaseUrl}/{realm}/organizations"; + return ProcessRequestAsync( + url, + HttpMethod.Post, + accessToken, + "Unable to create organization", + organization, + "application/json", + cancellationToken); + } + + public Task> UpdateAsync( + string realm, + string accessToken, + string organizationId, + KcOrganization organization, + CancellationToken cancellationToken = default) + { + ValidateAccess(realm, accessToken); + ValidateRequiredString(nameof(organizationId), organizationId); + ValidateNotNull(nameof(organization), organization); + + var url = $"{BaseUrl}/{realm}/organizations/{organizationId}"; + return ProcessRequestAsync( + url, + HttpMethod.Put, + accessToken, + "Unable to update organization", + organization, + "application/json", + cancellationToken); + } + + public Task> DeleteAsync( + string realm, + string accessToken, + string organizationId, + CancellationToken cancellationToken = default) + { + ValidateAccess(realm, accessToken); + ValidateRequiredString(nameof(organizationId), organizationId); + + var url = $"{BaseUrl}/{realm}/organizations/{organizationId}"; + return ProcessRequestAsync( + url, + HttpMethod.Delete, + accessToken, + "Unable to delete organization", + null, + "application/json", + cancellationToken); + } + + public Task> GetAsync( + string realm, + string accessToken, + string organizationId, + CancellationToken cancellationToken = default) + { + ValidateAccess(realm, accessToken); + ValidateRequiredString(nameof(organizationId), organizationId); + + var url = $"{BaseUrl}/{realm}/organizations/{organizationId}"; + return ProcessRequestAsync( + url, + HttpMethod.Get, + accessToken, + "Unable to get organization", + null, + "application/json", + cancellationToken); + } + + public Task>> ListAsync( + string realm, + string accessToken, + KcOrganizationFilter filter = null, + CancellationToken cancellationToken = default) + { + ValidateAccess(realm, accessToken); + filter ??= new KcOrganizationFilter(); + + var url = $"{BaseUrl}/{realm}/organizations{filter.BuildQuery()}"; + return ProcessRequestAsync>( + url, + HttpMethod.Get, + accessToken, + "Unable to list organizations", + null, + "application/json", + cancellationToken); + } + + public Task> CountAsync( + string realm, + string accessToken, + KcOrganizationFilter filter = null, + CancellationToken cancellationToken = default) + { + ValidateAccess(realm, accessToken); + filter ??= new KcOrganizationFilter(); + + var url = $"{BaseUrl}/{realm}/organizations/count{filter.BuildQuery()}"; + return ProcessRequestAsync( + url, + HttpMethod.Get, + accessToken, + "Unable to count organizations", + null, + "application/json", + cancellationToken); + } +} diff --git a/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs b/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs index a62d077..9ced037 100644 --- a/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs +++ b/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs @@ -44,6 +44,9 @@ public sealed class KeycloakClient : IKeycloakClient /// public IKcScopeMappings ScopeMappings { get; } + /// + public IKcOrganizations Organizations { get; } + /// /// Initializes a new instance of the class. /// Provides access to various Keycloak API services through respective clients. @@ -86,5 +89,6 @@ public KeycloakClient(string baseUrl, ILogger logger = null) ProtocolMappers = new KcProtocolMappers(adminUrl, logger); ScopeMappings = new KcScopeMappings(adminUrl, logger); RoleMappings = new KcRoleMappings(adminUrl, logger); + Organizations = new KcOrganizations(adminUrl, logger); } } diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs new file mode 100644 index 0000000..7704067 --- /dev/null +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs @@ -0,0 +1,59 @@ +using System.Text.Json.Serialization; + +namespace NETCore.Keycloak.Client.Models.Organizations; + +/// +/// Represents an organization resource. +/// +public sealed class KcOrganization +{ + /// + /// Gets or sets the organization id. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// Gets or sets the organization name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + /// + /// Gets or sets the organization alias. + /// + [JsonPropertyName("alias")] + public string Alias { get; set; } + + /// + /// Gets or sets a value indicating whether the organization is enabled. + /// + [JsonPropertyName("enabled")] + public bool? Enabled { get; set; } + + /// + /// Gets or sets the organization description. + /// + [JsonPropertyName("description")] + public string Description { get; set; } + + /// + /// Gets or sets the redirect URL for the organization. + /// + [JsonPropertyName("redirectUrl")] + public string RedirectUrl { get; set; } + + /// + /// Custom attributes. + /// Key = attribute name + /// Value = list of values (Keycloak style) + /// + [JsonPropertyName("attributes")] + public Dictionary> Attributes { get; set; } + + /// + /// Gets or sets the organization domains. + /// + [JsonPropertyName("domains")] + public List Domains { get; set; } = new(); +} diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs new file mode 100644 index 0000000..7a0b79e --- /dev/null +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace NETCore.Keycloak.Client.Models.Organizations; + +/// +/// Represents organization domain information. +/// +public sealed class KcOrganizationDomain +{ + /// + /// Gets or sets the domain name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + /// + /// Gets or sets a value indicating whether the domain is verified. + /// + [JsonPropertyName("verified")] + public bool? Verified { get; set; } +} diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs new file mode 100644 index 0000000..693294d --- /dev/null +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs @@ -0,0 +1,114 @@ +using System.Globalization; +using System.Text; +using System.Text.Json.Serialization; +using NETCore.Keycloak.Client.Models.Common; + +namespace NETCore.Keycloak.Client.Models.Organizations; + +/// +/// Filter model for organizations queries. +/// +public sealed class KcOrganizationFilter : KcFilter +{ + /// + /// Filter by organization name (partial or exact if Exact = true) + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + /// + /// Filter by organization alias + /// + [JsonPropertyName("alias")] + public string Alias { get; set; } + + /// + /// Filter by enabled state + /// + [JsonPropertyName("enabled")] + public bool? Enabled { get; set; } + + /// + /// Filter by domain name + /// + [JsonPropertyName("domain")] + public string Domain { get; set; } + + /// + /// Filter by verified domain + /// + [JsonPropertyName("domainVerified")] + public bool? DomainVerified { get; set; } + + /// + /// Require exact matching for name / alias + /// + [JsonPropertyName("exact")] + public bool? Exact { get; set; } + + /// + /// Free text search (Keycloak standard search param) + /// + [JsonPropertyName("search")] + public string Search { get; set; } + + /// + /// Build query string + /// + public new string BuildQuery() + { + var sb = new StringBuilder($"?max={Max}"); + + if ( BriefRepresentation.HasValue ) + { + _ = sb.Append("&briefRepresentation=") + .Append(BriefRepresentation.Value.ToString().ToLower(CultureInfo.CurrentCulture)); + } + + if ( !string.IsNullOrWhiteSpace(Name) ) + { + _ = sb.Append("&name=").Append(Name); + } + + if ( !string.IsNullOrWhiteSpace(Alias) ) + { + _ = sb.Append("&alias=").Append(Alias); + } + + if ( Enabled.HasValue ) + { + _ = sb.Append("&enabled=") + .Append(Enabled.Value.ToString().ToLower(CultureInfo.CurrentCulture)); + } + + if ( !string.IsNullOrWhiteSpace(Domain) ) + { + _ = sb.Append("&domain=").Append(Domain); + } + + if ( DomainVerified.HasValue ) + { + _ = sb.Append("&domainVerified=") + .Append(DomainVerified.Value.ToString().ToLower(CultureInfo.CurrentCulture)); + } + + if ( Exact.HasValue ) + { + _ = sb.Append("&exact=") + .Append(Exact.Value.ToString().ToLower(CultureInfo.CurrentCulture)); + } + + if ( First.HasValue ) + { + _ = sb.Append("&first=") + .Append(First.Value.ToString(CultureInfo.CurrentCulture)); + } + + if ( !string.IsNullOrWhiteSpace(Search) ) + { + _ = sb.Append("&search=").Append(Search); + } + + return sb.ToString(); + } +} diff --git a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj index 2cbe2e8..bdd5ec3 100644 --- a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj +++ b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj @@ -28,17 +28,16 @@ - - - - - - - - - - - - + + + + + + + + + + + From dfe0064ca131bbe518c3a549eba7aa475cd6f6d1 Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 13 Jan 2026 09:57:27 +0300 Subject: [PATCH 02/13] Refactor Keycloak models and enhance documentation Updated XML documentation for `KcOrganization`, `KcOrganizationDomain`, and `KcOrganizationFilter` to improve clarity and align with Keycloak API references. Added `` tags for property descriptions. Refactored `KcOrganizationFilter`: - Replaced multiple filtering properties with a generic `Q` property. - Updated `Exact` property to indicate exact match behavior. - Switched from `System.Text.Json` to `Newtonsoft.Json` for serialization. - Rewrote `BuildQuery` for cleaner and more maintainable query string construction. Improved consistency in naming conventions and descriptions across classes. Removed redundant properties and comments. Aligned code structure with Keycloak API standards. --- .../Models/Organizations/KcOrganization.cs | 32 ++++- .../Organizations/KcOrganizationDomain.cs | 10 +- .../Organizations/KcOrganizationFilter.cs | 111 ++++++------------ 3 files changed, 74 insertions(+), 79 deletions(-) diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs index 7704067..08f3e37 100644 --- a/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs @@ -3,57 +3,81 @@ namespace NETCore.Keycloak.Client.Models.Organizations; /// -/// Represents an organization resource. +/// Represents an organization resource in Keycloak. +/// /// public sealed class KcOrganization { /// /// Gets or sets the organization id. /// + /// + /// A string representing the unique identifier of the organization. + /// [JsonPropertyName("id")] public string Id { get; set; } /// /// Gets or sets the organization name. /// + /// + /// A string representing the display name of the organization. + /// [JsonPropertyName("name")] public string Name { get; set; } /// /// Gets or sets the organization alias. /// + /// + /// A string representing an alternate identifier or alias for the organization. + /// [JsonPropertyName("alias")] public string Alias { get; set; } /// /// Gets or sets a value indicating whether the organization is enabled. /// + /// + /// A nullable boolean that is true when the organization is enabled, false when disabled, + /// or null if the enabled state is not set. + /// [JsonPropertyName("enabled")] public bool? Enabled { get; set; } /// /// Gets or sets the organization description. /// + /// + /// A string containing a human-readable description for the organization. + /// [JsonPropertyName("description")] public string Description { get; set; } /// /// Gets or sets the redirect URL for the organization. /// + /// + /// A string representing the redirect URL associated with the organization (for example, after login or registration flows). + /// [JsonPropertyName("redirectUrl")] public string RedirectUrl { get; set; } /// - /// Custom attributes. - /// Key = attribute name - /// Value = list of values (Keycloak style) + /// Custom attributes associated with the organization. /// + /// + /// A dictionary where the key is the attribute name and the value is a list of values for that attribute (Keycloak style). + /// [JsonPropertyName("attributes")] public Dictionary> Attributes { get; set; } /// /// Gets or sets the organization domains. /// + /// + /// A collection of representing domains associated with the organization. + /// [JsonPropertyName("domains")] public List Domains { get; set; } = new(); } diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs index 7a0b79e..492541a 100644 --- a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs @@ -3,19 +3,27 @@ namespace NETCore.Keycloak.Client.Models.Organizations; /// -/// Represents organization domain information. +/// Represents an organization domain in Keycloak. +/// /// public sealed class KcOrganizationDomain { /// /// Gets or sets the domain name. /// + /// + /// A string representing the domain name (for example, "example.com"). + /// [JsonPropertyName("name")] public string Name { get; set; } /// /// Gets or sets a value indicating whether the domain is verified. /// + /// + /// A nullable boolean indicating whether the domain has been verified by Keycloak. + /// True if verified; false if not; null if the verification state is unknown. + /// [JsonPropertyName("verified")] public bool? Verified { get; set; } } diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs index 693294d..df9b80d 100644 --- a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs @@ -1,114 +1,77 @@ using System.Globalization; using System.Text; -using System.Text.Json.Serialization; using NETCore.Keycloak.Client.Models.Common; +using Newtonsoft.Json; namespace NETCore.Keycloak.Client.Models.Organizations; /// -/// Filter model for organizations queries. +/// Represents a filter for querying Keycloak organizations. /// public sealed class KcOrganizationFilter : KcFilter { /// - /// Filter by organization name (partial or exact if Exact = true) + /// Gets or sets a query string for searching custom attributes, formatted as 'key1:value1 key2:value2'. /// - [JsonPropertyName("name")] - public string Name { get; set; } + /// + /// A string representing the custom attribute search query. + /// + [JsonProperty("q")] + public string Q { get; set; } /// - /// Filter by organization alias + /// Gets or sets a value indicating whether the query parameters must match exactly. /// - [JsonPropertyName("alias")] - public string Alias { get; set; } - - /// - /// Filter by enabled state - /// - [JsonPropertyName("enabled")] - public bool? Enabled { get; set; } - - /// - /// Filter by domain name - /// - [JsonPropertyName("domain")] - public string Domain { get; set; } - - /// - /// Filter by verified domain - /// - [JsonPropertyName("domainVerified")] - public bool? DomainVerified { get; set; } - - /// - /// Require exact matching for name / alias - /// - [JsonPropertyName("exact")] + /// + /// true if the parameters must match exactly; otherwise, false. + /// + [JsonProperty("exact")] public bool? Exact { get; set; } /// - /// Free text search (Keycloak standard search param) - /// - [JsonPropertyName("search")] - public string Search { get; set; } - - /// - /// Build query string + /// Builds the query string based on the filter properties. /// + /// + /// A string containing the query parameters to be appended to a URL. + /// public new string BuildQuery() { - var sb = new StringBuilder($"?max={Max}"); - - if ( BriefRepresentation.HasValue ) - { - _ = sb.Append("&briefRepresentation=") - .Append(BriefRepresentation.Value.ToString().ToLower(CultureInfo.CurrentCulture)); - } - - if ( !string.IsNullOrWhiteSpace(Name) ) - { - _ = sb.Append("&name=").Append(Name); - } - if ( !string.IsNullOrWhiteSpace(Alias) ) - { - _ = sb.Append("&alias=").Append(Alias); - } + var builder = new StringBuilder($"?max={Max}"); - if ( Enabled.HasValue ) + // Include brief representation if specified + if ( BriefRepresentation != null ) { - _ = sb.Append("&enabled=") - .Append(Enabled.Value.ToString().ToLower(CultureInfo.CurrentCulture)); + _ = builder.Append(CultureInfo.CurrentCulture, + $"&briefRepresentation={BriefRepresentation.ToString().ToLower(CultureInfo.CurrentCulture)}"); } - if ( !string.IsNullOrWhiteSpace(Domain) ) + // Include pagination offset if specified + if ( First != null ) { - _ = sb.Append("&domain=").Append(Domain); + _ = builder.Append(CultureInfo.CurrentCulture, + $"&first={string.Create(CultureInfo.CurrentCulture, $"{First}").ToLower(CultureInfo.CurrentCulture)}"); } - if ( DomainVerified.HasValue ) + // Include custom attribute query if specified + if ( !string.IsNullOrWhiteSpace(Q) ) { - _ = sb.Append("&domainVerified=") - .Append(DomainVerified.Value.ToString().ToLower(CultureInfo.CurrentCulture)); + _ = builder.Append(CultureInfo.CurrentCulture, $"&q={Q}"); } - if ( Exact.HasValue ) - { - _ = sb.Append("&exact=") - .Append(Exact.Value.ToString().ToLower(CultureInfo.CurrentCulture)); - } - - if ( First.HasValue ) + // Include general search query if specified + if ( !string.IsNullOrWhiteSpace(Search) ) { - _ = sb.Append("&first=") - .Append(First.Value.ToString(CultureInfo.CurrentCulture)); + _ = builder.Append(CultureInfo.CurrentCulture, $"&search={Search}"); } - if ( !string.IsNullOrWhiteSpace(Search) ) + // Include exact match filter if specified + if ( Exact != null ) { - _ = sb.Append("&search=").Append(Search); + _ = builder.Append(CultureInfo.CurrentCulture, + $"&exact={Exact.ToString().ToLower(CultureInfo.CurrentCulture)}"); } - return sb.ToString(); + return builder.ToString(); } } From 9c190dd364ee6826a8f70fc10efe1f52f639270f Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 13 Jan 2026 10:05:34 +0300 Subject: [PATCH 03/13] Improve Keycloak client documentation and consistency Updated `IKcOrganizations` interface and `KcOrganizations` implementation to enhance XML documentation, align with the latest Keycloak REST API, and improve maintainability. - Added `using` directives for required dependencies in `IKcOrganizations.cs`. - Updated XML documentation for all methods in `IKcOrganizations` to include detailed return type descriptions and exception handling details. - Replaced outdated Keycloak API links in `KcOrganization` and `KcOrganizationDomain` with updated references. - Used `` tags in `KcOrganizations` to ensure consistency with the interface and reduce redundancy. - Implemented methods in `KcOrganizations` with improved documentation and alignment with the updated interface. --- .../Abstraction/IKcOrganizations.cs | 34 +++++++++++++++---- .../Implementation/KcOrganizations.cs | 13 ++++--- .../Models/Organizations/KcOrganization.cs | 2 +- .../Organizations/KcOrganizationDomain.cs | 2 +- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs b/NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs index 2911a4d..99c0a23 100644 --- a/NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs +++ b/NETCore.Keycloak.Client/HttpClients/Abstraction/IKcOrganizations.cs @@ -1,10 +1,12 @@ +using NETCore.Keycloak.Client.Exceptions; using NETCore.Keycloak.Client.Models; using NETCore.Keycloak.Client.Models.Organizations; namespace NETCore.Keycloak.Client.HttpClients.Abstraction; /// -/// Keycloak organizations REST client +/// Keycloak organizations REST client. +/// /// public interface IKcOrganizations { @@ -17,7 +19,10 @@ public interface IKcOrganizations /// The access token used for authentication. /// The organization representation to create. /// Optional cancellation token. - /// A indicating the result. + /// + /// A indicating the result of the operation. + /// + /// Thrown if any required parameter is null, empty, or invalid. Task> CreateAsync( string realm, string accessToken, @@ -34,7 +39,10 @@ Task> CreateAsync( /// The ID of the organization to update. /// The updated organization representation. /// Optional cancellation token. - /// A indicating the result. + /// + /// A indicating the result of the operation. + /// + /// Thrown if any required parameter is null, empty, or invalid. Task> UpdateAsync( string realm, string accessToken, @@ -51,7 +59,10 @@ Task> UpdateAsync( /// The access token used for authentication. /// The ID of the organization to delete. /// Optional cancellation token. - /// A indicating the result. + /// + /// A indicating the result of the operation. + /// + /// Thrown if any required parameter is null, empty, or invalid. Task> DeleteAsync( string realm, string accessToken, @@ -67,7 +78,10 @@ Task> DeleteAsync( /// The access token used for authentication. /// The ID of the organization to retrieve. /// Optional cancellation token. - /// A with the organization details. + /// + /// A containing the details. + /// + /// Thrown if any required parameter is null, empty, or invalid. Task> GetAsync( string realm, string accessToken, @@ -83,7 +97,10 @@ Task> GetAsync( /// The access token used for authentication. /// Optional filter criteria. /// Optional cancellation token. - /// A containing an enumerable of organizations. + /// + /// A containing an enumerable of objects. + /// + /// Thrown if any required parameter is null, empty, or invalid. Task>> ListAsync( string realm, string accessToken, @@ -99,7 +116,10 @@ Task>> ListAsync( /// The access token used for authentication. /// Optional filter criteria. /// Optional cancellation token. - /// A with the count of organizations. + /// + /// A with the count of organizations. + /// + /// Thrown if any required parameter is null, empty, or invalid. Task> CountAsync( string realm, string accessToken, diff --git a/NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs b/NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs index 036ed76..3bc7098 100644 --- a/NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs +++ b/NETCore.Keycloak.Client/HttpClients/Implementation/KcOrganizations.cs @@ -5,13 +5,13 @@ namespace NETCore.Keycloak.Client.HttpClients.Implementation; -/// -/// Organization service API for managing organizations in Keycloak. -/// -internal sealed class KcOrganizations(string baseUrl, ILogger logger) : KcHttpClientBase(logger, baseUrl), IKcOrganizations +/// +internal sealed class KcOrganizations(string baseUrl, + ILogger logger) : KcHttpClientBase(logger, baseUrl), IKcOrganizations { // Primary constructor on the class declaration is used; no explicit ctor body required. + /// public Task> CreateAsync( string realm, string accessToken, @@ -32,6 +32,7 @@ public Task> CreateAsync( cancellationToken); } + /// public Task> UpdateAsync( string realm, string accessToken, @@ -54,6 +55,7 @@ public Task> UpdateAsync( cancellationToken); } + /// public Task> DeleteAsync( string realm, string accessToken, @@ -74,6 +76,7 @@ public Task> DeleteAsync( cancellationToken); } + /// public Task> GetAsync( string realm, string accessToken, @@ -94,6 +97,7 @@ public Task> GetAsync( cancellationToken); } + /// public Task>> ListAsync( string realm, string accessToken, @@ -114,6 +118,7 @@ public Task>> ListAsync( cancellationToken); } + /// public Task> CountAsync( string realm, string accessToken, diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs index 08f3e37..a59f7e8 100644 --- a/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs @@ -4,7 +4,7 @@ namespace NETCore.Keycloak.Client.Models.Organizations; /// /// Represents an organization resource in Keycloak. -/// +/// /// public sealed class KcOrganization { diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs index 492541a..f4424e6 100644 --- a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationDomain.cs @@ -4,7 +4,7 @@ namespace NETCore.Keycloak.Client.Models.Organizations; /// /// Represents an organization domain in Keycloak. -/// +/// /// public sealed class KcOrganizationDomain { From c4cb15f01e866af43a9842ec75b67be713cb794c Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 13 Jan 2026 10:09:40 +0300 Subject: [PATCH 04/13] Refactor BuildQuery in KcOrganizationFilter --- .../Models/Organizations/KcOrganizationFilter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs index df9b80d..81910a1 100644 --- a/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganizationFilter.cs @@ -36,7 +36,6 @@ public sealed class KcOrganizationFilter : KcFilter /// public new string BuildQuery() { - var builder = new StringBuilder($"?max={Max}"); // Include brief representation if specified From c2cbfa4bcbc4b9659f84fc8fee1b8718e9a7d52d Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 13 Jan 2026 11:04:21 +0300 Subject: [PATCH 05/13] Refactor Domains property in KcOrganization class --- NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs index a59f7e8..339dd82 100644 --- a/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs +++ b/NETCore.Keycloak.Client/Models/Organizations/KcOrganization.cs @@ -79,5 +79,5 @@ public sealed class KcOrganization /// A collection of representing domains associated with the organization. /// [JsonPropertyName("domains")] - public List Domains { get; set; } = new(); + public ICollection Domains { get; set; } = []; } From d26f217f3140049a20d4416da1e30ba1822fbbcf Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 13 Jan 2026 11:05:34 +0300 Subject: [PATCH 06/13] Extend target frameworks and update dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated `TargetFrameworks` in both `NETCore.Keycloak.Client.Tests.csproj` and `NETCore.Keycloak.Client.csproj` to include `net9.0` and `net10.0`. Introduced framework-specific dependency management using conditional `ItemGroup` sections to ensure proper versioning for each target framework. Updated several dependencies, including `Newtonsoft.Json` (13.0.3 → 13.0.4) and `System.IdentityModel.Tokens.Jwt` (8.3.0 → 8.15.0). Removed redundant dependencies and adjusted formatting for consistency. Removed the `Cake` package reference from the test project. --- .../NETCore.Keycloak.Client.Tests.csproj | 19 +++---- .../NETCore.Keycloak.Client.csproj | 56 +++++++++++++++---- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj index 756a2dd..1e3bdf5 100644 --- a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj +++ b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj @@ -1,8 +1,8 @@ - + - net6.0;net7.0;net8.0 - latest + net6.0;net7.0;net8.0;net9.0;net10.0 + latest enable disable false @@ -17,21 +17,20 @@ - - + - - - + + + - + - + diff --git a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj index bdd5ec3..f4f37b9 100644 --- a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj +++ b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj @@ -14,7 +14,7 @@ git README.md https://github.com/Black-Cockpit/NETCore.Keycloak - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0;net10.0 latest enable disable @@ -28,16 +28,52 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7e16f4243475f11d3e8d60f9b7ba350c021a5d8e Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 13 Jan 2026 11:41:42 +0300 Subject: [PATCH 07/13] Refactor and update dependencies in Keycloak client Updated string manipulation logic to use the range operator for trailing slash removal in `KeycloakClient` and `KcHttpClientBase`. Refactored `NETCore.Keycloak.Client.Tests.csproj` to adjust formatting and added `PasswordGenerator` dependency. Enhanced `NETCore.Keycloak.Client.csproj` by reorganizing package references for target frameworks, adding `Newtonsoft.Json`, and including metadata files in the package. Updated `NoWarn` settings to suppress additional warnings. These changes improve code consistency, dependency management, and project configuration. --- .../NETCore.Keycloak.Client.Tests.csproj | 6 ++--- .../Implementation/KeycloakClient.cs | 2 +- .../HttpClients/KcHttpClientBase.cs | 2 +- .../NETCore.Keycloak.Client.csproj | 26 +++++++++++++------ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj index 1e3bdf5..bb3af4a 100644 --- a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj +++ b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj @@ -19,9 +19,9 @@ - - - + + + diff --git a/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs b/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs index 9ced037..d7d1ad8 100644 --- a/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs +++ b/NETCore.Keycloak.Client/HttpClients/Implementation/KeycloakClient.cs @@ -70,7 +70,7 @@ public KeycloakClient(string baseUrl, ILogger logger = null) // Remove the trailing slash from the base URL if it exists. baseUrl = baseUrl.EndsWith("/", StringComparison.Ordinal) - ? baseUrl.Remove(baseUrl.Length - 1, 1) + ? baseUrl[..^1] : baseUrl; // Define the admin API base URL for realm-specific administrative operations. diff --git a/NETCore.Keycloak.Client/HttpClients/KcHttpClientBase.cs b/NETCore.Keycloak.Client/HttpClients/KcHttpClientBase.cs index 2335ec5..ce24dc6 100644 --- a/NETCore.Keycloak.Client/HttpClients/KcHttpClientBase.cs +++ b/NETCore.Keycloak.Client/HttpClients/KcHttpClientBase.cs @@ -41,7 +41,7 @@ protected KcHttpClientBase(ILogger logger, string baseUrl) // Ensure the base URL does not end with a trailing slash. BaseUrl = baseUrl.EndsWith("/", StringComparison.Ordinal) - ? baseUrl.Remove(baseUrl.Length - 1, 1) + ? baseUrl[..^1] : baseUrl; Logger = logger; diff --git a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj index f4f37b9..13ab1e3 100644 --- a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj +++ b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj @@ -24,14 +24,14 @@ true true All - $(NoWarn);CA1031, CA1054, CA1056, CA1865, CA1815, CA1711 + $(NoWarn);CA1031, CA1054, CA1056, CA1865, CA1815, CA1711, CA1873, IDE0040 - - + + @@ -40,9 +40,11 @@ + - + + @@ -50,22 +52,28 @@ + + - - + + + + - - + + + + @@ -74,6 +82,8 @@ + + From fdd3ee8b8e39e95e8a894700692abd68bdba4337 Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Mon, 19 Jan 2026 11:52:11 +0300 Subject: [PATCH 08/13] Update .NET targets, dependencies, and Python reqs Removed .NET 6/7 support from projects and related package refs. Updated Bogus to 35.6.5 in tests. requirements.txt now requires newer Ansible (>=13.2.0), ansible-core (>=2.17.0), and python-keycloak (7.0.2). Minor formatting fix for coverlet.collector. --- .../NETCore.Keycloak.Client.Tests.csproj | 6 ++--- .../requirements.txt | 6 ++--- .../NETCore.Keycloak.Client.csproj | 23 +------------------ 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj index bb3af4a..a0600c5 100644 --- a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj +++ b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0 latest enable disable @@ -16,12 +16,12 @@ - + - + diff --git a/NETCore.Keycloak.Client.Tests/requirements.txt b/NETCore.Keycloak.Client.Tests/requirements.txt index c125c57..b0fd8db 100644 --- a/NETCore.Keycloak.Client.Tests/requirements.txt +++ b/NETCore.Keycloak.Client.Tests/requirements.txt @@ -1,5 +1,5 @@ -ansible==8.7.0 -ansible-core==2.15.13 +ansible>=13.2.0 +ansible-core>=2.17.0 certifi==2024.12.14 cffi==1.17.1 charset-normalizer==3.4.1 @@ -14,7 +14,7 @@ packaging==24.2 pyasn1==0.6.1 pycparser==2.22 python-jose==3.3.0 -python-keycloak==3.3.0 +python-keycloak==7.0.2 PyYAML==6.0.2 requests==2.32.3 requests-toolbelt==1.0.0 diff --git a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj index 13ab1e3..8d01419 100644 --- a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj +++ b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj @@ -14,7 +14,7 @@ git README.md https://github.com/Black-Cockpit/NETCore.Keycloak - net6.0;net7.0;net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0 latest enable disable @@ -36,27 +36,6 @@ - - - - - - - - - - - - - - - - - - - - - From 2f19ed10d9935e1bc23e90a5c35d04c28d6ea8cd Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Mon, 19 Jan 2026 12:02:34 +0300 Subject: [PATCH 09/13] Mark test project dependencies as private assets Added `PrivateAssets="all"` to all `` elements in `NETCore.Keycloak.Client.Tests.csproj` to prevent transitive dependencies from leaking into referencing projects. This ensures cleaner dependency management and avoids unintended propagation of test-only packages. --- .../NETCore.Keycloak.Client.Tests.csproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj index a0600c5..b08f571 100644 --- a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj +++ b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj @@ -15,14 +15,14 @@ - - - - - - - - + + + + + + + + From 2cf9728a5640fafce1f63a7f9d4eef9220ced764 Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Mon, 19 Jan 2026 16:09:18 +0300 Subject: [PATCH 10/13] Update dependencies and document flagged license Updated `requirements.txt` to include or modify dependencies: - ansible>=13.2.0-ansible-core>=2.17.0+ansible-core>=2.17.7 - certifi==2024.12.14 - cffi==1.17.1 - charset-normalizer==3.4.1 - cryptography==44.0.0 - deprecation==2.1.0 Added a new section to `LICENSING.md` to document the flagged `Microsoft.NET.Test.Sdk` package and its `MS-NET` license. Provided details on why the dependency is safe for consumers and recommended actions for auditors to handle the flagged license appropriately. --- NETCore.Keycloak.Client.Tests/LICENSING.md | 28 +++++++++++++++++++ .../requirements.txt | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 NETCore.Keycloak.Client.Tests/LICENSING.md diff --git a/NETCore.Keycloak.Client.Tests/LICENSING.md b/NETCore.Keycloak.Client.Tests/LICENSING.md new file mode 100644 index 0000000..7961238 --- /dev/null +++ b/NETCore.Keycloak.Client.Tests/LICENSING.md @@ -0,0 +1,28 @@ +Summary +======= + +This repository contains test-only dependencies that were flagged by a license scanner. This file documents the flagged Microsoft license related to the `Microsoft.NET.Test.Sdk` package and the intended handling for auditors. + +Flagged package +--------------- + +- Package: `Microsoft.NET.Test.Sdk` +- Version found: `17.6.0` +- Locator: `nuget+Microsoft.NET.Test.Sdk$17.6.0` +- Detected license: MS-NET (Microsoft Software License Terms) +- Project: `NETCore.Keycloak.Client.Tests` (direct dependency in `NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj`) + +Why this is safe for consumers +------------------------------ + +- `Microsoft.NET.Test.Sdk` is a test-runner/test-SDK dependency used only to execute unit tests. It is not part of the runtime or production shipping artifacts for the library. +- The test project includes `IsTestProject=true` and the project-level `PackageReference`s have been marked with `PrivateAssets="all"` to prevent transitive flow to consuming packages. + +Recommended actions for auditors +-------------------------------- + +1. If your organization policy accepts MS-NET for development/test tooling, allowlist the package or the MS-NET license in your scanner. +2. Alternatively, configure the license scanner to ignore dev/test-only dependencies or projects that have `true`. +3. If your policy forbids MS-NET entirely, remove or relocate test automation to an isolated repository or CI container and consult legal. + +If you need, provide the scanner name and I can suggest or apply a scanner-specific ignore/allowlist configuration. diff --git a/NETCore.Keycloak.Client.Tests/requirements.txt b/NETCore.Keycloak.Client.Tests/requirements.txt index b0fd8db..84fd4fd 100644 --- a/NETCore.Keycloak.Client.Tests/requirements.txt +++ b/NETCore.Keycloak.Client.Tests/requirements.txt @@ -1,5 +1,5 @@ ansible>=13.2.0 -ansible-core>=2.17.0 +ansible-core>=2.17.7 certifi==2024.12.14 cffi==1.17.1 charset-normalizer==3.4.1 From f0ca7d03bec028d41e5a44e9718a95e48ffc9005 Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 20 Jan 2026 10:23:57 +0300 Subject: [PATCH 11/13] Bump dependency versions in requirements.txt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgraded the following packages in requirements.txt: cryptography (44.0.0 → 44.0.1), pyasn1 (0.6.1 → 0.6.2), python-jose (3.3.0 → 3.4.0), requests (2.32.3 → 2.32.4), and urllib3 (2.3.0 → 2.6.3). Removed the older versions and replaced them with the latest ones. --- NETCore.Keycloak.Client.Tests/requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NETCore.Keycloak.Client.Tests/requirements.txt b/NETCore.Keycloak.Client.Tests/requirements.txt index 84fd4fd..8f262d5 100644 --- a/NETCore.Keycloak.Client.Tests/requirements.txt +++ b/NETCore.Keycloak.Client.Tests/requirements.txt @@ -3,7 +3,7 @@ ansible-core>=2.17.7 certifi==2024.12.14 cffi==1.17.1 charset-normalizer==3.4.1 -cryptography==44.0.0 +cryptography==44.0.1 deprecation==2.1.0 ecdsa==0.19.0 idna==3.10 @@ -11,14 +11,14 @@ importlib-resources==5.0.7 Jinja2==3.1.5 MarkupSafe==3.0.2 packaging==24.2 -pyasn1==0.6.1 +pyasn1==0.6.2 pycparser==2.22 -python-jose==3.3.0 +python-jose==3.4.0 python-keycloak==7.0.2 PyYAML==6.0.2 -requests==2.32.3 +requests==2.32.4 requests-toolbelt==1.0.0 resolvelib==1.0.1 rsa==4.9 six==1.17.0 -urllib3==2.3.0 +urllib3==2.6.3 From 08bce705e71e7fa2c60bc24069372a96a0e9aa24 Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 20 Jan 2026 10:51:30 +0300 Subject: [PATCH 12/13] Update test SDK, coverlet; remove System.Text.Json refs Upgraded Microsoft.NET.Test.Sdk to 18.0.1 and coverlet.collector to 6.0.4 in test project. Removed System.Text.Json package references for all target frameworks in main project. --- .../NETCore.Keycloak.Client.Tests.csproj | 4 ++-- NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj index b08f571..31608f8 100644 --- a/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj +++ b/NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj @@ -17,11 +17,11 @@ - + - + diff --git a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj index 8d01419..4a870bb 100644 --- a/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj +++ b/NETCore.Keycloak.Client/NETCore.Keycloak.Client.csproj @@ -43,7 +43,6 @@ - @@ -53,7 +52,6 @@ - @@ -63,6 +61,5 @@ - From 73c1274618c9b3a12eb9190f8b005cc4219feec1 Mon Sep 17 00:00:00 2001 From: Ghaith Prosoft Date: Tue, 20 Jan 2026 11:09:43 +0300 Subject: [PATCH 13/13] Simplify license handling with FOSSA config Removed detailed licensing documentation for `Microsoft.NET.Test.Sdk` from `LICENSING.md` and replaced it with a `.fossa.yml` configuration file. The `.fossa.yml` file excludes the `NETCore.Keycloak.Client.Tests/**` directory and specific flagged packages (`nuget+Microsoft.NET.Test.Sdk$17.6.0`, `pip+ansible$13.2.0`, `pip+ansible-core$2.17.7`) from FOSSA license/security analysis. Added notes to guide users on triggering a new FOSSA scan and managing settings centrally if required. This change reduces manual intervention while ensuring compliance with organizational policies. --- .fossa.yml | 19 +++++++++++++++ NETCore.Keycloak.Client.Tests/LICENSING.md | 28 ---------------------- 2 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 .fossa.yml delete mode 100644 NETCore.Keycloak.Client.Tests/LICENSING.md diff --git a/.fossa.yml b/.fossa.yml new file mode 100644 index 0000000..6387980 --- /dev/null +++ b/.fossa.yml @@ -0,0 +1,19 @@ +version: 1 + +analysis: + # Paths to exclude from license/security analysis (relative to repo root) + ignorePaths: + - "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" + +# Notes: +# - Commit and push this file and then trigger a new FOSSA scan so the server-side +# analysis picks up the repo-level exclusions. Local changes alone won't modify +# the existing project findings until FOSSA re-analyzes the repository. +# - If your organization manages FOSSA centrally, you may need to update project +# settings in the FOSSA UI instead of relying on this file. diff --git a/NETCore.Keycloak.Client.Tests/LICENSING.md b/NETCore.Keycloak.Client.Tests/LICENSING.md deleted file mode 100644 index 7961238..0000000 --- a/NETCore.Keycloak.Client.Tests/LICENSING.md +++ /dev/null @@ -1,28 +0,0 @@ -Summary -======= - -This repository contains test-only dependencies that were flagged by a license scanner. This file documents the flagged Microsoft license related to the `Microsoft.NET.Test.Sdk` package and the intended handling for auditors. - -Flagged package ---------------- - -- Package: `Microsoft.NET.Test.Sdk` -- Version found: `17.6.0` -- Locator: `nuget+Microsoft.NET.Test.Sdk$17.6.0` -- Detected license: MS-NET (Microsoft Software License Terms) -- Project: `NETCore.Keycloak.Client.Tests` (direct dependency in `NETCore.Keycloak.Client.Tests/NETCore.Keycloak.Client.Tests.csproj`) - -Why this is safe for consumers ------------------------------- - -- `Microsoft.NET.Test.Sdk` is a test-runner/test-SDK dependency used only to execute unit tests. It is not part of the runtime or production shipping artifacts for the library. -- The test project includes `IsTestProject=true` and the project-level `PackageReference`s have been marked with `PrivateAssets="all"` to prevent transitive flow to consuming packages. - -Recommended actions for auditors --------------------------------- - -1. If your organization policy accepts MS-NET for development/test tooling, allowlist the package or the MS-NET license in your scanner. -2. Alternatively, configure the license scanner to ignore dev/test-only dependencies or projects that have `true`. -3. If your policy forbids MS-NET entirely, remove or relocate test automation to an isolated repository or CI container and consult legal. - -If you need, provide the scanner name and I can suggest or apply a scanner-specific ignore/allowlist configuration.