Moving folder structure

This commit is contained in:
2025-07-10 10:12:50 +02:00
parent 450aa5f1c9
commit 29f6c4ae81
172 changed files with 109 additions and 162 deletions

View File

@@ -0,0 +1,36 @@
using System.Net.Http;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Implements the interface to authenticate using an API key and email address.
/// </summary>
public class ApiKeyAuthentication : IAuthentication
{
private readonly string _emailAddress;
private readonly string _apiKey;
/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyAuthentication"/> class.
/// </summary>
/// <param name="emailAddress">The email address.</param>
/// <param name="apiKey">The global API key.</param>
public ApiKeyAuthentication(string emailAddress, string apiKey)
{
emailAddress.ValidateCloudflareEmailAddress();
if (string.IsNullOrWhiteSpace(apiKey))
throw new ArgumentNullException(nameof(apiKey));
_emailAddress = emailAddress;
_apiKey = apiKey;
}
/// <inheritdoc />
public void AddHeader(HttpClient httpClient)
{
httpClient.DefaultRequestHeaders.Add("X-Auth-Email", _emailAddress);
httpClient.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey);
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Net.Http;
using System.Net.Http.Headers;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Implements the interface to authenticate using an API token.
/// </summary>
public class ApiTokenAuthentication : IAuthentication
{
private readonly string _apiToken;
/// <summary>
/// Initializes a new instance of the <see cref="ApiTokenAuthentication"/> class.
/// </summary>
/// <param name="apiToken">The API token.</param>
public ApiTokenAuthentication(string apiToken)
{
if (string.IsNullOrWhiteSpace(apiToken))
throw new ArgumentNullException(nameof(apiToken));
_apiToken = apiToken;
}
/// <inheritdoc />
public void AddHeader(HttpClient httpClient)
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiToken);
}
}
}

View File

