public GeneralSettingsController(IServiceProvider sp, GeneralSettingsService service) : base(sp) { _service = service; }
public GeneralSettingsController(IServiceProvider sp, GeneralSettingsService service, ILogger <GeneralSettingsController> logger, ISettingsCache settingsCache) : base(sp) { _service = service; _logger = logger; _settingsCache = settingsCache; }
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { // (1) Make sure the API caller have provided a tenantId, and extract it try { var cancellation = context.HttpContext.RequestAborted; int tenantId = _tenantIdAccessor.GetTenantId(); // Init the database connection... // The client sometimes makes ambient API calls, not in response to user interaction // Such calls should not update LastAccess of that user bool silent = context.HttpContext.Request.Query["silent"].FirstOrDefault()?.ToString()?.ToLower() == "true"; await _appRepo.InitConnectionAsync(tenantId, setLastActive : !silent, cancellation); // (2) Make sure the user is a member of this tenant UserInfo userInfo = await _appRepo.GetUserInfoAsync(cancellation); if (userInfo.UserId == null) { // If there is no user cut the pipeline short and return a Forbidden 403 context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden); // This indicates to the client to discard all cached information about this // company since the user is no longer a member of it context.HttpContext.Response.Headers.Add("x-settings-version", Constants.Unauthorized); context.HttpContext.Response.Headers.Add("x-definitions-version", Constants.Unauthorized); context.HttpContext.Response.Headers.Add("x-permissions-version", Constants.Unauthorized); context.HttpContext.Response.Headers.Add("x-user-settings-version", Constants.Unauthorized); return; } var userId = userInfo.UserId.Value; var externalId = _externalUserAccessor.GetUserId(); var externalEmail = _externalUserAccessor.GetUserEmail(); // (3) If the user exists but new, set the External Id if (userInfo.ExternalId == null) { // Update external Id in this tenant database await _appRepo.Users__SetExternalIdByUserId(userId, externalId); // Update external Id in the central Admin database too (To avoid an awkward situation // where a user exists on the tenant but not on the Admin db, if they change their email in between) var adminRepo = _serviceProvider.GetRequiredService <AdminRepository>(); await adminRepo.DirectoryUsers__SetExternalIdByEmail(externalEmail, externalId); } else if (userInfo.ExternalId != externalId) { // Note: there is the edge case of identity providers who allow email recycling. I.e. we can get the same email twice with // two different external Ids. This issue is so unlikely to naturally occur and cause problems here that we are not going // to handle it for now. It can however happen artificually if the application is re-configured to a new identity provider, // or if someone messed with the identity database directly, but again out of scope for now. context.Result = new BadRequestObjectResult("The sign-in email already exists but with a different external Id"); return; } // (4) If the user's email address has changed at the identity server, update it locally else if (userInfo.Email != externalEmail) { await _appRepo.Users__SetEmailByUserId(userId, externalEmail); } // (5) Set the tenant info in the context, to make it accessible for model metadata providers var tenantInfo = await _appRepo.GetTenantInfoAsync(cancellation); _tenantInfoAccessor.SetInfo(tenantId, tenantInfo); // (6) Ensure the freshness of the definitions and settings caches { var databaseVersion = tenantInfo.DefinitionsVersion; var serverVersion = _definitionsCache.GetDefinitionsIfCached(tenantId)?.Version; if (serverVersion == null || serverVersion != databaseVersion) { // Update the cache var definitions = await DefinitionsService.LoadDefinitionsForClient(_appRepo, cancellation); if (!cancellation.IsCancellationRequested) { _definitionsCache.SetDefinitions(tenantId, definitions); } } } { var databaseVersion = tenantInfo.SettingsVersion; var serverVersion = _settingsCache.GetSettingsIfCached(tenantId)?.Version; if (serverVersion == null || serverVersion != databaseVersion) { // Update the cache var settings = await GeneralSettingsService.LoadSettingsForClient(_appRepo, cancellation); if (!cancellation.IsCancellationRequested) { _settingsCache.SetSettings(tenantId, settings); } } } // (7) If any version headers are supplied: examine their freshness { // Permissions var clientVersion = context.HttpContext.Request.Headers["X-Permissions-Version"].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(clientVersion)) { var databaseVersion = userInfo.PermissionsVersion; context.HttpContext.Response.Headers.Add("x-permissions-version", clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale); } } { // User Settings var clientVersion = context.HttpContext.Request.Headers["X-User-Settings-Version"].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(clientVersion)) { var databaseVersion = userInfo.UserSettingsVersion; context.HttpContext.Response.Headers.Add("x-user-settings-version", clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale); } } { // Definitions var clientVersion = context.HttpContext.Request.Headers["X-Definitions-Version"].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(clientVersion)) { var databaseVersion = tenantInfo.DefinitionsVersion; context.HttpContext.Response.Headers.Add("x-definitions-version", clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale); } } { // Settings var clientVersion = context.HttpContext.Request.Headers["X-Settings-Version"].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(clientVersion)) { var databaseVersion = tenantInfo.SettingsVersion; context.HttpContext.Response.Headers.Add("x-settings-version", clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale); } } // Call the Action itself await next(); } catch (TaskCanceledException) { context.Result = new OkResult(); return; } catch (MultitenancyException ex) { // If the tenant Id is not provided cut the pipeline short and return a Bad Request 400 context.Result = new BadRequestObjectResult(ex.Message); return; } catch (BadRequestException ex) { // If the tenant Id is not provided cut the pipeline short and return a Bad Request 400 context.Result = new BadRequestObjectResult(ex.Message); return; } catch (Exception ex) { // TODO: Return to logging and 500 status code context.Result = new BadRequestObjectResult(ex.GetType().Name + ": " + ex.Message); //_logger.LogError(ex.Message); //context.Result = new StatusCodeResult(StatusCodes.Status500InternalServerError); return; } }