Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 1, 2025

Why make this change?

The current OpenAPI behavior shows all REST methods even when they are not available. For example, read-only entities show all REST methods (GET, POST, PUT, PATCH, DELETE) in the OpenAPI document, which misrepresents the available options.

Related issues:

What is this change?

Permission-based filtering (OpenApiDocumentor.cs):

  • GetConfiguredRestOperations - Filters operations based on permissions superset across all roles
    • Critical Fix: PUT/PATCH now require BOTH Create AND Update permissions (matches runtime upsert semantics)
  • FilterFieldsByPermissions - Filters fields based on include/exclude settings across all roles
    • Bug Fix: Returns all exposedColumnNames when role matches but Actions is null
  • HasAnyAvailableOperations - Checks if entity has any permissions with actions defined
  • Entities with no available operations are completely omitted (no paths, tags, or schemas)

Role-specific OpenAPI endpoint (RestController.cs):

  • /openapi - Returns superset of all roles (existing behavior)
  • /openapi/{role} - Returns OpenAPI document filtered for a specific role
  • Case-insensitive role matching with URL-decoded parameter
  • Returns 404 if role not found or empty/whitespace

Request Body Strict Mode:

  • Request body schemas (_NoAutoPK, _NoPK, _sp_request) set AdditionalPropertiesAllowed based on RuntimeConfig.IsRequestBodyStrict
  • When request-body-strict: true (default): additionalProperties: false
  • When request-body-strict: false: additionalProperties: true
  • Response body schemas always allow additional properties

Wildcard (*) Behavior:

  • Tables/Views: * expands to all CRUD operations (Create→POST, Read→GET, Update+Create→PUT/PATCH, Delete→DELETE)
  • Stored Procedures: * maps to Execute only; HTTP methods determined by entity.Rest.Methods

Permission → REST mapping:

Permission REST Methods
Read GET
Create POST
Update (none - requires Create too)
Create + Update PUT, PATCH
Delete DELETE

Code Review Fixes:

  • Added empty/whitespace validation for role parameter
  • Replaced foreach loop with LINQ .Any() for role existence check
  • Simplified AdditionalPropertiesAllowed ternary expression
  • Simplified field filtering logic with ternary operator
  • Improved comment clarity for catch block behavior
  • Fixed FilterFieldsByPermissions to return all fields when role matches but Actions is null
  • Fixed PUT/PATCH to require both Create and Update permissions
  • Added role-specific field visibility tests to ensure fields don't leak between roles
  • Fixed code formatting to comply with whitespace rules

How was this tested?

  • Integration Tests
  • Unit Tests

