applicationId uz neni povinne

This commit is contained in:
JiriUhlir
2026-06-15 08:42:56 +02:00
parent b01c09006a
commit cadfdaef1b
7 changed files with 127 additions and 10 deletions
+79
View File
@@ -0,0 +1,79 @@
using System.Net.Http;
using IdokladSdk;
using IdokladSdk.Authentication;
using IdokladSdk.Authentication.Models;
using IdokladSdk.Exceptions;
using Newtonsoft.Json;
namespace Idoklad.Client;
/// <summary>
/// iDoklad <c>client_credentials</c> authentication that sends only <c>client_id</c> and
/// <c>client_secret</c> (no <c>application_id</c>).
///
/// The official SDK's <see cref="ClientCredentialsAuthentication"/> mandates an applicationId and
/// always posts <c>application_id</c>, but iDoklad's standard client_credentials grant does not
/// require it. This implementation is used when the caller does not supply an ApplicationId.
/// </summary>
public sealed class ClientSecretAuthentication : IAuthentication
{
private const string Scope = "idoklad_api";
private readonly string _clientId;
private readonly string _clientSecret;
public ClientSecretAuthentication(string clientId, string clientSecret)
{
_clientId = clientId;
_clientSecret = clientSecret;
}
public DokladConfiguration Configuration { get; set; } = default!;
public string RefreshToken => null!;
public bool UseRefreshToken { get => false; set { } }
public async Task<Tokenizer> GetTokenAsync(HttpClient httpClient, CancellationToken cancellationToken = default)
{
var postData = new List<KeyValuePair<string, string>>
{
new("grant_type", "client_credentials"),
new("client_id", _clientId),
new("client_secret", _clientSecret),
new("scope", Scope),
};
// The SDK targets the v2 token endpoint (server/v2/connect/token), which mandates
// application_id. Without an applicationId we use the classic endpoint
// (server/connect/token), which authenticates with client_id + client_secret only.
var tokenUrl = Configuration.IdentityServerTokenUrl.ToString()
.Replace("/server/v2/connect/token", "/server/connect/token");
using var request = new HttpRequestMessage(HttpMethod.Post, tokenUrl)
{
Content = new FormUrlEncodedContent(postData),
};
using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
throw new IdokladAuthenticationException(content);
}
var tokenizer = JsonConvert.DeserializeObject<Tokenizer>(content);
if (tokenizer is null || string.IsNullOrEmpty(tokenizer.AccessToken))
{
var error = JsonConvert.DeserializeObject<AuthenticationError>(content);
throw new IdokladAuthenticationException(error);
}
// GrantType defaults to ClientCredentials (enum value 0), which is correct here.
return tokenizer;
}
public Task<Tokenizer> RefreshAccessTokenAsync(HttpClient httpClient, CancellationToken cancellationToken = default)
=> GetTokenAsync(httpClient, cancellationToken);
}
+21
View File
@@ -0,0 +1,21 @@
using IdokladSdk;
using IdokladSdk.Authentication;
using IdokladSdk.Builders;
namespace Idoklad.Client;
/// <summary>
/// <see cref="DokladApiBuilder"/> variant that uses a caller-supplied <see cref="IAuthentication"/>
/// (here <see cref="ClientSecretAuthentication"/>) instead of the SDK's applicationId-mandatory
/// client credentials. The rest of the builder pipeline (HttpClient, options, urls, Build) is reused.
/// </summary>
public sealed class ClientSecretDokladApiBuilder : DokladApiBuilder
{
private readonly IAuthentication _authentication;
public ClientSecretDokladApiBuilder(string appName, string appVersion, IAuthentication authentication)
: base(appName, appVersion)
=> _authentication = authentication;
protected override IAuthentication GetAuthentication() => _authentication;
}
+11 -2
View File
@@ -27,8 +27,17 @@ public sealed class DokladApiFactory
{
var httpClient = _httpClientFactory.CreateClient(HttpClientName);
var builder = new DokladApiBuilder(_settings.AppName, _settings.AppVersion)
.AddClientCredentialsAuthentication(credentials.ClientId, credentials.ClientSecret, credentials.ApplicationId)
// With an ApplicationId, use the SDK's client_credentials (sends application_id).
// Without it, authenticate with client_id + client_secret only (iDoklad's default).
DokladApiBuilder builder = string.IsNullOrWhiteSpace(credentials.ApplicationId)
? new ClientSecretDokladApiBuilder(
_settings.AppName,
_settings.AppVersion,
new ClientSecretAuthentication(credentials.ClientId, credentials.ClientSecret))
: new DokladApiBuilder(_settings.AppName, _settings.AppVersion)
.AddClientCredentialsAuthentication(credentials.ClientId, credentials.ClientSecret, credentials.ApplicationId);
builder = builder
.AddHttpClient(httpClient)
.AddApiContextOptions(options => options.Language = credentials.Language);