This commit is contained in:
@@ -9,5 +9,5 @@ public record Token
|
|||||||
public string TokenType { get; init; } = String.Empty;
|
public string TokenType { get; init; } = String.Empty;
|
||||||
|
|
||||||
//For JSON deserialization
|
//For JSON deserialization
|
||||||
public int ExpiresIn { get => (int)DateTimeOffset.Now.Subtract(Expires).TotalSeconds; init => Expires = DateTimeOffset.Now.AddSeconds(value); }
|
public int ExpiresIn { get => (int)Expires.Subtract(DateTimeOffset.Now).TotalSeconds; init => Expires = DateTimeOffset.Now.AddSeconds(value); }
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using ProofOfConcept.Models;
|
using ProofOfConcept.Models;
|
||||||
@@ -18,20 +19,12 @@ public class TeslaAuthenticatorService
|
|||||||
const string authEndpointURL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token";
|
const string authEndpointURL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token";
|
||||||
const string euBaseURL = "https://fleet-api.prd.eu.vn.cloud.tesla.com";
|
const string euBaseURL = "https://fleet-api.prd.eu.vn.cloud.tesla.com";
|
||||||
|
|
||||||
private JsonSerializerOptions serializerOptions;
|
|
||||||
|
|
||||||
public TeslaAuthenticatorService(ILogger<TeslaAuthenticatorService> logger, IOptions<TeslaAuthenticatorServiceConfiguration> options, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache)
|
public TeslaAuthenticatorService(ILogger<TeslaAuthenticatorService> logger, IOptions<TeslaAuthenticatorServiceConfiguration> options, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.httpClientFactory = httpClientFactory;
|
this.httpClientFactory = httpClientFactory;
|
||||||
this.memoryCache = memoryCache;
|
this.memoryCache = memoryCache;
|
||||||
this.configuration = options.Value;
|
this.configuration = options.Value;
|
||||||
|
|
||||||
serializerOptions = new JsonSerializerOptions()
|
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true,
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asynchronously retrieves an authentication token from the Tesla partner API.
|
/// Asynchronously retrieves an authentication token from the Tesla partner API.
|
||||||
@@ -39,7 +32,7 @@ public class TeslaAuthenticatorService
|
|||||||
/// OAuth2 access token that grants access to Tesla partner services.
|
/// OAuth2 access token that grants access to Tesla partner services.
|
||||||
/// <return>Returns a string containing the authentication token received from the API.</return>
|
/// <return>Returns a string containing the authentication token received from the API.</return>
|
||||||
/// <exception cref="HttpRequestException">Thrown when the HTTP request to the API fails.</exception>
|
/// <exception cref="HttpRequestException">Thrown when the HTTP request to the API fails.</exception>
|
||||||
public async Task<Token> AcquirePartnerAuthenticationTokenAsync()
|
public async Task<Result<Token>> AcquirePartnerAuthenticationTokenAsync()
|
||||||
{
|
{
|
||||||
this.logger.LogTrace("Acquiring authentication token from Tesla partner API...");
|
this.logger.LogTrace("Acquiring authentication token from Tesla partner API...");
|
||||||
|
|
||||||
@@ -60,30 +53,65 @@ public class TeslaAuthenticatorService
|
|||||||
|
|
||||||
FormUrlEncodedContent content = new FormUrlEncodedContent(formData);
|
FormUrlEncodedContent content = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
// Send POST request
|
string json = "";
|
||||||
HttpResponseMessage response = await client.PostAsync(authEndpointURL, content);
|
|
||||||
this.logger.LogTrace("Response status code: {0}", response.StatusCode);
|
|
||||||
|
|
||||||
// Throw if not successful
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
// Read the response body as a string
|
|
||||||
string json = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Token? result = JsonSerializer.Deserialize<Token>(json);
|
// Send POST request
|
||||||
|
HttpResponseMessage response = await client.PostAsync(authEndpointURL, content);
|
||||||
if (result is null)
|
this.logger.LogTrace("Response status code: {0}", response.StatusCode);
|
||||||
throw new FormatException("Response could not be deserialized (null).");
|
|
||||||
|
// Throw if not successful
|
||||||
// Return the token response
|
response.EnsureSuccessStatusCode();
|
||||||
return result;
|
|
||||||
|
// Read the response body as a string
|
||||||
|
json = await response.Content.ReadAsStringAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch (HttpRequestException httpRequestException)
|
||||||
|
{
|
||||||
|
this.logger.LogError(httpRequestException, "HTTP Request error while acquiring partner authentication token from Tesla API");
|
||||||
|
return new Result<Token>(httpRequestException);
|
||||||
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
throw new AggregateException($"Could not deserialize token response: {json}", e);
|
this.logger.LogError(e, "Unexpected communication error while acquiring partner authentication token from Tesla API");
|
||||||
|
return new Result<Token>(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if the response is empty
|
||||||
|
if (String.IsNullOrWhiteSpace(json))
|
||||||
|
throw new JsonException("Response could not be deserialized (empty).");
|
||||||
|
|
||||||
|
// Deserializer options: {"access_token":"999 random chars including some special characters","expires_in":28800,"token_type":"Bearer"}
|
||||||
|
JsonSerializerOptions serializerOptions = new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deserialize the JSON response
|
||||||
|
Token? result = JsonSerializer.Deserialize<Token>(json, serializerOptions);
|
||||||
|
|
||||||
|
if (result is null)
|
||||||
|
throw new JsonException("Response could not be deserialized (null).");
|
||||||
|
|
||||||
|
this.logger.LogInformation("Authentication token acquired from Tesla partner API");
|
||||||
|
return new Result<Token>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (JsonException jsonException)
|
||||||
|
{
|
||||||
|
this.logger.LogError(jsonException, "JSON deserialization error while acquiring partner authentication token from Tesla API");
|
||||||
|
return new Result<Token>(jsonException);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.logger.LogError(e, "Unexpected serialization error while acquiring partner authentication token from Tesla API");
|
||||||
|
return new Result<Token>(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +122,7 @@ public class TeslaAuthenticatorService
|
|||||||
/// <return>Returns a Token object containing the authentication token and its expiration details.</return>
|
/// <return>Returns a Token object containing the authentication token and its expiration details.</return>
|
||||||
/// <exception cref="HttpRequestException">Thrown when the HTTP request to acquire a new partner token fails.</exception>
|
/// <exception cref="HttpRequestException">Thrown when the HTTP request to acquire a new partner token fails.</exception>
|
||||||
/// <exception cref="Exception">Thrown for any unexpected errors during the token retrieval or caching process.</exception>
|
/// <exception cref="Exception">Thrown for any unexpected errors during the token retrieval or caching process.</exception>
|
||||||
public async Task<Token> GetPartnerAuthenticationTokenAsync()
|
public async Task<Result<Token>> GetPartnerAuthenticationTokenAsync()
|
||||||
{
|
{
|
||||||
this.logger.LogTrace("Getting partner authentication token...");
|
this.logger.LogTrace("Getting partner authentication token...");
|
||||||
|
|
||||||
@@ -102,17 +130,25 @@ public class TeslaAuthenticatorService
|
|||||||
if (this.memoryCache.TryGetValue(Keys.TeslaPartnerToken, out Token? token) && token is not null)
|
if (this.memoryCache.TryGetValue(Keys.TeslaPartnerToken, out Token? token) && token is not null)
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Partner authentication token provided from cache");
|
this.logger.LogInformation("Partner authentication token provided from cache");
|
||||||
return token;
|
return Result.Success(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Acquire token
|
//Acquire token
|
||||||
token = await AcquirePartnerAuthenticationTokenAsync();
|
Result<Token> aquirationResult = await AcquirePartnerAuthenticationTokenAsync();
|
||||||
|
|
||||||
//Save to cache
|
//Save to cache
|
||||||
this.memoryCache.Set(Keys.TeslaPartnerToken, token, token.Expires.Subtract(this.configuration.MemoryCacheDelta));
|
if (aquirationResult.IsSuccessful)
|
||||||
|
{
|
||||||
this.logger.LogInformation("Partner authentication token provided provided by acquiring from API");
|
token = aquirationResult.Value;
|
||||||
return token;
|
this.memoryCache.Set(Keys.TeslaPartnerToken, token, token.Expires.Subtract(this.configuration.MemoryCacheDelta));
|
||||||
|
|
||||||
|
this.logger.LogInformation("Partner authentication token provided by acquiring from API");
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
this.logger.LogError("Failed to get partner authentication token");
|
||||||
|
|
||||||
|
return aquirationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> RegisterApplicationAsync(CancellationToken cancellationToken = default(CancellationToken))
|
public async Task<Result> RegisterApplicationAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||||
@@ -121,8 +157,14 @@ public class TeslaAuthenticatorService
|
|||||||
|
|
||||||
//Create client and get partner token
|
//Create client and get partner token
|
||||||
HttpClient httpClient = httpClientFactory.CreateClient();
|
HttpClient httpClient = httpClientFactory.CreateClient();
|
||||||
Token partnerToken = await GetPartnerAuthenticationTokenAsync();
|
Result<Token> partnerTokenResult = await GetPartnerAuthenticationTokenAsync();
|
||||||
|
|
||||||
|
if (!partnerTokenResult.IsSuccessful)
|
||||||
|
return Result.Fail(partnerTokenResult.Exception ?? new Exception("Partner authentication token not found"));
|
||||||
|
|
||||||
|
//Set partner token
|
||||||
|
Token partnerToken = partnerTokenResult.Value;
|
||||||
|
|
||||||
//Set headers
|
//Set headers
|
||||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken);
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken);
|
||||||
@@ -135,7 +177,29 @@ public class TeslaAuthenticatorService
|
|||||||
bool success = response.IsSuccessStatusCode;
|
bool success = response.IsSuccessStatusCode;
|
||||||
|
|
||||||
logger.LogInformation("Application registration result: {Success}", success ? "Success" : "Failed");
|
logger.LogInformation("Application registration result: {Success}", success ? "Success" : "Failed");
|
||||||
return new Result(success);
|
|
||||||
|
//Load response from server
|
||||||
|
string json = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
this.logger.LogTrace("Registration response from server: {Response}", json);
|
||||||
|
|
||||||
|
//{"isSuccessful":true,"isFailed":false,"exceptions":[],"exception":null}
|
||||||
|
//Parse JSON
|
||||||
|
JsonNode? root = JsonNode.Parse(json);
|
||||||
|
bool isSuccessful = root?["isSuccessful"]?.GetValue<bool>() ?? false;
|
||||||
|
bool isFailed = root?["isFailed"]?.GetValue<bool>() ?? false;
|
||||||
|
|
||||||
|
if (success && isSuccessful && !isFailed)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Application registered successfully");
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogError("Application registration failed");
|
||||||
|
|
||||||
|
return Result.Fail(new AggregateException(new Exception(root?["exception"]?.ToJsonString()), new Exception(root?["exceptions"]?.ToJsonString())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<bool>> CheckApplicationRegistrationAsync(CancellationToken cancellationToken = default(CancellationToken))
|
public async Task<Result<bool>> CheckApplicationRegistrationAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||||
@@ -144,14 +208,20 @@ public class TeslaAuthenticatorService
|
|||||||
|
|
||||||
//Create client and get partner token
|
//Create client and get partner token
|
||||||
HttpClient httpClient = httpClientFactory.CreateClient();
|
HttpClient httpClient = httpClientFactory.CreateClient();
|
||||||
Token partnerToken = await GetPartnerAuthenticationTokenAsync();
|
Result<Token> partnerTokenResult = await GetPartnerAuthenticationTokenAsync();
|
||||||
|
|
||||||
|
if (!partnerTokenResult.IsSuccessful)
|
||||||
|
return Result.Fail(partnerTokenResult.Exception ?? new Exception("Partner authentication token not found"));
|
||||||
|
|
||||||
|
//Set partner token
|
||||||
|
Token partnerToken = partnerTokenResult.Value;
|
||||||
|
|
||||||
//Set headers
|
//Set headers
|
||||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken);
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken);
|
||||||
|
|
||||||
//Send request
|
//Send request
|
||||||
HttpResponseMessage response = await httpClient.GetAsync($"{euBaseURL}/api/1/partner_accounts", cancellationToken);
|
HttpResponseMessage response = await httpClient.GetAsync($"{euBaseURL}/api/1/partner_accounts/public_key?domain={this.configuration.Domain}", cancellationToken);
|
||||||
string responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
string responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
|
||||||
logger.LogInformation("Application registration result: {Result}", responseBody);
|
logger.LogInformation("Application registration result: {Result}", responseBody);
|
||||||
|
|||||||
Reference in New Issue
Block a user