public Task HandleAsync()

in src/Core/Authorization/RestAuthorizationHandler.cs [51:258]


    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        // Catch clause to ensure multiple requirements are not sent at one time, to ensure
        // that requirements are evaluated in order, and fail the request upon first requirement failure.
        // Order not maintained by pendingRequirements as ASP.NET Core implementation is HashSet.
        // This will prevent extraneous computation on later authorization steps that shouldn't occur for a request
        // that has already been evaluated as Unauthorized.
        if (context.PendingRequirements.Count() > 1)
        {
            throw new DataApiBuilderException(
                message: "Multiple requirements are not supported.",
                statusCode: HttpStatusCode.Forbidden,
                subStatusCode: DataApiBuilderException.SubStatusCodes.AuthorizationCheckFailed
            );
        }

        HttpContext? httpContext = _contextAccessor.HttpContext;

        if (httpContext is null)
        {
            throw new DataApiBuilderException(
                message: "HTTP Context Unavailable, Something went wrong",
                statusCode: HttpStatusCode.Unauthorized,
                subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError
                );
        }

        // DataApiBuilder requires that only 1 requirement be processed at a time.
        IAuthorizationRequirement requirement = context.PendingRequirements.First();

        if (requirement is RoleContextPermissionsRequirement)
        {
            if (_authorizationResolver.IsValidRoleContext(httpContext))
            {
                context.Succeed(requirement);
            }
        }
        else if (requirement is EntityRoleOperationPermissionsRequirement)
        {
            if (context.Resource is not null)
            {
                string? entityName = context.Resource as string;

                if (entityName is null)
                {
                    throw new DataApiBuilderException(
                        message: "restContext Resource Null, Something went wrong",
                        statusCode: HttpStatusCode.Unauthorized,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError
                    );
                }

                string roleName = httpContext.Request.Headers[AuthorizationResolver.CLIENT_ROLE_HEADER].ToString();
                IEnumerable<EntityActionOperation> operations = HttpVerbToOperations(httpContext.Request.Method);

                foreach (EntityActionOperation operation in operations)
                {
                    bool isAuthorized = _authorizationResolver.AreRoleAndOperationDefinedForEntity(entityName, roleName, operation);
                    if (!isAuthorized)
                    {
                        context.Fail();
                        break;
                    }
                }

                // All requirement checks must pass.
                if (!context.HasFailed)
                {
                    context.Succeed(requirement);
                }
            }
            else
            {
                context.Fail();
            }
        }
        else if (requirement is ColumnsPermissionsRequirement)
        {
            if (context.Resource is not null)
            {
                RestRequestContext? restContext = context.Resource as RestRequestContext;

                if (restContext is null)
                {
                    throw new DataApiBuilderException(
                        message: "restContext Resource Null, Something went wrong",
                        statusCode: HttpStatusCode.Unauthorized,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError
                    );
                }

                string entityName = restContext.EntityName;
                string roleName = httpContext.Request.Headers[AuthorizationResolver.CLIENT_ROLE_HEADER].ToString();
                IEnumerable<EntityActionOperation> operations = HttpVerbToOperations(httpContext.Request.Method);

                // Delete operations do not have column level restrictions.
                // If the operation is allowed for the role, the column requirement is implicitly successful,
                // and the authorization check can be short circuited here.
                if (operations.Count() == 1 && operations.Contains(EntityActionOperation.Delete))
                {
                    context.Succeed(requirement);
                    return Task.CompletedTask;
                }

                // Attempts to get list of unique columns present in request metadata.
                restContext.CalculateCumulativeColumns(_logger, httpContext);

                // Two operations must be checked when HTTP operation is PUT or PATCH,
                // otherwise, just one operation is checked.
                // PUT and PATCH resolve to operations 'Create' and 'Update'.
                // A user must fulfill all operations' permissions requirements to proceed.
                foreach (EntityActionOperation operation in operations)
                {
                    // Get a list of all columns present in a request that need to be authorized.
                    IEnumerable<string> columnsToCheck = restContext.CumulativeColumns;

                    // When Issue #XX for REST Column Aliases is merged, the request field names(which may be aliases)
                    // will be converted to field names denoted in the permissions config.
                    // i.e. columnsToCheck = convertExposedNamesToBackingColumns()

                    // Authorize field names present in a request.
                    if (columnsToCheck.Count() > 0 && _authorizationResolver.AreColumnsAllowedForOperation(entityName, roleName, operation, columnsToCheck))
                    {
                        // Find operations with no column filter in the query string will have FieldsToBeReturned == 0.
                        // Then, the "allowed columns" resolved, will be set on FieldsToBeReturned.
                        // When FieldsToBeReturned is originally >=1 column, the field is NOT modified here.
                        if (restContext.FieldsToBeReturned.Count == 0 && restContext.OperationType == EntityActionOperation.Read)
                        {
                            // Union performed to avoid duplicate field names in FieldsToBeReturned.
                            IEnumerable<string> fieldsReturnedForFind = _authorizationResolver.GetAllowedExposedColumns(entityName, roleName, operation);
                            restContext.UpdateReturnFields(fieldsReturnedForFind);
                        }
                    }
                    else if (columnsToCheck.Count() == 0 && restContext.OperationType is EntityActionOperation.Read)
                    {
                        // - Find operations typically return all metadata of a database record.
                        // This check resolves all 'included' columns defined in permissions
                        // so only those included columns are present in the result(s).
                        // - For other operation types, columnsToCheck is a result of identifying
                        // any reference to a column in all parts of a request (body, URL, querystring)
                        IEnumerable<string> fieldsReturnedForFind = _authorizationResolver.GetAllowedExposedColumns(entityName, roleName, operation);
                        if (fieldsReturnedForFind.Count() == 0)
                        {
                            // READ operations with no accessible fields fail authorization.
                            context.Fail();
                        }

                        restContext.UpdateReturnFields(fieldsReturnedForFind);
                    }
                    else if (columnsToCheck.Count() == 0 && restContext.OperationType is EntityActionOperation.Insert)
                    {
                        // It's possible that a INSERT operation has no columns in the request
                        // body, but the operation is still allowed in cases where the table
                        // contains default values for all columns. In such cases, we check
                        // all the columns if the insert operation is allowed.
                        IEnumerable<string> fieldsForCreate = _authorizationResolver.GetAllowedExposedColumns(entityName, roleName, operation);
                        if (fieldsForCreate.Count() == 0)
                        {
                            context.Fail();
                        }
                    }
                    else
                    {
                        context.Fail();
                    }
                }

                if (!context.HasFailed)
                {
                    context.Succeed(requirement);
                }
            }
        }
        else if (requirement is StoredProcedurePermissionsRequirement)
        {
            if (context.Resource is not null)
            {
                string? entityName = context.Resource as string;

                if (entityName is null)
                {
                    throw new DataApiBuilderException(
                        message: "restContext Resource Null, Something went wrong",
                        statusCode: HttpStatusCode.Unauthorized,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError
                    );
                }

                string roleName = httpContext.Request.Headers[AuthorizationResolver.CLIENT_ROLE_HEADER].ToString();
                Enum.TryParse<SupportedHttpVerb>(httpContext.Request.Method, ignoreCase: true, out SupportedHttpVerb httpVerb);
                bool isAuthorized = _authorizationResolver.IsStoredProcedureExecutionPermitted(entityName, roleName, httpVerb);
                if (!isAuthorized)
                {
                    context.Fail();
                }
                else
                {
                    context.Succeed(requirement);
                }
            }
            else
            {
                context.Fail();
            }
        }

        return Task.CompletedTask;
    }