Beispiel #1
0
        public async Task <IActionResult> Reload(string id)
        {
            if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageTenants))
            {
                return(Unauthorized());
            }

            if (!IsDefaultShell())
            {
                return(Unauthorized());
            }

            var shellSettings = _shellHost.GetAllSettings()
                                .OrderBy(x => x.Name)
                                .Where(x => string.Equals(x.Name, id, StringComparison.OrdinalIgnoreCase))
                                .FirstOrDefault();

            if (shellSettings == null)
            {
                return(NotFound());
            }

            // Generating routes can fail while the tenant is recycled as routes can use services.
            // It could be fixed by waiting for the next request or the end of the current one
            // to actually release the tenant. Right now we render the url before recycling the tenant.

            var redirectUrl = Url.Action(nameof(Index));

            await _shellHost.ReloadShellContextAsync(shellSettings);

            return(Redirect(redirectUrl));
        }
        public override async Task <IDisplayResult> UpdateAsync(GoogleAuthenticationSettings settings, BuildEditorContext context)
        {
            if (context.GroupId == GoogleConstants.Features.GoogleAuthentication)
            {
                var user = _httpContextAccessor.HttpContext?.User;
                if (user == null || !await _authorizationService.AuthorizeAsync(user, Permissions.ManageGoogleAuthentication))
                {
                    return(null);
                }

                var model = new GoogleAuthenticationSettingsViewModel();
                await context.Updater.TryUpdateModelAsync(model, Prefix);

                if (context.Updater.ModelState.IsValid)
                {
                    var protector = _dataProtectionProvider.CreateProtector(GoogleConstants.Features.GoogleAuthentication);

                    settings.ClientID     = model.ClientID;
                    settings.ClientSecret = protector.Protect(model.ClientSecret);
                    settings.CallbackPath = model.CallbackPath;
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }
            return(await EditAsync(settings, context));
        }
Beispiel #3
0
        /// <summary>
        /// The auto setup middleware invoke.
        /// </summary>
        /// <param name="httpContext">
        /// The http context.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task InvokeAsync(HttpContext httpContext)
        {
            if (_setupOptions != null && _shellSettings.State == TenantState.Uninitialized)
            {
                // Try to acquire a lock before starting installation, it guaranties an atomic setup in multi instances environment.
                (var locker, var locked) = await _distributedLock.TryAcquireAutoSetupLockAsync(_lockOptions);

                if (!locked)
                {
                    throw new TimeoutException($"Fails to acquire an auto setup lock for the tenant: {_setupOptions.ShellName}");
                }

                await using var acquiredLock = locker;

                if (_shellSettings.State == TenantState.Uninitialized)
                {
                    var pathBase = httpContext.Request.PathBase;
                    if (!pathBase.HasValue)
                    {
                        pathBase = "/";
                    }

                    // Check if the tenant was installed by another instance.
                    var settings = await _shellSettingsManager.LoadSettingsAsync(_shellSettings.Name);

                    if (settings.State != TenantState.Uninitialized)
                    {
                        await _shellHost.ReloadShellContextAsync(_shellSettings, eventSource : false);

                        httpContext.Response.Redirect(pathBase);

                        return;
                    }

                    var setupService = httpContext.RequestServices.GetRequiredService <ISetupService>();
                    if (await SetupTenantAsync(setupService, _setupOptions, _shellSettings))
                    {
                        if (_setupOptions.IsDefault)
                        {
                            // Create the rest of the shells for further on demand setup.
                            foreach (var setupOptions in _options.Tenants)
                            {
                                if (_setupOptions != setupOptions)
                                {
                                    await CreateTenantSettingsAsync(setupOptions);
                                }
                            }
                        }

                        httpContext.Response.Redirect(pathBase);

                        return;
                    }
                }
            }

            await _next.Invoke(httpContext);
        }
Beispiel #4
0
        public async Task UpdateCustomStyleSiteSettingsAsync(ISite site)
        {
            // Persists new data.
            Session.Save(site);

            // Invalidates the cache after the session is committed.
            _memoryCache.Remove(CacheKey);
            _signal.DeferredSignalToken(CacheKey);

            //Reset ShellHost to apply settings
            await _orchardHost.ReloadShellContextAsync(_currentShellSettings);
        }
Beispiel #5
0
        public override async Task <IDisplayResult> UpdateAsync(ReCaptchaSettings section, BuildEditorContext context)
        {
            if (context.GroupId == GroupId)
            {
                var model = new ReCaptchaSettingsViewModel();

                if (await context.Updater.TryUpdateModelAsync(model, Prefix))
                {
                    section.SiteKey   = model.SiteKey?.Trim();
                    section.SecretKey = model.SecretKey?.Trim();

                    // Reload tenant to apply settings.
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }

            return(await EditAsync(section, context));
        }
Beispiel #6
0
        public override async Task <IDisplayResult> UpdateAsync(FacebookLoginSettings settings, BuildEditorContext context)
        {
            if (context.GroupId == FacebookConstants.Features.Login)
            {
                var user = _httpContextAccessor.HttpContext?.User;
                if (user == null || !await _authorizationService.AuthorizeAsync(user, Permissions.ManageFacebookApp))
                {
                    return(null);
                }

                var model = new FacebookLoginSettingsViewModel();
                await context.Updater.TryUpdateModelAsync(model, Prefix);

                if (context.Updater.ModelState.IsValid)
                {
                    settings.CallbackPath = model.CallbackPath;
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }
            return(await EditAsync(settings, context));
        }
Beispiel #7
0
        public override async Task <IDisplayResult> UpdateAsync(HttpsSettings settings, BuildEditorContext context)
        {
            if (context.GroupId == SettingsGroupId)
            {
                var model = new HttpsSettingsViewModel();

                await context.Updater.TryUpdateModelAsync(model, Prefix);

                settings.EnableStrictTransportSecurity = model.EnableStrictTransportSecurity;
                settings.RequireHttps          = model.RequireHttps;
                settings.RequireHttpsPermanent = model.RequireHttpsPermanent;
                settings.SslPort = model.SslPort;

                // If the settings are valid, reload the current tenant.
                if (context.Updater.ModelState.IsValid)
                {
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }

            return(await EditAsync(settings, context));
        }
Beispiel #8
0
        public override async Task <IDisplayResult> UpdateAsync(ISite site, UpdateEditorContext context)
        {
            if (context.GroupId == GroupId)
            {
                var model = new SiteSettingsViewModel();

                if (await context.Updater.TryUpdateModelAsync(model, Prefix))
                {
                    site.SiteName          = model.SiteName;
                    site.BaseUrl           = model.BaseUrl;
                    site.TimeZoneId        = model.TimeZone;
                    site.UseCdn            = model.UseCdn;
                    site.CdnBaseUrl        = model.CdnBaseUrl;
                    site.ResourceDebugMode = model.ResourceDebugMode;
                    site.AppendVersion     = model.AppendVersion;

                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }

            return(Edit(site));
        }
        public override async Task <IDisplayResult> UpdateAsync(ISite model, UpdateEditorContext context)
        {
            var user = _httpContextAccessor.HttpContext?.User;

            if (!await _authorizationService.AuthorizeAsync(user, Permissions.ManageNotificationSettings))
            {
                return(null);
            }

            if (context.GroupId == GroupId)
            {
                var viewmodel = new AzureHubSettingsViewModel();
                await context.Updater.TryUpdateModelAsync(viewmodel, Prefix);

                if (!string.IsNullOrWhiteSpace(viewmodel.NotificationTags))
                {
                    model.Properties.TryAdd(nameof(AzureHubSettingsViewModel.NotificationTags),
                                            JArray.FromObject(viewmodel.NotificationTags.Split(',').Select(t => t.Trim())));
                }
                else
                {
                    model.Properties.Remove(nameof(AzureHubSettingsViewModel.NotificationTags));
                }

                var settings = new AzureHubSettings
                {
                    Hub        = viewmodel.Hub,
                    Connection = viewmodel.Connection
                };

                await _tenantConfigStore.SaveAsync(GroupId, settings);

                // Reload the tenant to apply the settings
                await _orchardHost.ReloadShellContextAsync(_currentShellSettings);
            }

            return(await EditAsync(model, context));
        }
        public async Task <IActionResult> IndexPost()
        {
            if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageServerSettings))
            {
                return(Forbid());
            }

            var settings = await _serverService.GetSettingsAsync();

            var shape = await _serverSettingsDisplayManager.UpdateEditorAsync(settings, updater : _updateModelAccessor.ModelUpdater, isNew : false);

            if (!ModelState.IsValid)
            {
                return(View(shape));
            }

            foreach (var result in await _serverService.ValidateSettingsAsync(settings))
            {
                if (result != ValidationResult.Success)
                {
                    var key = result.MemberNames.FirstOrDefault() ?? string.Empty;
                    ModelState.AddModelError(key, result.ErrorMessage);
                }
            }

            if (!ModelState.IsValid)
            {
                return(View(shape));
            }

            await _serverService.UpdateSettingsAsync(settings);

            _notifier.Success(H["OpenID server configuration successfully updated."]);

            await _shellHost.ReloadShellContextAsync(_shellSettings);

            return(RedirectToAction(nameof(Index)));
        }
        public override async Task <IDisplayResult> UpdateAsync(AzureADSettings settings, BuildEditorContext context)
        {
            if (context.GroupId == MicrosoftAuthenticationConstants.Features.AAD)
            {
                var user = _httpContextAccessor.HttpContext?.User;
                if (user == null || !await _authorizationService.AuthorizeAsync(user, Permissions.ManageMicrosoftAuthentication))
                {
                    return(null);
                }
                var model = new AzureADSettingsViewModel();
                await context.Updater.TryUpdateModelAsync(model, Prefix);

                if (context.Updater.ModelState.IsValid)
                {
                    settings.DisplayName  = model.DisplayName;
                    settings.AppId        = model.AppId;
                    settings.TenantId     = model.TenantId;
                    settings.CallbackPath = model.CallbackPath;
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }
            return(await EditAsync(settings, context));
        }
Beispiel #12
0
        public override async Task <IDisplayResult> UpdateAsync(OpenIdValidationSettings settings, BuildEditorContext context)
        {
            var user = _httpContextAccessor.HttpContext?.User;

            if (user == null || !await _authorizationService.AuthorizeAsync(user, Permissions.ManageValidationSettings))
            {
                return(null);
            }

            if (context.GroupId == SettingsGroupId)
            {
                var model = new OpenIdValidationSettingsViewModel();

                await context.Updater.TryUpdateModelAsync(model, Prefix);

                settings.Authority = model.Authority?.Trim();
                settings.Audience  = model.Audience?.Trim();
                settings.Tenant    = model.Tenant;

                foreach (var result in await _validationService.ValidateSettingsAsync(settings))
                {
                    if (result != ValidationResult.Success)
                    {
                        var key = result.MemberNames.FirstOrDefault() ?? string.Empty;
                        context.Updater.ModelState.AddModelError(key, result.ErrorMessage);
                    }
                }

                // If the settings are valid, reload the current tenant.
                if (context.Updater.ModelState.IsValid)
                {
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }

            return(await EditAsync(settings, context));
        }
Beispiel #13
0
        public override async Task <IDisplayResult> UpdateAsync(ISite site, UpdateEditorContext context)
        {
            if (context.GroupId == GroupId)
            {
                var model = new SiteSettingsViewModel();

                if (await context.Updater.TryUpdateModelAsync(model, Prefix))
                {
                    site.SiteName          = model.SiteName;
                    site.BaseUrl           = model.BaseUrl;
                    site.TimeZoneId        = model.TimeZone;
                    site.Culture           = model.Culture;
                    site.UseCdn            = model.UseCdn;
                    site.ResourceDebugMode = model.ResourceDebugMode;
                }

                // We always reset the tenant for the default culture and also supported cultures to take effect
                await _shellHost.ReloadShellContextAsync(_shellSettings);

                _notifier.Warning(H["The site has been restarted for the settings to take effect"]);
            }

            return(Edit(site));
        }
        public override async Task <IDisplayResult> UpdateAsync(CommerceSettings section, BuildEditorContext context)
        {
            var user = _httpContextAccessor.HttpContext?.User;

            if (!await _authorizationService.AuthorizeAsync(user, Permissions.ManageCommerceSettings))
            {
                return(null);
            }

            if (context.GroupId == GroupId)
            {
                var model = new CommerceSettingsViewModel();

                if (await context.Updater.TryUpdateModelAsync(model, Prefix))
                {
                    section.DefaultCurrency = model.DefaultCurrency;
                }

                // Reload the tenant to apply the settings
                await _orchardHost.ReloadShellContextAsync(_currentShellSettings);
            }

            return(await EditAsync(section, context));
        }
Beispiel #15
0
        public override async Task <IDisplayResult> UpdateAsync(OpenIdServerSettings settings, BuildEditorContext context)
        {
            var user = _httpContextAccessor.HttpContext?.User;

            if (user == null || !await _authorizationService.AuthorizeAsync(user, Permissions.ManageServerSettings))
            {
                return(null);
            }

            if (context.GroupId == SettingsGroupId)
            {
                var model = new OpenIdServerSettingsViewModel();
                await context.Updater.TryUpdateModelAsync(model, Prefix);

                settings.TestingModeEnabled = model.TestingModeEnabled;
                settings.AccessTokenFormat  = model.AccessTokenFormat;
                settings.Authority          = model.Authority;

                settings.CertificateStoreLocation = model.CertificateStoreLocation;
                settings.CertificateStoreName     = model.CertificateStoreName;
                settings.CertificateThumbprint    = model.CertificateThumbprint;

                settings.AuthorizationEndpointPath = model.EnableAuthorizationEndpoint ?
                                                     new PathString("/connect/authorize") : PathString.Empty;
                settings.LogoutEndpointPath = model.EnableLogoutEndpoint ?
                                              new PathString("/connect/logout") : PathString.Empty;
                settings.TokenEndpointPath = model.EnableTokenEndpoint ?
                                             new PathString("/connect/token") : PathString.Empty;
                settings.UserinfoEndpointPath = model.EnableUserInfoEndpoint ?
                                                new PathString("/connect/userinfo") : PathString.Empty;

                if (model.AllowAuthorizationCodeFlow)
                {
                    settings.GrantTypes.Add(GrantTypes.AuthorizationCode);
                }
                else
                {
                    settings.GrantTypes.Remove(GrantTypes.AuthorizationCode);
                }

                if (model.AllowImplicitFlow)
                {
                    settings.GrantTypes.Add(GrantTypes.Implicit);
                }
                else
                {
                    settings.GrantTypes.Remove(GrantTypes.Implicit);
                }

                if (model.AllowClientCredentialsFlow)
                {
                    settings.GrantTypes.Add(GrantTypes.ClientCredentials);
                }
                else
                {
                    settings.GrantTypes.Remove(GrantTypes.ClientCredentials);
                }

                if (model.AllowPasswordFlow)
                {
                    settings.GrantTypes.Add(GrantTypes.Password);
                }
                else
                {
                    settings.GrantTypes.Remove(GrantTypes.Password);
                }

                if (model.AllowRefreshTokenFlow)
                {
                    settings.GrantTypes.Add(GrantTypes.RefreshToken);
                }
                else
                {
                    settings.GrantTypes.Remove(GrantTypes.RefreshToken);
                }

                settings.UseRollingTokens = model.UseRollingTokens;

                foreach (var result in await _serverService.ValidateSettingsAsync(settings))
                {
                    if (result != ValidationResult.Success)
                    {
                        var key = result.MemberNames.FirstOrDefault() ?? string.Empty;
                        context.Updater.ModelState.AddModelError(key, result.ErrorMessage);
                    }
                }

                // If the settings are valid, reload the current tenant.
                if (context.Updater.ModelState.IsValid)
                {
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }

            return(await EditAsync(settings, context));
        }
        public override async Task <IDisplayResult> UpdateAsync(OpenIdClientSettings settings, BuildEditorContext context)
        {
            var user = _httpContextAccessor.HttpContext?.User;

            if (user == null || !await _authorizationService.AuthorizeAsync(user, Permissions.ManageClientSettings))
            {
                return(null);
            }

            if (context.GroupId == SettingsGroupId)
            {
                var previousClientSecret = settings.ClientSecret;
                var model = new OpenIdClientSettingsViewModel();
                await context.Updater.TryUpdateModelAsync(model, Prefix);

                model.Scopes = model.Scopes ?? string.Empty;

                settings.DisplayName           = model.DisplayName;
                settings.Scopes                = model.Scopes.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
                settings.Authority             = model.Authority;
                settings.CallbackPath          = model.CallbackPath;
                settings.ClientId              = model.ClientId;
                settings.SignedOutCallbackPath = model.SignedOutCallbackPath;
                settings.SignedOutRedirectUri  = model.SignedOutRedirectUri;
                settings.ResponseMode          = model.ResponseMode;

                bool useClientSecret = true;

                if (model.UseCodeFlow)
                {
                    settings.ResponseType = OpenIdConnectResponseType.Code;
                }
                else if (model.UseCodeIdTokenFlow)
                {
                    settings.ResponseType = OpenIdConnectResponseType.CodeIdToken;
                }
                else if (model.UseCodeIdTokenTokenFlow)
                {
                    settings.ResponseType = OpenIdConnectResponseType.CodeIdTokenToken;
                }
                else if (model.UseCodeTokenFlow)
                {
                    settings.ResponseType = OpenIdConnectResponseType.CodeToken;
                }
                else if (model.UseIdTokenFlow)
                {
                    settings.ResponseType = OpenIdConnectResponseType.IdToken;
                    useClientSecret       = false;
                }
                else if (model.UseIdTokenTokenFlow)
                {
                    settings.ResponseType = OpenIdConnectResponseType.IdTokenToken;
                    useClientSecret       = false;
                }
                else
                {
                    settings.ResponseType = OpenIdConnectResponseType.None;
                    useClientSecret       = false;
                }

                if (!useClientSecret)
                {
                    model.ClientSecret = previousClientSecret = null;
                }

                // Restore the client secret if the input is empty (i.e if it hasn't been reset).
                if (string.IsNullOrEmpty(model.ClientSecret))
                {
                    settings.ClientSecret = previousClientSecret;
                }
                else
                {
                    var protector = _dataProtectionProvider.CreateProtector(nameof(OpenIdClientConfiguration));
                    settings.ClientSecret = protector.Protect(model.ClientSecret);
                }

                foreach (var result in await _clientService.ValidateSettingsAsync(settings))
                {
                    if (result != ValidationResult.Success)
                    {
                        var key = result.MemberNames.FirstOrDefault() ?? string.Empty;
                        context.Updater.ModelState.AddModelError(key, result.ErrorMessage);
                    }
                }

                // If the settings are valid, reload the current tenant.
                if (context.Updater.ModelState.IsValid)
                {
                    await _shellHost.ReloadShellContextAsync(_shellSettings);
                }
            }

            return(await EditAsync(settings, context));
        }
Beispiel #17
0
 public override async Task PublishedAsync(PublishContentContext context, CustomStyleSettingsPart part)
 {
     // Reload the tenant to apply the settings
     await _orchardHost.ReloadShellContextAsync(_currentShellSettings);
 }
        /// <summary>
        /// Keep in sync tenants by sharing shell identifiers through an <see cref="IDistributedCache"/>.
        /// </summary>
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            stoppingToken.Register(() =>
            {
                _logger.LogInformation("'{ServiceName}' is stopping.", nameof(DistributedShellHostedService));
            });

            // Init the idle time.
            var idleTime = MinIdleTime;

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    // Wait for the current idle time on each loop.
                    if (!await TryWaitAsync(idleTime, stoppingToken))
                    {
                        break;
                    }

                    // If there is no default tenant or it is not running, nothing to do.
                    if (!_shellHost.TryGetShellContext(ShellHelper.DefaultShellName, out var defaultContext) ||
                        defaultContext.Settings.State != TenantState.Running)
                    {
                        continue;
                    }

                    // Get or create a new distributed context if the default tenant has changed.
                    var context = await GetOrCreateDistributedContextAsync(defaultContext);

                    // If the required distributed features are not enabled, nothing to do.
                    var distributedCache = context?.DistributedCache;
                    if (distributedCache == null)
                    {
                        continue;
                    }

                    // Try to retrieve the tenant changed global identifier from the distributed cache.
                    string shellChangedId;
                    try
                    {
                        shellChangedId = await distributedCache.GetStringAsync(ShellChangedIdKey);
                    }
                    catch (Exception ex) when(!ex.IsFatal())
                    {
                        // Get the next idle time before retrying to read the distributed cache.
                        idleTime = NextIdleTimeBeforeRetry(idleTime, ex);
                        continue;
                    }

                    // Reset the idle time.
                    idleTime = MinIdleTime;

                    // Check if at least one tenant has changed.
                    if (shellChangedId == null || _shellChangedId == shellChangedId)
                    {
                        continue;
                    }

                    // Try to retrieve the tenant created global identifier from the distributed cache.
                    string shellCreatedId;
                    try
                    {
                        shellCreatedId = await distributedCache.GetStringAsync(ShellCreatedIdKey);
                    }
                    catch (Exception ex) when(!ex.IsFatal())
                    {
                        _logger.LogError(ex, "Unable to read the distributed cache before checking if a tenant has been created.");
                        continue;
                    }

                    // Retrieve all tenant settings that are already loaded.
                    var allSettings = _shellHost.GetAllSettings().ToList();

                    // Check if at least one tenant has been created.
                    if (shellCreatedId != null && _shellCreatedId != shellCreatedId)
                    {
                        // Retrieve all new created tenants that are not already loaded.
                        var names = (await _shellSettingsManager.LoadSettingsNamesAsync())
                                    .Except(allSettings.Select(s => s.Name))
                                    .ToArray();

                        // Load and enlist the settings of all new created tenant.
                        foreach (var name in names)
                        {
                            allSettings.Add(await _shellSettingsManager.LoadSettingsAsync(name));
                        }
                    }

                    // Init the busy start time.
                    var _busyStartTime = DateTime.UtcNow;
                    var syncingSuccess = true;

                    // Keep in sync all tenants by checking their specific identifiers.
                    foreach (var settings in allSettings)
                    {
                        // Wait for the min idle time after the max busy time.
                        if (!await TryWaitAfterBusyTime(stoppingToken))
                        {
                            break;
                        }

                        var semaphore = _semaphores.GetOrAdd(settings.Name, name => new SemaphoreSlim(1));
                        await semaphore.WaitAsync();

                        try
                        {
                            // Try to retrieve the release identifier of this tenant from the distributed cache.
                            var releaseId = await distributedCache.GetStringAsync(ReleaseIdKey(settings.Name));

                            if (releaseId != null)
                            {
                                // Check if the release identifier of this tenant has changed.
                                var identifier = _identifiers.GetOrAdd(settings.Name, name => new ShellIdentifier());
                                if (identifier.ReleaseId != releaseId)
                                {
                                    // Upate the local identifier.
                                    identifier.ReleaseId = releaseId;

                                    // Keep in sync this tenant by releasing it locally.
                                    await _shellHost.ReleaseShellContextAsync(settings, eventSource : false);
                                }
                            }

                            // Try to retrieve the reload identifier of this tenant from the distributed cache.
                            var reloadId = await distributedCache.GetStringAsync(ReloadIdKey(settings.Name));

                            if (reloadId != null)
                            {
                                // Check if the reload identifier of this tenant has changed.
                                var identifier = _identifiers.GetOrAdd(settings.Name, name => new ShellIdentifier());
                                if (identifier.ReloadId != reloadId)
                                {
                                    // Upate the local identifier.
                                    identifier.ReloadId = reloadId;

                                    // Keep in sync this tenant by reloading it locally.
                                    await _shellHost.ReloadShellContextAsync(settings, eventSource : false);
                                }
                            }
                        }
                        catch (Exception ex) when(!ex.IsFatal())
                        {
                            syncingSuccess = false;
                            _logger.LogError(ex, "Unable to read the distributed cache while syncing the tenant '{TenantName}'.", settings.Name);
                            break;
                        }
                        finally
                        {
                            semaphore.Release();
                        }
                    }

                    // Keep in sync the tenant global identifiers.
                    if (syncingSuccess)
                    {
                        _shellChangedId = shellChangedId;
                        _shellCreatedId = shellCreatedId;
                    }
                }
                catch (Exception ex) when(!ex.IsFatal())
                {
                    _logger.LogError(ex, "Error while executing '{ServiceName}'", nameof(DistributedShellHostedService));
                }
            }

            _terminated = true;
            _context?.Release();
            _defaultContext = null;
            _context        = null;
        }