From c9e0a9be5203c18172dfa6aeb71c00dd396d440f Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Tue, 26 Nov 2024 00:22:42 +0300 Subject: [PATCH 01/34] update: move enum to .cs file + new UserRequest model --- AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- AuthentikNet.Api/Models/User.cs | 16 +----------- AuthentikNet.Api/Models/UserRequest.cs | 16 ++++++++++++ AuthentikNet.Api/Models/UserTypeEnum.cs | 33 ++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 AuthentikNet.Api/Models/UserRequest.cs create mode 100644 AuthentikNet.Api/Models/UserTypeEnum.cs diff --git a/AuthentikNet.Api/AuthentikNet.Api.csproj b/AuthentikNet.Api/AuthentikNet.Api.csproj index 97f9498..29802c5 100644 --- a/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev + 0.0.1-dev.1 Saph1s Authentik api client Copyright (c) Saph1s 2024 diff --git a/AuthentikNet.Api/Models/User.cs b/AuthentikNet.Api/Models/User.cs index 4c5668a..b4bdd73 100644 --- a/AuthentikNet.Api/Models/User.cs +++ b/AuthentikNet.Api/Models/User.cs @@ -1,5 +1,4 @@ -using System.Runtime.Serialization; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace AuthentikNet.Api.Models; @@ -20,17 +19,4 @@ public class User [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; [JsonPropertyName("type")] public UserTypeEnum Type { get; set; } [JsonPropertyName("uuid")] public required Guid Uuid { get; init; } -} - -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum UserTypeEnum -{ - [EnumMember(Value = "internal")] Internal, - [EnumMember(Value = "external")] External, - - [EnumMember(Value = "service_account")] - ServiceAccount, - - [EnumMember(Value = "internal_service_account")] - InternalServiceAccount } \ No newline at end of file diff --git a/AuthentikNet.Api/Models/UserRequest.cs b/AuthentikNet.Api/Models/UserRequest.cs new file mode 100644 index 0000000..664958c --- /dev/null +++ b/AuthentikNet.Api/Models/UserRequest.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class UserRequest +{ + [JsonPropertyName("username")] public required string Username { get; set; } + [JsonPropertyName("name")] public required string Name { get; set; } + [JsonPropertyName("is_active")] public bool IsActive { get; set; } + [JsonPropertyName("last_login")] public DateTime? LastLogin { get; set; } + [JsonPropertyName("groups")] public List Groups { get; set; } = []; + [JsonPropertyName("email")] public string Email { get; set; } = string.Empty; + [JsonPropertyName("attributes")] public Dictionary Attributes { get; set; } = new(); + [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + [JsonPropertyName("type")] public UserTypeEnum Type { get; set; } +} \ No newline at end of file diff --git a/AuthentikNet.Api/Models/UserTypeEnum.cs b/AuthentikNet.Api/Models/UserTypeEnum.cs new file mode 100644 index 0000000..14236c3 --- /dev/null +++ b/AuthentikNet.Api/Models/UserTypeEnum.cs @@ -0,0 +1,33 @@ +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +/// +/// The type of account +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum UserTypeEnum +{ + /// + /// Internal + /// + [EnumMember(Value = "internal")] Internal, + + /// + /// External + /// + [EnumMember(Value = "external")] External, + + /// + /// Service account + /// + [EnumMember(Value = "service_account")] + ServiceAccount, + + /// + /// Internal service account + /// + [EnumMember(Value = "internal_service_account")] + InternalServiceAccount +} \ No newline at end of file From d47d8b90634cc882df7e76bdb543175ebc562797 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Tue, 26 Nov 2024 00:28:11 +0300 Subject: [PATCH 02/34] update: move to src --- AuthentikNet.sln | 2 +- .../AuthentikNet.Api}/AuthentikNet.Api.csproj | 0 {AuthentikNet.Api => src/AuthentikNet.Api}/Models/Group.cs | 0 .../AuthentikNet.Api}/Models/GroupMember.cs | 0 {AuthentikNet.Api => src/AuthentikNet.Api}/Models/Role.cs | 0 {AuthentikNet.Api => src/AuthentikNet.Api}/Models/User.cs | 0 {AuthentikNet.Api => src/AuthentikNet.Api}/Models/UserGroup.cs | 0 .../AuthentikNet.Api}/Models/UserRequest.cs | 0 .../AuthentikNet.Api}/Models/UserTypeEnum.cs | 0 {AuthentikNet.Api => src/AuthentikNet.Api}/README.md | 0 10 files changed, 1 insertion(+), 1 deletion(-) rename {AuthentikNet.Api => src/AuthentikNet.Api}/AuthentikNet.Api.csproj (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/Models/Group.cs (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/Models/GroupMember.cs (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/Models/Role.cs (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/Models/User.cs (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/Models/UserGroup.cs (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/Models/UserRequest.cs (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/Models/UserTypeEnum.cs (100%) rename {AuthentikNet.Api => src/AuthentikNet.Api}/README.md (100%) diff --git a/AuthentikNet.sln b/AuthentikNet.sln index c509662..ed3fea4 100644 --- a/AuthentikNet.sln +++ b/AuthentikNet.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthentikNet.Api", "AuthentikNet.Api\AuthentikNet.Api.csproj", "{D0BDC4CF-C6DC-4DCC-A38A-589FA8FA9ACF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthentikNet.Api", "src\AuthentikNet.Api\AuthentikNet.Api.csproj", "{D0BDC4CF-C6DC-4DCC-A38A-589FA8FA9ACF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj similarity index 100% rename from AuthentikNet.Api/AuthentikNet.Api.csproj rename to src/AuthentikNet.Api/AuthentikNet.Api.csproj diff --git a/AuthentikNet.Api/Models/Group.cs b/src/AuthentikNet.Api/Models/Group.cs similarity index 100% rename from AuthentikNet.Api/Models/Group.cs rename to src/AuthentikNet.Api/Models/Group.cs diff --git a/AuthentikNet.Api/Models/GroupMember.cs b/src/AuthentikNet.Api/Models/GroupMember.cs similarity index 100% rename from AuthentikNet.Api/Models/GroupMember.cs rename to src/AuthentikNet.Api/Models/GroupMember.cs diff --git a/AuthentikNet.Api/Models/Role.cs b/src/AuthentikNet.Api/Models/Role.cs similarity index 100% rename from AuthentikNet.Api/Models/Role.cs rename to src/AuthentikNet.Api/Models/Role.cs diff --git a/AuthentikNet.Api/Models/User.cs b/src/AuthentikNet.Api/Models/User.cs similarity index 100% rename from AuthentikNet.Api/Models/User.cs rename to src/AuthentikNet.Api/Models/User.cs diff --git a/AuthentikNet.Api/Models/UserGroup.cs b/src/AuthentikNet.Api/Models/UserGroup.cs similarity index 100% rename from AuthentikNet.Api/Models/UserGroup.cs rename to src/AuthentikNet.Api/Models/UserGroup.cs diff --git a/AuthentikNet.Api/Models/UserRequest.cs b/src/AuthentikNet.Api/Models/UserRequest.cs similarity index 100% rename from AuthentikNet.Api/Models/UserRequest.cs rename to src/AuthentikNet.Api/Models/UserRequest.cs diff --git a/AuthentikNet.Api/Models/UserTypeEnum.cs b/src/AuthentikNet.Api/Models/UserTypeEnum.cs similarity index 100% rename from AuthentikNet.Api/Models/UserTypeEnum.cs rename to src/AuthentikNet.Api/Models/UserTypeEnum.cs diff --git a/AuthentikNet.Api/README.md b/src/AuthentikNet.Api/README.md similarity index 100% rename from AuthentikNet.Api/README.md rename to src/AuthentikNet.Api/README.md From 16c1ba9ef5750b907f23a876eee6d85ebeddb7fc Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Tue, 26 Nov 2024 21:56:19 +0300 Subject: [PATCH 03/34] update: new models + client + ep's --- src/AuthentikNet.Api/Client/Admin/AdminApi.cs | 99 +++++++++++++++++++ .../Client/AuthentikClient.cs | 95 ++++++++++++++++++ .../Client/AuthentikClientOptions.cs | 8 ++ .../Client/AuthentikException.cs | 32 ++++++ src/AuthentikNet.Api/Client/Core/CoreApi.cs | 63 ++++++++++++ src/AuthentikNet.Api/Models/App.cs | 9 ++ src/AuthentikNet.Api/Models/Coordinate.cs | 9 ++ src/AuthentikNet.Api/Models/LoginMetrics.cs | 10 ++ .../Models/PaginatedUserList.cs | 9 ++ src/AuthentikNet.Api/Models/Pagination.cs | 14 +++ src/AuthentikNet.Api/Models/Settings.cs | 31 ++++++ src/AuthentikNet.Api/Models/SystemInfo.cs | 19 ++++ .../Models/SystemInfoRuntime.cs | 19 ++++ src/AuthentikNet.Api/Models/UserRequest.cs | 18 ++++ src/AuthentikNet.Api/Models/Version.cs | 16 +++ src/AuthentikNet.Api/Models/VersionHistory.cs | 11 +++ src/AuthentikNet.Api/Models/Workers.cs | 8 ++ 17 files changed, 470 insertions(+) create mode 100644 src/AuthentikNet.Api/Client/Admin/AdminApi.cs create mode 100644 src/AuthentikNet.Api/Client/AuthentikClient.cs create mode 100644 src/AuthentikNet.Api/Client/AuthentikClientOptions.cs create mode 100644 src/AuthentikNet.Api/Client/AuthentikException.cs create mode 100644 src/AuthentikNet.Api/Client/Core/CoreApi.cs create mode 100644 src/AuthentikNet.Api/Models/App.cs create mode 100644 src/AuthentikNet.Api/Models/Coordinate.cs create mode 100644 src/AuthentikNet.Api/Models/LoginMetrics.cs create mode 100644 src/AuthentikNet.Api/Models/PaginatedUserList.cs create mode 100644 src/AuthentikNet.Api/Models/Pagination.cs create mode 100644 src/AuthentikNet.Api/Models/Settings.cs create mode 100644 src/AuthentikNet.Api/Models/SystemInfo.cs create mode 100644 src/AuthentikNet.Api/Models/SystemInfoRuntime.cs create mode 100644 src/AuthentikNet.Api/Models/Version.cs create mode 100644 src/AuthentikNet.Api/Models/VersionHistory.cs create mode 100644 src/AuthentikNet.Api/Models/Workers.cs diff --git a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs new file mode 100644 index 0000000..9fa2c5f --- /dev/null +++ b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs @@ -0,0 +1,99 @@ +using AuthentikNet.Api.Models; +using Version = AuthentikNet.Api.Models.Version; + +namespace AuthentikNet.Api.Client.Admin; + +public class AdminApi +{ + private readonly AuthentikClient _client; + + public AdminApi(AuthentikClient client) + { + _client = client; + } + + public async Task> AdminAppsList(CancellationToken cancellationToken = default) + { + return await _client.SendAsync>(HttpMethod.Get, "/admin/apps/", cancellationToken: cancellationToken); + } + + public async Task AdminMetricsRetrieve(CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Get, "/admin/metrics/", + cancellationToken: cancellationToken); + } + + public async Task> AdminModelsList(CancellationToken cancellationToken = default) + { + return await _client.SendAsync>(HttpMethod.Get, "/admin/models/", + cancellationToken: cancellationToken); + } + + public async Task AdminSettingsRetrieve(CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Get, "/admin/settings/", + cancellationToken: cancellationToken); + } + + public async Task AdminSettingsUpdate(Settings data, CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Put, "/admin/settings/", data, cancellationToken); + } + + public async Task AdminSettingsPartialUpdate(Settings data, CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Patch, "/admin/settings/", data, cancellationToken); + } + + public async Task AdminSystemRetrieve(CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Get, "/admin/system/", + cancellationToken: cancellationToken); + } + + public async Task AdminSystemCreate(CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Post, "/admin/system/", + cancellationToken: cancellationToken); + } + + public async Task AdminVersionRetrieve(CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Get, "/admin/version/", + cancellationToken: cancellationToken); + } + + public async Task> AdminVersionHistoryList( + string? build, + string? ordering, + string? search, + string? version, + CancellationToken cancellationToken = default) + { + var url = "/admin/version/history/"; + var queryParameters = new Dictionary(); + if (build != null) queryParameters.Add("build", build); + if (ordering != null) queryParameters.Add("ordering", ordering); + if (search != null) queryParameters.Add("search", search); + if (version != null) queryParameters.Add("version", version); + + if (queryParameters.Count > 0) + { + url += "?" + string.Join("&", queryParameters.Select(x => $"{x.Key}={x.Value}")); + } + + return await _client.SendAsync>(HttpMethod.Get, url, cancellationToken: cancellationToken); + } + + public async Task AdminVersionHistoryRetrieve(int id, CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Get, $"/admin/version/history/{id}/", + cancellationToken: cancellationToken); + } + + public async Task AdminWorkersRetrieve(CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Get, "/admin/workers/", + cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Client/AuthentikClient.cs b/src/AuthentikNet.Api/Client/AuthentikClient.cs new file mode 100644 index 0000000..3faf9f0 --- /dev/null +++ b/src/AuthentikNet.Api/Client/AuthentikClient.cs @@ -0,0 +1,95 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using AuthentikNet.Api.Client.Admin; + +namespace AuthentikNet.Api.Client; + +public class AuthentikClient +{ + private readonly HttpClient _client; + private readonly AuthentikClientOptions _options; + + public AdminApi Admin { get; } + + public AuthentikClient(AuthentikClientOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + + _client = new HttpClient + { + BaseAddress = new Uri(options.BaseUrl), + Timeout = options.Timeout + }; + + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.Token); + _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AuthentikNet.Api.Client", + $"{GetType().Assembly.GetName().Version}")); // TODO: Need to set correct version + + Admin = new AdminApi(this); + } + + public async Task SendAsync(HttpMethod method, string path, object? data = null, + CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(method, path); + + if (data != null) + { + request.Content = new StringContent( + JsonSerializer.Serialize(data), + Encoding.UTF8, + "application/json" + ); + } + + var response = await _client.SendAsync(request, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + await HandleError(response); + } + + return await DeserializeResponseAsync(response); + } + + private async Task DeserializeResponseAsync(HttpResponseMessage response) + { + var content = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(content) ?? + throw new AuthentikException("Failed to deserialize response", 500); + } + + private async Task HandleError(HttpResponseMessage response) + { + var content = await response.Content.ReadAsStringAsync(); + string errorMessage; + + if (response.Content.Headers.ContentType?.MediaType == "application/json") + { + try + { + var json = JsonDocument.Parse(content); + errorMessage = json.RootElement.ToString(); + } + catch (JsonException) + { + errorMessage = content; + } + } + else + { + errorMessage = content; + } + + throw response.StatusCode switch + { + HttpStatusCode.BadRequest => new AuthentikBadRequestException(errorMessage), + HttpStatusCode.Unauthorized => new AuthentikUnauthorizedException(errorMessage), + HttpStatusCode.Forbidden => new AuthentikForbiddenException(errorMessage), + _ => new AuthentikException(errorMessage, (int)response.StatusCode) + }; + } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Client/AuthentikClientOptions.cs b/src/AuthentikNet.Api/Client/AuthentikClientOptions.cs new file mode 100644 index 0000000..911e656 --- /dev/null +++ b/src/AuthentikNet.Api/Client/AuthentikClientOptions.cs @@ -0,0 +1,8 @@ +namespace AuthentikNet.Api.Client; + +public class AuthentikClientOptions +{ + public string BaseUrl { get; set; } = string.Empty; + public string Token { get; set; } = string.Empty; + public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Client/AuthentikException.cs b/src/AuthentikNet.Api/Client/AuthentikException.cs new file mode 100644 index 0000000..5b7a35b --- /dev/null +++ b/src/AuthentikNet.Api/Client/AuthentikException.cs @@ -0,0 +1,32 @@ +namespace AuthentikNet.Api.Client; + +public class AuthentikException : Exception +{ + public int StatusCode { get; } + + public AuthentikException(string message, int statusCode) : base(message) + { + StatusCode = statusCode; + } +} + +public class AuthentikBadRequestException : AuthentikException +{ + public AuthentikBadRequestException(string message) : base(message, 400) + { + } +} + +public class AuthentikUnauthorizedException : AuthentikException +{ + public AuthentikUnauthorizedException(string message) : base(message, 401) + { + } +} + +public class AuthentikForbiddenException : AuthentikException +{ + public AuthentikForbiddenException(string message) : base(message, 403) + { + } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs new file mode 100644 index 0000000..1a7bfe1 --- /dev/null +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -0,0 +1,63 @@ +using AuthentikNet.Api.Models; + +namespace AuthentikNet.Api.Client.Core; + +public class CoreApi +{ + private readonly AuthentikClient _client; + + public CoreApi(AuthentikClient client) + { + _client = client; + } + + public async Task CoreUsersList( + string? attributes, + string? email, + string[]? groupsByName, + string[]? groupsByPk, + bool? isActive, + bool? isSuperuser, + string? name, + string? ordering, + int? page, + int? pageSize, + string? path, + string? pathStartswith, + string? search, + string[]? type_, + string? username, + Guid? uuid, + bool includeGroups = true, CancellationToken cancellationToken = default) + + { + var url = "/core/users/"; + var queryDict = new Dictionary + { + { "attributes", attributes }, + { "email", email }, + { "groups_by_name", groupsByName }, + { "groups_by_pk", groupsByPk }, + { "is_active", isActive }, + { "is_superuser", isSuperuser }, + { "name", name }, + { "ordering", ordering }, + { "page", page }, + { "page_size", pageSize }, + { "path", path }, + { "path_startswith", pathStartswith }, + { "search", search }, + { "type", type_ }, + { "username", username }, + { "uuid", uuid }, + { "include_groups", includeGroups } + }.Where(kv => kv.Value != null) + .ToDictionary(kv => kv.Key, kv => kv.Value); + + url += "?" + string.Join("&", queryDict.Select(x => $"{x.Key}={x.Value}")); + + + return await _client.SendAsync(HttpMethod.Get, url, + cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/App.cs b/src/AuthentikNet.Api/Models/App.cs new file mode 100644 index 0000000..8db717e --- /dev/null +++ b/src/AuthentikNet.Api/Models/App.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class App +{ + [JsonPropertyName("name")] public required string Name { get; set; } + [JsonPropertyName("label")] public required string Label { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/Coordinate.cs b/src/AuthentikNet.Api/Models/Coordinate.cs new file mode 100644 index 0000000..0704dbd --- /dev/null +++ b/src/AuthentikNet.Api/Models/Coordinate.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class Coordinate +{ + [JsonPropertyName("x_cord")] public required int XCord { get; init; } + [JsonPropertyName("y_cord")] public required int YCord { get; init; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/LoginMetrics.cs b/src/AuthentikNet.Api/Models/LoginMetrics.cs new file mode 100644 index 0000000..98d800c --- /dev/null +++ b/src/AuthentikNet.Api/Models/LoginMetrics.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class LoginMetrics +{ + [JsonPropertyName("logins")] public required List Logins { get; init; } + [JsonPropertyName("logins_failed")] public required List LoginsFailed { get; init; } + [JsonPropertyName("authorizations")] public required List Authorizations { get; init; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/PaginatedUserList.cs b/src/AuthentikNet.Api/Models/PaginatedUserList.cs new file mode 100644 index 0000000..89bf784 --- /dev/null +++ b/src/AuthentikNet.Api/Models/PaginatedUserList.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class PaginatedUserList +{ + [JsonPropertyName("pagination")] public required List Pagination { get; set; } + [JsonPropertyName("results")] public required List Results { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/Pagination.cs b/src/AuthentikNet.Api/Models/Pagination.cs new file mode 100644 index 0000000..f75a435 --- /dev/null +++ b/src/AuthentikNet.Api/Models/Pagination.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class Pagination +{ + [JsonPropertyName("next")] public required float Next { get; set; } + [JsonPropertyName("previous")] public required float Previous { get; set; } + [JsonPropertyName("count")] public required float Count { get; set; } + [JsonPropertyName("current")] public required float Current { get; set; } + [JsonPropertyName("total_pages")] public required float TotalPages { get; set; } + [JsonPropertyName("start_index")] public required float StartIndex { get; set; } + [JsonPropertyName("end_index")] public required float EndIndex { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/Settings.cs b/src/AuthentikNet.Api/Models/Settings.cs new file mode 100644 index 0000000..efd66d0 --- /dev/null +++ b/src/AuthentikNet.Api/Models/Settings.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class Settings +{ + [JsonPropertyName("avatars")] public string Avatars { get; set; } = string.Empty; + + [JsonPropertyName("default_user_change_name")] + public bool DefaultUserChangeName { get; set; } + + [JsonPropertyName("default_user_change_email")] + public bool DefaultUserChangeEmail { get; set; } + + [JsonPropertyName("default_user_change_username")] + public bool DefaultUserChangeUsername { get; set; } + + [JsonPropertyName("event_retention")] public string EventRetention { get; set; } = string.Empty; + [JsonPropertyName("footer_links")] public List<(string href, string name)> FooterLinks { get; set; } + [JsonPropertyName("gdpr_compliance")] public bool GdprCompliance { get; set; } + [JsonPropertyName("impersonation")] public bool Impersonation { get; set; } + + [JsonPropertyName("impersonation_require_reason")] + public bool ImpersonationRequireReason { get; set; } + + [JsonPropertyName("default_token_duration")] + public string DefaultTokenDuration { get; set; } = string.Empty; + + [JsonPropertyName("default_token_length")] + public int DefaultTokenLength { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/SystemInfo.cs b/src/AuthentikNet.Api/Models/SystemInfo.cs new file mode 100644 index 0000000..e371f20 --- /dev/null +++ b/src/AuthentikNet.Api/Models/SystemInfo.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class SystemInfo +{ + [JsonPropertyName("http_headers")] public required Dictionary HttpHeaders { get; init; } + [JsonPropertyName("http_host")] public required string HttpHost { get; init; } + [JsonPropertyName("http_is_secure")] public required bool HttpIsSecure { get; init; } + [JsonPropertyName("runtime")] public required SystemInfoRuntime Runtime { get; init; } + [JsonPropertyName("brand")] public required string Brand { get; init; } + [JsonPropertyName("server_time")] public required DateTime ServerTime { get; init; } + + [JsonPropertyName("embedded_outpost_disabled")] + public required bool EmbeddedOutpostDisabled { get; init; } + + [JsonPropertyName("embedded_outpost_host")] + public required string EmbeddedOutpostHost { get; init; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/SystemInfoRuntime.cs b/src/AuthentikNet.Api/Models/SystemInfoRuntime.cs new file mode 100644 index 0000000..1e99a7a --- /dev/null +++ b/src/AuthentikNet.Api/Models/SystemInfoRuntime.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class SystemInfoRuntime +{ + [JsonPropertyName("python_version")] public required string PythonVersion { get; set; } + [JsonPropertyName("environment")] public required string Environment { get; set; } + [JsonPropertyName("architecture")] public required string Architecture { get; set; } + [JsonPropertyName("platform")] public required string Platform { get; set; } + [JsonPropertyName("uname")] public required string Uname { get; set; } + [JsonPropertyName("openssl_version")] public required string OpensslVersion { get; set; } + + [JsonPropertyName("openssl_fips_enabled")] + public required bool? OpensslFipsEnabled { get; set; } + + [JsonPropertyName("authentik_version")] + public required string AuthentikVersion { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/UserRequest.cs b/src/AuthentikNet.Api/Models/UserRequest.cs index 664958c..24a7dbf 100644 --- a/src/AuthentikNet.Api/Models/UserRequest.cs +++ b/src/AuthentikNet.Api/Models/UserRequest.cs @@ -13,4 +13,22 @@ public class UserRequest [JsonPropertyName("attributes")] public Dictionary Attributes { get; set; } = new(); [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; [JsonPropertyName("type")] public UserTypeEnum Type { get; set; } + + /// + /// UserRequest constructor + /// + public UserRequest() + { + } + + /// + /// UserRequest constructor with username and name + /// + /// + /// + public UserRequest(string username, string name) + { + Username = username; + Name = name; + } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/Version.cs b/src/AuthentikNet.Api/Models/Version.cs new file mode 100644 index 0000000..c5cfc0d --- /dev/null +++ b/src/AuthentikNet.Api/Models/Version.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class Version +{ + [JsonPropertyName("version_current")] public required string VersionCurrent { get; init; } + [JsonPropertyName("version_latest")] public required string VersionLatest { get; init; } + + [JsonPropertyName("version_latest_valid")] + public required bool VersionLatestValid { get; init; } + + [JsonPropertyName("build_hash")] public required string BuildHash { get; init; } + [JsonPropertyName("outdated")] public required bool Outdated { get; init; } + [JsonPropertyName("outpost_outdated")] public required bool OutpostOutdated { get; init; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/VersionHistory.cs b/src/AuthentikNet.Api/Models/VersionHistory.cs new file mode 100644 index 0000000..d172d46 --- /dev/null +++ b/src/AuthentikNet.Api/Models/VersionHistory.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class VersionHistory +{ + [JsonPropertyName("id")] public required int Id { get; init; } + [JsonPropertyName("timestamp")] public required DateTime Timestamp { get; set; } + [JsonPropertyName("version")] public required string Version { get; set; } + [JsonPropertyName("build")] public required string Build { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/Workers.cs b/src/AuthentikNet.Api/Models/Workers.cs new file mode 100644 index 0000000..90b42fa --- /dev/null +++ b/src/AuthentikNet.Api/Models/Workers.cs @@ -0,0 +1,8 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class Workers +{ + [JsonPropertyName("id")] public required int Id { get; set; } +} \ No newline at end of file From adc044b805ccdddc2a0dbc5db35a8c3a87d2ddf9 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Tue, 26 Nov 2024 23:40:03 +0300 Subject: [PATCH 04/34] bump version --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index 29802c5..1c31981 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev.1 + 0.0.1-dev.2 Saph1s Authentik api client Copyright (c) Saph1s 2024 From e718bc9acb34830e4a5d679ae9ce749d1e1a4e43 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Tue, 26 Nov 2024 23:43:09 +0300 Subject: [PATCH 05/34] add LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b4a0206 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Anton "Saph1s" Babenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From 1ef450628dff3c04613f61ae932a24cab434732a Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Wed, 27 Nov 2024 22:49:00 +0300 Subject: [PATCH 06/34] update: new utils + models update --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- src/AuthentikNet.Api/Client/Admin/AdminApi.cs | 21 +++--- .../Client/AuthentikClient.cs | 45 +++++++----- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 32 ++++----- src/AuthentikNet.Api/Models/Group.cs | 7 +- src/AuthentikNet.Api/Models/GroupMember.cs | 7 +- .../Models/PaginatedUserList.cs | 2 +- src/AuthentikNet.Api/Models/User.cs | 9 ++- src/AuthentikNet.Api/Models/UserGroup.cs | 6 +- src/AuthentikNet.Api/Models/UserRequest.cs | 7 +- src/AuthentikNet.Api/Models/UserTypeEnum.cs | 3 +- .../Utils/DynamicAttributesJsonConverter.cs | 70 +++++++++++++++++++ .../Utils/SourceGenerationContext.cs | 32 +++++++++ .../Utils/UserTypeEnumConverter.cs | 34 +++++++++ 14 files changed, 227 insertions(+), 50 deletions(-) create mode 100644 src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs create mode 100644 src/AuthentikNet.Api/Utils/SourceGenerationContext.cs create mode 100644 src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index 1c31981..cd2c02d 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev.2 + 0.0.1-dev.16 Saph1s Authentik api client Copyright (c) Saph1s 2024 diff --git a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs index 9fa2c5f..6e9858f 100644 --- a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs +++ b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs @@ -64,18 +64,21 @@ public async Task AdminVersionRetrieve(CancellationToken cancellationTo } public async Task> AdminVersionHistoryList( - string? build, - string? ordering, - string? search, - string? version, + string? build = null, + string? ordering = null, + string? search = null, + string? version = null, CancellationToken cancellationToken = default) { var url = "/admin/version/history/"; - var queryParameters = new Dictionary(); - if (build != null) queryParameters.Add("build", build); - if (ordering != null) queryParameters.Add("ordering", ordering); - if (search != null) queryParameters.Add("search", search); - if (version != null) queryParameters.Add("version", version); + var queryParameters = new Dictionary + { + { "build", build }, + { "ordering", ordering }, + { "search", search }, + { "version", version } + }.Where(kv => kv.Value != null) + .ToDictionary(kv => kv.Key, kv => kv.Value); if (queryParameters.Count > 0) { diff --git a/src/AuthentikNet.Api/Client/AuthentikClient.cs b/src/AuthentikNet.Api/Client/AuthentikClient.cs index 3faf9f0..90ed866 100644 --- a/src/AuthentikNet.Api/Client/AuthentikClient.cs +++ b/src/AuthentikNet.Api/Client/AuthentikClient.cs @@ -3,6 +3,8 @@ using System.Text; using System.Text.Json; using AuthentikNet.Api.Client.Admin; +using AuthentikNet.Api.Client.Core; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Client; @@ -10,8 +12,9 @@ public class AuthentikClient { private readonly HttpClient _client; private readonly AuthentikClientOptions _options; - + public AdminApi Admin { get; } + public CoreApi Core { get; } public AuthentikClient(AuthentikClientOptions options) { @@ -27,38 +30,46 @@ public AuthentikClient(AuthentikClientOptions options) _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AuthentikNet.Api.Client", $"{GetType().Assembly.GetName().Version}")); // TODO: Need to set correct version - + Admin = new AdminApi(this); + Core = new CoreApi(this); } public async Task SendAsync(HttpMethod method, string path, object? data = null, CancellationToken cancellationToken = default) { - var request = new HttpRequestMessage(method, path); - - if (data != null) + var request = new HttpRequestMessage(method, _options.BaseUrl + path); + try { - request.Content = new StringContent( - JsonSerializer.Serialize(data), - Encoding.UTF8, - "application/json" - ); - } + if (data != null) + { + request.Content = new StringContent( + JsonSerializer.Serialize(data), + Encoding.UTF8, + "application/json" + ); + } - var response = await _client.SendAsync(request, cancellationToken); + var response = await _client.SendAsync(request, cancellationToken); - if (!response.IsSuccessStatusCode) + if (!response.IsSuccessStatusCode) + { + await HandleError(response); + } + + return await DeserializeResponseAsync(response); + } + catch (Exception e) { - await HandleError(response); + Console.WriteLine(e); + throw; } - - return await DeserializeResponseAsync(response); } private async Task DeserializeResponseAsync(HttpResponseMessage response) { var content = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(content) ?? + return JsonSerializer.Deserialize(content, SourceGenerationContext.Default.Options) ?? throw new AuthentikException("Failed to deserialize response", 500); } diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index 1a7bfe1..0d8b5e2 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -12,22 +12,22 @@ public CoreApi(AuthentikClient client) } public async Task CoreUsersList( - string? attributes, - string? email, - string[]? groupsByName, - string[]? groupsByPk, - bool? isActive, - bool? isSuperuser, - string? name, - string? ordering, - int? page, - int? pageSize, - string? path, - string? pathStartswith, - string? search, - string[]? type_, - string? username, - Guid? uuid, + string? attributes = null, + string? email = null, + string[]? groupsByName = null, + string[]? groupsByPk = null, + bool? isActive = null, + bool? isSuperuser = null, + string? name = null, + string? ordering = null, + int? page = null, + int? pageSize = null, + string? path = null, + string? pathStartswith = null, + string? search = null, + string[]? type_ = null, + string? username = null, + Guid? uuid = null, bool includeGroups = true, CancellationToken cancellationToken = default) { diff --git a/src/AuthentikNet.Api/Models/Group.cs b/src/AuthentikNet.Api/Models/Group.cs index 2805bd8..8ff1e56 100644 --- a/src/AuthentikNet.Api/Models/Group.cs +++ b/src/AuthentikNet.Api/Models/Group.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Models; @@ -12,7 +13,11 @@ public class Group [JsonPropertyName("parent_name")] public required string? ParentName { get; init; } [JsonPropertyName("users")] public List Users { get; set; } = []; [JsonPropertyName("users_obj")] public required List UsersObj { get; init; } - [JsonPropertyName("attributes")] public Dictionary Attributes { get; set; } = new(); + + [JsonPropertyName("attributes")] + [JsonConverter(typeof(DynamicAttributesJsonConverter))] + public object Attributes { get; set; } = new(); + [JsonPropertyName("roles")] public List Roles { get; set; } = []; [JsonPropertyName("roles_obj")] public required List RolesObj { get; init; } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/GroupMember.cs b/src/AuthentikNet.Api/Models/GroupMember.cs index 75f76c5..a9e4220 100644 --- a/src/AuthentikNet.Api/Models/GroupMember.cs +++ b/src/AuthentikNet.Api/Models/GroupMember.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Models; @@ -15,6 +16,10 @@ public class GroupMember [JsonPropertyName("is_active")] public bool IsActive { get; set; } [JsonPropertyName("last_login")] public DateTime? LastLogin { get; set; } [JsonPropertyName("email")] public string Email { get; set; } = string.Empty; - [JsonPropertyName("attributes")] public Dictionary Attributes { get; set; } = new(); + + [JsonPropertyName("attributes")] + [JsonConverter(typeof(DynamicAttributesJsonConverter))] + public object Attributes { get; set; } = new(); + [JsonPropertyName("uid")] public required string Uid { get; init; } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/PaginatedUserList.cs b/src/AuthentikNet.Api/Models/PaginatedUserList.cs index 89bf784..792241c 100644 --- a/src/AuthentikNet.Api/Models/PaginatedUserList.cs +++ b/src/AuthentikNet.Api/Models/PaginatedUserList.cs @@ -4,6 +4,6 @@ namespace AuthentikNet.Api.Models; public class PaginatedUserList { - [JsonPropertyName("pagination")] public required List Pagination { get; set; } + [JsonPropertyName("pagination")] public required Pagination Pagination { get; set; } [JsonPropertyName("results")] public required List Results { get; set; } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/User.cs b/src/AuthentikNet.Api/Models/User.cs index b4bdd73..a66e1e8 100644 --- a/src/AuthentikNet.Api/Models/User.cs +++ b/src/AuthentikNet.Api/Models/User.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Models; @@ -14,9 +15,15 @@ public class User [JsonPropertyName("groups_obj")] public required List GroupsObj { get; init; } [JsonPropertyName("email")] public string Email { get; set; } = string.Empty; [JsonPropertyName("avatar")] public required string Avatar { get; init; } - [JsonPropertyName("attributes")] public Dictionary Attributes { get; set; } = new(); + + [JsonPropertyName("attributes")] + [JsonConverter(typeof(DynamicAttributesJsonConverter))] + public object Attributes { get; set; } = new(); + [JsonPropertyName("uid")] public required string Uid { get; init; } [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + [JsonPropertyName("type")] public UserTypeEnum Type { get; set; } + [JsonPropertyName("uuid")] public required Guid Uuid { get; init; } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/UserGroup.cs b/src/AuthentikNet.Api/Models/UserGroup.cs index ef00d6c..0e14175 100644 --- a/src/AuthentikNet.Api/Models/UserGroup.cs +++ b/src/AuthentikNet.Api/Models/UserGroup.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Models; @@ -10,5 +11,8 @@ public class UserGroup [JsonPropertyName("is_superuser")] public bool IsSuperUser { get; set; } [JsonPropertyName("parent")] public Guid? Parent { get; init; } [JsonPropertyName("parent_name")] public required string? ParentName { get; init; } - [JsonPropertyName("attributes")] public Dictionary Attributes { get; set; } = new(); + + [JsonPropertyName("attributes")] + [JsonConverter(typeof(DynamicAttributesJsonConverter))] + public object Attributes { get; set; } = new(); } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/UserRequest.cs b/src/AuthentikNet.Api/Models/UserRequest.cs index 24a7dbf..ac0fd16 100644 --- a/src/AuthentikNet.Api/Models/UserRequest.cs +++ b/src/AuthentikNet.Api/Models/UserRequest.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Models; @@ -10,7 +11,11 @@ public class UserRequest [JsonPropertyName("last_login")] public DateTime? LastLogin { get; set; } [JsonPropertyName("groups")] public List Groups { get; set; } = []; [JsonPropertyName("email")] public string Email { get; set; } = string.Empty; - [JsonPropertyName("attributes")] public Dictionary Attributes { get; set; } = new(); + + [JsonPropertyName("attributes")] + [JsonConverter(typeof(DynamicAttributesJsonConverter))] + public object Attributes { get; set; } = new(); + [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; [JsonPropertyName("type")] public UserTypeEnum Type { get; set; } diff --git a/src/AuthentikNet.Api/Models/UserTypeEnum.cs b/src/AuthentikNet.Api/Models/UserTypeEnum.cs index 14236c3..e27a672 100644 --- a/src/AuthentikNet.Api/Models/UserTypeEnum.cs +++ b/src/AuthentikNet.Api/Models/UserTypeEnum.cs @@ -1,12 +1,13 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Models; /// /// The type of account /// -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(UserTypeEnumConverter))] public enum UserTypeEnum { /// diff --git a/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs b/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs new file mode 100644 index 0000000..101241d --- /dev/null +++ b/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs @@ -0,0 +1,70 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Utils; + +public class DynamicAttributesJsonConverter : JsonConverter +{ + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return ParseValue(ref reader, options); + } + + private static object? ParseValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.StartObject => ParseObject(ref reader, options), + JsonTokenType.StartArray => ParseArray(ref reader, options), + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.TryGetInt64(out var l) ? l : reader.GetDouble(), + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.Null => null, + _ => throw new JsonException($"Unsupported JSON token: {reader.TokenType}") + }; + } + + private static Dictionary ParseObject(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var dictionary = new Dictionary(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Expected a property name."); + + var propertyName = reader.GetString(); + reader.Read(); + + var value = ParseValue(ref reader, options); + dictionary[propertyName!] = value; + } + + return dictionary; + } + + private static List ParseArray(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var list = new List(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; + + var value = ParseValue(ref reader, options); + list.Add(value); + } + + return list; + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); + } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs b/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs new file mode 100644 index 0000000..a588752 --- /dev/null +++ b/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; +using AuthentikNet.Api.Models; +using Version = AuthentikNet.Api.Models.Version; + +namespace AuthentikNet.Api.Utils; + +[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] +[JsonSerializable(typeof(App))] +[JsonSerializable(typeof(Coordinate))] +[JsonSerializable(typeof(Group))] +[JsonSerializable(typeof(GroupMember))] +[JsonSerializable(typeof(LoginMetrics))] +[JsonSerializable(typeof(PaginatedUserList))] +[JsonSerializable(typeof(Pagination))] +[JsonSerializable(typeof(Role))] +[JsonSerializable(typeof(Settings))] +[JsonSerializable(typeof(SystemInfo))] +[JsonSerializable(typeof(SystemInfoRuntime))] +[JsonSerializable(typeof(User))] +[JsonSerializable(typeof(UserGroup))] +[JsonSerializable(typeof(UserRequest))] +[JsonSerializable(typeof(UserTypeEnum))] +[JsonSerializable(typeof(Version))] +[JsonSerializable(typeof(VersionHistory))] +[JsonSerializable(typeof(Workers))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(double))] +[JsonSerializable(typeof(object))] +public partial class SourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs b/src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs new file mode 100644 index 0000000..e2bc401 --- /dev/null +++ b/src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs @@ -0,0 +1,34 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using AuthentikNet.Api.Models; + +namespace AuthentikNet.Api.Utils; + +public class UserTypeEnumConverter : JsonConverter +{ + public override UserTypeEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return value switch + { + "internal" => UserTypeEnum.Internal, + "external" => UserTypeEnum.External, + "service_account" => UserTypeEnum.ServiceAccount, + "internal_service_account" => UserTypeEnum.InternalServiceAccount, + _ => throw new JsonException($"Unknown value: {value}") + }; + } + + public override void Write(Utf8JsonWriter writer, UserTypeEnum value, JsonSerializerOptions options) + { + var stringValue = value switch + { + UserTypeEnum.Internal => "internal", + UserTypeEnum.External => "external", + UserTypeEnum.ServiceAccount => "service_account", + UserTypeEnum.InternalServiceAccount => "internal_service_account", + _ => throw new JsonException($"Unknown value: {value}") + }; + writer.WriteStringValue(stringValue); + } +} \ No newline at end of file From 0363da3c3a63a68255b3f9121d8665f0f80e941d Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Wed, 11 Dec 2024 20:11:15 +0300 Subject: [PATCH 07/34] update: disable SourceGenerationContext --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- .../Client/AuthentikClient.cs | 3 +- .../Client/AuthentikClientOptions.cs | 14 ++++ .../Utils/SourceGenerationContext.cs | 64 +++++++++---------- 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index cd2c02d..b5fbd89 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev.16 + 0.0.1-dev.17 Saph1s Authentik api client Copyright (c) Saph1s 2024 diff --git a/src/AuthentikNet.Api/Client/AuthentikClient.cs b/src/AuthentikNet.Api/Client/AuthentikClient.cs index 90ed866..56e76ed 100644 --- a/src/AuthentikNet.Api/Client/AuthentikClient.cs +++ b/src/AuthentikNet.Api/Client/AuthentikClient.cs @@ -4,7 +4,6 @@ using System.Text.Json; using AuthentikNet.Api.Client.Admin; using AuthentikNet.Api.Client.Core; -using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Client; @@ -69,7 +68,7 @@ public async Task SendAsync(HttpMethod method, string path, object? data = private async Task DeserializeResponseAsync(HttpResponseMessage response) { var content = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(content, SourceGenerationContext.Default.Options) ?? + return JsonSerializer.Deserialize(content) ?? throw new AuthentikException("Failed to deserialize response", 500); } diff --git a/src/AuthentikNet.Api/Client/AuthentikClientOptions.cs b/src/AuthentikNet.Api/Client/AuthentikClientOptions.cs index 911e656..a27f67d 100644 --- a/src/AuthentikNet.Api/Client/AuthentikClientOptions.cs +++ b/src/AuthentikNet.Api/Client/AuthentikClientOptions.cs @@ -1,8 +1,22 @@ namespace AuthentikNet.Api.Client; +/// +/// Authentik client options +/// public class AuthentikClientOptions { + /// + /// Base URL for the Authentik API + /// public string BaseUrl { get; set; } = string.Empty; + + /// + /// API token + /// public string Token { get; set; } = string.Empty; + + /// + /// Timeout for the HTTP client + /// public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs b/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs index a588752..eeb3bee 100644 --- a/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs +++ b/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs @@ -1,32 +1,32 @@ -using System.Text.Json.Serialization; -using AuthentikNet.Api.Models; -using Version = AuthentikNet.Api.Models.Version; - -namespace AuthentikNet.Api.Utils; - -[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] -[JsonSerializable(typeof(App))] -[JsonSerializable(typeof(Coordinate))] -[JsonSerializable(typeof(Group))] -[JsonSerializable(typeof(GroupMember))] -[JsonSerializable(typeof(LoginMetrics))] -[JsonSerializable(typeof(PaginatedUserList))] -[JsonSerializable(typeof(Pagination))] -[JsonSerializable(typeof(Role))] -[JsonSerializable(typeof(Settings))] -[JsonSerializable(typeof(SystemInfo))] -[JsonSerializable(typeof(SystemInfoRuntime))] -[JsonSerializable(typeof(User))] -[JsonSerializable(typeof(UserGroup))] -[JsonSerializable(typeof(UserRequest))] -[JsonSerializable(typeof(UserTypeEnum))] -[JsonSerializable(typeof(Version))] -[JsonSerializable(typeof(VersionHistory))] -[JsonSerializable(typeof(Workers))] -[JsonSerializable(typeof(Dictionary))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(double))] -[JsonSerializable(typeof(object))] -public partial class SourceGenerationContext : JsonSerializerContext -{ -} \ No newline at end of file +// using System.Text.Json.Serialization; +// using AuthentikNet.Api.Models; +// using Version = AuthentikNet.Api.Models.Version; +// +// namespace AuthentikNet.Api.Utils; +// +// [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(App))] +// [JsonSerializable(typeof(Coordinate))] +// [JsonSerializable(typeof(Group))] +// [JsonSerializable(typeof(GroupMember))] +// [JsonSerializable(typeof(LoginMetrics))] +// [JsonSerializable(typeof(PaginatedUserList))] +// [JsonSerializable(typeof(Pagination))] +// [JsonSerializable(typeof(Role))] +// [JsonSerializable(typeof(Settings))] +// [JsonSerializable(typeof(SystemInfo))] +// [JsonSerializable(typeof(SystemInfoRuntime))] +// [JsonSerializable(typeof(User))] +// [JsonSerializable(typeof(UserGroup))] +// [JsonSerializable(typeof(UserRequest))] +// [JsonSerializable(typeof(UserTypeEnum))] +// [JsonSerializable(typeof(Version))] +// [JsonSerializable(typeof(VersionHistory))] +// [JsonSerializable(typeof(Workers))] +// [JsonSerializable(typeof(Dictionary))] +// [JsonSerializable(typeof(List))] +// [JsonSerializable(typeof(double))] +// [JsonSerializable(typeof(object))] +// public partial class SourceGenerationContext : JsonSerializerContext +// { +// } \ No newline at end of file From 8c07db86b025f2ea1e93aae7d3f05d53beb93090 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 11 Dec 2024 22:53:29 +0300 Subject: [PATCH 08/34] update: add CoreUsersRetrieve --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 34 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index b5fbd89..ebd346c 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev.17 + 0.0.1-dev.18 Saph1s Authentik api client Copyright (c) Saph1s 2024 diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index 0d8b5e2..f4471b5 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -11,6 +11,28 @@ public CoreApi(AuthentikClient client) _client = client; } + /// + /// User Viewset + /// + /// Attributes + /// + /// + /// + /// + /// + /// + /// Which field to use when ordering the results. + /// A page number within the paginated result set. + /// Number of results to return per page. + /// + /// + /// A search term. + /// + /// + /// + /// + /// + /// public async Task CoreUsersList( string? attributes = null, string? email = null, @@ -60,4 +82,16 @@ public async Task CoreUsersList( return await _client.SendAsync(HttpMethod.Get, url, cancellationToken: cancellationToken); } + + /// + /// User Viewset + /// + /// A unique integer value identifying this User. + /// + /// + public async Task CoreUsersRetrieve(string id, CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Get, $"/core/users/{id}/", + cancellationToken: cancellationToken); + } } \ No newline at end of file From 0666e9cbc73b62ba8de4dfad0c3b963b46bdf737 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 11 Dec 2024 22:54:42 +0300 Subject: [PATCH 09/34] fix: change string to int --- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index f4471b5..a03bf66 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -89,7 +89,7 @@ public async Task CoreUsersList( /// A unique integer value identifying this User. /// /// - public async Task CoreUsersRetrieve(string id, CancellationToken cancellationToken = default) + public async Task CoreUsersRetrieve(int id, CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Get, $"/core/users/{id}/", cancellationToken: cancellationToken); From ab0879e9350f2d09a1a3bcdc8edd994dd5546b21 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Fri, 13 Dec 2024 17:48:34 +0300 Subject: [PATCH 10/34] update: add 404 exception --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- src/AuthentikNet.Api/Client/AuthentikClient.cs | 1 + src/AuthentikNet.Api/Client/AuthentikException.cs | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index ebd346c..ac3706a 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev.18 + 0.0.1-dev.19 Saph1s Authentik api client Copyright (c) Saph1s 2024 diff --git a/src/AuthentikNet.Api/Client/AuthentikClient.cs b/src/AuthentikNet.Api/Client/AuthentikClient.cs index 56e76ed..ced44c0 100644 --- a/src/AuthentikNet.Api/Client/AuthentikClient.cs +++ b/src/AuthentikNet.Api/Client/AuthentikClient.cs @@ -99,6 +99,7 @@ private async Task HandleError(HttpResponseMessage response) HttpStatusCode.BadRequest => new AuthentikBadRequestException(errorMessage), HttpStatusCode.Unauthorized => new AuthentikUnauthorizedException(errorMessage), HttpStatusCode.Forbidden => new AuthentikForbiddenException(errorMessage), + HttpStatusCode.NotFound => new AuthentikNotFoundException(errorMessage), _ => new AuthentikException(errorMessage, (int)response.StatusCode) }; } diff --git a/src/AuthentikNet.Api/Client/AuthentikException.cs b/src/AuthentikNet.Api/Client/AuthentikException.cs index 5b7a35b..7886c96 100644 --- a/src/AuthentikNet.Api/Client/AuthentikException.cs +++ b/src/AuthentikNet.Api/Client/AuthentikException.cs @@ -29,4 +29,11 @@ public class AuthentikForbiddenException : AuthentikException public AuthentikForbiddenException(string message) : base(message, 403) { } +} + +public class AuthentikNotFoundException : AuthentikException +{ + public AuthentikNotFoundException(string message) : base(message, 404) + { + } } \ No newline at end of file From cd9bb00c217e19bb3f650bd7dcdbcfd6ee6c8ca6 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Fri, 13 Dec 2024 22:18:04 +0300 Subject: [PATCH 11/34] update: add doc --- src/AuthentikNet.Api/Client/AuthentikException.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/AuthentikNet.Api/Client/AuthentikException.cs b/src/AuthentikNet.Api/Client/AuthentikException.cs index 7886c96..46ed12f 100644 --- a/src/AuthentikNet.Api/Client/AuthentikException.cs +++ b/src/AuthentikNet.Api/Client/AuthentikException.cs @@ -1,5 +1,8 @@ namespace AuthentikNet.Api.Client; +/// +/// Generic Authentik exception +/// public class AuthentikException : Exception { public int StatusCode { get; } @@ -10,6 +13,9 @@ public AuthentikException(string message, int statusCode) : base(message) } } +/// +/// "Bad request" exception +/// public class AuthentikBadRequestException : AuthentikException { public AuthentikBadRequestException(string message) : base(message, 400) @@ -17,6 +23,9 @@ public AuthentikBadRequestException(string message) : base(message, 400) } } +/// +/// "Unauthorized" exception +/// public class AuthentikUnauthorizedException : AuthentikException { public AuthentikUnauthorizedException(string message) : base(message, 401) @@ -24,6 +33,9 @@ public AuthentikUnauthorizedException(string message) : base(message, 401) } } +/// +/// "Forbidden" exception +/// public class AuthentikForbiddenException : AuthentikException { public AuthentikForbiddenException(string message) : base(message, 403) @@ -31,6 +43,9 @@ public AuthentikForbiddenException(string message) : base(message, 403) } } +/// +/// "Not found" exception +/// public class AuthentikNotFoundException : AuthentikException { public AuthentikNotFoundException(string message) : base(message, 404) From 93c6703ccc6fa199d3f2c721d3632c7679f81692 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Wed, 15 Jan 2025 20:43:36 +0300 Subject: [PATCH 12/34] add: new CoreUsersPartialUpdate --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 13 ++++++++++++ .../Models/PatchedUserRequest.cs | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/AuthentikNet.Api/Models/PatchedUserRequest.cs diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index ac3706a..68b2c2d 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev.19 + 0.0.1-dev.21 Saph1s Authentik api client Copyright (c) Saph1s 2024 diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index a03bf66..b78560f 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -94,4 +94,17 @@ public async Task CoreUsersRetrieve(int id, CancellationToken cancellation return await _client.SendAsync(HttpMethod.Get, $"/core/users/{id}/", cancellationToken: cancellationToken); } + + /// + /// User Viewset + /// + /// A unique integer value identifying this User. + /// UserRequest + /// + /// + public async Task CoreUsersPartialUpdate(int id, PatchedUserRequest data, + CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Patch, $"/core/users/{id}/", data, cancellationToken); + } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/PatchedUserRequest.cs b/src/AuthentikNet.Api/Models/PatchedUserRequest.cs new file mode 100644 index 0000000..6be0c4c --- /dev/null +++ b/src/AuthentikNet.Api/Models/PatchedUserRequest.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; + +namespace AuthentikNet.Api.Models; + +public class PatchedUserRequest +{ + [JsonPropertyName("username")] public string? Username { get; set; } + [JsonPropertyName("name")] public string? Name { get; set; } + [JsonPropertyName("is_active")] public bool? IsActive { get; set; } + [JsonPropertyName("last_login")] public DateTime? LastLogin { get; set; } + [JsonPropertyName("groups")] public List? Groups { get; set; } + [JsonPropertyName("email")] public string? Email { get; set; } + + [JsonPropertyName("attributes")] + [JsonConverter(typeof(DynamicAttributesJsonConverter))] + public object? Attributes { get; set; } + + [JsonPropertyName("path")] public string? Path { get; set; } + [JsonPropertyName("type")] public UserTypeEnum? Type { get; set; } +} \ No newline at end of file From acdff6ba9a90a446c8a28eaa0e3cd980cc196f34 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Thu, 13 Mar 2025 18:59:12 +0300 Subject: [PATCH 13/34] update: improve documentation and add CoreUsersCreate method --- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index b78560f..d79a0bc 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -12,7 +12,7 @@ public CoreApi(AuthentikClient client) } /// - /// User Viewset + /// Retrieve all users /// /// Attributes /// @@ -84,7 +84,18 @@ public async Task CoreUsersList( } /// - /// User Viewset + /// Create user + /// + /// UserRequest + /// + /// + public async Task CoreUsersCreate(UserRequest data, CancellationToken cancellationToken = default) + { + return await _client.SendAsync(HttpMethod.Post, "/core/users/", data, cancellationToken); + } + + /// + /// Retrieve user /// /// A unique integer value identifying this User. /// @@ -96,7 +107,7 @@ public async Task CoreUsersRetrieve(int id, CancellationToken cancellation } /// - /// User Viewset + /// Partial update user /// /// A unique integer value identifying this User. /// UserRequest From 3413db9bf91ee4340ec59884b530311bb3885a5e Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Thu, 13 Mar 2025 19:03:09 +0300 Subject: [PATCH 14/34] update: bump version to 0.0.2 --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index 68b2c2d..4a761a6 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.1-dev.21 + 0.0.2 Saph1s Authentik api client Copyright (c) Saph1s 2024 From 0765998f660cc9771c865002f475f0414b5521a6 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 23 Apr 2025 18:44:25 +0300 Subject: [PATCH 15/34] update: enhance AdminApi with new settings methods and improve Settings model --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- src/AuthentikNet.Api/Client/Admin/AdminApi.cs | 32 +++++++ src/AuthentikNet.Api/Models/Settings.cs | 91 ++++++++++++++++--- 3 files changed, 113 insertions(+), 12 deletions(-) diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index 4a761a6..4f401af 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.2 + 0.0.3 Saph1s Authentik api client Copyright (c) Saph1s 2024 diff --git a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs index 6e9858f..b4ef844 100644 --- a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs +++ b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs @@ -12,34 +12,66 @@ public AdminApi(AuthentikClient client) _client = client; } + /// + /// Read-only view list all installed apps + /// + /// + /// public async Task> AdminAppsList(CancellationToken cancellationToken = default) { return await _client.SendAsync>(HttpMethod.Get, "/admin/apps/", cancellationToken: cancellationToken); } + /// + /// Login Metrics per 1h + /// + /// + /// public async Task AdminMetricsRetrieve(CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Get, "/admin/metrics/", cancellationToken: cancellationToken); } + /// + /// Read-only view list all installed models + /// + /// + /// public async Task> AdminModelsList(CancellationToken cancellationToken = default) { return await _client.SendAsync>(HttpMethod.Get, "/admin/models/", cancellationToken: cancellationToken); } + /// + /// Get settings + /// + /// + /// public async Task AdminSettingsRetrieve(CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Get, "/admin/settings/", cancellationToken: cancellationToken); } + /// + /// Update settings + /// + /// Settings model + /// + /// public async Task AdminSettingsUpdate(Settings data, CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Put, "/admin/settings/", data, cancellationToken); } + /// + /// Partial update settings + /// + /// PartialSettings moder + /// + /// public async Task AdminSettingsPartialUpdate(Settings data, CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Patch, "/admin/settings/", data, cancellationToken); diff --git a/src/AuthentikNet.Api/Models/Settings.cs b/src/AuthentikNet.Api/Models/Settings.cs index efd66d0..2b78be3 100644 --- a/src/AuthentikNet.Api/Models/Settings.cs +++ b/src/AuthentikNet.Api/Models/Settings.cs @@ -2,30 +2,99 @@ namespace AuthentikNet.Api.Models; +/// +/// Settings +/// public class Settings { - [JsonPropertyName("avatars")] public string Avatars { get; set; } = string.Empty; + private int _defaultTokenLength; + /// + /// Configure how authentik should show avatars for users. + /// + [JsonPropertyName("avatars")] + public required string Avatars { get; set; } + + /// + /// Enable the ability for users to change their name. + /// [JsonPropertyName("default_user_change_name")] - public bool DefaultUserChangeName { get; set; } + public required bool DefaultUserChangeName { get; set; } + /// + /// Enable the ability for users to change their email address. + /// [JsonPropertyName("default_user_change_email")] - public bool DefaultUserChangeEmail { get; set; } + public required bool DefaultUserChangeEmail { get; set; } + /// + /// Enable the ability for users to change their username. + /// [JsonPropertyName("default_user_change_username")] - public bool DefaultUserChangeUsername { get; set; } + public required bool DefaultUserChangeUsername { get; set; } + + /// + /// Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2). + /// + [JsonPropertyName("event_retention")] + public required string EventRetention { get; set; } + + /// + /// Reputation cannot decrease lower than this value. Zero or negative. + /// + [JsonPropertyName("reputation_lower_limit")] + public required int ReputationLowerLimit { get; set; } + + /// + /// Reputation cannot increase higher than this value. Zero or positive. + /// + [JsonPropertyName("reputation_upper_limit")] + public required int ReputationUpperLimit { get; set; } - [JsonPropertyName("event_retention")] public string EventRetention { get; set; } = string.Empty; - [JsonPropertyName("footer_links")] public List<(string href, string name)> FooterLinks { get; set; } - [JsonPropertyName("gdpr_compliance")] public bool GdprCompliance { get; set; } - [JsonPropertyName("impersonation")] public bool Impersonation { get; set; } + /// + /// The option configures the footer links on the flow executor pages. + /// + [JsonPropertyName("footer_links")] + public required List<(string href, string name)> FooterLinks { get; set; } = []; + /// + /// When enabled, all the events caused by a user will be deleted upon the user's deletion. + /// + [JsonPropertyName("gdpr_compliance")] + public required bool GdprCompliance { get; set; } + + /// + /// Globally enable/disable impersonation. + /// + [JsonPropertyName("impersonation")] + public required bool Impersonation { get; set; } + + /// + /// Require administrators to provide a reason for impersonating a user. + /// [JsonPropertyName("impersonation_require_reason")] - public bool ImpersonationRequireReason { get; set; } + public required bool ImpersonationRequireReason { get; set; } + /// + /// Default token duration + /// [JsonPropertyName("default_token_duration")] - public string DefaultTokenDuration { get; set; } = string.Empty; + public required string DefaultTokenDuration { get; set; } + /// + /// Default token length + /// [JsonPropertyName("default_token_length")] - public int DefaultTokenLength { get; set; } + public required int DefaultTokenLength + { + get => _defaultTokenLength; + set + { + if (value < 1) + throw new ArgumentOutOfRangeException(nameof(DefaultTokenLength), + "Value must be greater or equal to 1 and less or equal to 2147483647"); + + _defaultTokenLength = value; + } + } } \ No newline at end of file From 80f52d9837f4439e65ef30dae1165754629c1537 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 23 Apr 2025 18:52:07 +0300 Subject: [PATCH 16/34] update: add PatchedSettingsRequest model and modify AdminSettingsPartialUpdate method --- src/AuthentikNet.Api/Client/Admin/AdminApi.cs | 2 +- .../Models/PatchedSettingsRequest.cs | 107 ++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs diff --git a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs index b4ef844..dc65b4d 100644 --- a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs +++ b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs @@ -72,7 +72,7 @@ public async Task AdminSettingsUpdate(Settings data, CancellationToken /// PartialSettings moder /// /// - public async Task AdminSettingsPartialUpdate(Settings data, CancellationToken cancellationToken = default) + public async Task AdminSettingsPartialUpdate(PatchedSettingsRequest data, CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Patch, "/admin/settings/", data, cancellationToken); } diff --git a/src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs b/src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs new file mode 100644 index 0000000..9de103a --- /dev/null +++ b/src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs @@ -0,0 +1,107 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +/// +/// Settings PATCH request +/// +public class PatchedSettingsRequest +{ + private int? _defaultTokenLength; + + /// + /// Configure how authentik should show avatars for users. + /// + [JsonPropertyName("avatars")] + public string? Avatars { get; set; } + + /// + /// Enable the ability for users to change their name. + /// + [JsonPropertyName("default_user_change_name")] + public bool? DefaultUserChangeName { get; set; } + + /// + /// Enable the ability for users to change their email address. + /// + [JsonPropertyName("default_user_change_email")] + public bool? DefaultUserChangeEmail { get; set; } + + /// + /// Enable the ability for users to change their username. + /// + [JsonPropertyName("default_user_change_username")] + public bool? DefaultUserChangeUsername { get; set; } + + /// + /// Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2). + /// + [JsonPropertyName("event_retention")] + public string? EventRetention { get; set; } + + /// + /// Reputation cannot decrease lower than this value. Zero or negative. + /// + [JsonPropertyName("reputation_lower_limit")] + public int? ReputationLowerLimit { get; set; } + + /// + /// Reputation cannot increase higher than this value. Zero or positive. + /// + [JsonPropertyName("reputation_upper_limit")] + public int? ReputationUpperLimit { get; set; } + + /// + /// The option configures the footer links on the flow executor pages. + /// + [JsonPropertyName("footer_links")] + public List<(string href, string name)>? FooterLinks { get; set; } = []; + + /// + /// When enabled, all the events caused by a user will be deleted upon the user's deletion. + /// + [JsonPropertyName("gdpr_compliance")] + public bool? GdprCompliance { get; set; } + + /// + /// Globally enable/disable impersonation. + /// + [JsonPropertyName("impersonation")] + public bool? Impersonation { get; set; } + + /// + /// Require administrators to provide a reason for impersonating a user. + /// + [JsonPropertyName("impersonation_require_reason")] + public bool? ImpersonationRequireReason { get; set; } + + /// + /// Default token duration + /// + [JsonPropertyName("default_token_duration")] + public string? DefaultTokenDuration { get; set; } + + /// + /// Default token length + /// + [JsonPropertyName("default_token_length")] + public int? DefaultTokenLength + { + get => _defaultTokenLength; + set + { + if (value < 1) + throw new ArgumentOutOfRangeException(nameof(DefaultTokenLength), + "Value must be greater or equal to 1 and less or equal to 2147483647"); + + _defaultTokenLength = value; + } + } + + /// + /// PatchedSettingsRequest constructor + /// + public PatchedSettingsRequest() + { + } +} \ No newline at end of file From 8d2a0474068a55840ac997adeeada6aca0d7c602 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 23 Apr 2025 18:58:23 +0300 Subject: [PATCH 17/34] update: enhance JSON converters with documentation and improve enum descriptions --- src/AuthentikNet.Api/Models/UserTypeEnum.cs | 2 +- .../Utils/DynamicAttributesJsonConverter.cs | 4 +++ .../Utils/SourceGenerationContext.cs | 32 ------------------- .../Utils/UserTypeEnumConverter.cs | 5 +++ 4 files changed, 10 insertions(+), 33 deletions(-) delete mode 100644 src/AuthentikNet.Api/Utils/SourceGenerationContext.cs diff --git a/src/AuthentikNet.Api/Models/UserTypeEnum.cs b/src/AuthentikNet.Api/Models/UserTypeEnum.cs index e27a672..2fb9662 100644 --- a/src/AuthentikNet.Api/Models/UserTypeEnum.cs +++ b/src/AuthentikNet.Api/Models/UserTypeEnum.cs @@ -5,7 +5,7 @@ namespace AuthentikNet.Api.Models; /// -/// The type of account +/// User account type /// [JsonConverter(typeof(UserTypeEnumConverter))] public enum UserTypeEnum diff --git a/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs b/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs index 101241d..700612a 100644 --- a/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs +++ b/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs @@ -3,8 +3,12 @@ namespace AuthentikNet.Api.Utils; +/// +/// Custom JSON converter for handling dynamic attributes. +/// public class DynamicAttributesJsonConverter : JsonConverter { + /// public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return ParseValue(ref reader, options); diff --git a/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs b/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs deleted file mode 100644 index eeb3bee..0000000 --- a/src/AuthentikNet.Api/Utils/SourceGenerationContext.cs +++ /dev/null @@ -1,32 +0,0 @@ -// using System.Text.Json.Serialization; -// using AuthentikNet.Api.Models; -// using Version = AuthentikNet.Api.Models.Version; -// -// namespace AuthentikNet.Api.Utils; -// -// [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(App))] -// [JsonSerializable(typeof(Coordinate))] -// [JsonSerializable(typeof(Group))] -// [JsonSerializable(typeof(GroupMember))] -// [JsonSerializable(typeof(LoginMetrics))] -// [JsonSerializable(typeof(PaginatedUserList))] -// [JsonSerializable(typeof(Pagination))] -// [JsonSerializable(typeof(Role))] -// [JsonSerializable(typeof(Settings))] -// [JsonSerializable(typeof(SystemInfo))] -// [JsonSerializable(typeof(SystemInfoRuntime))] -// [JsonSerializable(typeof(User))] -// [JsonSerializable(typeof(UserGroup))] -// [JsonSerializable(typeof(UserRequest))] -// [JsonSerializable(typeof(UserTypeEnum))] -// [JsonSerializable(typeof(Version))] -// [JsonSerializable(typeof(VersionHistory))] -// [JsonSerializable(typeof(Workers))] -// [JsonSerializable(typeof(Dictionary))] -// [JsonSerializable(typeof(List))] -// [JsonSerializable(typeof(double))] -// [JsonSerializable(typeof(object))] -// public partial class SourceGenerationContext : JsonSerializerContext -// { -// } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs b/src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs index e2bc401..812b9ac 100644 --- a/src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs +++ b/src/AuthentikNet.Api/Utils/UserTypeEnumConverter.cs @@ -4,8 +4,12 @@ namespace AuthentikNet.Api.Utils; +/// +/// Converter for . +/// public class UserTypeEnumConverter : JsonConverter { + /// public override UserTypeEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var value = reader.GetString(); @@ -19,6 +23,7 @@ public override UserTypeEnum Read(ref Utf8JsonReader reader, Type typeToConvert, }; } + /// public override void Write(Utf8JsonWriter writer, UserTypeEnum value, JsonSerializerOptions options) { var stringValue = value switch From 9ebb7d04bfd1e32bfa2871721c1ad8b31529c42d Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 23 Apr 2025 19:18:35 +0300 Subject: [PATCH 18/34] update: bump version to 0.0.4, add LICENSE and NOTICE files, and improve project metadata --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 24 ++- src/AuthentikNet.Api/LICENSE | 201 ++++++++++++++++++ src/AuthentikNet.Api/NOTICE | 6 + .../Utils/DynamicAttributesJsonConverter.cs | 1 + 4 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 src/AuthentikNet.Api/LICENSE create mode 100644 src/AuthentikNet.Api/NOTICE diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index 4f401af..96194e9 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -4,20 +4,34 @@ net8.0 enable enable - 0.0.3 + + 0.0.4 + Saph1s - Authentik api client - Copyright (c) Saph1s 2024 - https://github.com/Saph1s/AuthentikNet Saph1s + Authentik API client + Copyright 2024-2025 Anton "Saph1s" Babenko + + https://github.com/Saph1s/AuthentikNet + https://github.com/Saph1s/AuthentikNet + git + true true + + Apache-2.0 README.md - https://github.com/Saph1s/AuthentikNet + + + + authentik;sso;identity + Authentik compatible version - 2025.2.4 + + diff --git a/src/AuthentikNet.Api/LICENSE b/src/AuthentikNet.Api/LICENSE new file mode 100644 index 0000000..886bc77 --- /dev/null +++ b/src/AuthentikNet.Api/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024-2025 Anton "Saph1s" Babenko (https://github.com/saph1s) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/AuthentikNet.Api/NOTICE b/src/AuthentikNet.Api/NOTICE new file mode 100644 index 0000000..30bbde1 --- /dev/null +++ b/src/AuthentikNet.Api/NOTICE @@ -0,0 +1,6 @@ +Copyright 2024-2025 Anton "Saph1s" Babenko (https://github.com/saph1s) + +This product includes software developed by: +- Anton Babenko (https://github.com/saph1s) + +Licensed under the Apache License, Version 2.0 - see LICENSE file. \ No newline at end of file diff --git a/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs b/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs index 700612a..ae576f7 100644 --- a/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs +++ b/src/AuthentikNet.Api/Utils/DynamicAttributesJsonConverter.cs @@ -67,6 +67,7 @@ public class DynamicAttributesJsonConverter : JsonConverter return list; } + /// public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, value, options); From 8bdd83e153219593e6f620963a579797764f58cb Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Thu, 24 Apr 2025 19:53:50 +0300 Subject: [PATCH 19/34] update: remove unused license from root dir --- LICENSE | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b4a0206..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Anton "Saph1s" Babenko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file From 2fe16fa28675449e80c7a059cbbe8fce52949908 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Sat, 3 May 2025 18:37:58 +0300 Subject: [PATCH 20/34] update: refactor AuthentikClient constructor and bump version to 0.0.5; add NuGet publish workflow --- .github/workflows/nuget-publish.yml | 91 +++++++++++++++++++ src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- .../Client/AuthentikClient.cs | 14 +-- 3 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/nuget-publish.yml diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml new file mode 100644 index 0000000..a7fe85a --- /dev/null +++ b/.github/workflows/nuget-publish.yml @@ -0,0 +1,91 @@ +name: NuGet | Build and Publish + +on: + workflow_dispatch: + push: + branches: + - main + - dev + tags: [ "[0-9]+.[0-9]+.[0-9]+" ] + release: + types: + - published + + +env: + PROJECT_PATH: ./src/AuthentikNet.Api/AuthentikNet.Api.csproj + PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}/output + NUGET_SOURCE_URL: "https://api.nuget.org/v3/index.json" + +jobs: + build-nuget: + name: Build NuGet package + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore ${{ env.PROJECT_PATH }} + + - name: Extract version from project file + id: extract_version + shell: bash + run: | + VERSION=$(grep -oPm1 "(?<=)[^<]+" ${{ env.PROJECT_PATH }}) + echo "PROJECT_VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Get short SHA + id: short_sha + run: echo "SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Build + run: dotnet build ${{ env.PROJECT_PATH }} --configuration Release --no-restore + + # - name: Pack + # run: dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + - name: Pack + run: | + if [[ "${{ github.ref }}" == refs/heads/dev ]]; then + dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} --version-suffix "-${{ steps.short_sha.outputs.SHA }}" + else + dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + fi + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: nuget-package + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/*.nupkg + + publish-nuget: + name: Publish NuGet package + needs: build-nuget + if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/dev' + runs-on: ubuntu-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: nuget-package + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Publish to NuGet + run: | + dotnet nuget push "${{ env.PACKAGE_OUTPUT_DIRECTORY }}/*.nupkg" \ + --api-key ${{ secrets.NUGET_API_KEY }} \ + --source ${{ env.NUGET_SOURCE_URL }} \ + --skip-duplicate \ No newline at end of file diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index 96194e9..cd8ad2e 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -5,7 +5,7 @@ enable enable - 0.0.4 + 0.0.5 Saph1s Saph1s diff --git a/src/AuthentikNet.Api/Client/AuthentikClient.cs b/src/AuthentikNet.Api/Client/AuthentikClient.cs index ced44c0..ca88f79 100644 --- a/src/AuthentikNet.Api/Client/AuthentikClient.cs +++ b/src/AuthentikNet.Api/Client/AuthentikClient.cs @@ -15,15 +15,13 @@ public class AuthentikClient public AdminApi Admin { get; } public CoreApi Core { get; } - public AuthentikClient(AuthentikClientOptions options) + public AuthentikClient(HttpClient client, AuthentikClientOptions options) { + _client = client ?? throw new ArgumentNullException(nameof(client)); _options = options ?? throw new ArgumentNullException(nameof(options)); - _client = new HttpClient - { - BaseAddress = new Uri(options.BaseUrl), - Timeout = options.Timeout - }; + _client.BaseAddress = new Uri(options.BaseUrl); + _client.Timeout = options.Timeout; _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.Token); _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); @@ -34,6 +32,10 @@ public AuthentikClient(AuthentikClientOptions options) Core = new CoreApi(this); } + public AuthentikClient(AuthentikClientOptions options) : this(new HttpClient(), options) + { + } + public async Task SendAsync(HttpMethod method, string path, object? data = null, CancellationToken cancellationToken = default) { From bc3682c81e156cafc900eafad4ba199ff9ae154d Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Sun, 4 May 2025 05:41:05 +0300 Subject: [PATCH 21/34] update: improve NuGet publish workflow --- .github/workflows/nuget-publish.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index a7fe85a..dedd651 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -54,7 +54,7 @@ jobs: # run: dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} - name: Pack run: | - if [[ "${{ github.ref }}" == refs/heads/dev ]]; then + if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} --version-suffix "-${{ steps.short_sha.outputs.SHA }}" else dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} @@ -85,7 +85,7 @@ jobs: - name: Publish to NuGet run: | - dotnet nuget push "${{ env.PACKAGE_OUTPUT_DIRECTORY }}/*.nupkg" \ - --api-key ${{ secrets.NUGET_API_KEY }} \ - --source ${{ env.NUGET_SOURCE_URL }} \ - --skip-duplicate \ No newline at end of file + for f in ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/*.nupkg; do + echo "Publishing $f to NuGet..." + dotnet nuget push "$f" --source ${{ env.NUGET_SOURCE_URL }} --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate + done \ No newline at end of file From 4a137a1508ae7d2de018096481bec52309ad009d Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Sun, 4 May 2025 05:45:50 +0300 Subject: [PATCH 22/34] update: modify NuGet pack command to include version suffix from extracted project version --- .github/workflows/nuget-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index dedd651..98ad876 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -55,7 +55,7 @@ jobs: - name: Pack run: | if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then - dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} --version-suffix "-${{ steps.short_sha.outputs.SHA }}" + dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} /p:Version="${{ steps.extract_version.outputs.PROJECT_VERSION }}-${{ steps.short_sha.outputs.SHA }}" else dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} fi From 2a768edc20168acb87a86c616f249811676fb0e2 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Sun, 4 May 2025 05:49:38 +0300 Subject: [PATCH 23/34] fix: fix workflow pt.2 --- .github/workflows/nuget-publish.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index 98ad876..7aa3bab 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -48,17 +48,15 @@ jobs: run: echo "SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Build - run: dotnet build ${{ env.PROJECT_PATH }} --configuration Release --no-restore - - # - name: Pack - # run: dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} - - name: Pack run: | if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then - dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} /p:Version="${{ steps.extract_version.outputs.PROJECT_VERSION }}-${{ steps.short_sha.outputs.SHA }}" + dotnet build ${{ env.PROJECT_PATH }} --configuration Release --no-restore /p:Version="${{ steps.extract_version.outputs.PROJECT_VERSION }}-${{ steps.short_sha.outputs.SHA }}" else - dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + dotnet build ${{ env.PROJECT_PATH }} --configuration Release --no-restore fi + + - name: Pack + run: dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} - name: Upload artifact uses: actions/upload-artifact@v4 From f9ace967feddffc447fbb4d8eef2e985879c0d19 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 7 May 2025 12:28:55 +0300 Subject: [PATCH 24/34] update: enhance NuGet versioning logic and streamline build process --- .github/workflows/nuget-publish.yml | 34 +++++++++++++++++++---------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index 7aa3bab..e92e241 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -40,23 +40,35 @@ jobs: shell: bash run: | VERSION=$(grep -oPm1 "(?<=)[^<]+" ${{ env.PROJECT_PATH }}) - echo "PROJECT_VERSION=$VERSION" >> $GITHUB_OUTPUT - echo "Extracted version: $VERSION" + PACKAGE_ID=$(grep -oPm1 "(?<=)[^<]+" ${{ env.PROJECT_PATH }} || basename ${{ env.PROJECT_PATH }} .csproj) + VERSIONS=$(curl -s "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID,,}/index.json" | jq -r '.versions[]' | grep "^${VERSION}-dev\." || true) + if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then + FINAL_VERSION="$VERSION" + elif [[ "${GITHUB_REF}" == "refs/heads/dev" ]]; then + VERSIONS=$(curl -s "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID,,}/index.json" | jq -r '.versions[]' | grep "^${VERSION}-dev\." || true) + if [[ -z "$VERSIONS" ]]; then + N=0 + else + N=$(echo "$VERSIONS" | sed -E "s/^${VERSION}-dev\.([0-9]+)$/\1/" | sort -nr | head -n1) + N=$((N+1)) + fi + FINAL_VERSION="${VERSION}-dev.${N}" + else + SHORT_SHA=$(git rev-parse --short HEAD) + FINAL_VERSION="${VERSION}-${SHORT_SHA}" + fi + echo "PROJECT_VERSION=$FINAL_VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $FINAL_VERSION" - name: Get short SHA id: short_sha run: echo "SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Build - run: | - if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then - dotnet build ${{ env.PROJECT_PATH }} --configuration Release --no-restore /p:Version="${{ steps.extract_version.outputs.PROJECT_VERSION }}-${{ steps.short_sha.outputs.SHA }}" - else - dotnet build ${{ env.PROJECT_PATH }} --configuration Release --no-restore - fi - + run: dotnet build ${{ env.PROJECT_PATH }} --configuration Release --no-restore /p:Version="${{ steps.extract_version.outputs.PROJECT_VERSION }}" + - name: Pack - run: dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: dotnet pack ${{ env.PROJECT_PATH }} --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} /p:Version="${{ steps.extract_version.outputs.PROJECT_VERSION }}" - name: Upload artifact uses: actions/upload-artifact@v4 @@ -67,7 +79,7 @@ jobs: publish-nuget: name: Publish NuGet package needs: build-nuget - if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/dev' + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' runs-on: ubuntu-latest steps: - name: Download artifact From 36d7b7ccd0cf61f90b0b9ea855f4a666bc011278 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 7 May 2025 12:36:01 +0300 Subject: [PATCH 25/34] update: enhance versioning logic to ensure new versions are greater than existing ones --- .github/workflows/nuget-publish.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index e92e241..aa9b7f0 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -41,15 +41,16 @@ jobs: run: | VERSION=$(grep -oPm1 "(?<=)[^<]+" ${{ env.PROJECT_PATH }}) PACKAGE_ID=$(grep -oPm1 "(?<=)[^<]+" ${{ env.PROJECT_PATH }} || basename ${{ env.PROJECT_PATH }} .csproj) - VERSIONS=$(curl -s "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID,,}/index.json" | jq -r '.versions[]' | grep "^${VERSION}-dev\." || true) + ALL_VERSIONS=$(curl -s "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID,,}/index.json" | jq -r '.versions[]' || true) + MAX_VERSION=$(echo "$ALL_VERSIONS" | sort -V | tail -n1) if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then FINAL_VERSION="$VERSION" elif [[ "${GITHUB_REF}" == "refs/heads/dev" ]]; then - VERSIONS=$(curl -s "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID,,}/index.json" | jq -r '.versions[]' | grep "^${VERSION}-dev\." || true) - if [[ -z "$VERSIONS" ]]; then + DEV_VERSIONS=$(echo "$ALL_VERSIONS" | grep "^${VERSION}-dev\." || true) + if [[ -z "$DEV_VERSIONS" ]]; then N=0 else - N=$(echo "$VERSIONS" | sed -E "s/^${VERSION}-dev\.([0-9]+)$/\1/" | sort -nr | head -n1) + N=$(echo "$DEV_VERSIONS" | sed -E "s/^${VERSION}-dev\.([0-9]+)$/\1/" | sort -nr | head -n1) N=$((N+1)) fi FINAL_VERSION="${VERSION}-dev.${N}" @@ -57,6 +58,14 @@ jobs: SHORT_SHA=$(git rev-parse --short HEAD) FINAL_VERSION="${VERSION}-${SHORT_SHA}" fi + + if [[ -n "$MAX_VERSION" ]]; then + if [[ "$(printf '%s\n' "$MAX_VERSION" "$FINAL_VERSION" | sort -V | tail -n1)" != "$FINAL_VERSION" ]]; then + echo "Error: The version $FINAL_VERSION is not greater than the latest version $MAX_VERSION." + exit 1 + fi + fi + echo "PROJECT_VERSION=$FINAL_VERSION" >> $GITHUB_OUTPUT echo "Extracted version: $FINAL_VERSION" From bba1a88477c05ebcc89a8f1708f0ac97cb2760c3 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 7 May 2025 15:27:41 +0300 Subject: [PATCH 26/34] update: update models + add universal converter for enum + use authentik versioning --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 2 +- src/AuthentikNet.Api/Client/Admin/AdminApi.cs | 47 +++++++++++++++++-- src/AuthentikNet.Api/Models/SystemInfo.cs | 47 ++++++++++++++++--- src/AuthentikNet.Api/Models/UserTypeEnum.cs | 2 +- src/AuthentikNet.Api/Models/Version.cs | 36 ++++++++++++-- src/AuthentikNet.Api/Models/Workers.cs | 4 +- .../Utils/JsonStringEnumMemberConverter.cs | 34 ++++++++++++++ 7 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 src/AuthentikNet.Api/Utils/JsonStringEnumMemberConverter.cs diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index cd8ad2e..ab69548 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -5,7 +5,7 @@ enable enable - 0.0.5 + 2025.4 Saph1s Saph1s diff --git a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs index dc65b4d..108d076 100644 --- a/src/AuthentikNet.Api/Client/Admin/AdminApi.cs +++ b/src/AuthentikNet.Api/Client/Admin/AdminApi.cs @@ -72,29 +72,54 @@ public async Task AdminSettingsUpdate(Settings data, CancellationToken /// PartialSettings moder /// /// - public async Task AdminSettingsPartialUpdate(PatchedSettingsRequest data, CancellationToken cancellationToken = default) + public async Task AdminSettingsPartialUpdate(PatchedSettingsRequest data, + CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Patch, "/admin/settings/", data, cancellationToken); } + /// + /// Get system information + /// + /// + /// public async Task AdminSystemRetrieve(CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Get, "/admin/system/", cancellationToken: cancellationToken); } + /// + /// Get system information + /// + /// + /// public async Task AdminSystemCreate(CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Post, "/admin/system/", cancellationToken: cancellationToken); } + /// + /// Get running and latest version + /// + /// + /// public async Task AdminVersionRetrieve(CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Get, "/admin/version/", cancellationToken: cancellationToken); } + /// + /// VersionHistory Viewset + /// + /// + /// Which field to use when ordering the results. + /// A search term. + /// + /// + /// public async Task> AdminVersionHistoryList( string? build = null, string? ordering = null, @@ -109,23 +134,37 @@ public async Task> AdminVersionHistoryList( { "ordering", ordering }, { "search", search }, { "version", version } - }.Where(kv => kv.Value != null) - .ToDictionary(kv => kv.Key, kv => kv.Value); + } + .Where(kv => kv.Value != null) + .ToDictionary(kv => kv.Key, kv => kv.Value!); if (queryParameters.Count > 0) { - url += "?" + string.Join("&", queryParameters.Select(x => $"{x.Key}={x.Value}")); + var query = string.Join("&", queryParameters + .Select(x => $"{x.Key}={Uri.EscapeDataString(x.Value)}")); + url += "?" + query; } return await _client.SendAsync>(HttpMethod.Get, url, cancellationToken: cancellationToken); } + /// + /// VersionHistory Viewset + /// + /// A unique integer value identifying this Version history. + /// + /// public async Task AdminVersionHistoryRetrieve(int id, CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Get, $"/admin/version/history/{id}/", cancellationToken: cancellationToken); } + /// + /// Get currently connected worker count. + /// + /// + /// public async Task AdminWorkersRetrieve(CancellationToken cancellationToken = default) { return await _client.SendAsync(HttpMethod.Get, "/admin/workers/", diff --git a/src/AuthentikNet.Api/Models/SystemInfo.cs b/src/AuthentikNet.Api/Models/SystemInfo.cs index e371f20..7550896 100644 --- a/src/AuthentikNet.Api/Models/SystemInfo.cs +++ b/src/AuthentikNet.Api/Models/SystemInfo.cs @@ -4,16 +4,51 @@ namespace AuthentikNet.Api.Models; public class SystemInfo { - [JsonPropertyName("http_headers")] public required Dictionary HttpHeaders { get; init; } - [JsonPropertyName("http_host")] public required string HttpHost { get; init; } - [JsonPropertyName("http_is_secure")] public required bool HttpIsSecure { get; init; } - [JsonPropertyName("runtime")] public required SystemInfoRuntime Runtime { get; init; } - [JsonPropertyName("brand")] public required string Brand { get; init; } - [JsonPropertyName("server_time")] public required DateTime ServerTime { get; init; } + /// + /// Get HTTP Request headers + /// + [JsonPropertyName("http_headers")] + public required Dictionary HttpHeaders { get; init; } + /// + /// Get HTTP host + /// + [JsonPropertyName("http_host")] + public required string HttpHost { get; init; } + + /// + /// Get HTTP Secure flag + /// + [JsonPropertyName("http_is_secure")] + public required bool HttpIsSecure { get; init; } + + /// + /// Get versions + /// + [JsonPropertyName("runtime")] + public required SystemInfoRuntime Runtime { get; init; } + + /// + /// Currently active brand + /// + [JsonPropertyName("brand")] + public required string Brand { get; init; } + + /// + /// Current server time + /// + [JsonPropertyName("server_time")] + public required DateTime ServerTime { get; init; } + + /// + /// Whether the embedded outpost is disabled + /// [JsonPropertyName("embedded_outpost_disabled")] public required bool EmbeddedOutpostDisabled { get; init; } + /// + /// Get the FQDN configured on the embedded outpost + /// [JsonPropertyName("embedded_outpost_host")] public required string EmbeddedOutpostHost { get; init; } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/UserTypeEnum.cs b/src/AuthentikNet.Api/Models/UserTypeEnum.cs index 2fb9662..659ca03 100644 --- a/src/AuthentikNet.Api/Models/UserTypeEnum.cs +++ b/src/AuthentikNet.Api/Models/UserTypeEnum.cs @@ -7,7 +7,7 @@ namespace AuthentikNet.Api.Models; /// /// User account type /// -[JsonConverter(typeof(UserTypeEnumConverter))] +[JsonConverter(typeof(JsonStringEnumMemberConverter))] public enum UserTypeEnum { /// diff --git a/src/AuthentikNet.Api/Models/Version.cs b/src/AuthentikNet.Api/Models/Version.cs index c5cfc0d..012983d 100644 --- a/src/AuthentikNet.Api/Models/Version.cs +++ b/src/AuthentikNet.Api/Models/Version.cs @@ -4,13 +4,39 @@ namespace AuthentikNet.Api.Models; public class Version { - [JsonPropertyName("version_current")] public required string VersionCurrent { get; init; } - [JsonPropertyName("version_latest")] public required string VersionLatest { get; init; } + /// + /// Get current version + /// + [JsonPropertyName("version_current")] + public required string VersionCurrent { get; init; } + /// + /// Get latest version from cache + /// + [JsonPropertyName("version_latest")] + public required string VersionLatest { get; init; } + + /// + /// Check if latest version is valid + /// [JsonPropertyName("version_latest_valid")] public required bool VersionLatestValid { get; init; } - [JsonPropertyName("build_hash")] public required string BuildHash { get; init; } - [JsonPropertyName("outdated")] public required bool Outdated { get; init; } - [JsonPropertyName("outpost_outdated")] public required bool OutpostOutdated { get; init; } + /// + /// Get build hash, if version is not latest or released + /// + [JsonPropertyName("build_hash")] + public required string BuildHash { get; init; } + + /// + /// Check if we're running the latest version + /// + [JsonPropertyName("outdated")] + public required bool Outdated { get; init; } + + /// + /// Check if any outpost is outdated/has a version mismatch + /// + [JsonPropertyName("outpost_outdated")] + public required bool OutpostOutdated { get; init; } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/Workers.cs b/src/AuthentikNet.Api/Models/Workers.cs index 90b42fa..9fa4303 100644 --- a/src/AuthentikNet.Api/Models/Workers.cs +++ b/src/AuthentikNet.Api/Models/Workers.cs @@ -4,5 +4,7 @@ namespace AuthentikNet.Api.Models; public class Workers { - [JsonPropertyName("id")] public required int Id { get; set; } + [JsonPropertyName("worker_id")] public required int WorkerId { get; set; } + [JsonPropertyName("version")] public required string Version { get; set; } + [JsonPropertyName("version_matching")] public required bool VersionMatching { get; set; } } \ No newline at end of file diff --git a/src/AuthentikNet.Api/Utils/JsonStringEnumMemberConverter.cs b/src/AuthentikNet.Api/Utils/JsonStringEnumMemberConverter.cs new file mode 100644 index 0000000..1481c4e --- /dev/null +++ b/src/AuthentikNet.Api/Utils/JsonStringEnumMemberConverter.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Utils; + +/// +/// Universal JSON converter for Enums with EnumMember attributes. +/// +public class JsonStringEnumMemberConverter : JsonConverter where T : struct, Enum +{ + /// + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + foreach (var field in typeof(T).GetFields()) + { + var attr = field.GetCustomAttribute(); + if (attr != null && attr.Value == value) + return (T)field.GetValue(null)!; + } + + return Enum.Parse(value!); + } + + /// + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + var field = typeof(T).GetField(value.ToString()); + var attr = field?.GetCustomAttribute(); + writer.WriteStringValue(attr?.Value ?? value.ToString()); + } +} \ No newline at end of file From ba5e4706c3a41972f7498959da9c1e6af7ce80ab Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 11 Jun 2025 00:28:29 +0300 Subject: [PATCH 27/34] update: add models for SSF API including enums and data structures --- src/AuthentikNet.Api/Client/Ssf/SsfApi.cs | 54 +++++++++++++++++++ .../Models/AccessDeniedChallenge.cs | 6 +++ .../Models/DeliveryMethodEnum.cs | 21 ++++++++ .../Models/EventsRequestedEnum.cs | 18 +++++++ src/AuthentikNet.Api/Models/IntentEnum.cs | 14 +++++ .../Models/PaginatedSSFStreamList.cs | 9 ++++ src/AuthentikNet.Api/Models/SSFProvider.cs | 47 ++++++++++++++++ src/AuthentikNet.Api/Models/SSFStream.cs | 16 ++++++ src/AuthentikNet.Api/Models/Token.cs | 16 ++++++ 9 files changed, 201 insertions(+) create mode 100644 src/AuthentikNet.Api/Client/Ssf/SsfApi.cs create mode 100644 src/AuthentikNet.Api/Models/AccessDeniedChallenge.cs create mode 100644 src/AuthentikNet.Api/Models/DeliveryMethodEnum.cs create mode 100644 src/AuthentikNet.Api/Models/EventsRequestedEnum.cs create mode 100644 src/AuthentikNet.Api/Models/IntentEnum.cs create mode 100644 src/AuthentikNet.Api/Models/PaginatedSSFStreamList.cs create mode 100644 src/AuthentikNet.Api/Models/SSFProvider.cs create mode 100644 src/AuthentikNet.Api/Models/SSFStream.cs create mode 100644 src/AuthentikNet.Api/Models/Token.cs diff --git a/src/AuthentikNet.Api/Client/Ssf/SsfApi.cs b/src/AuthentikNet.Api/Client/Ssf/SsfApi.cs new file mode 100644 index 0000000..7b7e26b --- /dev/null +++ b/src/AuthentikNet.Api/Client/Ssf/SsfApi.cs @@ -0,0 +1,54 @@ +using AuthentikNet.Api.Models; + +namespace AuthentikNet.Api.Client.Ssf; + +public class SsfApi +{ + private readonly AuthentikClient _client; + + public SsfApi(AuthentikClient client) + { + _client = client; + } + + /// + /// SSFStream Viewset + /// + /// + /// + /// Which field to use when ordering the results + /// A page number within the paginated result set + /// Number of results to return per page + /// + /// A search term + /// + /// + public async Task SsfStreamsList(string? deliveryMethod, string? endpointUrl, + string? ordering, int? page, + int? pageSize, int? provider, string? search, CancellationToken cancellationToken = default) + { + var url = "/ssf/streams/"; + var queryParameters = new Dictionary + { + { "delivery_method", deliveryMethod }, + { "endpoint_url", endpointUrl }, + { "ordering", ordering }, + { "page", page }, + { "page_size", pageSize }, + { "provider", provider }, + { "search", search } + } + .Where(kv => kv.Value != null) + .ToDictionary(kv => kv.Key, kv => kv.Value!); + + if (queryParameters.Count > 0) + { + var query = string.Join("&", queryParameters + .Select(x => $"{x.Key}={Uri.EscapeDataString(x.Value.ToString()!)}")); + url += "?" + query; + } + + return await _client.SendAsync(HttpMethod.Get, url, + cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/AccessDeniedChallenge.cs b/src/AuthentikNet.Api/Models/AccessDeniedChallenge.cs new file mode 100644 index 0000000..0e1f65d --- /dev/null +++ b/src/AuthentikNet.Api/Models/AccessDeniedChallenge.cs @@ -0,0 +1,6 @@ +namespace AuthentikNet.Api.Models; + +public class AccessDeniedChallenge +{ + +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/DeliveryMethodEnum.cs b/src/AuthentikNet.Api/Models/DeliveryMethodEnum.cs new file mode 100644 index 0000000..92b4626 --- /dev/null +++ b/src/AuthentikNet.Api/Models/DeliveryMethodEnum.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; +using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; + +namespace AuthentikNet.Api.Models; + +[JsonConverter(typeof(JsonStringEnumMemberConverter))] +public enum DeliveryMethodEnum +{ + /// + /// PUSH + /// + [EnumMember(Value = "https://schemas.openid.net/secevent/risc/delivery-method/push")] + Push, + + /// + /// POLL + /// + [EnumMember(Value = "https://schemas.openid.net/secevent/risc/delivery-method/poll")] + Poll +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/EventsRequestedEnum.cs b/src/AuthentikNet.Api/Models/EventsRequestedEnum.cs new file mode 100644 index 0000000..35e78e5 --- /dev/null +++ b/src/AuthentikNet.Api/Models/EventsRequestedEnum.cs @@ -0,0 +1,18 @@ +using System.Runtime.Serialization; +using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; + +namespace AuthentikNet.Api.Models; + +[JsonConverter(typeof(JsonStringEnumMemberConverter))] +public enum EventsRequestedEnum +{ + [EnumMember(Value = "https://schemas.openid.net/secevent/caep/event-type/session-revoked")] + CAEP_EVENT_TYPE_SESSION_REVOKED, + + [EnumMember(Value = "https://schemas.openid.net/secevent/caep/event-type/credential-change")] + CAEP_EVENT_TYPE_CREDENTIAL_CHANGE, + + [EnumMember(Value = "https://schemas.openid.net/secevent/ssf/event-type/verification")] + SSF_EVENT_TYPE_VERIFICATION +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/IntentEnum.cs b/src/AuthentikNet.Api/Models/IntentEnum.cs new file mode 100644 index 0000000..2deef8c --- /dev/null +++ b/src/AuthentikNet.Api/Models/IntentEnum.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; +using System.Text.Json.Serialization; +using AuthentikNet.Api.Utils; + +namespace AuthentikNet.Api.Models; + +[JsonConverter(typeof(JsonStringEnumMemberConverter))] +public enum IntentEnum +{ + [EnumMember(Value = "verification")] Verification, + [EnumMember(Value = "api")] Api, + [EnumMember(Value = "recovery")] Recovery, + [EnumMember(Value = "app_password")] AppPassword, +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/PaginatedSSFStreamList.cs b/src/AuthentikNet.Api/Models/PaginatedSSFStreamList.cs new file mode 100644 index 0000000..4b8ba0f --- /dev/null +++ b/src/AuthentikNet.Api/Models/PaginatedSSFStreamList.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class PaginatedSSFStreamList +{ + [JsonPropertyName("pagination")] public required Pagination Pagination { get; set; } + [JsonPropertyName("results")] public required List Results { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/SSFProvider.cs b/src/AuthentikNet.Api/Models/SSFProvider.cs new file mode 100644 index 0000000..215e492 --- /dev/null +++ b/src/AuthentikNet.Api/Models/SSFProvider.cs @@ -0,0 +1,47 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class SSFProvider +{ + [JsonPropertyName("pk")] public required int Pk { get; init; } + [JsonPropertyName("name")] public required string Name { get; set; } + + /// + /// Get object component so that we know how to edit the object + /// + [JsonPropertyName("component")] + public required string Component { get; init; } + + /// + /// Return object's verbose_name + /// + [JsonPropertyName("verbose_name")] + public required string VerboseName { get; init; } + + /// + /// Return object's plural verbose_name + /// + [JsonPropertyName("verbose_name_plural")] + public required string VerboseNamePlural { get; init; } + + /// + /// Return internal model name + /// + [JsonPropertyName("meta_model_name")] + public required string MetaModelName { get; init; } + + /// + /// Key used to sign the SSF Events. + /// + [JsonPropertyName("signing_key")] + public required Guid SigningKey { get; init; } + + [JsonPropertyName("token_obj")] public required Token TokenObj { get; init; } + + [JsonPropertyName("oidc_auth_providers")] + public List? OidcAuthProviders { get; set; } + + [JsonPropertyName("ssf_url")] public required string? SSFUrl { get; init; } + [JsonPropertyName("event_retention")] public string? EventRetention { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/SSFStream.cs b/src/AuthentikNet.Api/Models/SSFStream.cs new file mode 100644 index 0000000..2f05763 --- /dev/null +++ b/src/AuthentikNet.Api/Models/SSFStream.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class SSFStream +{ + [JsonPropertyName("pk")] public required Guid Pk { get; init; } + [JsonPropertyName("provider")] public required int Provider { get; set; } + [JsonPropertyName("provider_obj")] public required SSFProvider ProviderObj { get; init; } + [JsonPropertyName("delivery_method")] public required DeliveryMethodEnum DeliveryMethod { get; set; } + [JsonPropertyName("endpoint_url")] public string? EndpointUrl { get; set; } + [JsonPropertyName("events_requested")] public List? EventsRequested { get; set; } + [JsonPropertyName("format")] public required string Format { get; set; } + [JsonPropertyName("aud")] public List? Aud { get; init; } + [JsonPropertyName("iss")] public required string Iss { get; set; } +} \ No newline at end of file diff --git a/src/AuthentikNet.Api/Models/Token.cs b/src/AuthentikNet.Api/Models/Token.cs new file mode 100644 index 0000000..19a8cc7 --- /dev/null +++ b/src/AuthentikNet.Api/Models/Token.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class Token +{ + [JsonPropertyName("pk")] public required Guid Pk { get; init; } + [JsonPropertyName("managed")] public string? Managed { get; set; } + [JsonPropertyName("identifier")] public required string Identifier { get; set; } + [JsonPropertyName("intent")] public IntentEnum? Intent { get; set; } + [JsonPropertyName("user")] public int? User { get; set; } + [JsonPropertyName("user_obj")] public required User UserObj { get; init; } + [JsonPropertyName("description")] public string? Description { get; set; } + [JsonPropertyName("expires")] public DateTime? Expires { get; set; } + [JsonPropertyName("expiring")] public bool? Expiring { get; set; } +} \ No newline at end of file From 41f7553f93abfd1365fde0700fa00e0759f358d0 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 11 Jun 2025 00:35:45 +0300 Subject: [PATCH 28/34] update: bump version to 2025.4.2 and update release notes --- src/AuthentikNet.Api/AuthentikNet.Api.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AuthentikNet.Api/AuthentikNet.Api.csproj b/src/AuthentikNet.Api/AuthentikNet.Api.csproj index ab69548..3588225 100644 --- a/src/AuthentikNet.Api/AuthentikNet.Api.csproj +++ b/src/AuthentikNet.Api/AuthentikNet.Api.csproj @@ -5,7 +5,7 @@ enable enable - 2025.4 + 2025.4.2 Saph1s Saph1s @@ -25,7 +25,7 @@ authentik;sso;identity - Authentik compatible version - 2025.2.4 + Authentik compatible version - 2025.4.2 From 0027355cc83c8d34f7509149bc467fdadf2c0502 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 11 Jun 2025 00:42:41 +0300 Subject: [PATCH 29/34] update: add CoreGroupsList method to retrieve paginated group data with filtering options --- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index d79a0bc..8262f03 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -118,4 +118,55 @@ public async Task CoreUsersPartialUpdate(int id, PatchedUserRequest data, { return await _client.SendAsync(HttpMethod.Patch, $"/core/users/{id}/", data, cancellationToken); } + + /// + /// Group Viewset + /// + /// + /// Attributes + /// + /// Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only. + /// + /// Which field to use when ordering the results. + /// A page number within the paginated result set. + /// Number of results to return per page. + /// A search term. + /// + /// + /// + /// + public async Task CoreGroupsList( + int[] membersByPk, + string? attributes = null, + bool? isSuperuser = null, + string[]? membersByName = null, + string? name = null, + string? ordering = null, + int? page = null, + int? pageSize = null, + string? search = null, + string[]? membersByUsername = null, + bool includeUsers = true, CancellationToken cancellationToken = default) + { + var url = "/core/groups/"; + var queryDict = new Dictionary + { + { "attributes", attributes }, + { "is_superuser", isSuperuser }, + { "members_by_pk", membersByPk }, + { "members_by_name", membersByName }, + { "name", name }, + { "ordering", ordering }, + { "page", page }, + { "page_size", pageSize }, + { "search", search }, + { "members_by_username", membersByUsername }, + { "include_users", includeUsers } + }.Where(kv => kv.Value != null) + .ToDictionary(kv => kv.Key, kv => kv.Value); + + url += "?" + string.Join("&", queryDict.Select(x => $"{x.Key}={x.Value}")); + + return await _client.SendAsync(HttpMethod.Get, url, cancellationToken: cancellationToken); + } } \ No newline at end of file From bb4cd2a3d054f5f807067adc02022f48273d13c9 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Wed, 11 Jun 2025 00:42:54 +0300 Subject: [PATCH 30/34] update: add PaginatedGroupList model for handling paginated group data --- src/AuthentikNet.Api/Models/PaginatedGroupList.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/AuthentikNet.Api/Models/PaginatedGroupList.cs diff --git a/src/AuthentikNet.Api/Models/PaginatedGroupList.cs b/src/AuthentikNet.Api/Models/PaginatedGroupList.cs new file mode 100644 index 0000000..856a7e0 --- /dev/null +++ b/src/AuthentikNet.Api/Models/PaginatedGroupList.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace AuthentikNet.Api.Models; + +public class PaginatedGroupList +{ + [JsonPropertyName("pagination")] public required Pagination Pagination { get; set; } + [JsonPropertyName("results")] public required List Results { get; set; } +} \ No newline at end of file From 2ad34288bbee72c76fd60c8aa5e9442b7eba9e24 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Thu, 21 Aug 2025 21:04:50 +0300 Subject: [PATCH 31/34] update: make membersByPk parameter optional in CoreGroupsList method --- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index 8262f03..eec9c43 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -136,7 +136,7 @@ public async Task CoreUsersPartialUpdate(int id, PatchedUserRequest data, /// /// public async Task CoreGroupsList( - int[] membersByPk, + int[]? membersByPk = null, string? attributes = null, bool? isSuperuser = null, string[]? membersByName = null, @@ -146,7 +146,8 @@ public async Task CoreGroupsList( int? pageSize = null, string? search = null, string[]? membersByUsername = null, - bool includeUsers = true, CancellationToken cancellationToken = default) + bool includeUsers = true, + CancellationToken cancellationToken = default) { var url = "/core/groups/"; var queryDict = new Dictionary From e318980568ad0eb1b043c814d52326274412681e Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Fri, 22 Aug 2025 01:39:28 +0300 Subject: [PATCH 32/34] update: modify group viewset summary and add CoreGroupsRetrieve method for fetching a group by ID --- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index eec9c43..9635818 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -120,7 +120,7 @@ public async Task CoreUsersPartialUpdate(int id, PatchedUserRequest data, } /// - /// Group Viewset + /// Retrieve all groups /// /// /// Attributes @@ -170,4 +170,21 @@ public async Task CoreGroupsList( return await _client.SendAsync(HttpMethod.Get, url, cancellationToken: cancellationToken); } + + /// + /// Retrieve group + /// + /// A UUID string identifying this Group. + /// + /// + /// + public async Task CoreGroupsRetrieve( + Guid id, + bool includeUsers = true, + CancellationToken cancellationToken = default) + { + var url = $"/core/groups/{id}/?include_users={includeUsers}"; + + return await _client.SendAsync(HttpMethod.Get, url, cancellationToken: cancellationToken); + } } \ No newline at end of file From 6316576ed03ccb19a1497055a52e13cc751e3e99 Mon Sep 17 00:00:00 2001 From: "Anton \"Saph1s\" Babenko" Date: Sat, 30 Aug 2025 22:38:17 +0300 Subject: [PATCH 33/34] update: add JsonIgnore attribute to nullable properties in PatchedUserRequest and PatchedSettingsRequest --- .../Models/PatchedSettingsRequest.cs | 13 +++++++ .../Models/PatchedUserRequest.cs | 39 +++++++++++++++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs b/src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs index 9de103a..c84e336 100644 --- a/src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs +++ b/src/AuthentikNet.Api/Models/PatchedSettingsRequest.cs @@ -13,78 +13,91 @@ public class PatchedSettingsRequest /// Configure how authentik should show avatars for users. /// [JsonPropertyName("avatars")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Avatars { get; set; } /// /// Enable the ability for users to change their name. /// [JsonPropertyName("default_user_change_name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? DefaultUserChangeName { get; set; } /// /// Enable the ability for users to change their email address. /// [JsonPropertyName("default_user_change_email")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? DefaultUserChangeEmail { get; set; } /// /// Enable the ability for users to change their username. /// [JsonPropertyName("default_user_change_username")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? DefaultUserChangeUsername { get; set; } /// /// Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2). /// [JsonPropertyName("event_retention")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? EventRetention { get; set; } /// /// Reputation cannot decrease lower than this value. Zero or negative. /// [JsonPropertyName("reputation_lower_limit")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? ReputationLowerLimit { get; set; } /// /// Reputation cannot increase higher than this value. Zero or positive. /// [JsonPropertyName("reputation_upper_limit")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? ReputationUpperLimit { get; set; } /// /// The option configures the footer links on the flow executor pages. /// [JsonPropertyName("footer_links")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public List<(string href, string name)>? FooterLinks { get; set; } = []; /// /// When enabled, all the events caused by a user will be deleted upon the user's deletion. /// [JsonPropertyName("gdpr_compliance")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? GdprCompliance { get; set; } /// /// Globally enable/disable impersonation. /// [JsonPropertyName("impersonation")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Impersonation { get; set; } /// /// Require administrators to provide a reason for impersonating a user. /// [JsonPropertyName("impersonation_require_reason")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? ImpersonationRequireReason { get; set; } /// /// Default token duration /// [JsonPropertyName("default_token_duration")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? DefaultTokenDuration { get; set; } /// /// Default token length /// [JsonPropertyName("default_token_length")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? DefaultTokenLength { get => _defaultTokenLength; diff --git a/src/AuthentikNet.Api/Models/PatchedUserRequest.cs b/src/AuthentikNet.Api/Models/PatchedUserRequest.cs index 6be0c4c..f300812 100644 --- a/src/AuthentikNet.Api/Models/PatchedUserRequest.cs +++ b/src/AuthentikNet.Api/Models/PatchedUserRequest.cs @@ -5,17 +5,40 @@ namespace AuthentikNet.Api.Models; public class PatchedUserRequest { - [JsonPropertyName("username")] public string? Username { get; set; } - [JsonPropertyName("name")] public string? Name { get; set; } - [JsonPropertyName("is_active")] public bool? IsActive { get; set; } - [JsonPropertyName("last_login")] public DateTime? LastLogin { get; set; } - [JsonPropertyName("groups")] public List? Groups { get; set; } - [JsonPropertyName("email")] public string? Email { get; set; } + [JsonPropertyName("username")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Username { get; set; } + + [JsonPropertyName("name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Name { get; set; } + + [JsonPropertyName("is_active")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? IsActive { get; set; } + + [JsonPropertyName("last_login")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? LastLogin { get; set; } + + [JsonPropertyName("groups")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Groups { get; set; } + + [JsonPropertyName("email")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Email { get; set; } [JsonPropertyName("attributes")] [JsonConverter(typeof(DynamicAttributesJsonConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object? Attributes { get; set; } - [JsonPropertyName("path")] public string? Path { get; set; } - [JsonPropertyName("type")] public UserTypeEnum? Type { get; set; } + [JsonPropertyName("path")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Path { get; set; } + + [JsonPropertyName("type")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public UserTypeEnum? Type { get; set; } } \ No newline at end of file From 1b66dad925b9f6fe5ea275c6c87eee34f9c70fb9 Mon Sep 17 00:00:00 2001 From: Anton Saph1s Babenko Date: Tue, 6 Jan 2026 03:46:41 +0300 Subject: [PATCH 34/34] update: refactor query string construction in user and group retrieval methods --- src/AuthentikNet.Api/Client/Core/CoreApi.cs | 15 ++++--- .../Utils/QueryStringBuilder.cs | 40 +++++++++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/AuthentikNet.Api/Utils/QueryStringBuilder.cs diff --git a/src/AuthentikNet.Api/Client/Core/CoreApi.cs b/src/AuthentikNet.Api/Client/Core/CoreApi.cs index 9635818..c7453dd 100644 --- a/src/AuthentikNet.Api/Client/Core/CoreApi.cs +++ b/src/AuthentikNet.Api/Client/Core/CoreApi.cs @@ -1,4 +1,5 @@ using AuthentikNet.Api.Models; +using AuthentikNet.Api.Utils; namespace AuthentikNet.Api.Client.Core; @@ -53,8 +54,7 @@ public async Task CoreUsersList( bool includeGroups = true, CancellationToken cancellationToken = default) { - var url = "/core/users/"; - var queryDict = new Dictionary + var parameters = new Dictionary { { "attributes", attributes }, { "email", email }, @@ -76,8 +76,7 @@ public async Task CoreUsersList( }.Where(kv => kv.Value != null) .ToDictionary(kv => kv.Key, kv => kv.Value); - url += "?" + string.Join("&", queryDict.Select(x => $"{x.Key}={x.Value}")); - + var url = QueryStringBuilder.BuildQueryString("/core/users/", parameters); return await _client.SendAsync(HttpMethod.Get, url, cancellationToken: cancellationToken); @@ -149,8 +148,8 @@ public async Task CoreGroupsList( bool includeUsers = true, CancellationToken cancellationToken = default) { - var url = "/core/groups/"; - var queryDict = new Dictionary + // var url = "/core/groups/"; + var parameters = new Dictionary { { "attributes", attributes }, { "is_superuser", isSuperuser }, @@ -165,8 +164,8 @@ public async Task CoreGroupsList( { "include_users", includeUsers } }.Where(kv => kv.Value != null) .ToDictionary(kv => kv.Key, kv => kv.Value); - - url += "?" + string.Join("&", queryDict.Select(x => $"{x.Key}={x.Value}")); + + var url = QueryStringBuilder.BuildQueryString("/core/groups/", parameters); return await _client.SendAsync(HttpMethod.Get, url, cancellationToken: cancellationToken); } diff --git a/src/AuthentikNet.Api/Utils/QueryStringBuilder.cs b/src/AuthentikNet.Api/Utils/QueryStringBuilder.cs new file mode 100644 index 0000000..8111b79 --- /dev/null +++ b/src/AuthentikNet.Api/Utils/QueryStringBuilder.cs @@ -0,0 +1,40 @@ +using System.Collections; + +namespace AuthentikNet.Api.Utils; + +public static class QueryStringBuilder +{ + public static string BuildQueryString(string baseUrl, Dictionary parameters) + { + var queryParams = new List(); + + foreach (var (key, value) in parameters) + { + switch (value) + { + case null: + continue; + case string str: + queryParams.Add($"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(str)}"); + break; + case IEnumerable enumerable: + { + foreach (var item in enumerable) + { + queryParams.Add($"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(item?.ToString() ?? "")}"); + } + + break; + } + default: + queryParams.Add($"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(value.ToString() ?? "")}"); + break; + } + } + + if (queryParams.Count == 0) + return baseUrl; + + return baseUrl + (baseUrl.Contains('?') ? "&" : "?") + string.Join("&", queryParams); + } +} \ No newline at end of file