Esempio n. 1
0
        /// <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);
        }
Esempio n. 3
0
        /// <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());
        }
Esempio n. 6
0
        /// <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}'");
        }
Esempio n. 7
0
        /// <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);
        }