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); }