summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-06-04 21:05:47 +0200
committerivarlovlie <git@ivarlovlie.no>2022-06-04 21:13:00 +0200
commitcf9597de850de1ef721a35ad79ac67b9fdb9e1d4 (patch)
tree4805de53a24bedd91238d6d306476b1921ccc0ad /server
parentfdbeffe78e5cd7393d28915189ec518a06b941f1 (diff)
downloadgreatoffice-cf9597de850de1ef721a35ad79ac67b9fdb9e1d4.tar.xz
greatoffice-cf9597de850de1ef721a35ad79ac67b9fdb9e1d4.zip
refactor: Use Vault to get configuration
Diffstat (limited to 'server')
-rw-r--r--server/src/Data/Static/AppConfiguration.cs27
-rw-r--r--server/src/Data/Static/AppEnvironmentVariables.cs25
-rw-r--r--server/src/Program.cs63
-rw-r--r--server/src/Services/ForgotPasswordService.cs14
-rw-r--r--server/src/Services/MailService.cs12
-rw-r--r--server/src/Services/VaultService.cs80
-rw-r--r--server/src/Utilities/ConfigurationExtensions.cs33
-rw-r--r--server/src/Utilities/GithubAuthenticationHelpers.cs7
8 files changed, 183 insertions, 78 deletions
diff --git a/server/src/Data/Static/AppConfiguration.cs b/server/src/Data/Static/AppConfiguration.cs
new file mode 100644
index 0000000..a6494ea
--- /dev/null
+++ b/server/src/Data/Static/AppConfiguration.cs
@@ -0,0 +1,27 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public class AppConfiguration
+{
+ public string DB_HOST { get; set; }
+ public string DB_PORT { get; set; }
+ public string DB_USER { get; set; }
+ public string DB_PASSWORD { get; set; }
+ public string DB_NAME { get; set; }
+ public string QUARTZ_DB_HOST { get; set; }
+ public string QUARTZ_DB_PORT { get; set; }
+ public string QUARTZ_DB_USER { get; set; }
+ public string QUARTZ_DB_PASSWORD { get; set; }
+ public string QUARTZ_DB_NAME { get; set; }
+ public string SEQ_API_KEY { get; set; }
+ public string SEQ_API_URL { get; set; }
+ public string SMTP_HOST { get; set; }
+ public string SMTP_PORT { get; set; }
+ public string SMTP_USER { get; set; }
+ public string SMTP_PASSWORD { get; set; }
+ public string EMAIL_FROM_ADDRESS { get; set; }
+ public string EMAIL_FROM_DISPLAY_NAME { get; set; }
+ public string PORTAL_URL { get; set; }
+ public string GITHUB_CLIENT_ID { get; set; }
+ public string GITHUB_CLIENT_SECRET { get; set; }
+ public string APP_AES_KEY { get; set; }
+}
diff --git a/server/src/Data/Static/AppEnvironmentVariables.cs b/server/src/Data/Static/AppEnvironmentVariables.cs
index a734146..181eced 100644
--- a/server/src/Data/Static/AppEnvironmentVariables.cs
+++ b/server/src/Data/Static/AppEnvironmentVariables.cs
@@ -2,26 +2,7 @@ namespace IOL.GreatOffice.Api.Data.Static;
public static class AppEnvironmentVariables
{
- public const string DB_HOST = "DB_HOST";
- public const string DB_PORT = "DB_PORT";
- public const string DB_USER = "DB_USER";
- public const string DB_PASSWORD = "DB_PASSWORD";
- public const string DB_NAME = "DB_NAME";
- public const string QUARTZ_DB_HOST = "QUARTZ_DB_HOST";
- public const string QUARTZ_DB_PORT = "QUARTZ_DB_PORT";
- public const string QUARTZ_DB_USER = "QUARTZ_DB_USER";
- public const string QUARTZ_DB_PASSWORD = "QUARTZ_DB_PASSWORD";
- public const string QUARTZ_DB_NAME = "QUARTZ_DB_NAME";
- public const string SEQ_API_KEY = "SEQ_API_KEY";
- public const string SEQ_API_URL = "SEQ_API_URL";
- public const string SMTP_HOST = "SMTP_HOST";
- public const string SMTP_PORT = "SMTP_PORT";
- public const string SMTP_USER = "SMTP_USER";
- public const string SMTP_PASSWORD = "SMTP_PASSWORD";
- public const string EMAIL_FROM_ADDRESS = "EMAIL_FROM_ADDRESS";
- public const string EMAIL_FROM_DISPLAY_NAME = "EMAIL_FROM_DISPLAY_NAME";
- public const string ACCOUNTS_URL = "ACCOUNTS_URL";
- public const string GITHUB_CLIENT_ID = "GH_CLIENT_ID";
- public const string GITHUB_CLIENT_SECRET = "GH_CLIENT_SECRET";
- public const string APP_AES_KEY = "APP_AES_KEY";
+ public const string VAULT_TOKEN = "VAULT_TOKEN";
+ public const string VAULT_URL = "VAULT_URL";
+ public const string MAIN_CONFIG_SHEET = "MAIN_CONFIG_SHEET";
}
diff --git a/server/src/Program.cs b/server/src/Program.cs
index b449117..d6e8929 100644
--- a/server/src/Program.cs
+++ b/server/src/Program.cs
@@ -37,12 +37,12 @@ global using IOL.GreatOffice.Api.Data;
global using IOL.GreatOffice.Api.Data.Static;
global using IOL.GreatOffice.Api.Services;
global using IOL.GreatOffice.Api.Utilities;
-using System.Diagnostics;
using System.Reflection;
using IOL.GreatOffice.Api.Endpoints.V1;
using IOL.GreatOffice.Api.Jobs;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Versioning;
+using Microsoft.Extensions.Options;
using Quartz;
namespace IOL.GreatOffice.Api;
@@ -51,39 +51,50 @@ public static class Program
{
public static WebApplicationBuilder CreateAppBuilder(string[] args) {
var builder = WebApplication.CreateBuilder(args);
+ builder.Services.AddLogging();
+ builder.Services.AddHttpClient();
- var seqUrl = builder.Configuration.GetValue<string>(AppEnvironmentVariables.SEQ_API_URL);
- var seqKey = builder.Configuration.GetValue<string>(AppEnvironmentVariables.SEQ_API_KEY);
+ builder.Services.AddScoped<MailService>();
+ builder.Services.AddScoped<ForgotPasswordService>();
+ builder.Services.AddScoped<UserService>();
+ builder.Services.AddScoped<VaultService>();
+ var vaultService = builder.Services.BuildServiceProvider().GetRequiredService<VaultService>();
+ var configurationResponse = vaultService.GetSecretAsync<AppConfiguration>(builder.Configuration.GetValue<string>(AppEnvironmentVariables.MAIN_CONFIG_SHEET)).Result;
+ builder.Services.AddOptions<AppConfiguration>()
+ .Configure(o => {
+ foreach (var property in typeof(AppConfiguration).GetProperties().Where(p => p.CanWrite)) {
+ property.SetValue(o, property.GetValue(configurationResponse.Data.Data, null), null);
+ }
+ });
+ var configuration = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<AppConfiguration>>().Value;
var logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.ReadFrom.Configuration(builder.Configuration)
.WriteTo.Console();
- if (!builder.Environment.IsDevelopment() && seqUrl.HasValue() && seqKey.HasValue()) {
- logger.WriteTo.Seq(seqUrl, apiKey: seqKey);
+ if (!builder.Environment.IsDevelopment() && configuration.SEQ_API_KEY.HasValue() && configuration.SEQ_API_URL.HasValue()) {
+ logger.WriteTo.Seq(configuration.SEQ_API_URL, apiKey: configuration.SEQ_API_KEY);
}
Log.Logger = logger.CreateLogger();
- Log.Information("Starting web host"
+ Log.Information("Starting web host, "
+ JsonSerializer.Serialize(new {
DateTime.UtcNow,
PID = Environment.ProcessId,
- DB_HOST = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_HOST),
- DB_PORT = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_PORT),
- DB_USER = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_USER),
- DB_NAME = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_NAME),
- DB_PASS = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_PASSWORD).Obfuscate() ?? "!!!Empty!!!",
- QUARTZ_DB_HOST = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_HOST),
- QUARTZ_DB_PORT = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_PORT),
- QUARTZ_DB_USER = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_USER),
- QUARTZ_DB_NAME = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_NAME),
- QUARTZ_DB_PASS = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_PASSWORD).Obfuscate()
- ?? "!!!Empty!!!",
+ configuration.DB_HOST,
+ configuration.DB_PORT,
+ configuration.DB_USER,
+ configuration.DB_NAME,
+ DB_PASS = configuration.DB_PASSWORD.Obfuscate() ?? "!!!Empty!!!",
+ configuration.QUARTZ_DB_HOST,
+ configuration.QUARTZ_DB_PORT,
+ configuration.QUARTZ_DB_USER,
+ configuration.QUARTZ_DB_NAME,
+ QUARTZ_DB_PASS = configuration.QUARTZ_DB_PASSWORD.Obfuscate() ?? "!!!Empty!!!",
},
new JsonSerializerOptions() {
WriteIndented = true
}));
-
builder.Host.UseSerilog(Log.Logger);
builder.WebHost.ConfigureKestrel(kestrel => {
kestrel.AddServerHeader = false;
@@ -104,7 +115,7 @@ public static class Program
builder.Services.Configure(JsonSettings.Default);
builder.Services.AddQuartz(options => {
options.UsePersistentStore(o => {
- o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString());
+ o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString(configuration));
o.UseSerializer<QuartzJsonSerializer>();
});
options.UseMicrosoftDependencyInjectionJobFactory();
@@ -132,20 +143,20 @@ public static class Program
};
})
.AddGitHub(options => {
- options.ClientSecret = builder.Configuration.GetValue<string>(AppEnvironmentVariables.GITHUB_CLIENT_SECRET);
- options.ClientId = builder.Configuration.GetValue<string>(AppEnvironmentVariables.GITHUB_CLIENT_ID);
+ options.ClientSecret = builder.Configuration.GetValue<string>(configuration.GITHUB_CLIENT_SECRET);
+ options.ClientId = builder.Configuration.GetValue<string>(configuration.GITHUB_CLIENT_ID);
options.SaveTokens = true;
options.CorrelationCookie.Name = "gh_correlation";
options.CorrelationCookie.SameSite = SameSiteMode.Lax;
options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
options.CorrelationCookie.HttpOnly = true;
- options.Events.OnCreatingTicket = context => GithubAuthenticationHelpers.HandleGithubTicketCreation(context, builder.Configuration);
+ options.Events.OnCreatingTicket = context => GithubAuthenticationHelpers.HandleGithubTicketCreation(context, builder.Configuration, configuration);
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(AppConstants.BASIC_AUTH_SCHEME, default);
builder.Services.AddDbContext<AppDbContext>(options => {
- options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(),
+ options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(configuration),
npgsqlDbContextOptionsBuilder => {
npgsqlDbContextOptionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
npgsqlDbContextOptionsBuilder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), default);
@@ -193,16 +204,10 @@ public static class Program
});
});
- builder.Services.AddScoped<MailService>();
- builder.Services.AddScoped<ForgotPasswordService>();
- builder.Services.AddScoped<UserService>();
- builder.Services.AddLogging();
- builder.Services.AddHttpClient();
builder.Services
.AddControllers()
.AddJsonOptions(JsonSettings.Default);
-
return builder;
}
diff --git a/server/src/Services/ForgotPasswordService.cs b/server/src/Services/ForgotPasswordService.cs
index de38b29..e6b6acf 100644
--- a/server/src/Services/ForgotPasswordService.cs
+++ b/server/src/Services/ForgotPasswordService.cs
@@ -1,16 +1,18 @@
+using Microsoft.Extensions.Options;
+
namespace IOL.GreatOffice.Api.Services;
public class ForgotPasswordService
{
private readonly AppDbContext _context;
private readonly MailService _mailService;
- private readonly IConfiguration _configuration;
+ private readonly IOptions<AppConfiguration> _configuration;
private readonly ILogger<ForgotPasswordService> _logger;
public ForgotPasswordService(
AppDbContext context,
- IConfiguration configuration,
+ IOptions<AppConfiguration> configuration,
ILogger<ForgotPasswordService> logger,
MailService mailService
) {
@@ -57,9 +59,9 @@ public class ForgotPasswordService
var request = new ForgotPasswordRequest(user);
_context.ForgotPasswordRequests.Add(request);
await _context.SaveChangesAsync(cancellationToken);
- var accountsUrl = _configuration.GetValue<string>(AppEnvironmentVariables.ACCOUNTS_URL);
- var emailFromAddress = _configuration.GetValue<string>(AppEnvironmentVariables.EMAIL_FROM_ADDRESS);
- var emailFromDisplayName = _configuration.GetValue<string>(AppEnvironmentVariables.EMAIL_FROM_DISPLAY_NAME);
+ var portalUrl = _configuration.Value.PORTAL_URL;
+ var emailFromAddress = _configuration.Value.EMAIL_FROM_ADDRESS;
+ var emailFromDisplayName = _configuration.Value.EMAIL_FROM_DISPLAY_NAME;
var zonedExpirationDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(request.ExpirationDate, requestTz.Id);
var message = new MailMessage {
From = new MailAddress(emailFromAddress, emailFromDisplayName),
@@ -72,7 +74,7 @@ Hi {user.Username}
Go to the following link to set a new password.
-{accountsUrl}/#/reset-password?id={request.Id}
+{portalUrl}/#/reset-password?id={request.Id}
The link expires at {zonedExpirationDate:yyyy-MM-dd hh:mm}.
If you did not request a password reset, no action is required.
diff --git a/server/src/Services/MailService.cs b/server/src/Services/MailService.cs
index b271de4..d773303 100644
--- a/server/src/Services/MailService.cs
+++ b/server/src/Services/MailService.cs
@@ -1,3 +1,5 @@
+using Microsoft.Extensions.Options;
+
namespace IOL.GreatOffice.Api.Services;
public class MailService
@@ -13,12 +15,12 @@ public class MailService
/// </summary>
/// <param name="configuration"></param>
/// <param name="logger"></param>
- public MailService(IConfiguration configuration, ILogger<MailService> logger) {
+ public MailService(IOptions<AppConfiguration> configuration, ILogger<MailService> logger) {
_logger = logger;
- _emailHost = configuration.GetValue<string>(AppEnvironmentVariables.SMTP_HOST);
- _emailPort = configuration.GetValue<int>(AppEnvironmentVariables.SMTP_PORT);
- _emailUser = configuration.GetValue<string>(AppEnvironmentVariables.SMTP_USER);
- _emailPassword = configuration.GetValue<string>(AppEnvironmentVariables.SMTP_PASSWORD);
+ _emailHost = configuration.Value.SMTP_HOST;
+ _emailPort = Convert.ToInt32(configuration.Value.SMTP_PORT);
+ _emailUser = configuration.Value.SMTP_USER;
+ _emailPassword = configuration.Value.SMTP_PASSWORD;
}
/// <summary>
diff --git a/server/src/Services/VaultService.cs b/server/src/Services/VaultService.cs
new file mode 100644
index 0000000..388f8d4
--- /dev/null
+++ b/server/src/Services/VaultService.cs
@@ -0,0 +1,80 @@
+namespace IOL.GreatOffice.Api.Services;
+
+public class VaultService
+{
+ private readonly HttpClient _client;
+
+ public VaultService(HttpClient client, IConfiguration configuration) {
+ var token = configuration.GetValue<string>("VAULT_TOKEN");
+ var vaultUrl = configuration.GetValue<string>("VAULT_URL");
+ if (token.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_TOKEN is empty");
+ if (vaultUrl.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_URL is empty");
+ client.DefaultRequestHeaders.Add("X-Vault-Token", token);
+ client.BaseAddress = new Uri(vaultUrl);
+ _client = client;
+ }
+
+ public async Task<GetSecretResponse<T>> GetSecretAsync<T>(string path) {
+ return await _client.GetFromJsonAsync<GetSecretResponse<T>>("/v1/kv/data/" + path);
+ }
+
+ public async Task<RenewTokenResponse> RenewTokenAsync<T>(string token) {
+ var response = await _client.PostAsJsonAsync("v1/auth/token/renew",
+ new {
+ Token = token
+ });
+ if (response.IsSuccessStatusCode) {
+ return await response.Content.ReadFromJsonAsync<RenewTokenResponse>();
+ }
+
+ return default;
+ }
+
+ public class RenewTokenResponse
+ {
+ public Guid RequestId { get; set; }
+ public string LeaseId { get; set; }
+ public bool Renewable { get; set; }
+ public long LeaseDuration { get; set; }
+ public object Data { get; set; }
+ public object WrapInfo { get; set; }
+ public List<string> Warnings { get; set; }
+ public Auth Auth { get; set; }
+ }
+
+ public class Auth
+ {
+ public string ClientToken { get; set; }
+ public string Accessor { get; set; }
+ public List<string> Policies { get; set; }
+ public List<string> TokenPolicies { get; set; }
+ public object Metadata { get; set; }
+ public long LeaseDuration { get; set; }
+ public bool Renewable { get; set; }
+ public string EntityId { get; set; }
+ public string TokenType { get; set; }
+ public bool Orphan { get; set; }
+ public object MfaRequirement { get; set; }
+ public long NumUses { get; set; }
+ }
+
+ public class GetSecretResponse<T>
+ {
+ public VaultSecret<T> Data { get; set; }
+ }
+
+ public class VaultSecret<T>
+ {
+ public T Data { get; set; }
+ public VaultSecretMetadata Metadata { get; set; }
+ }
+
+ public class VaultSecretMetadata
+ {
+ public DateTimeOffset CreatedTime { get; set; }
+ public object CustomMetadata { get; set; }
+ public string DeletionTime { get; set; }
+ public bool Destroyed { get; set; }
+ public long Version { get; set; }
+ }
+}
diff --git a/server/src/Utilities/ConfigurationExtensions.cs b/server/src/Utilities/ConfigurationExtensions.cs
index 772059a..41b6ab3 100644
--- a/server/src/Utilities/ConfigurationExtensions.cs
+++ b/server/src/Utilities/ConfigurationExtensions.cs
@@ -1,27 +1,34 @@
+using Microsoft.Extensions.Options;
+
namespace IOL.GreatOffice.Api.Utilities;
public static class ConfigurationExtensions
{
- public static string GetAppDatabaseConnectionString(this IConfiguration configuration) {
- var host = configuration.GetValue<string>(AppEnvironmentVariables.DB_HOST);
- var port = configuration.GetValue<string>(AppEnvironmentVariables.DB_PORT);
- var database = configuration.GetValue<string>(AppEnvironmentVariables.DB_NAME);
- var user = configuration.GetValue<string>(AppEnvironmentVariables.DB_USER);
- var password = configuration.GetValue<string>(AppEnvironmentVariables.DB_PASSWORD);
+ public static string GetAppDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) {
+ var host = configuration.DB_HOST;
+ var port = configuration.DB_PORT;
+ var database = configuration.DB_NAME;
+ var user = configuration.DB_USER;
+ var password = configuration.DB_PASSWORD;
- if (configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development") {
+ if (config.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development") {
return $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true";
}
return $"Server={host};Port={port};Database={database};User Id={user};Password={password}";
}
- public static string GetQuartzDatabaseConnectionString(this IConfiguration Configuration) {
- var host = Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_HOST);
- var port = Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_PORT);
- var database = Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_NAME);
- var user = Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_USER);
- var password = Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_PASSWORD);
+ public static string GetQuartzDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) {
+ var host = configuration.QUARTZ_DB_HOST;
+ var port = configuration.QUARTZ_DB_PORT;
+ var database = configuration.QUARTZ_DB_NAME;
+ var user = configuration.QUARTZ_DB_USER;
+ var password = configuration.QUARTZ_DB_PASSWORD;
+ Log.Information(host);
+ if (config.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development") {
+ return $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true";
+ }
+
return $"Server={host};Port={port};Database={database};User Id={user};Password={password}";
}
diff --git a/server/src/Utilities/GithubAuthenticationHelpers.cs b/server/src/Utilities/GithubAuthenticationHelpers.cs
index cf0cabb..f924ecc 100644
--- a/server/src/Utilities/GithubAuthenticationHelpers.cs
+++ b/server/src/Utilities/GithubAuthenticationHelpers.cs
@@ -1,11 +1,12 @@
using Microsoft.AspNetCore.Authentication.OAuth;
+using Microsoft.Extensions.Options;
using Npgsql;
namespace IOL.GreatOffice.Api.Utilities;
public static class GithubAuthenticationHelpers
{
- public static async Task HandleGithubTicketCreation(OAuthCreatingTicketContext context, IConfiguration configuration) {
+ public static async Task HandleGithubTicketCreation(OAuthCreatingTicketContext context, IConfiguration configuration, AppConfiguration options) {
var githubId = context.Identity?.FindFirst(p => p.Type == ClaimTypes.NameIdentifier)?.Value;
var githubUsername = context.Identity?.FindFirst(p => p.Type == ClaimTypes.Name)?.Value;
var githubEmail = context.Identity?.FindFirst(p => p.Type == ClaimTypes.Email)?.Value;
@@ -19,7 +20,7 @@ public static class GithubAuthenticationHelpers
context.Identity.RemoveClaim(claim);
}
- var connstring = configuration.GetAppDatabaseConnectionString();
+ var connstring = configuration.GetAppDatabaseConnectionString(options);
var connection = new NpgsqlConnection(connstring);
Log.Information($"Getting user mappings for github user: {githubId}");
@@ -57,7 +58,7 @@ public static class GithubAuthenticationHelpers
await insertUserCommand.ExecuteNonQueryAsync();
await connection.CloseAsync();
- var refreshTokenEncryptionKey = configuration.GetValue<string>(AppEnvironmentVariables.APP_AES_KEY);
+ var refreshTokenEncryptionKey = options.APP_AES_KEY;
string insertMappingQuery;
if (context.RefreshToken.HasValue() && refreshTokenEncryptionKey.HasValue()) {