@@ -0,0 +1,53 @@
using System.Net;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Options for the Cloudflare API.
/// </summary>
public class ClientOptions
{
/// <summary>
/// Gets or sets the default base url for the API.
/// </summary>
public virtual string BaseUrl { get; set; } = "https://api.cloudflare.com/client/v4/";
/// <summary>
/// Gets or sets the default timeout for the API.
/// </summary>
public virtual TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(100);
/// <summary>
/// Gets or sets the maximum number of retries for the API.
/// </summary>
/// <remarks>
/// The API may respond with an 5xx error and a X-Should-Retry header indicating that the request should be retried.
/// </remarks>
public virtual int MaxRetries { get; set; } = 2;
/// <summary>
/// Gets or sets additional default headers to every request.
/// </summary>
public virtual IDictionary<string, string> DefaultHeaders { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Gets or sets additional default query parameters to every request.
/// </summary>
public virtual IDictionary<string, string> DefaultQueryParams { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Gets or sets a value indicating whether to allow redirects.
/// </summary>
public virtual bool AllowRedirects { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use a proxy.
/// </summary>
public virtual bool UseProxy { get; set; }
/// <summary>
/// Gets or sets the proxy information.
/// </summary>
public virtual IWebProxy? Proxy { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<NrtTagMatch>v[0-9]*</NrtTagMatch>
<PackageId>AMWD.Net.API.Cloudflare</PackageId>
<PackageTags>cloudflare api</PackageTags>
<AssemblyName>amwd-cloudflare-core</AssemblyName>
<RootNamespace>AMWD.Net.Api.Cloudflare</RootNamespace>
<Product>Cloudflare API - Core</Product>
<Description>Core features of the Cloudflare API</Description>
</PropertyGroup>
<!-- Only build package for tagged releases or Debug on CI (only dev NuGet feed) -->
<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(CI_COMMIT_TAG)', '^v[0-9.]+')) or ('$(Configuration)' == 'Debug' and '$(GITLAB_CI)' == 'true')">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,301 @@
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Implements the Core of the Cloudflare API client.
/// </summary>
public class CloudflareClient : ICloudflareClient, IDisposable
{
private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
{
Culture = CultureInfo.InvariantCulture,
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore,
};
private readonly ClientOptions _clientOptions;
private readonly HttpClient _httpClient;
private bool _isDisposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
/// </summary>
/// <param name="emailAddress">The email address of the Cloudflare account.</param>
/// <param name="apiKey">The API key of the Cloudflare account.</param>
/// <param name="clientOptions">The client options (optional).</param>
public CloudflareClient(string emailAddress, string apiKey, ClientOptions? clientOptions = null)
: this(new ApiKeyAuthentication(emailAddress, apiKey), clientOptions)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
/// </summary>
/// <param name="apiToken">The API token.</param>
/// <param name="clientOptions">The client options (optional).</param>
public CloudflareClient(string apiToken, ClientOptions? clientOptions = null)
: this(new ApiTokenAuthentication(apiToken), clientOptions)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
/// </summary>
/// <param name="authentication">The authentication information.</param>
/// <param name="clientOptions">The client options (optional).</param>
public CloudflareClient(IAuthentication authentication, ClientOptions? clientOptions = null)
{
if (authentication == null)
throw new ArgumentNullException(nameof(authentication));
_clientOptions = clientOptions ?? new ClientOptions();
ValidateClientOptions();
_httpClient = CreateHttpClient();
authentication.AddHeader(_httpClient);
}
/// <summary>
/// Disposes of the resources used by the <see cref="CloudflareClient"/> object.
/// </summary>
public void Dispose()
{
if (_isDisposed)
return;
_isDisposed = true;
_httpClient.Dispose();
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
public async Task<CloudflareResponse<TResponse>> GetAsync<TResponse>(string requestPath, IQueryParameterFilter? queryFilter = null, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
ValidateRequestPath(requestPath);
string requestUrl = BuildRequestUrl(requestPath, queryFilter);
var response = await _httpClient.GetAsync(requestUrl, cancellationToken).ConfigureAwait(false);
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task<CloudflareResponse<TResponse>> PostAsync<TResponse, TRequest>(string requestPath, TRequest? request, IQueryParameterFilter? queryFilter = null, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
ValidateRequestPath(requestPath);
string requestUrl = BuildRequestUrl(requestPath, queryFilter);
var httpContent = ConvertRequest(request);
var response = await _httpClient.PostAsync(requestUrl, httpContent, cancellationToken).ConfigureAwait(false);
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task<CloudflareResponse<TResponse>> PutAsync<TResponse, TRequest>(string requestPath, TRequest? request, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
ValidateRequestPath(requestPath);
string requestUrl = BuildRequestUrl(requestPath);
var httpContent = ConvertRequest(request);
var response = await _httpClient.PutAsync(requestUrl, httpContent, cancellationToken).ConfigureAwait(false);
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task<CloudflareResponse<TResponse>> DeleteAsync<TResponse>(string requestPath, IQueryParameterFilter? queryFilter = null, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
ValidateRequestPath(requestPath);
string requestUrl = BuildRequestUrl(requestPath, queryFilter);
var response = await _httpClient.DeleteAsync(requestUrl, cancellationToken).ConfigureAwait(false);
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task<CloudflareResponse<TResponse>> PatchAsync<TResponse, TRequest>(string requestPath, TRequest? request, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
ValidateRequestPath(requestPath);
string requestUrl = BuildRequestUrl(requestPath);
var httpContent = ConvertRequest(request);
var response = await _httpClient.PatchAsync(requestUrl, httpContent, cancellationToken).ConfigureAwait(false);
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
}
private void ThrowIfDisposed()
{
if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName);
}
private void ValidateClientOptions()
{
if (string.IsNullOrWhiteSpace(_clientOptions.BaseUrl))
throw new ArgumentNullException(nameof(_clientOptions.BaseUrl));
if (_clientOptions.Timeout <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(_clientOptions.Timeout), "Timeout must be positive.");
if (_clientOptions.MaxRetries < 0 || 10 < _clientOptions.MaxRetries)
throw new ArgumentOutOfRangeException(nameof(_clientOptions.MaxRetries), "MaxRetries should be between 0 and 10.");
if (_clientOptions.UseProxy && _clientOptions.Proxy == null)
throw new ArgumentNullException(nameof(_clientOptions.Proxy));
}
private void ValidateRequestPath(string requestPath)
{
if (string.IsNullOrWhiteSpace(requestPath))
throw new ArgumentNullException(nameof(requestPath));
if (requestPath.Contains("?"))
throw new ArgumentException("Query parameters are not allowed", nameof(requestPath));
}
private HttpClient CreateHttpClient()
{
string version = typeof(CloudflareClient).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion ?? "unknown";
HttpMessageHandler handler;
try
{
handler = new HttpClientHandler
{
AllowAutoRedirect = _clientOptions.AllowRedirects,
UseProxy = _clientOptions.UseProxy,
Proxy = _clientOptions.Proxy,
};
}
catch (PlatformNotSupportedException)
{
handler = new HttpClientHandler
{
AllowAutoRedirect = _clientOptions.AllowRedirects,
};
}
// Ensure a clean base URL
string baseUrl = _clientOptions.BaseUrl.Trim().TrimEnd('/');
var client = new HttpClient(handler, true)
{
BaseAddress = new Uri(baseUrl + '/'),
Timeout = _clientOptions.Timeout,
};
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", version));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (_clientOptions.DefaultHeaders.Count > 0)
{
foreach (var headerKvp in _clientOptions.DefaultHeaders)
client.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
}
return client;
}
private static async Task<CloudflareResponse<TRes>> GetCloudflareResponse<TRes>(HttpResponseMessage httpResponse, CancellationToken cancellationToken)
{
#if NET6_0_OR_GREATER
string content = await httpResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
string content = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
switch (httpResponse.StatusCode)
{
case HttpStatusCode.Forbidden:
case HttpStatusCode.Unauthorized:
var errorResponse = JsonConvert.DeserializeObject<CloudflareResponse<object>>(content, _jsonSerializerSettings)
?? throw new CloudflareException("Response is not a valid Cloudflare API response.");
string[] errors = errorResponse.Errors?.Select(e => $"{e.Code}: {e.Message}").ToArray() ?? [];
throw new AuthenticationException(string.Join(Environment.NewLine, errors));
default:
try
{
var response = JsonConvert.DeserializeObject<CloudflareResponse<TRes>>(content)
?? throw new CloudflareException("Response is not a valid Cloudflare API response.");
return response;
}
catch
{
if (typeof(TRes) == typeof(string))
{
object cObj = content.Replace("\\n", Environment.NewLine);
return new CloudflareResponse<TRes>
{
Success = true,
Result = (TRes)cObj,
};
}
throw;
}
}
}
private string BuildRequestUrl(string requestPath, IQueryParameterFilter? queryFilter = null)
{
// Ensure a clean request path
string reqPath = requestPath.Trim().TrimStart('/');
var dict = new Dictionary<string, string>();
if (_clientOptions.DefaultQueryParams.Count > 0)
{
foreach (var paramKvp in _clientOptions.DefaultQueryParams)
dict[paramKvp.Key] = paramKvp.Value;
}
var queryParams = queryFilter?.GetQueryParameters();
if (queryParams?.Count > 0)
{
foreach (var kvp in queryParams)
dict[kvp.Key] = kvp.Value;
}
if (dict.Count == 0)
return reqPath;
string[] param = dict.Select(kvp => $"{kvp.Key}={WebUtility.UrlEncode(kvp.Value)}").ToArray();
string query = string.Join("&", param);
return $"{reqPath}?{query}";
}
private static HttpContent? ConvertRequest<T>(T request)
{
if (request == null)
return null;
if (request is HttpContent httpContent)
return httpContent;
string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
return new StringContent(json, Encoding.UTF8, "application/json");
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// The Certificate Authority that will issue the certificate.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L110">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum CertificateCA
{
/// <summary>
/// DigiCert.
/// </summary>
[EnumMember(Value = "digicert")]
DigiCert = 1,
/// <summary>
/// Google.
/// </summary>
[EnumMember(Value = "google")]
Google = 2,
/// <summary>
/// Let's Encrypt.
/// </summary>
[EnumMember(Value = "lets_encrypt")]
LetsEncrypt = 3,
/// <summary>
/// SSL.com.
/// </summary>
[EnumMember(Value = "ssl_com")]
SslCom = 4
}
}

View File

@@ -0,0 +1,37 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A frequency at which to renew subscriptions, etc.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L730">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum RenewFrequency
{
/// <summary>
/// Weekly
/// </summary>
[EnumMember(Value = "weekly")]
Weekly = 1,
/// <summary>
/// Monthly
/// </summary>
[EnumMember(Value = "monthly")]
Monthly = 2,
/// <summary>
/// Quarterly
/// </summary>
[EnumMember(Value = "quarterly")]
Quarterly = 3,
/// <summary>
/// Yearly
/// </summary>
[EnumMember(Value = "yearly")]
Yearly = 4
}
}

View File

@@ -0,0 +1,25 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// The direction to sort the entity.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L698">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum SortDirection
{
/// <summary>
/// Sort in ascending order.
/// </summary>
[EnumMember(Value = "asc")]
Ascending = 1,
/// <summary>
/// Sort in descending order.
/// </summary>
[EnumMember(Value = "desc")]
Descending = 2
}
}

View File

@@ -0,0 +1,60 @@
using System.Runtime.Serialization;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Represents errors that occur during Cloudflare API calls.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class CloudflareException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="CloudflareException"/> class.
/// </summary>
public CloudflareException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CloudflareException"/> class with a specified error
/// message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public CloudflareException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CloudflareException"/> class with a specified error
/// message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">
/// The exception that is the cause of the current exception, or a <see langword="null"/> reference
/// if no inner exception is specified.
/// </param>
public CloudflareException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CloudflareException"/> class with serialized data.
/// </summary>
/// <param name="info">
/// The <see cref="SerializationInfo"/> that holds the serialized
/// object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="StreamingContext"/> that contains contextual information
/// about the source or destination.
/// </param>
/// <exception cref="ArgumentNullException">The info parameter is null.</exception>
/// <exception cref="SerializationException">The class name is <see langword="null"/> or <see cref="Exception.HResult"/> is zero (0).</exception>
protected CloudflareException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View File

@@ -0,0 +1,32 @@
using System.Linq;
using System.Runtime.Serialization;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Extension methods for <see cref="Enum"/>s.
/// </summary>
public static class EnumExtensions
{
/// <summary>
/// Gets the <see cref="EnumMemberAttribute.Value"/> of the <see cref="Enum"/> when available, otherwise the <see cref="Enum.ToString()"/>.
/// </summary>
/// <param name="value">The enum value.</param>
public static string? GetEnumMemberValue(this Enum value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
if (fieldInfo == null)
return value.ToString();
var enumMember = fieldInfo
.GetCustomAttributes(typeof(EnumMemberAttribute), inherit: false)
.Cast<EnumMemberAttribute>()
.FirstOrDefault();
if (enumMember == null)
return value.ToString();
return enumMember.Value;
}
}
}

View File

@@ -0,0 +1,53 @@
#if ! NET6_0_OR_GREATER
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Http
{
/// <summary>
/// Extension methods for <see cref="HttpClient"/>s.
/// </summary>
/// <remarks>
/// Copied from <see href="https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs">.NET 6 runtime / HttpClient</see>.
/// </remarks>
[Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static class HttpClientExtensions
{
private static readonly HttpMethod _httpMethodPatch = new("PATCH");
/// <summary>
/// Sends a PATCH request with a cancellation token to a Uri represented as a string as an asynchronous operation.
/// </summary>
/// <param name="client">A <see cref="HttpClient"/> instance.</param>
/// <param name="requestUri">The Uri the request is sent to.</param>
/// <param name="content">The HTTP request content sent to the server.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
public static Task<HttpResponseMessage> PatchAsync(this HttpClient client, string? requestUri, HttpContent? content, CancellationToken cancellationToken) =>
client.PatchAsync(CreateUri(requestUri), content, cancellationToken);
/// <summary>
/// Sends a PATCH request with a cancellation token as an asynchronous operation.
/// </summary>
/// <param name="client">A <see cref="HttpClient"/> instance.</param>
/// <param name="requestUri">The Uri the request is sent to.</param>
/// <param name="content">The HTTP request content sent to the server.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The task object representing the asynchrnous operation.</returns>
public static Task<HttpResponseMessage> PatchAsync(this HttpClient client, Uri? requestUri, HttpContent? content, CancellationToken cancellationToken)
{
var request = new HttpRequestMessage(_httpMethodPatch, requestUri)
{
Version = HttpVersion.Version11,
Content = content,
};
return client.SendAsync(request, cancellationToken);
}
private static Uri? CreateUri(string? uri) =>
string.IsNullOrEmpty(uri) ? null : new Uri(uri, UriKind.RelativeOrAbsolute);
}
}
#endif

View File

@@ -0,0 +1,78 @@
using System.Text.RegularExpressions;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Extension methods for <see cref="string"/>s.
/// </summary>
public static class StringExtensions
{
private static readonly Regex _idCheckRegex = new(@"^[0-9a-f]{32}$", RegexOptions.Compiled);
private static readonly Regex _emailCheckRegex = new(@"^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$", RegexOptions.Compiled);
/// <summary>
/// Validate basic information for a Cloudflare ID.
/// </summary>
/// <remarks>
/// An Cloudflare ID has max. 32 characters.
/// </remarks>
/// <param name="id">The string to check.</param>
/// <exception cref="ArgumentNullException">The <paramref name="id"/> is <see langword="null"/> or any kind of whitespace.</exception>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="id"/> has more than 32 characters.</exception>
public static void ValidateCloudflareId(this string id)
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException(nameof(id));
id.ValidateLength(32, nameof(id));
if (!_idCheckRegex.IsMatch(id))
throw new ArgumentException("Invalid Cloudflare ID", nameof(id));
}
/// <summary>
/// Validate basic information for a Cloudflare name.
/// </summary>
/// <remarks>
/// An Cloudflare name has max. 253 characters.
/// </remarks>
/// <param name="name">The string to check.</param>
/// <exception cref="ArgumentNullException">The <paramref name="name"/> is <see langword="null"/> or any kind of whitespace.</exception>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="name"/> has more than 253 characters.</exception>
public static void ValidateCloudflareName(this string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
name.ValidateLength(253, nameof(name));
}
/// <summary>
/// Validate basic information for an email address.
/// </summary>
/// <param name="emailAddress">The string to check.</param>
/// <exception cref="ArgumentNullException">The <paramref name="emailAddress"/> is <see langword="null"/> or any kind of whitespace.</exception>
/// <exception cref="ArgumentException">The <paramref name="emailAddress"/> does not match the regular expression pattern for an email address.</exception>
public static void ValidateCloudflareEmailAddress(this string emailAddress)
{
if (string.IsNullOrWhiteSpace(emailAddress))
throw new ArgumentNullException(nameof(emailAddress));
if (!_emailCheckRegex.IsMatch(emailAddress))
throw new ArgumentException("Invalid email address", nameof(emailAddress));
}
/// <summary>
/// Validate the length of a string.
/// </summary>
/// <param name="str">The string to check.</param>
/// <param name="length">The max. length.</param>
/// <param name="paramName">The name of the parameter to check.</param>
/// <exception cref="ArgumentException">The <paramref name="str"/> is longer than <paramref name="length"/>.</exception>
public static void ValidateLength(this string str, int length, string paramName)
{
if (str?.Length > length)
throw new ArgumentException($"The value of '{paramName}' is too long. Only {length} characters are allowed.");
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Net.Http;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Defines the interface to add authentication information.
/// </summary>
public interface IAuthentication
{
/// <summary>
/// Adds authentication headers to the given <see cref="HttpClient"/>.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/> to add the headers to.</param>
void AddHeader(HttpClient httpClient);
}
}

View File

@@ -0,0 +1,77 @@
using System.Threading;
using System.Threading.Tasks;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Represents a client for the Cloudflare API.
/// </summary>
public interface ICloudflareClient
{
/// <summary>
/// Makes a GET request to the Cloudflare API.
/// </summary>
/// <remarks>
/// The GET method requests a representation of the specified resource.
/// Requests using GET should only retrieve data and should not contain a request content.
/// </remarks>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <param name="requestPath">The request path (extending the base URL).</param>
/// <param name="queryFilter">The query parameters.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
Task<CloudflareResponse<TResponse>> GetAsync<TResponse>(string requestPath, IQueryParameterFilter? queryFilter = null, CancellationToken cancellationToken = default);
/// <summary>
/// Makes a POST request to the Cloudflare API.
/// </summary>
/// <remarks>
/// The POST method submits an entity to the specified resource, often causing a change in state or side effects on the server.
/// </remarks>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <typeparam name="TRequest">The request type.</typeparam>
/// <param name="requestPath">The request path (extending the base URL).</param>
/// <param name="request">The request content.</param>
/// <param name="queryFilter">The query parameters.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
Task<CloudflareResponse<TResponse>> PostAsync<TResponse, TRequest>(string requestPath, TRequest? request, IQueryParameterFilter? queryFilter = null, CancellationToken cancellationToken = default);
/// <summary>
/// Makes a PUT request to the Cloudflare API.
/// </summary>
/// <remarks>
/// The PUT method replaces all current representations of the target resource with the request content.
/// </remarks>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <typeparam name="TRequest">The request type.</typeparam>
/// <param name="requestPath">The request path (extending the base URL).</param>
/// <param name="request">The request content.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
Task<CloudflareResponse<TResponse>> PutAsync<TResponse, TRequest>(string requestPath, TRequest? request, CancellationToken cancellationToken = default);
/// <summary>
/// Makes a DELETE request to the Cloudflare API.
/// </summary>
/// <remarks>
/// The DELETE method deletes the specified resource.
/// </remarks>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <param name="requestPath">The request path (extending the base URL).</param>
/// <param name="queryFilter">The query parameters.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
/// <returns></returns>
Task<CloudflareResponse<TResponse>> DeleteAsync<TResponse>(string requestPath, IQueryParameterFilter? queryFilter = null, CancellationToken cancellationToken = default);
/// <summary>
/// Makes a PATCH request to the Cloudflare API.
/// </summary>
/// <remarks>
/// The PATCH method applies partial modifications to a resource.
/// </remarks>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <typeparam name="TRequest">The request type.</typeparam>
/// <param name="requestPath">The request path (extending the base URL).</param>
/// <param name="request">The request content.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
Task<CloudflareResponse<TResponse>> PatchAsync<TResponse, TRequest>(string requestPath, TRequest? request, CancellationToken cancellationToken = default);
}
}

View File

@@ -0,0 +1,13 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Represents filter options defined via query parameters.
/// </summary>
public interface IQueryParameterFilter
{
/// <summary>
/// Gets the query parameters.
/// </summary>
IDictionary<string, string> GetQueryParameters();
}
}

View File

@@ -0,0 +1,182 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// An audit log entry.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L10">Source</see>
/// </summary>
public class AuditLog
{
/// <summary>
/// A string that uniquely identifies the audit log.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// The action that was performed.
/// </summary>
[JsonProperty("action")]
public AuditLogAction? Action { get; set; }
/// <summary>
/// The actor that performed the action.
/// </summary>
[JsonProperty("actor")]
public AuditLogActor? Actor { get; set; }
/// <summary>
/// The source of the event.
/// </summary>
[JsonProperty("interface")]
public string? Interface { get; set; }
/// <summary>
/// An object which can lend more context to the action being logged.
/// This is a flexible value and varies between different actions.
/// </summary>
[JsonProperty("metadata")]
public object? MetaData { get; set; }
/// <summary>
/// The new value of the resource that was modified.
/// </summary>
[JsonProperty("newValue")]
public string? NewValue { get; set; }
/// <summary>
/// The value of the resource before it was modified.
/// </summary>
[JsonProperty("oldValue")]
public string? OldValue { get; set; }
/// <summary>
/// The owner of the resource that was modified.
/// </summary>
[JsonProperty("owner")]
public AuditLogOwner? Owner { get; set; }
/// <summary>
/// The resource that was modified.
/// </summary>
[JsonProperty("resource")]
public AuditLogResource? Resource { get; set; }
/// <summary>
/// A UTC RFC3339 timestamp that specifies when the action being logged occured.
/// </summary>
[JsonProperty("when")]
public DateTime? When { get; set; }
}
/// <summary>
/// The action that was performed.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L52">Soruce</see>
/// </summary>
public class AuditLogAction
{
/// <summary>
/// A boolean that indicates if the action attempted was successful.
/// </summary>
[JsonProperty("result")]
public bool? Result { get; set; }
/// <summary>
/// A short string that describes the action that was performed.
/// </summary>
[JsonProperty("type")]
public string? Type { get; set; }
}
/// <summary>
/// The actor that performed the action.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L64">Source</see>
/// </summary>
public class AuditLogActor
{
/// <summary>
/// The ID of the actor that performed the action.
/// If a user performed the action, this will be the user's ID.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// The email of the user that performed the action.
/// </summary>
[JsonProperty("email")]
public string? Email { get; set; }
/// <summary>
/// The IP address of the request that performed the action.
/// </summary>
[JsonProperty("ip")]
public string? IpAddress { get; set; }
/// <summary>
/// The type of actor, whether a User, Cloudflare Admin, or an Automated System.
/// </summary>
[JsonProperty("type")]
public AuditLogActorType? Type { get; set; }
}
/// <summary>
/// The type of actor.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L84">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum AuditLogActorType
{
/// <summary>
/// User interaction.
/// </summary>
[EnumMember(Value = "user")]
User = 1,
/// <summary>
/// Cloudflare admin interaction.
/// </summary>
[EnumMember(Value = "admin")]
Admin = 2,
/// <summary>
/// Cloudflare automated system interaction.
/// </summary>
[EnumMember(Value = "Cloudflare")]
Cloudflare = 3
}
/// <summary>
/// The owner of the resource that was modified.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L87">Source</see>
/// </summary>
public class AuditLogOwner
{
/// <summary>
/// Identifier.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
}
/// <summary>
/// The resource that was modified.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L94">Source</see>
/// </summary>
public class AuditLogResource
{
/// <summary>
/// An identifier for the resource that was affected by the action.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// A short string that describes the resource that was affected by the action.
/// </summary>
[JsonProperty("type")]
public string? Type { get; set; }
}
}

View File

@@ -0,0 +1,44 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// The base Cloudflare response.
/// Derived from <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L616">Source: Result</see>
/// </summary>
public class CloudflareResponse
{
/// <summary>
/// Errors returned by the API call.
/// </summary>
[JsonProperty("errors")]
public IReadOnlyCollection<ResponseInfo>? Errors { get; set; }
/// <summary>
/// Messages returned by the API call.
/// </summary>
[JsonProperty("messages")]
public IReadOnlyCollection<ResponseInfo>? Messages { get; set; }
/// <summary>
/// Whether the API call was successful.
/// </summary>
[JsonProperty("success")]
public bool Success { get; set; }
}
/// <inheritdoc/>
/// <typeparam name="T">The result type.</typeparam>
public class CloudflareResponse<T> : CloudflareResponse
{
/// <summary>
/// The result of the API call.
/// </summary>
[JsonProperty("result")]
public T? Result { get; set; }
/// <summary>
/// Information about the result of the request.
/// </summary>
[JsonProperty("result_info")]
public PaginationInfo? ResultInfo { get; set; }
}
}

View File

@@ -0,0 +1,229 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A Cloudflare Tunnel that connects your origin to Cloudflare's edge.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L132">Source</see>
/// </summary>
public class CloudflareTunnel
{
/// <summary>
/// UUID of the tunnel.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// Cloudflare account ID.
/// </summary>
[JsonProperty("account_tag")]
public string? AccountTag { get; set; }
/// <summary>
/// Active connections.
/// </summary>
[JsonProperty("connections")]
[Obsolete("This field will start returning an empty array. To fetch the connections of a given tunnel, please use the dedicated endpoint '/accounts/{account_id}/{tunnel_type}/{tunnel_id}/connections'.")]
public IReadOnlyCollection<CloudflareTunnelConnection>? Connections { get; set; }
/// <summary>
/// Timestamp of when the tunnel established at least one connection to Cloudflare's edge.
/// If <see langword="null"/>, the tunnel is inactive.
/// </summary>
[JsonProperty("conns_active_at")]
public DateTime? ConnectionsActiveAt { get; set; }
/// <summary>
/// Timestamp of when the tunnel became inactive (no connections to Cloudflare's edge).
/// If <see langword="null"/>, the tunnel is active.
/// </summary>
[JsonProperty("conns_inactive_at")]
public DateTime? ConnectionsInactiveAt { get; set; }
/// <summary>
/// Timestamp of when the resource was created.
/// </summary>
[JsonProperty("created_at")]
public DateTime? CreatedAt { get; set; }
/// <summary>
/// Timestamp of when the resource was deleted.
/// If <see langword="null"/>, the resource has not been deleted.
/// </summary>
[JsonProperty("deleted_at")]
public DateTime? DeletedAt { get; set; }
/// <summary>
/// Metadata associated with the tunnel.
/// </summary>
[JsonProperty("metadata")]
public object? MetaData { get; set; }
/// <summary>
/// A user-friendly name for a tunnel.
/// </summary>
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>
/// If <see langword="true"/>, the tunnel can be configured remotely from the Zero Trust dashboard.
/// If <see langword="false"/>, the tunnel must be configured locally on the origin machine.
/// </summary>
[JsonProperty("remote_config")]
public bool? RemoteConfiguration { get; set; }
/// <summary>
/// The status of the tunnel.
/// </summary>
[JsonProperty("status")]
public CloudflareTunnelStatus? Status { get; set; }
/// <summary>
/// The type of tunnel.
/// </summary>
[JsonProperty("tun_type")]
public CloudflareTunnelType? TunType { get; set; }
}
/// <summary>
/// A connection to Cloudflare's edge.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L204">Source</see>
/// </summary>
public class CloudflareTunnelConnection
{
/// <summary>
/// UUID of the Cloudflare Tunnel connection.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// UUID of the Cloudflare Tunnel connector.
/// </summary>
[JsonProperty("client_id")]
public string? ClientId { get; set; }
/// <summary>
/// The cloudflared version used to establish this connection.
/// </summary>
[JsonProperty("client_version")]
public string? ClientVersion { get; set; }
/// <summary>
/// The Cloudflare data center used for this connection.
/// </summary>
[JsonProperty("colo_name")]
public string? ColocationName { get; set; }
/// <summary>
/// Cloudflare continues to track connections for several minutes after they disconnect.
/// This is an optimization to improve latency and reliability of reconnecting.
/// <br/>
/// If <see langword="true"/>, the connection has disconnected but is still being tracked.
/// If <see langword="false"/>, the connection is actively serving traffic.
/// </summary>
[JsonProperty("is_pending_reconnect")]
public bool? IsPendingReconnect { get; set; }
/// <summary>
/// Timestamp of when the connection was established.
/// </summary>
[JsonProperty("opened_at")]
public DateTime? OpenedAt { get; set; }
/// <summary>
/// The public IP address of the host running cloudflared.
/// </summary>
[JsonProperty("origin_ip")]
public string? OriginIp { get; set; }
/// <summary>
/// UUID of the Cloudflare Tunnel connection.
/// </summary>
[JsonProperty("uuid")]
public string? UUID { get; set; }
}
/// <summary>
/// The status of the tunnel.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L195">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum CloudflareTunnelStatus
{
/// <summary>
/// The tunnel has never been run.
/// </summary>
[EnumMember(Value = "inactive")]
Inactive = 1,
/// <summary>
/// The tunnel is active and able to serve traffic but in an unhealthy state.
/// </summary>
[EnumMember(Value = "degraded")]
Degraded = 2,
/// <summary>
/// The tunnel is active and able to serve traffic.
/// </summary>
[EnumMember(Value = "healthy")]
Healthy = 3,
/// <summary>
/// The tunnel can not serve traffic as it has no connections to the Cloudflare Edge.
/// </summary>
[EnumMember(Value = "down")]
Down = 4
}
/// <summary>
/// The type of tunnel.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L200">Source</see>
/// </summary>
public enum CloudflareTunnelType
{
/// <summary>
/// Cloudflared.
/// </summary>
[EnumMember(Value = "cfd_tunnel")]
Cloudflared = 1,
/// <summary>
/// WARP Connector.
/// </summary>
[EnumMember(Value = "warp_connector")]
WarpConnector = 2,
/// <summary>
/// WARP.
/// </summary>
[EnumMember(Value = "warp")]
Warp = 3,
/// <summary>
/// Magic WAN.
/// </summary>
[EnumMember(Value = "magic")]
MagicWAN = 4,
/// <summary>
/// IPsec.
/// </summary>
[EnumMember(Value = "ip_sec")]
IpSec = 5,
/// <summary>
/// GRE.
/// </summary>
[EnumMember(Value = "gre")]
Gre = 6,
/// <summary>
/// CNI.
/// </summary>
[EnumMember(Value = "cni")]
Cni = 7
}
}

View File

@@ -0,0 +1,46 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// An error message.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L250">Source</see>
/// </summary>
public class ErrorData
{
/// <summary>
/// The error code.
/// </summary>
[JsonProperty("code")]
public int? Code { get; set; }
/// <summary>
/// A link to documentation about the error.
/// </summary>
[JsonProperty("documentation_url")]
public string? DocumentationUrl { get; set; }
/// <summary>
/// The error message.
/// </summary>
[JsonProperty("message")]
public string? Message { get; set; }
/// <summary>
/// The source of the error.
/// </summary>
[JsonProperty("source")]
public ErrorDataSource? Source { get; set; }
}
/// <summary>
/// The source of the error.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L261">Source</see>
/// </summary>
public class ErrorDataSource
{
/// <summary>
/// The pointer to the source of the error.
/// </summary>
[JsonProperty("pointer")]
public string? Pointer { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A Cloudflare identifier.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L266">Source</see>
/// </summary>
public class Identifier
{
/// <summary>
/// Identifier.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A load balancer preview.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L273">Source</see>
/// </summary>
public class LoadBalancerPreview
{
/// <summary>
/// Monitored pool IDs mapped to their respective names.
/// </summary>
[JsonProperty("pools")]
public IDictionary<string, string>? Pools { get; set; }
/// <summary>
/// Preview ID.
/// </summary>
[JsonProperty("preview_id")]
public string? PreviewId { get; set; }
}
}

View File

@@ -0,0 +1,321 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A Cloudflare member.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L282">Source</see>
/// </summary>
public class Member
{
/// <summary>
/// Membership identifier tag.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// Access policy for the membership.
/// </summary>
[JsonProperty("policies")]
public IReadOnlyCollection<MemberPolicy>? Policies { get; set; }
/// <summary>
/// Roles assigned to this Member.
/// </summary>
[JsonProperty("roles")]
public IReadOnlyCollection<Role>? Roles { get; set; }
/// <summary>
/// A member's status in the account.
/// </summary>
[JsonProperty("status")]
public MemberStatus? Status { get; set; }
/// <summary>
/// Details of the user associated to the membership.
/// </summary>
[JsonProperty("user")]
public MemberUser? User { get; set; }
}
/// <summary>
/// A member's access policy.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L310">Source</see>
/// </summary>
public class MemberPolicy
{
/// <summary>
/// Policy identifier.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// Allow or deny operations against the resources.
/// </summary>
[JsonProperty("access")]
public MemberPolicyAccess? Access { get; set; }
/// <summary>
/// A set of permission groups that are specified to the policy.
/// </summary>
[JsonProperty("permission_groups")]
public IReadOnlyCollection<MemberPolicyPermissionGroup>? PermissionGroups { get; set; }
/// <summary>
/// A list of resource groups that the policy applies to.
/// </summary>
[JsonProperty("resource_groups")]
public IReadOnlyCollection<MemberPolicyResourceGroup>? ResourceGroups { get; set; }
}
/// <summary>
/// A member's status.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L301">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum MemberStatus
{
/// <summary>
/// The member has accepted the invitation.
/// </summary>
[EnumMember(Value = "accepted")]
Accepted = 1,
/// <summary>
/// The member has not yet accepted the invitation.
/// </summary>
[EnumMember(Value = "pending")]
Pending = 2
}
/// <summary>
/// A member's policy access.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L319">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum MemberPolicyAccess
{
/// <summary>
/// Allow operations against the resource.
/// </summary>
[EnumMember(Value = "allow")]
Allow = 1,
/// <summary>
/// Deny operations against the resource.
/// </summary>
[EnumMember(Value = "deny")]
Deny = 2
}
/// <summary>
/// A member's permission group.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L337">Source</see>
/// </summary>
public class MemberPolicyPermissionGroup
{
/// <summary>
/// Initializes a new instance of the <see cref="MemberPolicyPermissionGroup"/> class.
/// </summary>
/// <param name="id">Identifier of the permission group.</param>
public MemberPolicyPermissionGroup(string id)
{
Id = id;
}
/// <summary>
/// Identifier of the permission group.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// Attributes associated to the permission group.
/// </summary>
[JsonProperty("meta")]
public MemberPolicyPermissionGroupMeta? Meta { get; set; }
/// <summary>
/// Name of the permission group.
/// </summary>
[JsonProperty("name")]
public string? Name { get; set; }
}
/// <summary>
/// Attributes associated to the permission group.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L358">Source</see>
/// </summary>
public class MemberPolicyPermissionGroupMeta
{
/// <summary>
/// The key of the attribute.
/// </summary>
[JsonProperty("key")]
public string? Key { get; set; }
/// <summary>
/// The value of the attribute.
/// </summary>
[JsonProperty("value")]
public string? Value { get; set; }
}
/// <summary>
/// A group of scoped resources.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L368">Source</see>
/// </summary>
public class MemberPolicyResourceGroup
{
/// <summary>
/// Initializes a new instance of the <see cref="MemberPolicyResourceGroup"/> class.
/// </summary>
/// <param name="id">Identifier of the resource group.</param>
public MemberPolicyResourceGroup(string id)
{
Id = id;
}
/// <summary>
/// Identifier of the resource group.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// The scope associated to the resource group.
/// </summary>
[JsonProperty("scope")]
public IReadOnlyCollection<MemberPolicyResourceGroupScope> Scope { get; set; } = [];
/// <summary>
/// Attributes associated to the resource group.
/// </summary>
[JsonProperty("meta")]
public MemberPolicyResourceGroupMeta? Meta { get; set; }
/// <summary>
/// Name of the resource group.
/// </summary>
[JsonProperty("name")]
public string? Name { get; set; }
}
/// <summary>
/// Attributes associated to the resource group.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L394">Source</see>
/// </summary>
public class MemberPolicyResourceGroupScope
{
/// <summary>
/// Initializes a new instance of the <see cref="MemberPolicyResourceGroupScope"/> class.
/// </summary>
/// <param name="key">Combination of pre-defined resource name and identifier.</param>
public MemberPolicyResourceGroupScope(string key)
{
Key = key;
}
/// <summary>
/// This is a combination of pre-defined resource name and identifier (like Account ID etc.)
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
/// <summary>
/// A list of scope objects for additional context.
/// </summary>
[JsonProperty("objects")]
public IReadOnlyCollection<MemberPolicyResourceGroupScopeObject> Objects { get; set; } = [];
}
/// <summary>
/// A scope object for additional context.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L412">Source</see>
/// </summary>
public class MemberPolicyResourceGroupScopeObject
{
/// <summary>
/// Initializes a new instance of the <see cref="MemberPolicyResourceGroupScopeObject"/> class.
/// </summary>
/// <param name="key">Combination of pre-defined resource name and identifier.</param>
public MemberPolicyResourceGroupScopeObject(string key)
{
Key = key;
}
/// <summary>
/// This is a combination of pre-defined resource name and identifier (like Zone ID etc.)
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
}
/// <summary>
/// Attributes associated to the resource group.
/// </summary>
public class MemberPolicyResourceGroupMeta
{
/// <summary>
/// The key of the attribute.
/// </summary>
[JsonProperty("key")]
public string? Key { get; set; }
/// <summary>
/// The value of the attribute.
/// </summary>
[JsonProperty("value")]
public string? Value { get; set; }
}
/// <summary>
/// Details of the user associated to the membership.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L435">Source</see>
/// </summary>
public class MemberUser
{
/// <summary>
/// Initializes a new instance of the <see cref="MemberUser"/> class.
/// </summary>
/// <param name="email">The contact email address of the user.</param>
public MemberUser(string email)
{
Email = email;
}
/// <summary>
/// The contact email address of the user.
/// </summary>
[JsonProperty("email")]
public string Email { get; set; }
/// <summary>
/// Identifier.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// User's first name.
/// </summary>
[JsonProperty("first_name")]
public string? FirstName { get; set; }
/// <summary>
/// User's last name.
/// </summary>
[JsonProperty("last_name")]
public string? LastName { get; set; }
/// <summary>
/// Indicates whether two-factor authentication is enabled for the user account.
/// Does not apply to API authentication.
/// </summary>
[JsonProperty("two_factor_authentication_enabled")]
public bool? TwoFactorAuthEnabled { get; set; }
}
}

View File

@@ -0,0 +1,33 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// Information about pagination.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L464">Source</see>
/// </summary>
public class PaginationInfo
{
/// <summary>
/// Total number of results for the requested service.
/// </summary>
[JsonProperty("count")]
public int? Count { get; set; }
/// <summary>
/// Current page within paginated list of results.
/// </summary>
[JsonProperty("page")]
public int? Page { get; set; }
/// <summary>
/// Number of results per page of results.
/// </summary>
[JsonProperty("per_page")]
public int? PerPage { get; set; }
/// <summary>
/// Total results available without any search parameters.
/// </summary>
[JsonProperty("total_count")]
public int? TotalCount { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A permission grant.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L488">Source</see>
/// </summary>
public class PermissionGrant
{
/// <summary>
/// Whether the member can read the resource.
/// </summary>
[JsonProperty("read")]
public bool? CanRead { get; set; }
/// <summary>
/// Whether the member can write to the resource.
/// </summary>
[JsonProperty("write")]
public bool? CanWrite { get; set; }
}
}

View File

@@ -0,0 +1,122 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// The rate plan applied to the subscription.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L503">Source</see>
/// </summary>
public class RatePlan
{
/// <summary>
/// The ID of the rate plan.
/// </summary>
[JsonProperty("id")]
public RatePlanId? Id { get; set; }
/// <summary>
/// The currency applied to the rate plan subscription.
/// </summary>
[JsonProperty("currency")]
public string? Currency { get; set; }
/// <summary>
/// Whether this rate plan is managed externally from Cloudflare.
/// </summary>
[JsonProperty("externally_managed")]
public bool? ExternallyManaged { get; set; }
/// <summary>
/// Whether a rate plan is enterprise-based (or newly adopted term contract).
/// </summary>
[JsonProperty("is_contract")]
public bool? IsContract { get; set; }
/// <summary>
/// The full name of the rate plan.
/// </summary>
[JsonProperty("public_name")]
public string? PublicName { get; set; }
/// <summary>
/// The scope that this rate plan applies to.
/// </summary>
[JsonProperty("scope")]
public string? Scope { get; set; }
/// <summary>
/// The list of sets this rate plan applies to.
/// </summary>
[JsonProperty("sets")]
public IReadOnlyCollection<string>? Sets { get; set; }
}
/// <summary>
/// Available rate plan ids.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L507">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum RatePlanId
{
/// <summary>
/// The free rate plan.
/// </summary>
[EnumMember(Value = "free")]
Free = 1,
/// <summary>
/// The lite rate plan.
/// </summary>
[EnumMember(Value = "lite")]
Lite = 2,
/// <summary>
/// The pro rate plan.
/// </summary>
[EnumMember(Value = "pro")]
Pro = 3,
/// <summary>
/// The pro+ rate plan.
/// </summary>
[EnumMember(Value = "pro_plus")]
ProPlus = 4,
/// <summary>
/// The business rate plan.
/// </summary>
[EnumMember(Value = "business")]
Business = 5,
/// <summary>
/// The enterprise rate plan.
/// </summary>
[EnumMember(Value = "enterprise")]
Enterprise = 6,
/// <summary>
/// The partners free rate plan.
/// </summary>
[EnumMember(Value = "partners_free")]
PartnersFree = 7,
/// <summary>
/// The partners pro rate plan.
/// </summary>
[EnumMember(Value = "partners_pro")]
PartnersPro = 8,
/// <summary>
/// The partners business rate plan.
/// </summary>
[EnumMember(Value = "partners_business")]
PartnersBusiness = 9,
/// <summary>
/// The partners enterprise rate plan.
/// </summary>
[EnumMember(Value = "partners_enterprise")]
PartnersEnterprise = 10
}
}

View File

@@ -0,0 +1,57 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A response info.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L600">Source</see>
/// </summary>
public class ResponseInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="ResponseInfo"/> class.
/// </summary>
/// <param name="code">The response code.</param>
/// <param name="message">The response message.</param>
public ResponseInfo(int code, string message)
{
Code = code;
Message = message;
}
/// <summary>
/// The response code.
/// </summary>
[JsonProperty("code")]
public int Code { get; set; }
/// <summary>
/// The response message.
/// </summary>
[JsonProperty("message")]
public string Message { get; set; }
/// <summary>
/// The documentation URL.
/// </summary>
[JsonProperty("documentation_url")]
public string? DocumentationUrl { get; set; }
/// <summary>
/// The response source.
/// </summary>
[JsonProperty("source")]
public ResponseInfoSource? Source { get; set; }
}
/// <summary>
/// A response info source.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L611">Source</see>
/// </summary>
public class ResponseInfoSource
{
/// <summary>
/// The pointer.
/// </summary>
[JsonProperty("pointer")]
public string? Pointer { get; set; }
}
}

View File

@@ -0,0 +1,127 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A Cloudflare role.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L641">Source</see>
/// </summary>
public class Role
{
/// <summary>
/// Initializes a new instance of the <see cref="Role"/> class.
/// </summary>
/// <param name="id">Role identifier tag.</param>
/// <param name="name">Role name.</param>
/// <param name="description">Description of role's permissions.</param>
/// <param name="permissions">Permissions.</param>
public Role(string id, string name, string description, RolePermissions permissions)
{
Id = id;
Name = name;
Description = description;
Permissions = permissions;
}
/// <summary>
/// Role identifier tag.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// Description of role's permissions.
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Role name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Role permissions.
/// </summary>
[JsonProperty("permissions")]
public RolePermissions Permissions { get; set; }
}
/// <summary>
/// Role permissions.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L661">Source</see>
/// </summary>
public class RolePermissions
{
/// <summary>
/// Analytics permissions.
/// </summary>
[JsonProperty("analytics")]
public PermissionGrant? Analytics { get; set; }
/// <summary>
/// Billing permissions.
/// </summary>
[JsonProperty("billing")]
public PermissionGrant? Billing { get; set; }
/// <summary>
/// Cache Purge permissions.
/// </summary>
[JsonProperty("cache_purge")]
public PermissionGrant? CachePurge { get; set; }
/// <summary>
/// DNS permissions.
/// </summary>
[JsonProperty("dns")]
public PermissionGrant? Dns { get; set; }
/// <summary>
/// DNS Records permissions.
/// </summary>
[JsonProperty("dns_records")]
public PermissionGrant? DnsRecords { get; set; }
/// <summary>
/// Load Balancer permissions.
/// </summary>
[JsonProperty("lb")]
public PermissionGrant? LoadBalancer { get; set; }
/// <summary>
/// Logs permissions.
/// </summary>
[JsonProperty("logs")]
public PermissionGrant? Logs { get; set; }
/// <summary>
/// Organization permissions.
/// </summary>
[JsonProperty("organization")]
public PermissionGrant? Organization { get; set; }
/// <summary>
/// SSL permissions.
/// </summary>
[JsonProperty("ssl")]
public PermissionGrant? Ssl { get; set; }
/// <summary>
/// WAF permissions.
/// </summary>
[JsonProperty("waf")]
public PermissionGrant? WebApplicationFirewall { get; set; }
/// <summary>
/// Zone Settings permissions.
/// </summary>
[JsonProperty("zone_settings")]
public PermissionGrant? ZoneSettings { get; set; }
/// <summary>
/// Zones permissions.
/// </summary>
[JsonProperty("zones")]
public PermissionGrant? Zones { get; set; }
}
}

View File

@@ -0,0 +1,110 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A Cloudflare subscription.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L705">Source</see>
/// </summary>
public class Subscription
{
/// <summary>
/// Subscription identifier tag.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// The monetary unit in which pricing information is displayed.
/// </summary>
[JsonProperty("currency")]
public string? Currency { get; set; }
/// <summary>
/// The end of the current period and also when the next billing is due.
/// </summary>
[JsonProperty("current_period_end")]
public DateTime? CurrentPeriodEnd { get; set; }
/// <summary>
/// When the current billing period started.
/// </summary>
[JsonProperty("current_period_start")]
public DateTime? CurrentPeriodStart { get; set; }
/// <summary>
/// How often the subscription is renewed automatically.
/// </summary>
[JsonProperty("frequency")]
public RenewFrequency? Frequency { get; set; }
/// <summary>
/// The price of the subscription that will be billed, in US dollars.
/// </summary>
[JsonProperty("price")]
public decimal? Price { get; set; }
/// <summary>
/// The rate plan applied to the subscription.
/// </summary>
[JsonProperty("rate_plan")]
public RatePlan? RatePlan { get; set; }
/// <summary>
/// The state that the subscription is in.
/// </summary>
[JsonProperty("state")]
public SubscriptionState? State { get; set; }
}
/// <summary>
/// The state that the subscription is in.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L745">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum SubscriptionState
{
/// <summary>
/// The subscription is in the trial period.
/// </summary>
[EnumMember(Value = "Trial")]
Trial = 1,
/// <summary>
/// The subscription is provisioned.
/// </summary>
[EnumMember(Value = "Provisioned")]
Provisioned = 2,
/// <summary>
/// The subscription is paid.
/// </summary>
[EnumMember(Value = "Paid")]
Paid = 3,
/// <summary>
/// The subscription is awaiting payment.
/// </summary>
[EnumMember(Value = "AwaitingPayment")]
AwaitingPayment = 4,
/// <summary>
/// The subscription is cancelled.
/// </summary>
[EnumMember(Value = "Cancelled")]
Cancelled = 5,
/// <summary>
/// The subscription has failed.
/// </summary>
[EnumMember(Value = "Failed")]
Failed = 6,
/// <summary>
/// The subscription has expired.
/// </summary>
[EnumMember(Value = "Expired")]
Expired = 7
}
}

