NUC-33: Implement authentication handling for TelemetryPublisherFirestore
This commit is contained in:
parent
29b7b5ca13
commit
9d4135d6d3
3 changed files with 94 additions and 6 deletions
|
@ -72,6 +72,9 @@ The Telemetry:Publisher must be set to: Firestore
|
|||
Example connection string:
|
||||
`ProjectId=nucuhub;CollectionName=sensors-telemetry-test;Timeout=1000`
|
||||
|
||||
If you want to use Authentication you can do so by providing the following keys
|
||||
in the connection string: WebApiEmail, WebApiPassword, WebApiKey.
|
||||
|
||||
### Reader
|
||||
|
||||
You will need use a firebase client or rest API.
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -19,6 +21,9 @@ namespace NucuCar.Domain.Telemetry
|
|||
/// The connection string has the following parameters:
|
||||
/// ProjectId (required) — The string for the Firestore project id.
|
||||
/// CollectionName (required) — The string for the Firestore collection name.
|
||||
/// WebApiKey (optional) — The web api key of the firebase project.
|
||||
/// WebApiEmail (optional) — An email to use when requesting id tokens.
|
||||
/// WebApiPassword (optional) — The password to use when requesting id tokens.
|
||||
/// Timeout (optional) — The number in milliseconds in which to timeout if publishing fails. Default: 10000
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
|
@ -27,6 +32,13 @@ namespace NucuCar.Domain.Telemetry
|
|||
private readonly HttpClient _httpClient;
|
||||
private readonly int _timeout;
|
||||
|
||||
private string _idToken;
|
||||
|
||||
// Variables used for authentication
|
||||
private readonly string _webEmail;
|
||||
private readonly string _webPassword;
|
||||
private readonly string _webApiKey;
|
||||
|
||||
public TelemetryPublisherFirestore(TelemetryPublisherBuilderOptions opts) : base(opts)
|
||||
{
|
||||
var options = ConnectionStringParser.Parse(opts.ConnectionString);
|
||||
|
@ -44,7 +56,10 @@ namespace NucuCar.Domain.Telemetry
|
|||
$"Missing CollectionName!");
|
||||
}
|
||||
|
||||
_timeout = int.Parse(options.GetValueOrDefault("Timeout", "10000"));
|
||||
_timeout = int.Parse(options.GetValueOrDefault("Timeout", "10000") ?? "10000");
|
||||
_webApiKey = options.GetValueOrDefault("WebApiKey", null);
|
||||
_webEmail = options.GetValueOrDefault("WebApiEmail", null);
|
||||
_webPassword = options.GetValueOrDefault("WebApiPassword", null);
|
||||
|
||||
var requestUrl = $"https://firestore.googleapis.com/v1/projects/{firestoreProjectId}/" +
|
||||
$"databases/(default)/documents/{firestoreCollection}/";
|
||||
|
@ -53,6 +68,46 @@ namespace NucuCar.Domain.Telemetry
|
|||
Logger?.LogInformation($"Initialized {nameof(TelemetryPublisherFirestore)}");
|
||||
}
|
||||
|
||||
private async Task SetupAuthenticationHeaders()
|
||||
{
|
||||
// Make request
|
||||
var requestUrl = $"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={_webApiKey}";
|
||||
var data = new Dictionary<string, object>()
|
||||
{
|
||||
["email"] = _webEmail,
|
||||
["password"] = _webPassword,
|
||||
["returnSecureToken"] = true
|
||||
};
|
||||
// Handle response & setup headers
|
||||
var cts = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
cts.CancelAfter(_timeout);
|
||||
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
|
||||
var responseMessage = await _httpClient.PostAsync(requestUrl, content, cts.Token);
|
||||
var responseJson =
|
||||
JsonConvert.DeserializeObject<dynamic>(await responseMessage.Content.ReadAsStringAsync());
|
||||
if (responseMessage.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
_idToken = responseJson.idToken;
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _idToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpRequestException(responseJson.message ?? "unknown");
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
Logger.LogWarning($"FireStore authenticate: Timeout or cancellation occured. Message {e.Message}.\n");
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Logger?.LogError($"Failed to authenticate!\n{e.GetType().FullName}: {e.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task PublishAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
|
@ -61,19 +116,42 @@ namespace NucuCar.Domain.Telemetry
|
|||
}
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(_timeout);
|
||||
try
|
||||
{
|
||||
cts.CancelAfter(_timeout);
|
||||
var data = FirebaseRestTranslator.Translator.Translate(null, GetTelemetry());
|
||||
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
|
||||
await _httpClient.PostAsync("", content, cts.Token);
|
||||
Logger?.LogInformation("Published data to Firestore!");
|
||||
var responseMessage = await _httpClient.PostAsync("", content, cts.Token);
|
||||
var responseJson =
|
||||
JsonConvert.DeserializeObject<dynamic>(await responseMessage.Content.ReadAsStringAsync());
|
||||
|
||||
switch (responseMessage.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.OK:
|
||||
Logger?.LogInformation("Published data to Firestore!");
|
||||
break;
|
||||
case HttpStatusCode.Forbidden:
|
||||
Logger.LogWarning("Failed to publish telemetry! Forbidden!");
|
||||
await SetupAuthenticationHeaders();
|
||||
break;
|
||||
default:
|
||||
throw new HttpRequestException(responseJson.message ?? "unknown error");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
Logger.LogWarning(
|
||||
$"Firestore publish telemetry: Timeout or cancellation occured. Message {e.Message}.\n");
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Logger?.LogError($"Failed to publish telemetry data!\n{e.GetType().FullName}: {e.Message}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cts.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Telemetry
|
||||
{
|
||||
public class TelemetryPublisherFirestoreTest
|
||||
{
|
||||
// TODO after refactoring
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue