Beispiel #1
0
        private void WriteTenantHierarchy(TenantWithChildren root, IList <ITenant>?allTenants = null)
        {
            allTenants ??= this.FlattenHierarchy(root);

            string spacing = " |";

            for (int i = 1; i < root.Depth; i++)
            {
                spacing += "     |";
            }

            if (root.Depth > 0)
            {
                Console.WriteLine(spacing);
                Console.Write(spacing);
                Console.Write("-> ");
            }

            Console.WriteLine($"{root.Tenant.Name} - ({root.Tenant.Id})");
            Console.Write(spacing);
            Console.WriteLine($"     [Type: {root.Tenant.GetMarainTenantType()}]");

            var enrollments = root.Tenant.GetEnrollments().ToList();

            foreach (string enrollment in enrollments)
            {
                ITenant?serviceTenant = allTenants.FirstOrDefault(x => x.Id == enrollment);
                Console.Write(spacing);

                if (serviceTenant?.GetServiceManifest().DependsOnServiceTenants.Count > 0)
                {
                    string  delegatedTenantId = root.Tenant.GetDelegatedTenantIdForServiceId(serviceTenant.Id);
                    ITenant?delegatedTenant   = allTenants.FirstOrDefault(x => x.Id == delegatedTenantId);
                    Console.WriteLine($"     [Enrollment: '{serviceTenant.Name}', with delegated tenant '{delegatedTenant?.Name ?? delegatedTenantId}']");
                }
                else
                {
                    Console.WriteLine($"     [Enrollment: {serviceTenant?.Name ?? enrollment}]");
                }
            }

            foreach (TenantWithChildren child in root.Children)
            {
                this.WriteTenantHierarchy(child, allTenants);
            }
        }
        /// <summary>
        /// Retrieves the complete list of configuration items required in order to enroll a tenant to a service. This will
        /// include configuration entries required by any dependent services.
        /// </summary>
        /// <param name="tenantStore">The tenant store.</param>
        /// <param name="serviceTenant">The service to gather configuration for.</param>
        /// <returns>
        /// A list of <see cref="ServiceManifestRequiredConfigurationEntry"/> representing the configuration requirements.
        /// </returns>
        public static async Task <ServiceManifestRequiredConfigurationEntry[]> GetServiceEnrollmentConfigurationRequirementsAsync(this ITenantStore tenantStore, ITenant serviceTenant)
        {
            serviceTenant.EnsureTenantIsOfType(MarainTenantType.Service);

            var             requirements    = new List <ServiceManifestRequiredConfigurationEntry>();
            ServiceManifest serviceManifest = serviceTenant.GetServiceManifest();

            requirements.AddRange(serviceManifest.RequiredConfigurationEntries);

            ServiceManifestRequiredConfigurationEntry[][] dependentServicesConfigRequirements =
                await Task.WhenAll(
                    serviceManifest.DependsOnServiceTenants.Select(
                        x => tenantStore.GetServiceEnrollmentConfigurationRequirementsAsync(x.Id))).ConfigureAwait(false);

            requirements.AddRange(dependentServicesConfigRequirements.SelectMany(x => x));

            return(requirements.ToArray());
        }
        /// <summary>
        /// Unenrolls the specified tenant from the service.
        /// </summary>
        /// <param name="tenantStore">The tenant store.</param>
        /// <param name="enrolledTenant">The tenant that is currently enrolled.</param>
        /// <param name="serviceTenant">The service they need to be unenrolled from.</param>
        /// <param name="logger">Optional logger.</param>
        /// <returns>A task which completes when the unenrollment has finished.</returns>
        public static async Task UnenrollFromServiceAsync(
            this ITenantStore tenantStore,
            ITenant enrolledTenant,
            ITenant serviceTenant,
            ILogger?logger = null)
        {
            if (!enrolledTenant.IsEnrolledForService(serviceTenant.Id))
            {
                throw new InvalidOperationException(
                          $"Cannot unenroll tenant '{enrolledTenant.Name}' with Id '{enrolledTenant.Id}' from service with Id '{serviceTenant.Id}' because it is not currently enrolled");
            }

            logger?.LogDebug(
                "Unenrolling tenant '{enrolledTenantName}' with Id '{enrolledTenantId}' from service '{serviceTenantName}' with Id '{serviceTenantId}'",
                enrolledTenant.Name,
                enrolledTenant.Id,
                serviceTenant.Name,
                serviceTenant.Id);

            var propertiesToRemove = new List <string>();

            ServiceManifest manifest = serviceTenant.GetServiceManifest();

            // If there are dependencies, we first need to unenroll from each of those and then remove the delegated tenant.
            if (manifest.DependsOnServiceTenants.Count > 0)
            {
                logger?.LogDebug(
                    "Service '{serviceTenantName}' has dependencies. Retrieving delegated tenant for unenrollment.",
                    serviceTenant.Name);

                string delegatedTenantId = enrolledTenant.GetDelegatedTenantIdForServiceId(serviceTenant.Id);

                logger?.LogDebug(
                    "Retrieved delegated tenant with Id '{delegatedTenantId}. Unenrolling from dependencies.",
                    delegatedTenantId);

                foreach (ServiceDependency current in manifest.DependsOnServiceTenants)
                {
                    await tenantStore.UnenrollFromServiceAsync(delegatedTenantId, current.Id).ConfigureAwait(false);
                }

                // Now delete the delegated tenant.
                logger?.LogDebug(
                    "Deleting delegated tenant with Id '{delegatedTenantId}'.",
                    delegatedTenantId);
                await tenantStore.DeleteTenantAsync(delegatedTenantId).ConfigureAwait(false);

                propertiesToRemove.AddRange(enrolledTenant.GetPropertiesToRemoveDelegatedTenantForService(serviceTenant));
            }

            if (manifest.RequiredConfigurationEntries.Count > 0)
            {
                // Now remove any config for the service that's being unenrolled from.
                logger?.LogDebug(
                    "Removing configuration for service '{serviceTenantName}' from '{enrolledTenantName}'",
                    serviceTenant.Name,
                    enrolledTenant.Name);

                foreach (ServiceManifestRequiredConfigurationEntry current in manifest.RequiredConfigurationEntries)
                {
                    logger?.LogDebug(
                        "Removing configuration item '{requiredConfigurationEntryKey}' for service '{serviceTenantName}' from '{enrolledTenantName}'",
                        current.Key,
                        serviceTenant.Name,
                        enrolledTenant.Name);
                    propertiesToRemove.AddRange(current.GetPropertiesToRemoveFromTenant(enrolledTenant));
                }
            }

            // Finally, remove the enrollment entry for the service.
            logger?.LogDebug(
                "Removing enrollment entry for service '{serviceTenantName}' from '{enrolledTenantName}'",
                serviceTenant.Name,
                enrolledTenant.Name);

            IEnumerable <KeyValuePair <string, object> > propertiesToChange =
                enrolledTenant.GetPropertyUpdatesToRemoveServiceEnrollment(serviceTenant.Id);

            logger?.LogDebug(
                "Updating tenant '{enrolledTenantName}'",
                enrolledTenant.Name);

            await tenantStore.UpdateTenantAsync(
                enrolledTenant.Id,
                propertiesToSetOrAdd : propertiesToChange,
                propertiesToRemove : propertiesToRemove).ConfigureAwait(false);

            logger?.LogInformation(
                "Successfully unenrolled tenant '{enrolledTenantName}' with Id '{enrolledTenantId}' from service '{serviceTenantName}' with Id '{serviceTenantId}'",
                enrolledTenant.Name,
                enrolledTenant.Id,
                serviceTenant.Name,
                serviceTenant.Id);
        }
        /// <summary>
        /// Enrolls the specified tenant in the service.
        /// </summary>
        /// <param name="tenantStore">The tenant store.</param>
        /// <param name="enrollingTenant">The tenant to enroll.</param>
        /// <param name="serviceTenant">The service to enroll in.</param>
        /// <param name="configurationItems">Configuration for the enrollment.</param>
        /// <param name="logger">Optional logger.</param>
        /// <returns>A task which completes when the enrollment has finished.</returns>
        public static async Task EnrollInServiceAsync(
            this ITenantStore tenantStore,
            ITenant enrollingTenant,
            ITenant serviceTenant,
            EnrollmentConfigurationItem[] configurationItems,
            ILogger?logger = null)
        {
            if (enrollingTenant == null)
            {
                throw new ArgumentNullException(nameof(enrollingTenant));
            }

            enrollingTenant.EnsureTenantIsOfType(MarainTenantType.Client, MarainTenantType.Delegated);

            if (serviceTenant == null)
            {
                throw new ArgumentNullException(nameof(serviceTenant));
            }

            serviceTenant.EnsureTenantIsOfType(MarainTenantType.Service);

            if (configurationItems == null)
            {
                throw new ArgumentNullException(nameof(configurationItems));
            }

            logger?.LogDebug(
                "Enrolling tenant '{enrollingTenantName}' with Id '{enrollingTenantId}' from service '{serviceTenantName}' with Id '{serviceTenantId}'",
                enrollingTenant.Name,
                enrollingTenant.Id,
                serviceTenant.Name,
                serviceTenant.Id);

            // First we need to ensure that all the required config items for both the service being enrolled in,
            // as well as any dependent services is provided.
            ServiceManifestRequiredConfigurationEntry[] requiredConfig = await tenantStore.GetServiceEnrollmentConfigurationRequirementsAsync(serviceTenant).ConfigureAwait(false);

            logger?.LogDebug("Validating supplied configuration against required config.");
            configurationItems.ValidateAndThrow(requiredConfig);

            ServiceManifest manifest = serviceTenant.GetServiceManifest();

            // Now, match up the required config items for this service to the relevent supplied config (we may
            // have been supplied with config for dependent services as well, so we can't just attach all
            // of the supplied config items to the enrolling tenant - some of it could belong on delegated
            // tenants.
            logger?.LogDebug(
                "Attaching required configuration items to tenant '{serviceTenantName}' with Id '{serviceTenantId}'",
                serviceTenant.Name,
                serviceTenant.Id);

            IEnumerable <(ServiceManifestRequiredConfigurationEntry RequiredConfigurationEntry, EnrollmentConfigurationItem ProvidedConfigurationItem)> matchedConfigItems =
                manifest.RequiredConfigurationEntries.Select(
                    requiredConfigItem =>
                    (requiredConfigItem, configurationItems.Single(item => item.Key == requiredConfigItem.Key)));

            IEnumerable <KeyValuePair <string, object> > propertiesToAddToEnrollingTenant = PropertyBagValues.Empty;

            foreach ((ServiceManifestRequiredConfigurationEntry RequiredConfigurationEntry, EnrollmentConfigurationItem ProvidedConfigurationItem)current in matchedConfigItems)
            {
                logger?.LogDebug(
                    "Adding configuration entry '{requiredConfigurationEntryKey}' to tenant '{serviceTenantName}' with Id '{serviceTenantId}'",
                    current.RequiredConfigurationEntry.Key,
                    serviceTenant.Name,
                    serviceTenant.Id);

                propertiesToAddToEnrollingTenant = current.RequiredConfigurationEntry.AddToTenantProperties(
                    propertiesToAddToEnrollingTenant, current.ProvidedConfigurationItem);
            }

            // Add an enrollment entry to the tenant.
            logger?.LogDebug(
                "Adding service enrollment to tenant '{serviceTenantName}' with Id '{serviceTenantId}'",
                serviceTenant.Name,
                serviceTenant.Id);

            propertiesToAddToEnrollingTenant = propertiesToAddToEnrollingTenant.AddServiceEnrollment(
                enrollingTenant, serviceTenant.Id);

            // Update the tenant now, so that the tenant type is correctly set - otherwise
            // recursive enrollments will fail
            logger?.LogDebug(
                "Updating tenant '{enrollingTenantName}' with Id '{enrollingTenantId}'",
                enrollingTenant.Name,
                enrollingTenant.Id);

            enrollingTenant = await tenantStore.UpdateTenantAsync(
                enrollingTenant.Id,
                propertiesToSetOrAdd : propertiesToAddToEnrollingTenant)
                              .ConfigureAwait(false);

            propertiesToAddToEnrollingTenant = PropertyBagValues.Empty;

            // If this service has dependencies, we need to create a new delegated tenant for the service to use when
            // accessing those dependencies.
            if (manifest.DependsOnServiceTenants.Count > 0)
            {
                logger?.LogDebug(
                    "Service '{serviceTenantName}' has dependencies. Creating delegated tenant for enrollment.",
                    serviceTenant.Name);

                ITenant delegatedTenant = await tenantStore.CreateDelegatedTenant(enrollingTenant, serviceTenant).ConfigureAwait(false);

                // Now enroll the new delegated tenant for all of the dependent services.
                await Task.WhenAll(manifest.DependsOnServiceTenants.Select(
                                       dependsOnService => tenantStore.EnrollInServiceAsync(
                                           delegatedTenant,
                                           dependsOnService.Id,
                                           configurationItems))).ConfigureAwait(false);

                // Add the delegated tenant Id to the enrolling tenant
                logger?.LogDebug(
                    "Setting delegated tenant for client '{enrollingTenantName}' with Id '{enrollingTenantId}' in service '{serviceTenantName}' with Id '{serviceTenantId}' to new tenant '{delegatedTenantName}' with Id '{delegatedTenantId}'.",
                    enrollingTenant.Name,
                    enrollingTenant.Id,
                    serviceTenant.Name,
                    serviceTenant.Id,
                    delegatedTenant.Name,
                    delegatedTenant.Id);

                propertiesToAddToEnrollingTenant = propertiesToAddToEnrollingTenant.SetDelegatedTenantForService(
                    enrollingTenant, serviceTenant, delegatedTenant);

                logger?.LogDebug(
                    "Updating tenant '{enrollingTenantName}' with Id '{enrollingTenantId}'",
                    enrollingTenant.Name,
                    enrollingTenant.Id);

                await tenantStore.UpdateTenantAsync(
                    enrollingTenant.Id,
                    propertiesToSetOrAdd : propertiesToAddToEnrollingTenant)
                .ConfigureAwait(false);
            }

            logger?.LogInformation(
                "Successfully enrolled tenant '{enrollingTenantName}' with Id '{enrollingTenant.Id}' for service '{serviceTenantName}' with Id '{serviceTenantId}'",
                enrollingTenant.Name,
                enrollingTenant.Id,
                serviceTenant.Name,
                serviceTenant.Id);
        }