/// <summary> /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task /// generated by calling the next delegate in the request chain with the provided context. /// </summary> /// <param name="context">The invocation request governing data in this pipeline run.</param> /// <param name="next">The next delegate in the chain to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken) { FieldAuthorizationResult result = FieldAuthorizationResult.Default(); if (context.IsValid) { // execute the authorization pipeline var authRequest = new GraphFieldAuthorizationRequest(context.Request); var authContext = new GraphFieldAuthorizationContext(context, authRequest); await _authPipeline.InvokeAsync(authContext, cancelToken).ConfigureAwait(false); result = authContext.Result ?? FieldAuthorizationResult.Default(); // by default, deny any stati not explicitly declared as "successful" by this component. if (!result.Status.IsAuthorized()) { context.Messages.Critical( $"Access Denied to field {context.Field.Route.Path}", Constants.ErrorCodes.ACCESS_DENIED, context.Request.Origin); } } if (!result.Status.IsAuthorized()) { context.ResolvedSourceItems.AddRange(context.Request.DataSource.Items); context.ResolvedSourceItems.ForEach(x => x.Fail()); } await next(context, cancelToken).ConfigureAwait(false); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldAuthorizationContext context, GraphMiddlewareInvocationDelegate <GraphFieldAuthorizationContext> next, CancellationToken cancelToken) { context.Logger?.FieldResolutionSecurityChallenge(context); var result = await this.AuthorizeRequest(context.Request, context.User).ConfigureAwait(false); context.Result = result ?? FieldAuthorizationResult.Default(); context.Logger?.FieldResolutionSecurityChallengeResult(context); await next(context, cancelToken).ConfigureAwait(false); }
public void FieldSecurityChallengeCompletedLogEntry() { var builder = new TestServerBuilder() .AddGraphType <LogTestController>(); builder.User.SetUsername("bobSmith"); var server = builder.Build(); var package = server.CreateFieldContextBuilder <LogTestController>(nameof(LogTestController.ExecuteField2)); var fieldRequest = package.FieldRequest; var authContext = package.CreateAuthorizationContext(); authContext.Result = FieldAuthorizationResult.Fail("test message 1"); var entry = new FieldAuthorizationCompletedLogEntry(authContext); Assert.AreEqual(LogEventIds.FieldAuthorizationCompleted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id, entry.PipelineRequestId); Assert.AreEqual(fieldRequest.Field.Route.Path, entry.FieldPath); Assert.AreEqual(authContext.User?.RetrieveUsername(), entry.Username); Assert.AreEqual(authContext.Result.Status.ToString(), entry.AuthorizationStatus); Assert.IsNotNull(entry.ToString()); Assert.AreEqual(authContext.Result.LogMessage, entry.LogMessage); }
/// <summary> /// Iterates over every secure field in the operation on the context, attempting to authorize the /// user to each one. /// </summary> /// <param name="context">The primary query context.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns><c>true</c> if authorization was successful, otherwise false.</returns> private async Task <bool> AuthorizeOperation(GraphQueryExecutionContext context, CancellationToken cancelToken) { var authTasks = new List <Task>(); bool anyFieldFailed = false; foreach (var fieldContext in context.QueryOperation.SecureFieldContexts) { var authRequest = new GraphFieldAuthorizationRequest(fieldContext); var authContext = new GraphFieldAuthorizationContext(context, authRequest); var pipelineTask = _authPipeline.InvokeAsync(authContext, cancelToken) .ContinueWith( (_) => { var authResult = authContext.Result ?? FieldAuthorizationResult.Default(); // fake the path elements from the field route. since we don't have a full resolution chain // when doing query level authorization (no indexers on potential child fields since // nothing is actually resolved yet) if (!authResult.Status.IsAuthorized()) { context.Messages.Critical( $"Access Denied to field {fieldContext.Field.Route.Path}", Constants.ErrorCodes.ACCESS_DENIED, fieldContext.Origin); anyFieldFailed = true; } }, cancelToken); authTasks.Add(pipelineTask); } await Task.WhenAll(authTasks).ConfigureAwait(false); return(anyFieldFailed); }
/// <summary> /// Attempts to authorize the request using the field based <see cref="IAuthorizeData" /> attributes. /// </summary> /// <param name="authRequest">The request to authorize.</param> /// <param name="claimsUser">The claims user provisioned for this run.</param> /// <returns>FieldAuthorizationResult.</returns> private async Task <FieldAuthorizationResult> AuthorizeRequest(IGraphFieldAuthorizationRequest authRequest, ClaimsPrincipal claimsUser) { Validation.ThrowIfNull(authRequest?.Field, nameof(authRequest.Field)); var securityGroups = authRequest.Field.SecurityGroups; if (securityGroups == null || !securityGroups.Any()) { return(FieldAuthorizationResult.Skipped()); } // each security group represents one level of security requirements (e.g. the controller group then the action) // the user must autenticate to each of them in turn. foreach (var group in securityGroups) { if (group.AllowAnonymous) { continue; } // dont inspect these services before this // point in case the only security requirements set are all "allow anonymous" // in which case auth services don't matter if (claimsUser?.Identity == null) { return(FieldAuthorizationResult.Fail("The request contains no user context to validate.")); } if (!claimsUser.Identity.IsAuthenticated) { return(FieldAuthorizationResult.Fail($"The supplied {nameof(ClaimsPrincipal)} was not successfully authenticated.")); } foreach (var rule in group) { if (rule.IsNamedPolicy) { if (_authService == null) { return(FieldAuthorizationResult.Fail( "The field defines authorization policies but " + $"no '{nameof(IAuthorizationService)}' exists to process them.")); } // policy check via the authorization service var authResult = await _authService.AuthorizeAsync(claimsUser, rule.PolicyName).ConfigureAwait(false); if (!authResult.Succeeded) { return(FieldAuthorizationResult.Fail($"Access denied via policy '{rule.PolicyName}'.")); } } if (rule.AllowedRoles.Count > 0) { // check any defined roles if (rule.AllowedRoles.All(x => !claimsUser.IsInRole(x))) { return(FieldAuthorizationResult.Fail("Access denied due to missing a required role.")); } } if (rule.AuthenticationSchemes.Count > 0) { // check against any limiting authentication schemes if (claimsUser.Identities.All(x => !rule.AuthenticationSchemes.Contains(x.AuthenticationType))) { return(FieldAuthorizationResult.Fail("Access denied due to missing a required authentication scheme.")); } } // all checks passed } } return(FieldAuthorizationResult.Success()); }
private void AssertAuthorizationSkipped(FieldAuthorizationResult result) { Assert.IsNotNull(result); Assert.AreEqual(FieldAuthorizationStatus.Skipped, result.Status); Assert.IsTrue(string.IsNullOrWhiteSpace(result.LogMessage)); }
private void AssertAuthorizationFails(FieldAuthorizationResult result) { Assert.IsNotNull(result); Assert.AreEqual(FieldAuthorizationStatus.Unauthorized, result.Status); Assert.IsFalse(string.IsNullOrWhiteSpace(result.LogMessage)); }