/// <inheritdoc/> public async Task <ITenant> GetRequestingTenantAsync(string tenantId) { ITenant tenant = await this.GetTenantAsync(tenantId).ConfigureAwait(false); // Validate it's of the expected type. This will throw an ArgumentException if the tenant is not of the expected // type. This is not particularly useful, so we will catch this and instead throw an exception that will result // in a Not Found response. try { tenant.EnsureTenantIsOfType(MarainTenantType.Client, MarainTenantType.Delegated); } catch (ArgumentException) { throw new OpenApiNotFoundException($"The specified tenant Id, '{tenantId}', is of the wrong type for this request"); } // Ensure the tenant is enrolled for the service. if (!tenant.IsEnrolledForService(this.serviceConfiguration.ServiceTenantId)) { throw OpenApiForbiddenException.WithProblemDetails( "Tenant not enrolled for service", $"The tenant with Id '{tenantId}' is not enrolled in the service '{this.serviceConfiguration.ServiceDisplayName}' with Service Tenant Id '{this.serviceConfiguration.ServiceTenantId}'"); } return(tenant); }
/// <summary> /// Retrieves the tenant with the specified Id and ensures it is of the correct type. /// </summary> /// <param name="tenantStore">The tenant store.</param> /// <param name="tenantId">The Id of the tenant to retrieve.</param> /// <param name="allowableTenantTypes">The list of valid types for the tenant.</param> /// <returns>The client tenant.</returns> /// <exception cref="TenantNotFoundException">There is no tenant with the specified Id.</exception> /// <exception cref="InvalidMarainTenantTypeException">The tenant Id provided is not for a tenant with the specified type.</exception> public static async Task <ITenant> GetTenantOfTypeAsync(this ITenantStore tenantStore, string tenantId, params MarainTenantType[] allowableTenantTypes) { ITenant tenant = await tenantStore.GetTenantAsync(tenantId).ConfigureAwait(false); tenant.EnsureTenantIsOfType(allowableTenantTypes); return(tenant); }
/// <summary> /// Clears the delegated Id for a specific tenant and service. /// </summary> /// <param name="tenant"> /// The tenant who was able to make calls to the service represented by the specified Service Tenant. /// </param> /// <param name="serviceTenant">The Service Tenant which was using the Delegated Tenant.</param> /// <remarks> /// This method does not persist the tenant. Calling code should pass the resulting list to /// <see cref="ITenantStore.UpdateTenantAsync(string, string?, IEnumerable{KeyValuePair{string, object}}?, IEnumerable{string}?)"/>. /// </remarks> /// <returns> /// A single-entry list of properties that can be passed to /// <see cref="ITenantStore.UpdateTenantAsync(string, string?, IEnumerable{KeyValuePair{string, object}}?, IEnumerable{string}?)"/> /// to remove the delegate Id. /// </returns> internal static IEnumerable <string> GetPropertiesToRemoveDelegatedTenantForService( this ITenant tenant, ITenant serviceTenant) { tenant.EnsureTenantIsOfType(MarainTenantType.Client, MarainTenantType.Delegated); if (serviceTenant == null) { throw new ArgumentNullException(nameof(serviceTenant)); } serviceTenant.EnsureTenantIsOfType(MarainTenantType.Service); return(new string[] { TenantPropertyKeys.DelegatedTenantId(serviceTenant.Id) }); }
/// <summary> /// Add or updates arbitrary storage configuration for a tenant. /// </summary> /// <param name="tenantStore">The tenant store.</param> /// <param name="tenant">The tenant to enroll.</param> /// <param name="configurationItems">Configuration to add.</param> /// <param name="logger">Optional logger.</param> /// <returns>A task which completes when the configuration has been added.</returns> public static async Task AddOrUpdateStorageConfigurationAsync(this ITenantStore tenantStore, ITenant tenant, ConfigurationItem[] configurationItems, ILogger?logger = null) { if (tenant == null) { throw new ArgumentNullException(nameof(tenant)); } tenant.EnsureTenantIsOfType(MarainTenantType.Client); if (configurationItems == null) { throw new ArgumentNullException(nameof(configurationItems)); } logger?.LogDebug( "Add configuration for tenant '{tenantName}' with Id '{tenantId}'", tenant.Name, tenant.Id); configurationItems.ValidateAndThrow(); IEnumerable <KeyValuePair <string, object> > propertiesToAddToTenant = PropertyBagValues.Empty; foreach (ConfigurationItem configurationItem in configurationItems) { logger?.LogDebug( "Adding configuration entry to tenant '{tenantName}' with Id '{tenantId}'", tenant.Name, tenant.Id); propertiesToAddToTenant = configurationItem.AddConfiguration(propertiesToAddToTenant); } logger?.LogDebug( "Updating tenant '{tenantName}' with Id '{tenantId}'", tenant.Name, tenant.Id); tenant = await tenantStore.UpdateTenantAsync( tenant.Id, propertiesToSetOrAdd : propertiesToAddToTenant) .ConfigureAwait(false); logger?.LogInformation( "Successfully added configuration to tenant '{tenantName}' with Id '{tenantId}'", tenant.Name, tenant.Id); }
/// <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> /// Gets the Id of the delegated tenant that has been created for a service to use when accessing a dependent service /// on the tenant's behalf. /// </summary> /// <param name="tenant"> /// The tenant who is able to make calls to the service represented by the specified Service Tenant. /// </param> /// <param name="serviceTenantId">The Id of the Service Tenant which will uses the Delegated Tenant.</param> /// <returns>The Id of the delegated tenant.</returns> public static string GetDelegatedTenantIdForServiceId(this ITenant tenant, string serviceTenantId) { tenant.EnsureTenantIsOfType(MarainTenantType.Client, MarainTenantType.Delegated); if (string.IsNullOrWhiteSpace(serviceTenantId)) { throw new ArgumentException(nameof(serviceTenantId)); } if (tenant.Properties.TryGet(TenantPropertyKeys.DelegatedTenantId(serviceTenantId), out string delegatedTenantId)) { if (!string.IsNullOrEmpty(delegatedTenantId)) { return(delegatedTenantId); } } throw new ArgumentException($"Tenant '{tenant.Name}' with Id '{tenant.Id}' does not contain a delegated tenant Id for service tenant with Id '{serviceTenantId}'"); }
/// <summary> /// Stores the Id of a delegated tenant that has been created for a service to use when accessing a dependent service /// on the tenant's behalf. /// </summary> /// <param name="values">Existing configuration values to which to append these.</param> /// <param name="tenant"> /// The tenant who is able to make calls to the service represented by the specified Service Tenant. /// </param> /// <param name="serviceTenant">The Service Tenant which will be using the Delegated Tenant.</param> /// <param name="delegatedTenant">The tenant that has been created for the service to use.</param> /// <remarks> /// This method does not persist the tenant. Calling code should pass the resulting list to /// <see cref="ITenantStore.UpdateTenantAsync(string, string?, IEnumerable{KeyValuePair{string, object}}?, IEnumerable{string}?)"/>. /// </remarks> /// <returns> /// Properties to pass to /// <see cref="ITenantStore.UpdateTenantAsync(string, string?, IEnumerable{KeyValuePair{string, object}}?, IEnumerable{string}?)"/>. /// </returns> internal static IEnumerable <KeyValuePair <string, object> > SetDelegatedTenantForService( this IEnumerable <KeyValuePair <string, object> > values, ITenant tenant, ITenant serviceTenant, ITenant delegatedTenant) { tenant.EnsureTenantIsOfType(MarainTenantType.Client, MarainTenantType.Delegated); if (serviceTenant == null) { throw new ArgumentNullException(nameof(serviceTenant)); } serviceTenant.EnsureTenantIsOfType(MarainTenantType.Service); if (delegatedTenant == null) { throw new ArgumentNullException(nameof(delegatedTenant)); } delegatedTenant.EnsureTenantIsOfType(MarainTenantType.Delegated); return(values.Append(new KeyValuePair <string, object>(TenantPropertyKeys.DelegatedTenantId(serviceTenant.Id), delegatedTenant.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); }