/// <inheritdoc /> public Task <IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> > CheckAccessPoliciesAsync( IOpenApiContext context, params AccessCheckOperationDescriptor[] requests) { return(OpenApiAccessPolicyAggregator.EvaluteAccessPoliciesConcurrentlyAsync( this.accessControlPolicies, context, requests)); }
public async Task <OpenApiResult> GetClaimPermissionResourceAccessRulesAsync( IOpenApiContext context, string claimPermissionsId) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (claimPermissionsId is null) { throw new ArgumentNullException(nameof(claimPermissionsId)); } ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId).ConfigureAwait(false); IClaimPermissionsStore store = await this.permissionsStoreFactory.GetClaimPermissionsStoreAsync(tenant).ConfigureAwait(false); try { ClaimPermissions claimPermissions = await store.GetAsync(claimPermissionsId).ConfigureAwait(false); return(this.OkResult(claimPermissions.AllResourceAccessRules, "application/json")); } catch (ClaimPermissionsNotFoundException) { return(this.NotFoundResult()); } }
public async Task <OpenApiResult> GetNotificationsForUserAsync( IOpenApiContext context, string userId, string?sinceNotificationId, int?maxItems, string?continuationToken) { // We can guarantee tenant Id is available because it's part of the Uri. ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId !).ConfigureAwait(false); IUserNotificationStore userNotificationStore = await this.userNotificationStoreFactory.GetUserNotificationStoreForTenantAsync(tenant).ConfigureAwait(false); maxItems ??= 50; GetNotificationsResult results = await this.GetNotificationsAsync( userId, sinceNotificationId, maxItems.Value, continuationToken, userNotificationStore).ConfigureAwait(false); await this.EnsureAllNotificationsMarkedAsDelivered(context, results).ConfigureAwait(false); HalDocument result = await this.userNotificationsMapper.MapAsync( results, new UserNotificationsMappingContext(context, userId, sinceNotificationId, maxItems.Value, continuationToken)).ConfigureAwait(false); return(this.OkResult(result)); }
/// <inheritdoc /> public Task LogAsync(IOpenApiContext context, AuditLog log) { string data = JsonConvert.SerializeObject(log, Formatting.Indented, this.openApiConfiguration.SerializerSettings); Console.WriteLine(data); return(Task.CompletedTask); }
public ShouldAllowArgs( AccessCheckOperationDescriptor[] requests, IOpenApiContext context) { this.Requests = requests; this.Context = context; }
public async Task <OpenApiResult> GenerateTemplateAsync( IOpenApiContext context, CreateNotificationsRequest body) { // We can guarantee tenant Id is available because it's part of the Uri. ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId !).ConfigureAwait(false); var registeredCommunicationChannels = new List <CommunicationType>() { CommunicationType.WebPush, CommunicationType.Email, CommunicationType.Sms }; // TODO: In the future, check if these registeredCommunicationChannels are actually usable for the current Tenant. if (registeredCommunicationChannels is null || registeredCommunicationChannels.Count == 0) { throw new Exception($"There are no communication channel set up for the user {body.UserIds[0]} for notification type {body.NotificationType} for tenant {tenant.Id}"); } // Gets the AzureBlobTemplateStore INotificationTemplateStore templateStore = await this.tenantedTemplateStoreFactory.GetTemplateStoreForTenantAsync(tenant).ConfigureAwait(false); NotificationTemplate?responseTemplate = await this.generateTemplateComposer.GenerateTemplateAsync(templateStore, body.Properties, registeredCommunicationChannels, body.NotificationType).ConfigureAwait(false); return(this.OkResult(responseTemplate)); }
/// <summary> /// Build an audit log for a given result, operation, and context. /// </summary> /// <param name="context">The OpenAPI context.</param> /// <param name="result">The result of the operation.</param> /// <param name="operation">The OpenAPI operation.</param> /// <returns>The audit log entry for the result, operation, and context.</returns> private AuditLog?BuildAuditLog(IOpenApiContext context, object result, OpenApiOperation operation) { if (this.logger.IsEnabled(LogLevel.Debug)) { this.logger.LogDebug( "Building audit log for [{operation}]", operation.OperationId); } foreach (IAuditLogBuilder auditLogBuilder in this.auditLogBuilders) { if (auditLogBuilder.CanBuildAuditLog(context, result, operation)) { return(auditLogBuilder.BuildAuditLog(context, result, operation)); } } if (this.logger.IsEnabled(LogLevel.Warning)) { this.logger.LogWarning( "Unable to build audit log for [{operation}]", operation.OperationId); } return(null); }
/// <inheritdoc/> public async Task <IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> > ShouldAllowAsync( IOpenApiContext context, params AccessCheckOperationDescriptor[] requests) { IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> firstPolicyResults = await this.firstPolicy.ShouldAllowAsync(context, requests).ConfigureAwait(false); // Get the "denys" from the first policy results so we can feed them into the second. AccessCheckOperationDescriptor[] deniedByFirstPolicy = firstPolicyResults.Where(x => !x.Value.Allow).Select(x => x.Key).ToArray(); // If there weren't any denies from the first policy, then we can return immediately as // there's no further work to do. if (deniedByFirstPolicy.Length == 0) { return(firstPolicyResults); } // Evaluate the remaining requests. IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> otherPolicyResults = await OpenApiAccessPolicyAggregator.EvaluteAccessPoliciesConcurrentlyAsync( this.otherPolicies, context, deniedByFirstPolicy).ConfigureAwait(false); // Merge the result from the other policies into that from the first. otherPolicyResults.ForEach(x => firstPolicyResults[x.Key] = x.Value); return(firstPolicyResults); }
public async Task <OpenApiResult> CreateNotificationsAsync( IOpenApiContext context, CreateNotificationsRequest body) { // We can guarantee tenant Id is available because it's part of the Uri. ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId !).ConfigureAwait(false); string delegatedTenantId = await this.marainServicesTenancy.GetDelegatedTenantIdForRequestingTenantAsync(tenant.Id).ConfigureAwait(false); var operationId = Guid.NewGuid(); CreateOperationHeaders response = await this.operationsControlClient.CreateOperationAsync( delegatedTenantId, operationId).ConfigureAwait(false); // Create a new CreateNotificationForDeliveryChannelsRequest Object which supports the communication types and delivery channels var createNotificationForDeliveryChannelsRequestObject = new CreateNotificationForDeliveryChannelsRequest( body.NotificationType, body.UserIds, body.Timestamp, body.Properties, body.CorrelationIds); IDurableOrchestrationClient orchestrationClient = context.AsDurableFunctionsOpenApiContext().OrchestrationClient ?? throw new OpenApiServiceMismatchException($"Operation {CreateNotificationsOperationId} has been invoked, but no Durable Orchestration Client is available on the OpenApi context."); await orchestrationClient.StartNewAsync( nameof(CreateAndDispatchNotificationsOrchestration), new TenantedFunctionData <CreateNotificationForDeliveryChannelsRequest>(context.CurrentTenantId !, createNotificationForDeliveryChannelsRequestObject, operationId)).ConfigureAwait(false); return(this.AcceptedResult(response.Location)); }
public async Task <OpenApiResult> HandleTrigger(IOpenApiContext context, IWorkflowTrigger body) { ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId).ConfigureAwait(false); string delegatedTenantId = await this.marainServicesTenancy.GetDelegatedTenantIdForRequestingTenantAsync(tenant.Id).ConfigureAwait(false); var operationId = Guid.NewGuid(); CreateOperationHeaders operationHeaders = await this.operationsControl.CreateOperationAsync(delegatedTenantId, operationId).ConfigureAwait(false); var envelope = new WorkflowMessageEnvelope(this.propertyBagFactory.Create(PropertyBagValues.Empty)) { Trigger = body, OperationId = operationId, TenantId = context.CurrentTenantId, }; var durableFunctionsOpenApiContext = (DurableFunctionsOpenApiContext)context; await durableFunctionsOpenApiContext.OrchestrationClient.StartNewWithCustomSerializationSettingsAsync( nameof(TriggerExecutionOrchestrator), operationId.ToString(), envelope, this.serializerSettingsProvider.Instance).ConfigureAwait(false); return(this.AcceptedResult(operationHeaders.Location)); }
private async Task CheckAccessPoliciesAsync( IOpenApiContext context, string path, string method, string operationId) { AccessControlPolicyResult result = await this.accessChecker.CheckAccessPolicyAsync(context, path, operationId, method).ConfigureAwait(false); if (result.ResultType == AccessControlPolicyResultType.NotAuthenticated) { Exception x = this.configuration.AccessPolicyUnauthenticatedResponse switch { ResponseWhenUnauthenticated.Unauthorized => new OpenApiUnauthorizedException("Unauthorized"), ResponseWhenUnauthenticated.Forbidden => OpenApiForbiddenException.WithoutProblemDetails("Forbidden"), ResponseWhenUnauthenticated.ServerError => new OpenApiServiceMismatchException("Unauthenticated requests should not be reaching this service"), _ => new OpenApiServiceMismatchException($"Unknown AccessPolicyUnauthenticatedResponse: {this.configuration.AccessPolicyUnauthenticatedResponse}"), }; if (!string.IsNullOrWhiteSpace(result.Explanation)) { x.AddProblemDetailsExplanation(result.Explanation !); // ! required as netstandard2.0 lacks nullable attributes } throw x; } if (!result.Allow) { throw string.IsNullOrWhiteSpace(result.Explanation) ? OpenApiForbiddenException.WithoutProblemDetails("Forbidden") : OpenApiForbiddenException.WithProblemDetails("Forbidden", result.Explanation); } }
public async Task <OpenApiResult> UpdateTenantAsync( string tenantId, JsonPatchDocument body, IOpenApiContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (string.IsNullOrEmpty(tenantId)) { throw new OpenApiBadRequestException("Bad request"); } if (body is null) { throw new OpenApiBadRequestException(); } if (tenantId == RootTenant.RootTenantId) { // Updates to the root tenant are blocked because in Marain services, the root // tenant is locally synthesized, and not fetched from the tenancy service. // This enables service-specific fallback settings to be configured on the root. // But it also means services will never see settings configured on the root // via the Marain.Tenancy service, and so, to avoid disappointment, we don't // let anyone try to do this. return(new OpenApiResult { StatusCode = (int)HttpStatusCode.MethodNotAllowed }); } try { string?name = null; Dictionary <string, object>?propertiesToSet = null; List <string>?propertiesToRemove = null; foreach (Operation operation in body.Operations) { if (operation.path == "/name") { if (operation.OperationType == OperationType.Replace && operation.value is string newTenantName) { name = newTenantName; } else { return(new OpenApiResult { StatusCode = 422 }); // Unprocessable entity } } else { if (operation.path.StartsWith("/properties/")) { string propertyName = operation.path[12..];
public CheckAccessArguments( IOpenApiContext context, AccessCheckOperationDescriptor[] requests) { this.Context = context; this.Requests = requests; }
public async Task <OpenApiResult> MarkNotificationReadAsync( IOpenApiContext context, string notificationId) { // We're going to forward this on to the management API, which already implements all of the long running // operation semantics we would like here. At present, this is via the "batch update" endpoint - if at // some point in the future we add the ability to update a single notification status, we should switch // over to that in this code. // We can guarantee tenant Id is available because it's part of the Uri. // We don't actually need the tenant, but this method has the benefit of validating that the requesting // tenant is valid and is enrolled for this service. _ = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId !).ConfigureAwait(false); BatchReadStatusUpdateRequestItem[] body = new[] { new BatchReadStatusUpdateRequestItem { DeliveryChannelId = Constants.ApiDeliveryChannelId, NewStatus = UpdateNotificationReadStatusRequestNewStatus.Read, NotificationId = notificationId, UpdateTimestamp = DateTimeOffset.UtcNow, }, }; ApiResponse response = await this.managementApiClient.BatchReadStatusUpdateAsync( context.CurrentTenantId, body).ConfigureAwait(false); return(this.AcceptedResult(response.Headers["Location"])); }
public async Task <OpenApiResult> GetNotificationAsync( IOpenApiContext context, string notificationId) { // We can guarantee tenant Id is available because it's part of the Uri. ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId !).ConfigureAwait(false); IUserNotificationStore userNotificationStore = await this.userNotificationStoreFactory.GetUserNotificationStoreForTenantAsync(tenant).ConfigureAwait(false); UserNotification notifications; try { notifications = await userNotificationStore.GetByIdAsync(notificationId).ConfigureAwait(false); } catch (ArgumentException) { // This will happen if the supplied notification Id is invalid. Return a BadRequest response. throw new OpenApiBadRequestException("The supplied notificationId is not valid"); } HalDocument response = await this.userNotificationMapper.MapAsync(notifications, context).ConfigureAwait(false); return(this.OkResult(response, "application/json")); }
/// <summary> /// Evaluates multiple policies concurrently, and aggregates the results, returning an Allow /// result if all policies says Allow, and otherwise returning a Deny result where the /// Explanation is formed by appending any Explanations produced by the individual policies. /// </summary> /// <param name="accessControlPolicies"> /// The policies to evaluate and aggregate. /// </param> /// <param name="context"> /// The context for which to perform the check. /// </param> /// <param name="requests"> /// The list of operation descriptors to check. /// </param> /// <returns> /// A task that produces an <see cref="AccessControlPolicyResultType"/> indicating whether /// access is allowed, and if it is not, an optional textual explanation (which may be null). /// </returns> internal static async Task <IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> > EvaluteAccessPoliciesConcurrentlyAsync( IEnumerable <IOpenApiAccessControlPolicy> accessControlPolicies, IOpenApiContext context, params AccessCheckOperationDescriptor[] requests) { // Evaluate the set of requests with all policies simultaneously. IEnumerable <Task <IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> > > policyEvaluationTasks = accessControlPolicies.Select( policy => policy.ShouldAllowAsync( context, requests)); // Wait for all policy evaluation to complete. IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult>[] results = await Task.WhenAll(policyEvaluationTasks).ConfigureAwait(false); // "Roll up" the results. The results of the policy aggregation is a list of dictionaries, each dictionary // containing an entry for each of the requests supplied in the parameters. To build our result dictionary // we need to aggregate the entry for each request from each dictionary. To do this we use the // CombineResultTypes method for each result type, and concatenate any result explanations together. return(requests.ToDictionary( request => request, request => { (AccessControlPolicyResultType resultType, string?explanation) = results.Aggregate( (resultType: AccessControlPolicyResultType.Allowed, explanation: default(string)), (acc, result) => (CombineResultTypes(acc.resultType, result[request].ResultType), string.IsNullOrWhiteSpace(acc.explanation) ? result[request].Explanation : string.IsNullOrWhiteSpace(result[request].Explanation) ? acc.explanation : acc.explanation + "; " + result[request].Explanation)); return new AccessControlPolicyResult(resultType, explanation); }));
public async Task <OpenApiResult> GetContentSummary(IOpenApiContext context, string slug, string contentId, string ifNoneMatch) { IContentStore contentStore = await this.contentStoreFactory.GetContentStoreForTenantAsync(context.CurrentTenantId).ConfigureAwait(false); ContentSummary result = await contentStore.GetContentSummaryAsync(contentId, slug).ConfigureAwait(false); string etag = EtagHelper.BuildEtag(nameof(ContentSummary), result.ETag); // If the etag in the result matches ifNoneMatch then we return 304 Not Modified if (EtagHelper.IsMatch(ifNoneMatch, etag)) { return(this.NotModifiedResult()); } HalDocument resultDocument = this.contentSummaryMapper.Map(result, new ResponseMappingContext { TenantId = context.CurrentTenantId }); OpenApiResult response = this.OkResult(resultDocument); response.Results.Add(HeaderNames.ETag, etag); // Since content is immutable we can allow clients to cache it indefinitely. response.Results.Add(HeaderNames.CacheControl, Constants.CacheControlHeaderOptions.NeverExpire); return(response); }
public Task <OpenApiResult> GetNotificationsAsync( IOpenApiContext context, string notificationId, int?maxItems, string?continuationToken) { return(Task.FromResult(this.NotImplementedResult())); }
/// <summary> /// Adds an entry to each of a set of audit services. /// </summary> /// <param name="auditSinks">A collection of audit services.</param> /// <param name="context">The current OpenApi context.</param> /// <param name="log">The audit log to write.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static async Task LogAsync( this IEnumerable <IAuditLogSink> auditSinks, IOpenApiContext context, AuditLog log) { IEnumerable <Task> tasks = auditSinks.Select(x => x.LogAsync(context, log)); await Task.WhenAll(tasks).ConfigureAwait(false); }
/// <inheritdoc/> public Task <IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> > ShouldAllowAsync( IOpenApiContext context, params AccessCheckOperationDescriptor[] requests) { IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> result = requests.ToDictionary(request => request, request => this.ShouldAllow(request.OperationId)); return(Task.FromResult(result)); }
public Task <IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> > CheckAccessPoliciesAsync( IOpenApiContext context, params AccessCheckOperationDescriptor[] descriptors) { return(this.context.AccessCheckCalls == null ? Task.FromResult <IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> >(descriptors.ToDictionary(d => d, __ => Allowed)) : this.context.AccessCheckCalls.GetTask(new CheckAccessArguments(context, descriptors))); }
public async Task <OpenApiResult> CreateTemplateAsync( IOpenApiContext context, ICommunicationTemplate body, [OpenApiParameter("If-None-Match")] string etag) { if (string.IsNullOrWhiteSpace(body.NotificationType)) { throw new OpenApiNotFoundException("The NotificationType was not found in the object"); } if (string.IsNullOrWhiteSpace(body.ContentType)) { throw new OpenApiNotFoundException("The ContentType was not found in the object"); } // We can guarantee tenant Id is available because it's part of the Uri. ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId !).ConfigureAwait(false); // Gets the AzureBlobTemplateStore INotificationTemplateStore store = await this.tenantedTemplateStoreFactory.GetTemplateStoreForTenantAsync(tenant).ConfigureAwait(false); try { if (body is EmailTemplate emailTemplate) { emailTemplate.ETag = etag; await store.CreateOrUpdate(body.NotificationType, CommunicationType.Email, emailTemplate.ETag, emailTemplate).ConfigureAwait(false); } else if (body is SmsTemplate smsTemplate) { smsTemplate.ETag = etag; await store.CreateOrUpdate(body.NotificationType, CommunicationType.Sms, smsTemplate.ETag, smsTemplate).ConfigureAwait(false); } else if (body is WebPushTemplate webPushTemplate) { webPushTemplate.ETag = etag; await store.CreateOrUpdate(body.NotificationType, CommunicationType.WebPush, webPushTemplate.ETag, webPushTemplate).ConfigureAwait(false); } else { // this should be removed in future updates throw new OpenApiNotFoundException($"The template for ContentType: {body.ContentType} is not a valid content type"); } } catch (StorageException e) { if (e?.RequestInformation?.HttpStatusCode == (int)System.Net.HttpStatusCode.PreconditionFailed) { throw new OpenApiBadRequestException("Precondition failure. Blob's ETag does not match ETag provided."); } throw; } return(this.OkResult()); }
public async Task <OpenApiResult> GetChildrenAsync( string tenantId, int?maxItems, string continuationToken, IOpenApiContext context) { if (string.IsNullOrEmpty(tenantId)) { throw new OpenApiBadRequestException("Bad request"); } if (context is null) { throw new ArgumentNullException(nameof(context)); } try { TenantCollectionResult result = await this.tenantStore.GetChildrenAsync(tenantId, maxItems ?? 20, continuationToken).ConfigureAwait(false); HalDocument document = await this.tenantCollectionResultMapper.MapAsync(result).ConfigureAwait(false); if (result.ContinuationToken != null) { OpenApiWebLink link = maxItems.HasValue ? this.linkResolver.ResolveByOperationIdAndRelationType(GetChildrenOperationId, "next", ("tenantId", tenantId), ("continuationToken", result.ContinuationToken), ("maxItems", maxItems)) : this.linkResolver.ResolveByOperationIdAndRelationType(GetChildrenOperationId, "next", ("tenantId", tenantId), ("continuationToken", result.ContinuationToken)); document.AddLink("next", link); } var values = new List <(string, object?)> { ("tenantId", tenantId) }; if (maxItems.HasValue) { values.Add(("maxItems", maxItems)); } if (!string.IsNullOrEmpty(continuationToken)) { values.Add(("continuationToken", continuationToken)); } OpenApiWebLink selfLink = this.linkResolver.ResolveByOperationIdAndRelationType(GetChildrenOperationId, "self", values.ToArray()); document.AddLink("self", selfLink); return(this.OkResult(document, "application/json")); } catch (TenantNotFoundException) { return(this.NotFoundResult()); } catch (TenantConflictException) { return(this.ConflictResult()); } }
/// <inheritdoc /> public AuditLog BuildAuditLog(IOpenApiContext context, object result, OpenApiOperation operation) { return(new AuditLog(operation.OperationId) { CreatedDateTimeUtc = DateTimeOffset.UtcNow, Result = 200, TenantId = context.CurrentTenantId, UserId = context.CurrentPrincipal?.Identity?.Name, }); }
public Task <OpenApiResult> ShowSecretPet(IOpenApiContext openApiContext) { if (openApiContext.CurrentPrincipal?.IsInRole("admin") == true) { return(this.MapAndReturnPetAsync(this.secretPet)); } return(Task.FromResult(new OpenApiResult { StatusCode = (int)HttpStatusCode.Unauthorized })); }
/// <inheritdoc/> public async Task AuditResultAsync(IOpenApiContext context, object result, OpenApiOperation operation) { if (this.IsAuditingEnabled) { AuditLog?log = this.BuildAuditLog(context, result, operation); if (log != null) { await this.auditSinks.LogAsync(context, log).ConfigureAwait(false); } } }
/// <summary> /// An extension method that provides simpler syntax when using the <see cref="IOpenApiAccessChecker"/> /// to check access for a single operation. /// </summary> /// <param name="checker">The underlying <see cref="IOpenApiAccessChecker"/> to use.</param> /// <param name="context">The current <see cref="IOpenApiContext"/>.</param> /// <param name="path">The request path.</param> /// <param name="operationId">The request Operation Id.</param> /// <param name="httpMethod">The request Http method.</param> /// <returns>A task that resolves to the result of the access check.</returns> public static async Task <AccessControlPolicyResult> CheckAccessPolicyAsync( this IOpenApiAccessChecker checker, IOpenApiContext context, string path, string operationId, string httpMethod) { var request = new AccessCheckOperationDescriptor(path, operationId, httpMethod); IDictionary <AccessCheckOperationDescriptor, AccessControlPolicyResult> result = await checker.CheckAccessPoliciesAsync(context, request).ConfigureAwait(false); return(result.Values.Single()); }
/// <inheritdoc/> public async Task AuditFailureAsync(IOpenApiContext context, int statusCode, OpenApiOperation operation) { if (this.IsAuditingEnabled) { var log = new AuditLog(operation.OperationId) { Result = statusCode, UserId = context.CurrentPrincipal?.Identity?.Name, }; await this.auditSinks.LogAsync(context, log).ConfigureAwait(false); } }
/// <summary> /// Initializes a new instance of the <see cref="UserNotificationsMappingContext"/> class. /// </summary> /// <param name="openApiContext">The <see cref="OpenApiContext"/>.</param> /// <param name="userId">The <see cref="UserId"/>.</param> /// <param name="sinceNotificationId">The <see cref="SinceNotificationId"/>.</param> /// <param name="maxItems">The <see cref="MaxItems"/>.</param> /// <param name="continuationToken">The <see cref="ContinuationToken"/>.</param> public UserNotificationsMappingContext( IOpenApiContext openApiContext, string userId, string?sinceNotificationId, int maxItems, string?continuationToken) { this.OpenApiContext = openApiContext; this.SinceNotificationId = sinceNotificationId; this.MaxItems = maxItems; this.ContinuationToken = continuationToken; this.UserId = userId; }
/// <inheritdoc/> public async Task <TResponse> HandleRequestAsync(string path, string method, TRequest request, object parameters) { IOpenApiContext context = await this.BuildContextAsync(request, parameters).ConfigureAwait(false); // Try to find an Open API operation which matches the incoming request. if (this.matcher.FindOperationPathTemplate(path, method, out OpenApiOperationPathTemplate? operationPathTemplate)) { // Now execute the operation return(await this.operationInvoker.InvokeAsync(path, method, request, operationPathTemplate, context).ConfigureAwait(false)); } // We didn't find an operation which correspons to the path and method in the OpenAPI document return(this.BuildServiceOperationNotFoundResult()); }