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:
|
Example connection string:
|
||||||
`ProjectId=nucuhub;CollectionName=sensors-telemetry-test;Timeout=1000`
|
`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
|
### Reader
|
||||||
|
|
||||||
You will need use a firebase client or rest API.
|
You will need use a firebase client or rest API.
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -19,6 +21,9 @@ namespace NucuCar.Domain.Telemetry
|
||||||
/// The connection string has the following parameters:
|
/// The connection string has the following parameters:
|
||||||
/// ProjectId (required) — The string for the Firestore project id.
|
/// ProjectId (required) — The string for the Firestore project id.
|
||||||
/// CollectionName (required) — The string for the Firestore collection name.
|
/// 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
|
/// Timeout (optional) — The number in milliseconds in which to timeout if publishing fails. Default: 10000
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,6 +32,13 @@ namespace NucuCar.Domain.Telemetry
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly int _timeout;
|
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)
|
public TelemetryPublisherFirestore(TelemetryPublisherBuilderOptions opts) : base(opts)
|
||||||
{
|
{
|
||||||
var options = ConnectionStringParser.Parse(opts.ConnectionString);
|
var options = ConnectionStringParser.Parse(opts.ConnectionString);
|
||||||
|
@ -44,7 +56,10 @@ namespace NucuCar.Domain.Telemetry
|
||||||
$"Missing CollectionName!");
|
$"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}/" +
|
var requestUrl = $"https://firestore.googleapis.com/v1/projects/{firestoreProjectId}/" +
|
||||||
$"databases/(default)/documents/{firestoreCollection}/";
|
$"databases/(default)/documents/{firestoreCollection}/";
|
||||||
|
@ -53,27 +68,90 @@ namespace NucuCar.Domain.Telemetry
|
||||||
Logger?.LogInformation($"Initialized {nameof(TelemetryPublisherFirestore)}");
|
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)
|
public override async Task PublishAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
cts.CancelAfter(_timeout);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
cts.CancelAfter(_timeout);
|
||||||
var data = FirebaseRestTranslator.Translator.Translate(null, GetTelemetry());
|
var data = FirebaseRestTranslator.Translator.Translate(null, GetTelemetry());
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
|
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
|
||||||
await _httpClient.PostAsync("", content, cts.Token);
|
var responseMessage = await _httpClient.PostAsync("", content, cts.Token);
|
||||||
Logger?.LogInformation("Published data to Firestore!");
|
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}");
|
Logger?.LogError($"Failed to publish telemetry data!\n{e.GetType().FullName}: {e.Message}");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void 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