View File

@@ -0,0 +1,33 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A component value for a subscription.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L751">Source</see>
/// </summary>
public class SubscriptionComponent
{
/// <summary>
/// The default amount assigned.
/// </summary>
[JsonProperty("default")]
public int? Default { get; set; }
/// <summary>
/// The name of the component value.
/// </summary>
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>
/// The unit price for the component value.
/// </summary>
[JsonProperty("price")]
public decimal? Price { get; set; }
/// <summary>
/// The amount of the component value assigned.
/// </summary>
[JsonProperty("value")]
public int? Value { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A simple zone object. May have null properties if not a zone subscription.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L776">Source</see>
/// </summary>
public class SubscriptionZone
{
/// <summary>
/// Identifier.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// The domain name.
/// </summary>
[JsonProperty("name")]
public string? Name { get; set; }
}
}

View File

@@ -0,0 +1,123 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A token.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L788">Source</see>
/// </summary>
public class Token
{
/// <summary>
/// Token identifier tag.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// Token condition.
/// </summary>
[JsonProperty("condition")]
public TokenCondition? Condition { get; set; }
/// <summary>
/// The expiration time on or after which the JWT MUST NOT be accepted for processing.
/// </summary>
[JsonProperty("created_on")]
public DateTime? ExpiresOn { get; set; }
/// <summary>
/// The time on which the token was created.
/// </summary>
public DateTime? IssuedOn { get; set; }
/// <summary>
/// Last time the token was used.
/// </summary>
public DateTime? LastUsedOn { get; set; }
/// <summary>
/// Last time the token was modified.
/// </summary>
public DateTime? ModifiedOn { get; set; }
/// <summary>
/// Token name.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// The time before which the token MUST NOT be accepted for processing.
/// </summary>
public DateTime? NotBefore { get; set; }
/// <summary>
/// List of access policies assigned to the token.
/// </summary>
public IReadOnlyCollection<TokenPolicy>? Policies { get; set; }
/// <summary>
/// Status of the token.
/// </summary>
public TokenStatus? Status { get; set; }
}
/// <summary>
/// Token condition.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L839">Source</see>
/// </summary>
public class TokenCondition
{
/// <summary>
/// Client IP restrictions.
/// </summary>
[JsonProperty("request_ip")]
public TokenConditionRequestIP? RequestIp { get; set; }
}
/// <summary>
/// Client IP restrictions.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L850">Source</see>
/// </summary>
public class TokenConditionRequestIP
{
/// <summary>
/// List of IPv4/IPv6 CIDR addresses.
/// </summary>
[JsonProperty("in")]
public IReadOnlyCollection<string>? Allowed { get; set; }
/// <summary>
/// List of IPv4/IPv6 CIDR addresses.
/// </summary>
[JsonProperty("not_in")]
public IReadOnlyCollection<string>? Denied { get; set; }
}
/// <summary>
/// Status of the token.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L835">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum TokenStatus
{
/// <summary>
/// The token is active.
/// </summary>
[EnumMember(Value = "active")]
Active = 1,
/// <summary>
/// The token is disabled.
/// </summary>
[EnumMember(Value = "disabled")]
Disabled = 2,
/// <summary>
/// The token is expired.
/// </summary>
[EnumMember(Value = "expired")]
Expired = 3
}
}

View File

@@ -0,0 +1,120 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare
{
/// <summary>
/// A token policy.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L874">Source</see>
/// </summary>
public class TokenPolicy
{
/// <summary>
/// Initializes a new instance of the <see cref="TokenPolicy"/> class.
/// </summary>
/// <param name="id">Policy identifier.</param>
/// <param name="effect">Allow or deny operations against the resources.</param>
public TokenPolicy(string id, TokenPolicyEffect effect)
{
Id = id;
Effect = effect;
}
/// <summary>
/// Policy identifier.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// Allow or deny operations against the resources.
/// </summary>
[JsonProperty("effect")]
public TokenPolicyEffect Effect { get; set; }
/// <summary>
/// A set of permission groups that are specified to the policy.
/// </summary>
[JsonProperty("permission_groups")]
public IReadOnlyCollection<TokenPolicyPermissionGroup> PermissionGroups { get; set; } = [];
/// <summary>
/// A list of resource names that the policy applies to.
/// </summary>
[JsonProperty("resources")]
public IDictionary<string, string> Resources { get; set; } = new Dictionary<string, string>();
}
/// <summary>
/// Allow or deny operations against the resources.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L883">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum TokenPolicyEffect
{
/// <summary>
/// Allow operations against the resources.
/// </summary>
[EnumMember(Value = "allow")]
Allow = 1,
/// <summary>
/// Deny operations against the resources.
/// </summary>
[EnumMember(Value = "deny")]
Deny = 2
}
/// <summary>
/// A named group of permissions that map to a group of operations against resources.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L901">Source</see>
/// </summary>
public class TokenPolicyPermissionGroup
{
/// <summary>
/// Initializes a new instance of the <see cref="TokenPolicyPermissionGroup"/> class.
/// </summary>
/// <param name="id">Identifier of the permission group.</param>
public TokenPolicyPermissionGroup(string id)
{
Id = id;
}
/// <summary>
/// Identifier of the permission group.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// Attributes associated to the permission group.
/// </summary>
[JsonProperty("meta")]
public TokenPolicyPermissionGroupMeta? Meta { get; set; }
/// <summary>
/// Name of the permission group.
/// </summary>
[JsonProperty("name")]
public string? Name { get; set; }
}
/// <summary>
/// Attributes associated to the permission group.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/shared.ts#L922">Source</see>
/// </summary>
public class TokenPolicyPermissionGroupMeta
{
/// <summary>
/// Key.
/// </summary>
[JsonProperty("key")]
public string? Key { get; set; }
/// <summary>
/// Value.
/// </summary>
[JsonProperty("value")]
public string? Value { get; set; }
}
}

21
src/Cloudflare/README.md Normal file
View File

@@ -0,0 +1,21 @@
# Cloudflare API for .NET | Core
This is the core package for all extensions of the Cloudflare API implemented by [AM.WD].
## Contents
- The `(I)CloudflareClient` with base calls for `GET`, `POST`, `PUT`, `PATCH` and `DELETE` requests.
- Base classes to receive responses.
- `CloudflareException` to specify some errors.
- `IAuthentication` implementations to allow API-Token and API-Key (legacy) authentication.
Any specific request will be defined via extension packages.
---
Published under MIT License (see [choose a license])
[AM.WD]: https://www.nuget.org/packages?q=AMWD.&sortby=created-desc
[choose a license]: https://choosealicense.com/licenses/mit/