Tests split into focused files:

  • OperationFilteringTests.cs - Operation filtering tests (read-only, wildcard, no permissions, mixed roles)
  • FieldFilteringTests.cs - Field filtering tests (excluded fields, mixed role field permissions)
  • RoleIsolationTests.cs - Role isolation tests (anonymous vs superset, competing roles don't leak)
    • PutPatchOperations_RequireBothCreateAndUpdatePermissions - Validates PUT/PATCH require both Create and Update permissions
    • CompetingRoles_DoNotLeakFields - Validates role-specific field visibility (reader can't see writer fields and vice versa)
  • RequestBodyStrictTests.cs - Request-body-strict tests

Sample Request(s)

Superset OpenAPI (all roles combined):

GET /api/openapi

Role-specific OpenAPI:

GET /api/openapi/anonymous
GET /api/openapi/authenticated
GET /api/openapi/customrole

Returns 404 if role doesn't exist in any entity's permissions.

Config with read-only entity:

{
  "entities": {
    "Book": {
      "source": "dbo.books",
      "permissions": [
        { "role": "anonymous", "actions": ["read"] }
      ]
    }
  }
}

OpenAPI response shows only GET operations for /Book and /Book/id/{id} paths.

Config with update-only permission (no PUT/PATCH):

{
  "entities": {
    "Book": {
      "source": "dbo.books",
      "permissions": [
        { "role": "updater", "actions": ["update"] }
      ]
    }
  }
}

OpenAPI response shows NO operations because PUT/PATCH require both Create and Update permissions.

Config with create+update permissions (enables PUT/PATCH):

{
  "entities": {
    "Book": {
      "source": "dbo.books",
      "permissions": [
        { "role": "editor", "actions": ["create", "update"] }
      ]
    }
  }
}

OpenAPI response shows POST, PUT, and PATCH operations.

Config with excluded field:

{
  "entities": {
    "Book": {
      "source": "dbo.books",
      "permissions": [
        { 
          "role": "anonymous", 
          "actions": [{ "action": "*", "fields": { "exclude": ["secret_column"] } }]
        }
      ]
    }
  }
}

OpenAPI schema for Book entity will not include secret_column in its properties.

Config with competing roles and field restrictions:

{
  "entities": {
    "Book": {
      "source": "dbo.books",
      "permissions": [
        { 
          "role": "reader", 
          "actions": [{ "action": "read", "fields": { "include": ["id"] } }]
        },
        { 
          "role": "writer", 
          "actions": [
            { "action": "create", "fields": { "include": ["title"] } },
            { "action": "update", "fields": { "include": ["title"] } }
          ]
        }
      ]
    }
  }
}
  • /openapi shows GET, POST, PUT, and PATCH with superset of fields (id, title)
  • /openapi/reader shows only GET operations with only 'id' field
  • /openapi/writer shows POST, PUT, and PATCH operations with only 'title' field
  • /openapi/admin returns 404 (role not found)
Original prompt

This section details on the original issue you should resolve

<issue_title>[Enh]: OpenAPI REST methods should show only available methods</issue_title>
<issue_description>## What?

Show only the available REST methods in the OpenAPI document we produce.

Why?

The current OpenAPI behavior shows all REST methods even when they are not available.

For example, read-only entities show all the REST methods in the OpenAPI document.

How?

Available REST methods are limited in permissions for each role. In order to correctly show the available REST methods, we need to interrogate the current role and produce the role-specific OpenAPI that filters REST methods based on that role's permissions for each entity.

Notes

This does not require a change to the configuration. </issue_description>

<agent_instructions>Your goal is to satisfy this requirement with as little change or introduction of code as possible. Follow best practices and do not skip tests, but at the same time, do NOT introduce unnecessary code. Think and rethink if there is a simpler solution and ALWAYS choose the simplest and easiest and most terse that satisfies the requirement. You are a Principal Software Engineer at Microsoft on the Data API builder ENG team. Your code is intended to be an example to follow. Go. </agent_instructions>

Comments on the Issue (you are @copilot in this section)

@JerryNixon The current root OpenAPI shows the superset of the available permissions across all roles. If there are permissions for anonymous, authenticated, or custom roles, the root OpenAPI should show what is possible across all of them combined, not specific to any one role. However, if a permission like delete is not available to any role or a method is not available because there is no permission set (remembering that * represents create, read, update, and delete for tables and views while * represents execute for stored procedures) then those unavailable methods should be omitted from the root OpenAPI because it misrepresents the available options.

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copilot AI changed the title [WIP] Show only available REST methods in OpenAPI document Filter OpenAPI REST methods based on entity permissions Dec 1, 2025
Copilot AI requested a review from JerryNixon December 1, 2025 18:49
@JerryNixon
Copy link
Contributor

@copilot An entity with no possible methods should be completely omitted from the OpenAPI specification.

…ased on permissions

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 1, 2025

@copilot An entity with no possible methods should be completely omitted from the OpenAPI specification.

Implemented in commit 92da2fe. Entities with no available operations are now completely omitted from the OpenAPI specification - no paths, no tags, and no component schemas are generated for them. The HasAnyAvailableOperations helper and checks in BuildPaths, CreateDocument, and CreateComponentSchemas ensure this behavior.

@JerryNixon
Copy link
Contributor

@copilot Now take time to account for fields in the same way. Please note that fields are in the permissions / role / actions / fields property and can be designated in the configuration file with the * wildcard to indicate all and that there is both include and exclude which work together to create the available fields and that all of this is option in the configuration if the developer wants to simply expose all fields. That said, we cannot expose fields which are expressly excluded or not included.

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 1, 2025

@copilot Now take time to account for fields in the same way. Please note that fields are in the permissions / role / actions / fields property and can be designated in the configuration file with the...

Implemented in commit 48c76ae. Added FilterFieldsByPermissions method that:

  • Returns superset of available fields across all role/action permissions
  • Handles Include (null or * means all fields, otherwise explicit list)
  • Handles Exclude (removes specified fields, * excludes all)
  • If Fields is null on an action, all fields are available

The filtering is applied in CreateComponentSchemas right after getting exposed column names. Added tests for excluded fields and mixed role field permissions.

Copilot AI changed the title Filter OpenAPI REST methods based on entity permissions Filter OpenAPI REST methods and fields based on entity permissions Dec 1, 2025
@JerryNixon
Copy link
Contributor

@copilot Check that "none" and "anonymous" are not the same. As none should represent all roles together, anonymous is an explicit role like authenticated or some other custom role the user adds.

The next thing is to ensure that calling a single role results in what we want. I would like to see a test for this to ensure it works when, say, two competing roles are defined and the resulting openapi is correctly reprenseting the individual roles and not leaking information into the others.

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 1, 2025

@copilot Check that "none" and "anonymous" are not the same. As none should represent all roles together, anonymous is an explicit role like authenticated or some other custom role the user adds.

Th...

Implemented in commit 178910f:

  1. Role distinction: The filtering methods now have an optional role parameter. When null (no role specified), it returns the superset of all roles. When a specific role like "anonymous" is provided, it filters to only that role's permissions using case-insensitive comparison.

  2. Tests for competing roles:

    • AnonymousRole_IsDistinctFromSuperset - Verifies anonymous is a specific role, not the superset
    • CompetingRoles_DoNotLeakOperations - Reader role has only READ, writer role has only CREATE; superset has GET+POST but not PUT/PATCH/DELETE
    • CompetingRoles_DoNotLeakFields - Reader sees 'id', writer sees 'title'; superset correctly combines both without leaking

Copilot AI changed the title Filter OpenAPI REST methods and fields based on entity permissions Filter OpenAPI REST methods and fields based on entity permissions with role support Dec 1, 2025
@JerryNixon
Copy link
Contributor

@copilot Keep refactoring and simplifying. Remember we want to have good code coverage specifically for the lines you introduced in this PR. We also want to ensure existing tests that also might touch the code are updated or removed if required. Your goal is simplicity while still fulfilling the requirements. Every unit test should in this PR should test the code we introduced.

In addition, look at the routing to the OpenAPI endpoint. It is basically /OpenAPI which should have no role and shows the superset. But let's amend this to support /OpenAPI/{role} where this is how the user would indicate the unique role OpenAPI document they would like to see. This would take advantage of the code we just wrote. Make this case-insensitive and if the role is not found, it is a simple 404. There is no auth for the OpenAPI. The goal is not security but clarity so roles have the option to understand what they are allowed to do.

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 1, 2025

@copilot Keep refactoring and simplifying. Remember we want to have good code coverage specifically for the lines you introduced in this PR. We also want to ensure existing tests that also might touch...

Implemented in commit ae8166c:

  1. Refactored and simplified: Methods now use optional role parameter consistently
  2. Added /openapi/{role} routing:
    • /openapi returns superset of all roles
    • /openapi/{role} returns role-specific OpenAPI document
    • Case-insensitive role matching
    • URL-decoded role parameter for safety
    • Returns 404 if role not found in any entity's permissions
  3. Tests for competing roles: Added tests to verify operations and fields don't leak between roles

Copilot AI changed the title Filter OpenAPI REST methods and fields based on entity permissions with role support Filter OpenAPI REST methods and fields based on entity permissions with role-specific endpoint Dec 1, 2025
- Updated GetConfiguredRestOperations to only enable PUT/PATCH when both Create and Update permissions exist
- This matches runtime authorization behavior where PUT/PATCH map to both operations (upsert semantics)
- Added comprehensive test PutPatchOperations_RequireBothCreateAndUpdatePermissions to validate:
  * Create-only permission: No PUT/PATCH
  * Update-only permission: No PUT/PATCH
  * Create+Update permissions: PUT/PATCH enabled
- Fixes bug where OpenAPI incorrectly showed PUT/PATCH with only Update permission

Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com>
Copilot AI changed the title Filter OpenAPI REST methods and fields based on role Filter OpenAPI REST methods and fields based on role permissions Feb 7, 2026
Copilot AI requested a review from Aniruddh25 February 7, 2026 05:03
- Extended CompetingRoles_DoNotLeakFields test to verify role-specific field filtering
- Added GenerateOpenApiDocumentAsync overload with role parameter support
- Tests validate:
  * Reader role sees only 'id' field (not 'title')
  * Writer role sees only 'title' field (not 'id')
  * Superset (no role) sees both fields
- Ensures field permissions don't leak between roles

Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com>
@Aniruddh25
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@Aniruddh25 Aniruddh25 enabled auto-merge (squash) February 11, 2026 03:18
@Aniruddh25
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@Aniruddh25
Copy link
Collaborator

@copilot, fix formatting errors:

/home/vsts/work/1/s/src/Service.Tests/OpenApiDocumentor/RoleIsolationTests.cs(35,77): error WHITESPACE: Fix whitespace formatting. Replace 22 characters with '\n\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s'. [/home/vsts/work/1/s/src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj]
/home/vsts/work/1/s/src/Service.Tests/OpenApiDocumentor/RoleIsolationTests.cs(70,1): error WHITESPACE: Fix whitespace formatting. Replace 25 characters with '\n\s\s\s\s\s\s\s\s\s\s\s\s'. [/home/vsts/work/1/s/src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj]
/home/vsts/work/1/s/src/Service.Tests/OpenApiDocumentor/RoleIsolationTests.cs(71,79): error WHITESPACE: Fix whitespace formatting. Replace 13 characters with '\n\s\s\s\s\s\s\s\s\s\s\s\s'. [/home/vsts/work/1/s/src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj]
/home/vsts/work/1/s/src/Service.Tests/OpenApiDocumentor/RoleIsolationTests.cs(72,84): error WHITESPACE: Fix whitespace formatting. Replace 13 characters with '\n\s\s\s\s\s\s\s\s\s\s\s\s'. [/home/vsts/work/1/s/src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj]
/home/vsts/work/1/s/src/Service.Tests/OpenApiDocumentor/RoleIsolationTests.cs(73,91): error WHITESPACE: Fix whitespace formatting. Replace 13 characters with '\n\s\s\s\s\s\s\s\s\s\s\s\s'. [/home/vsts/work/1/s/src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj]
/home/vsts/work/1/s/src/Service.Tests/OpenApiDocumentor/RoleIsolationTests.cs(108,70): error WHITESPACE: Fix whitespace formatting. Replace 22 characters with '\n\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s\s'. [/home/vsts/work/1/s/src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj]

- Break long lines to comply with whitespace formatting rules
- Split EntityPermission constructor calls across multiple lines
- Split Assert statements across multiple lines for better readability
- Addresses CI build formatting errors

Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 11, 2026

@copilot, fix formatting errors:

/home/vsts/work/1/s/src/Service.Tests/OpenApiDocumentor/RoleIsolationTests.cs(35,77): error WHITESPACE: Fix whitespace formatting. Replace 22 characters with '\n\s...

Fixed in commit fbab53f. Split long lines in RoleIsolationTests.cs to comply with whitespace formatting rules by breaking EntityPermission constructor calls and Assert statements across multiple lines.

@Aniruddh25
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

[Enh]: OpenAPI REST methods should show only available methods

6 participants