Repository: graphql-dotnet/authorization Branch: master Commit: 21a629e1eb42 Files: 57 Total size: 113.0 KB Directory structure: gitextract_vfdy268n/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── codecov.yaml │ ├── dependabot.yml │ ├── labeler.yml │ └── workflows/ │ ├── build.yml │ ├── codeql-analysis.yml │ ├── format.yml │ ├── label.yml │ ├── publish.yml │ ├── test.yml │ └── wipcheck.yml ├── .gitignore ├── .vscode/ │ ├── launch.json │ └── tasks.json ├── LICENSE.md ├── README.md ├── codeql/ │ └── GraphQL.Authorization.CodeQL.sln └── src/ ├── BasicSample/ │ ├── BasicSample.csproj │ └── Program.cs ├── Directory.Build.props ├── Directory.Build.targets ├── GraphQL.Authorization/ │ ├── AuthorizationContext.cs │ ├── AuthorizationEvaluator.cs │ ├── AuthorizationPolicy.cs │ ├── AuthorizationPolicyBuilder.cs │ ├── AuthorizationResult.cs │ ├── AuthorizationSettings.cs │ ├── AuthorizationValidationRule.cs │ ├── Extensions/ │ │ └── AuthorizationGraphQLBuilderExtensions.cs │ ├── GraphQL.Authorization.csproj │ ├── IAuthorizationEvaluator.cs │ ├── IAuthorizationPolicy.cs │ └── Requirements/ │ ├── AuthenticatedUserRequirement.cs │ ├── ClaimAuthorizationRequirement.cs │ └── IAuthorizationRequirement.cs ├── GraphQL.Authorization.ApiTests/ │ ├── ApiApprovalTests.cs │ ├── GraphQL.Authorization.ApiTests.csproj │ └── GraphQL.Authorization.approved.txt ├── GraphQL.Authorization.Tests/ │ ├── AuthenticatedUserRequirementTests.cs │ ├── AuthorizationEvaluatorTests.cs │ ├── AuthorizationSchemaBuilderTests.cs │ ├── AuthorizationSettingsTests.cs │ ├── AuthorizationValidationRuleTests.cs │ ├── ClaimAuthorizationRequirementTests.cs │ ├── GraphQL.Authorization.Tests.csproj │ ├── ValidationTestBase.cs │ └── ValidationTestConfig.cs ├── GraphQL.Authorization.sln ├── Harness/ │ ├── GraphQL.cs │ ├── Harness.csproj │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── Tests.props └── xunit.runner.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: http://EditorConfig.org # Create portable, custom editor settings with EditorConfig # https://docs.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options # .NET coding convention settings for EditorConfig # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference?view=vs-2019 # Language conventions # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019 # Formatting conventions # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019 # .NET naming conventions for EditorConfig # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 # Top-most EditorConfig file root = true # Editor default newlines with a newline ending every file [*] insert_final_newline = true charset = utf-8 indent_style = space indent_size = 2 trim_trailing_whitespace = true [*.json] insert_final_newline = false [*.cs] indent_size = 4 [*.txt] insert_final_newline = false # Code files [*.{cs,vb}] # .NET code style settings - "This." and "Me." qualifiers # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#this-and-me dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_property = false:warning dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_event = false:warning # .NET code style settings - Language keywords instead of framework type names for type references # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#language-keywords dotnet_style_predefined_type_for_locals_parameters_members = true:error dotnet_style_predefined_type_for_member_access = true:error # .NET code style settings - Modifier preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#normalize-modifiers dotnet_style_require_accessibility_modifiers = always:warning csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning dotnet_style_readonly_field = true:warning # .NET code style settings - Parentheses preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#parentheses-preferences dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:suggestion dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion # .NET code style settings - Expression-level preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#expression-level-preferences dotnet_style_object_initializer = true:error dotnet_style_collection_initializer = true:error dotnet_style_explicit_tuple_names = true:warning dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion dotnet_style_prefer_conditional_expression_over_return = true:suggestion dotnet_style_prefer_compound_assignment = true:warning # .NET code style settings - Null-checking preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#null-checking-preferences dotnet_style_coalesce_expression = true:warning dotnet_style_null_propagation = true:error # .NET code quality settings - Parameter preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#parameter-preferences dotnet_code_quality_unused_parameters = all:warning # C# code style settings - Implicit and explicit types # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#implicit-and-explicit-types csharp_style_var_for_built_in_types = false:suggestion csharp_style_var_when_type_is_apparent = true:warning csharp_style_var_elsewhere = true:suggestion # C# code style settings - Expression-bodied members # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#expression-bodied-members csharp_style_expression_bodied_methods = when_on_single_line:suggestion csharp_style_expression_bodied_constructors = false:warning csharp_style_expression_bodied_operators = when_on_single_line:warning csharp_style_expression_bodied_properties = when_on_single_line:warning csharp_style_expression_bodied_indexers = when_on_single_line:warning csharp_style_expression_bodied_accessors = when_on_single_line:warning csharp_style_expression_bodied_lambdas = when_on_single_line:warning csharp_style_expression_bodied_local_functions = when_on_single_line:warning # C# code style settings - Pattern matching # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#pattern-matching csharp_style_pattern_matching_over_is_with_cast_check = true:error csharp_style_pattern_matching_over_as_with_null_check = true:error # C# code style settings - Inlined variable declaration # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#inlined-variable-declarations csharp_style_inlined_variable_declaration = true:error # C# code style settings - C# expression-level preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#c-expression-level-preferences csharp_prefer_simple_default_expression = true:suggestion # C# code style settings - C# null-checking preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#c-null-checking-preferences csharp_style_throw_expression = true:warning csharp_style_conditional_delegate_call = true:warning # C# code style settings - Code block preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#code-block-preferences csharp_prefer_braces = when_multiline:suggestion # C# code style - Unused value preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#unused-value-preferences csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion # C# code style - Index and range preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#index-and-range-preferences csharp_style_prefer_index_operator = true:suggestion csharp_style_prefer_range_operator = true:suggestion # C# code style - Miscellaneous preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#miscellaneous-preferences csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_using_directive_placement = outside_namespace:warning csharp_prefer_static_local_function = true:suggestion csharp_prefer_simple_using_statement = false:suggestion csharp_style_prefer_switch_expression = true:suggestion # .NET formatting settings - Organize using directives # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#organize-using-directives dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false # C# formatting settings - New-line options # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#new-line-options csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true # C# formatting settings - Indentation options # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#indentation-options csharp_indent_case_contents = true csharp_indent_switch_labels = true csharp_indent_labels = one_less_than_current csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents_when_block = false # C# formatting settings - Spacing options csharp_space_after_cast = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_between_parentheses = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_after_colon_in_inheritance_clause = true csharp_space_around_binary_operators = before_and_after csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_after_comma = true csharp_space_before_comma = false csharp_space_after_dot = false csharp_space_before_dot = false csharp_space_after_semicolon_in_for_statement = true csharp_space_before_semicolon_in_for_statement = false csharp_space_around_declaration_statements = false csharp_space_before_open_square_brackets = false csharp_space_between_empty_square_brackets = false csharp_space_between_square_brackets = false # C# formatting settings - Wrap options # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#wrap-options csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false # C# formatting settings - Namespace options csharp_style_namespace_declarations = file_scoped:suggestion ########## name all private fields using camelCase with underscore prefix ########## # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 # dotnet_naming_rule..symbols = dotnet_naming_rule.private_fields_with_underscore.symbols = private_fields # dotnet_naming_symbols.. = dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private # dotnet_naming_rule..style = dotnet_naming_rule.private_fields_with_underscore.style = prefix_underscore # dotnet_naming_style.. = dotnet_naming_style.prefix_underscore.capitalization = camel_case dotnet_naming_style.prefix_underscore.required_prefix = _ # dotnet_naming_rule..severity = dotnet_naming_rule.private_fields_with_underscore.severity = warning ########## name all constant fields using UPPER_CASE ########## # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 # dotnet_naming_rule..symbols = dotnet_naming_rule.constant_fields_should_be_upper_case.symbols = constant_fields # dotnet_naming_symbols.. = dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.applicable_accessibilities = * dotnet_naming_symbols.constant_fields.required_modifiers = const # dotnet_naming_rule..style = dotnet_naming_rule.constant_fields_should_be_upper_case.style = upper_case_style # dotnet_naming_style.. = dotnet_naming_style.upper_case_style.capitalization = all_upper dotnet_naming_style.upper_case_style.word_separator = _ # dotnet_naming_rule..severity = dotnet_naming_rule.constant_fields_should_be_upper_case.severity = warning ########## Async methods should have "Async" suffix ########## # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 # dotnet_naming_rule..symbols = dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods # dotnet_naming_symbols.. = dotnet_naming_symbols.any_async_methods.applicable_kinds = method dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * dotnet_naming_symbols.any_async_methods.required_modifiers = async # dotnet_naming_rule..style = dotnet_naming_rule.async_methods_end_in_async.style = end_in_async_style # dotnet_naming_style.. = dotnet_naming_style.end_in_async_style.capitalization = pascal_case dotnet_naming_style.end_in_async_style.word_separator = dotnet_naming_style.end_in_async_style.required_prefix = dotnet_naming_style.end_in_async_style.required_suffix = Async # dotnet_naming_rule..severity = dotnet_naming_rule.async_methods_end_in_async.severity = warning # Remove unnecessary import https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0005 dotnet_diagnostic.IDE0005.severity = error # Enforce formatting https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#rule-id-ide0055-fix-formatting dotnet_diagnostic.IDE0055.severity = error # https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS0060.md dotnet_diagnostic.RCS0060.severity = warning roslynator_blank_line_after_file_scoped_namespace_declaration = true # https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1080.md dotnet_diagnostic.RCS1080.severity = warning # ConfigureAwait https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1090.md dotnet_diagnostic.RCS1090.severity = warning roslynator_configure_await = true # https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1102.md # TODO: NullabilityInfo issue in Patching.cs in internal class SR dotnet_diagnostic.RCS1102.severity = suggestion # https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1194.md dotnet_diagnostic.RCS1194.severity = suggestion ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms open_collective: graphql-net ================================================ FILE: .github/codecov.yaml ================================================ # https://docs.codecov.com/docs/codecov-yaml comment: behavior: new ================================================ FILE: .github/dependabot.yml ================================================ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "nuget" directory: "/" schedule: interval: "daily" ignore: - dependency-name: "GraphQL" - dependency-name: "GraphQL.MicrosoftDI" - dependency-name: "GraphQL.SystemTextJson" - dependency-name: "GraphQL.Server.Transports.AspNetCore" - dependency-name: "GraphQL.Server.Ui.GraphiQL" - dependency-name: "GraphQL.Server.Ui.Playground" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/labeler.yml ================================================ test: - src/GraphQL.Authorization.Tests/**/* - src/GraphQL.Authorization.ApiTests/**/* CI: - .github/workflows/**/* - .github/dependabot.yml - .github/labeler.yml - .github/codecov.yml code style: - .editorconfig documentation: - README.md ================================================ FILE: .github/workflows/build.yml ================================================ name: Build artifacts # ==== NOTE: do not rename this yml file or the run_number will be reset ==== on: push: branches: - master - develop paths: - src/** - .github/workflows/** env: DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: pack: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Install dependencies working-directory: src run: dotnet restore - name: Build solution [Release] working-directory: src run: dotnet build --no-restore -c Release -p:VersionSuffix=$GITHUB_RUN_NUMBER - name: Pack solution [Release] working-directory: src run: dotnet pack --no-restore --no-build -c Release -p:VersionSuffix=$GITHUB_RUN_NUMBER -o out - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: Nuget packages path: | src/out/* - name: Publish Nuget packages to GitHub registry working-directory: src run: dotnet nuget push "out/*" -k ${{secrets.GITHUB_TOKEN}} ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # https://github.com/github/codeql # https://github.com/github/codeql-action name: CodeQL analysis on: push: branches: [master, develop] pull_request: branches: [master, develop] jobs: analyze: runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: queries: security-and-quality languages: csharp - name: Install dependencies working-directory: src run: dotnet restore - name: Build CodeQL solution # https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/troubleshooting-the-codeql-workflow#reduce-the-amount-of-code-being-analyzed-in-a-single-workflow working-directory: codeql run: dotnet build --no-restore - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 ================================================ FILE: .github/workflows/format.yml ================================================ name: Check formatting on: pull_request: paths: - src/** - .github/workflows/** env: DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: format: runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Install dependencies working-directory: src run: dotnet restore - name: Check formatting working-directory: src run: | dotnet format --no-restore --verify-no-changes --severity warn || (echo "Run 'dotnet format' to fix issues" && exit 1) ================================================ FILE: .github/workflows/label.yml ================================================ # This workflow will triage pull requests and apply a label based on the # paths that are modified in the pull request. # # To use this workflow, you will need to set up a .github/labeler.yml # file with configuration. For more information, see: # https://github.com/actions/labeler/blob/master/README.md name: Labeler on: pull_request_target: types: - opened # when PR is opened jobs: label: runs-on: ubuntu-latest steps: - uses: actions/labeler@v4 with: sync-labels: "" repo-token: "${{ secrets.GITHUB_TOKEN }}" continue-on-error: true ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish release on: release: types: - published env: DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check github.ref starts with 'refs/tags/' if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: | echo Error! github.ref does not start with 'refs/tags' echo github.ref: ${{ github.ref }} exit 1 - name: Set version number environment variable env: github_ref: ${{ github.ref }} run: | version="${github_ref:10}" echo version=$version echo "version=$version" >> $GITHUB_ENV - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: ${{secrets.NUGET_AUTH_TOKEN}} - name: Install dependencies working-directory: src run: dotnet restore - name: Build solution [Release] working-directory: src run: dotnet build --no-restore -c Release -p:Version=$version - name: Pack solution [Release] working-directory: src run: dotnet pack --no-restore --no-build -c Release -p:Version=$version -o out - name: Upload Nuget packages as workflow artifacts uses: actions/upload-artifact@v3 with: name: Nuget packages path: | src/out/* - name: Publish Nuget packages to Nuget registry working-directory: src run: dotnet nuget push "out/*" -k ${{secrets.NUGET_AUTH_TOKEN}} - name: Upload Nuget packages as release artifacts uses: actions/github-script@v6 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | console.log('environment', process.versions); const fs = require('fs').promises; const { repo: { owner, repo }, sha } = context; for (let file of await fs.readdir('src/out')) { console.log('uploading', file); await github.rest.repos.uploadReleaseAsset({ owner, repo, release_id: ${{ github.event.release.id }}, name: file, data: await fs.readFile(`src/out/${file}`) }); } ================================================ FILE: .github/workflows/test.yml ================================================ name: Run code tests on: pull_request: paths: - src/** - .github/workflows/** # Upload code coverage results when PRs are merged push: branches: - master - develop paths: - src/** - .github/workflows/** env: DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: test: strategy: matrix: os: [ubuntu-latest, windows-latest] graphqlversion: - 8.0.0 name: ${{ matrix.os }}/${{ matrix.graphqlversion }} runs-on: ${{ matrix.os }} steps: - name: Checkout source uses: actions/checkout@v4 - name: Setup .NET SDKs uses: actions/setup-dotnet@v4 with: dotnet-version: | 3.1.x 5.0.x 6.0.x 7.0.x 8.0.x source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Install dependencies with GraphQL version ${{ matrix.graphqlversion }} working-directory: src run: dotnet restore -p:GraphQLTestVersion=${{ matrix.graphqlversion }} - name: Build solution [Release] if: ${{ startsWith(matrix.os, 'ubuntu') }} working-directory: src run: dotnet build --no-restore -c Release -p:GraphQLTestVersion=${{ matrix.graphqlversion }} - name: Build solution [Debug] working-directory: src run: dotnet build --no-restore -c Debug -p:GraphQLTestVersion=${{ matrix.graphqlversion }} - name: Test solution [Debug] with code coverage if: ${{ startsWith(matrix.os, 'ubuntu') }} working-directory: src run: > dotnet test --no-restore --no-build --collect "XPlat Code Coverage" --results-directory .coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude=[BasicSample]*,[Harness]* - name: Test solution [Debug] without code coverage if: ${{ startsWith(matrix.os, 'windows') }} working-directory: src run: dotnet test --no-restore --no-build - name: Upload coverage to codecov if: ${{ startsWith(matrix.os, 'ubuntu') }} uses: codecov/codecov-action@v3 with: files: 'src/.coverage/**/coverage.opencover.xml' buildcheck: needs: - test runs-on: ubuntu-latest if: always() steps: - name: Pass build check if: ${{ needs.test.result == 'success' }} run: exit 0 - name: Fail build check if: ${{ needs.test.result != 'success' }} run: exit 1 ================================================ FILE: .github/workflows/wipcheck.yml ================================================ name: Check if PR title contains [WIP] on: pull_request: types: - opened # when PR is opened - edited # when PR is edited - synchronize # when code is added - reopened # when a closed PR is reopened jobs: check-title: runs-on: ubuntu-latest steps: - name: Fail build if pull request title contains [WIP] env: TITLE: ${{ github.event.pull_request.title }} if: ${{ contains(github.event.pull_request.title, '[WIP]') }} # This function is case insensitive. run: | echo Warning! PR title "$TITLE" contains [WIP]. Remove [WIP] from the title when PR is ready. exit 1 ================================================ FILE: .gitignore ================================================ *.DS_Store .vs/ .idea/ *.user *.suo *.nupkg [Oo]bj/ [Bb]in/ *.received.txt ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to find out which attributes exist for C# debugging // Use hover for the description of the existing attributes // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md "version": "0.2.0", "configurations": [ { "name": ".NET Core Launch (web)", "type": "coreclr", "request": "launch", "preLaunchTask": "build-harness", "program": "${workspaceFolder}/src/Harness/bin/Debug/netcoreapp3.1/Harness.dll", "args": [], "cwd": "${workspaceFolder}/src/Harness", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart", "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open", "args": "http://localhost:3000/ui/graphiql" }, "linux": { "command": "xdg-open" } }, "env": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "http://localhost:3000" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } }, { "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build-tests", "program": "${workspaceFolder}/src/GraphQL.Authorization.Tests/bin/Debug/netcoreapp3.1/GraphQL.Authorization.Tests.dll", "args": [], "cwd": "${workspaceFolder}/src/GraphQL.Authorization.Tests", "console": "internalConsole", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart" }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}" } ] } ================================================ FILE: .vscode/tasks.json ================================================ { "version": "2.0.0", "tasks": [ { "label": "build-harness", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/src/Harness/Harness.csproj" ], "problemMatcher": "$msCompile" }, { "label": "build-tests", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/src/GraphQL.Authorization.Tests/GraphQL.Authorization.Tests.csproj" ], "problemMatcher": "$msCompile" } ] } ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2017 Joseph T. McBride Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # GraphQL Authorization [![License](https://img.shields.io/github/license/graphql-dotnet/authorization)](LICENSE.md) [![codecov](https://codecov.io/gh/graphql-dotnet/authorization/branch/master/graph/badge.svg?token=TODO)](https://codecov.io/gh/graphql-dotnet/authorization) [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Authorization)](https://www.nuget.org/packages/GraphQL.Authorization) [![Nuget](https://img.shields.io/nuget/v/GraphQL.Authorization)](https://www.nuget.org/packages/GraphQL.Authorization) [![GitHub Release Date](https://img.shields.io/github/release-date/graphql-dotnet/authorization?label=released)](https://github.com/graphql-dotnet/authorization/releases) [![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/graphql-dotnet/authorization/latest?label=new+commits)](https://github.com/graphql-dotnet/authorization/commits/master) ![Size](https://img.shields.io/github/repo-size/graphql-dotnet/authorization) [![GitHub contributors](https://img.shields.io/github/contributors/graphql-dotnet/authorization)](https://github.com/graphql-dotnet/authorization/graphs/contributors) ![Activity](https://img.shields.io/github/commit-activity/w/graphql-dotnet/authorization) ![Activity](https://img.shields.io/github/commit-activity/m/graphql-dotnet/authorization) ![Activity](https://img.shields.io/github/commit-activity/y/graphql-dotnet/authorization) A toolset for authorizing access to graph types for [GraphQL.NET](https://github.com/graphql-dotnet/graphql-dotnet). Provides the following packages: | Package | Downloads | NuGet Latest | | --------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | GraphQL.Authorization | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Authorization)](https://www.nuget.org/packages/GraphQL.Authorization) | [![Nuget](https://img.shields.io/nuget/v/GraphQL.Authorization)](https://www.nuget.org/packages/GraphQL.Authorization) | You can get all preview versions from [GitHub Packages](https://github.com/orgs/graphql-dotnet/packages?repo_name=authorization). Note that GitHub requires authentication to consume the feed. See [here](https://docs.github.com/en/free-pro-team@latest/packages/publishing-and-managing-packages/about-github-packages#authenticating-to-github-packages). # Usage - Register the authorization classes in your DI container - call `AddAuthorization` on the provided `IGraphQLBuilder` inside `AddGraphQL` extension method. - Provide the `ClaimsPrincipal` through `ExecutionOptions.User`. - Add policies to the `AuthorizationSettings`. - Apply a policy to a GraphType or Field - both implement `IProvideMetadata`: - using `AuthorizeWithPolicy(string policy)` extension method - or with `AuthorizeAttribute` attribute if using Schema + Handler syntax. - The `AuthorizationValidationRule` will run and verify the policies based on the registered policies. - You can write your own `IAuthorizationRequirement`. # Limitations `@skip` and `@include` directives are ignored; all selected fields of the selected operation will be checked for authentication requirements, including referenced fragments. (Other operations in the same document will correctly be skipped.) This authorization framework only supports policy-based authorization. It does not support role-based authorization, or the `[AllowAnonymous]` attribute/extension, or the `[Authorize]` attribute/extension indicating authorization is required but without specifying a policy. It also does not integrate with ASP.NET Core's authorization framework. The [GraphQL.Server](https://www.github.com/graphql-dotnet/server) repository contains an authorization rule which has the above missing features, intended for use with ASP.NET Core. It may also be tailored with custom authentication code if desired, rather than relying on ASP.NET Core's authentication framework. # Examples 1. Fully functional basic [Console sample](src/BasicSample/Program.cs). 2. Fully functional [ASP.NET Core sample](src/Harness/Program.cs). 3. GraphType first syntax - use `AuthorizeWithPolicy` extension method on `IGraphType` or `IFieldType`. ```csharp public class MyType : ObjectGraphType { public MyType() { this.AuthorizeWithPolicy("AdminPolicy"); Field("name").AuthorizeWithPolicy("SomePolicy"); } } ``` 4. Schema first syntax - use `AuthorizeAttribute` attribute on type, method or property. ```csharp [Authorize("MyPolicy")] public class MutationType { [Authorize("AnotherPolicy")] public async Task CreateSomething(MyInput input) { return await SomeMethodAsync(input); } [Authorize("SuperPolicy")] public string SomeProperty => Guid.NewGuid().ToString(); } ``` # Known Issues - It is currently not possible to add a policy to Input objects using Schema first approach. ================================================ FILE: codeql/GraphQL.Authorization.CodeQL.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31919.166 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Authorization", "..\src\GraphQL.Authorization\GraphQL.Authorization.csproj", "{C9953353-2407-4A2D-8E2A-A5F62DEED89C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C9953353-2407-4A2D-8E2A-A5F62DEED89C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C9953353-2407-4A2D-8E2A-A5F62DEED89C}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9953353-2407-4A2D-8E2A-A5F62DEED89C}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9953353-2407-4A2D-8E2A-A5F62DEED89C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A0696AC3-8123-4422-B489-C58B78E24B27} EndGlobalSection EndGlobal ================================================ FILE: src/BasicSample/BasicSample.csproj ================================================ Exe net8.0 false ================================================ FILE: src/BasicSample/Program.cs ================================================ using System.Security.Claims; using GraphQL; using GraphQL.SystemTextJson; using GraphQL.Types; using GraphQL.Validation; using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection().AddGraphQL(builder => builder .AddAuthorization(settings => settings.AddPolicy("AdminPolicy", p => p.RequireClaim("role", "Admin")))); using var serviceProvider = services.BuildServiceProvider(); const string definitions = """ type User { id: ID name: String } type Query { viewer: User users: [User] } """; var schema = Schema.For(definitions, builder => builder.Types.Include()); // Claims principal must look something like this to allow access. // GraphQLUserContext.User alternates below for demonstration purposes. int counter = 0; var authorizedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("role", "Admin") })); var nonAuthorizedUser = new ClaimsPrincipal(new ClaimsIdentity()); while (true) { string json = await schema.ExecuteAsync(options => { options.Query = "{ viewer { id name } }"; options.Root = new Query(); options.ValidationRules = DocumentValidator.CoreRules.Concat(serviceProvider.GetServices()); options.RequestServices = serviceProvider; options.User = counter++ % 2 == 0 ? authorizedUser : nonAuthorizedUser; }).ConfigureAwait(false); Console.WriteLine(json); Console.WriteLine(); Console.WriteLine("Press ENTER to continue"); Console.ReadLine(); } ================================================ FILE: src/Directory.Build.props ================================================ 8.0.0-preview 9.0.0 latest Joe McBride MIT logo.64x64.png true git true true True embedded enable true true true true false enable all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: src/Directory.Build.targets ================================================ README.md See https://github.com/graphql-dotnet/authorization/releases $(NoWarn);1591 $(WarningsNotAsErrors);IDE0053 <_ProjectReferenceWithExplicitPackageVersion Include="@(ProjectReference->'%(FullPath)')" Condition="'%(ProjectReference.PackageVersion)' != ''" /> <_ProjectReferenceWithExactPackageVersion Include="@(ProjectReference->'%(FullPath)')" Condition="'%(ProjectReference.ExactVersion)' == 'true'" /> <_ProjectReferenceWithReassignedVersion Include="@(_ProjectReferencesWithVersions)" Condition="'%(Identity)' != '' And '@(_ProjectReferenceWithExplicitPackageVersion)' == '@(_ProjectReferencesWithVersions)'"> @(_ProjectReferenceWithExplicitPackageVersion->'%(PackageVersion)') <_ProjectReferenceWithReassignedVersion Include="@(_ProjectReferencesWithVersions)" Condition="'%(Identity)' != '' And '@(_ProjectReferenceWithExactPackageVersion)' == '@(_ProjectReferencesWithVersions)'"> [@(_ProjectReferencesWithVersions->'%(ProjectVersion)')] <_ProjectReferencesWithVersions Remove="@(_ProjectReferenceWithReassignedVersion)" /> <_ProjectReferencesWithVersions Include="@(_ProjectReferenceWithReassignedVersion)" /> ================================================ FILE: src/GraphQL.Authorization/AuthorizationContext.cs ================================================ using System.Security.Claims; namespace GraphQL.Authorization; /// /// Provides context information for . /// public class AuthorizationContext { private List? _errors; /// /// Current user. /// public ClaimsPrincipal? User { get; set; } /// /// Arbitrary user defined context represented as dictionary. /// public IDictionary? UserContext { get; set; } /// /// Represents a readonly dictionary of variables to an executed document. /// public Inputs? Variables { get; set; } /// /// Returns a set of authorization errors. /// public IEnumerable Errors => _errors ?? Enumerable.Empty(); /// /// Returns whether there are any errors. /// public bool HasErrors => _errors?.Count > 0; /// /// Reports an error during evaluation of policy requirement. /// /// Error message. public void ReportError(string error) => (_errors ??= new()).Add(error); } ================================================ FILE: src/GraphQL.Authorization/AuthorizationEvaluator.cs ================================================ using System.Security.Claims; namespace GraphQL.Authorization; /// /// Default implementation of . /// public class AuthorizationEvaluator : IAuthorizationEvaluator { private readonly AuthorizationSettings _settings; /// /// Creates an instance of with the /// specified authorization settings. /// public AuthorizationEvaluator(AuthorizationSettings settings) { _settings = settings; } /// public async Task Evaluate( ClaimsPrincipal? principal, IDictionary? userContext, Inputs? variables, IEnumerable? requiredPolicies) { if (requiredPolicies == null) return AuthorizationResult.Success(); var context = new AuthorizationContext { User = principal ?? new ClaimsPrincipal(new ClaimsIdentity()), UserContext = userContext, Variables = variables }; var tasks = new List(); foreach (string requiredPolicy in requiredPolicies) { var authorizationPolicy = _settings.GetPolicy(requiredPolicy); if (authorizationPolicy == null) { context.ReportError($"Required policy '{requiredPolicy}' is not present."); } else { foreach (var r in authorizationPolicy.Requirements) { var task = r.Authorize(context); tasks.Add(task); } } } await Task.WhenAll(tasks).ConfigureAwait(false); return context.HasErrors ? AuthorizationResult.Fail(context.Errors) : AuthorizationResult.Success(); } } ================================================ FILE: src/GraphQL.Authorization/AuthorizationPolicy.cs ================================================ namespace GraphQL.Authorization; /// /// Default implementation for . /// public class AuthorizationPolicy : IAuthorizationPolicy { private readonly List _requirements = new(); /// /// Creates a policy with a set of specified requirements. /// /// Specified requirements. public AuthorizationPolicy(IEnumerable requirements) { if (requirements != null) { _requirements.AddRange(requirements); _requirements.ForEach(req => { if (req == null) throw new ArgumentNullException(nameof(requirements), $"One of the ({_requirements.Count}) requirements is null"); }); } } /// public IEnumerable Requirements => _requirements; } ================================================ FILE: src/GraphQL.Authorization/AuthorizationPolicyBuilder.cs ================================================ namespace GraphQL.Authorization; /// /// Configures and then builds authorization policy from various authorization requirements. /// Provides fluent API. /// public class AuthorizationPolicyBuilder { private readonly List _requirements = new(); /// /// Build authorization policy. /// /// Created policy. public AuthorizationPolicy Build() => new(_requirements); /// /// Adds with the specified claim type. /// /// Type of the claim. /// Reference to the same builder. public AuthorizationPolicyBuilder RequireClaim(string claimType) { _requirements.Add(new ClaimAuthorizationRequirement(claimType)); return this; } /// /// Adds with the specified claim type and allowed values. /// /// Type of the claim. /// Allowed values for this claim. /// Reference to the same builder. public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues) { _requirements.Add(new ClaimAuthorizationRequirement(claimType, allowedValues)); return this; } /// /// Adds with the specified claim type, allowed values and display values. /// /// Type of the claim. /// Allowed values for this claim. /// /// Display values for this claim. If no allowed claims are found, display values will be used to generate /// an error message for . /// /// Reference to the same builder. public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable? allowedValues, IEnumerable? displayValues) { _requirements.Add(new ClaimAuthorizationRequirement(claimType, allowedValues, displayValues)); return this; } /// /// Adds . /// /// Reference to the same builder. public AuthorizationPolicyBuilder RequireAuthenticatedUser() { _requirements.Add(AuthenticatedUserRequirement.Instance); return this; } /// /// Adds specified authorization requirement. /// /// Authorization requirement. /// Reference to the same builder. public AuthorizationPolicyBuilder AddRequirement(IAuthorizationRequirement requirement) { _requirements.Add(requirement ?? throw new ArgumentNullException(nameof(requirement))); return this; } } ================================================ FILE: src/GraphQL.Authorization/AuthorizationResult.cs ================================================ namespace GraphQL.Authorization; /// /// Represents the result of an authorization evaluation. /// public class AuthorizationResult { // allocation optimization for green path private static readonly AuthorizationResult _success = new() { Succeeded = true }; /// /// Is the authorization result successful? /// public bool Succeeded { get; private set; } /// /// Returns a set of authorization errors if the authorization result is unsuccessful. /// public IEnumerable? Errors { get; private set; } /// /// Creates successful authorization result. /// /// Instance of . public static AuthorizationResult Success() => _success; /// /// Creates unsuccessful authorization result /// /// A set of authorization errors. /// Instance of . public static AuthorizationResult Fail(IEnumerable errors) => new() { Errors = errors }; } ================================================ FILE: src/GraphQL.Authorization/AuthorizationSettings.cs ================================================ namespace GraphQL.Authorization; /// /// Authorization settings are represented by a set of named policies /// where each policy has a set of authorization requirements. /// public class AuthorizationSettings { private readonly IDictionary _policies = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Returns all policies. /// public IEnumerable Policies => _policies.Values; /// /// Returns policies with the specified names. /// /// A set of policies names. /// Policies with matched names. public IEnumerable GetPolicies(IEnumerable policies) { List? found = null; if (policies != null) { foreach (string name in policies) { var policy = GetPolicy(name); if (policy != null) (found ??= new()).Add(policy); } } return found ?? Enumerable.Empty(); } /// /// Returns one policy with the specified name. /// /// Name of the required policy. /// Required policy if exists, otherwise . public IAuthorizationPolicy? GetPolicy(string name) => _policies.TryGetValue(name, out var policy) ? policy : null; /// /// Adds a policy with the specified name. If a policy with that name already exists then it will be replaced. /// /// Policy name. /// Policy to add. public void AddPolicy(string name, IAuthorizationPolicy policy) => _policies[name] = policy; /// /// Adds a policy built from with the specified name. /// /// Policy name. /// Delegate to configure provided policy builder. public void AddPolicy(string name, Action configure) { if (configure == null) throw new ArgumentNullException(nameof(configure)); var builder = new AuthorizationPolicyBuilder(); configure(builder); _policies[name] = builder.Build(); } } ================================================ FILE: src/GraphQL.Authorization/AuthorizationValidationRule.cs ================================================ using GraphQL.Types; using GraphQL.Validation; using GraphQLParser.AST; namespace GraphQL.Authorization; /// /// GraphQL authorization validation rule which evaluates configured /// (via policies) requirements on schema elements: types, fields, etc. /// public class AuthorizationValidationRule : ValidationRuleBase { private readonly IAuthorizationEvaluator _evaluator; /// /// Creates an instance of with /// the specified authorization evaluator. /// public AuthorizationValidationRule(IAuthorizationEvaluator evaluator) { _evaluator = evaluator; } /// public override async ValueTask GetPreNodeVisitorAsync(ValidationContext context) { var visitor = new Visitor(_evaluator); await visitor.AuthorizeAsync(null, context.Schema, context).ConfigureAwait(false); // this could leak info about hidden fields or types in error messages // it would be better to implement a filter on the Schema so it // acts as if they just don't exist vs. an auth denied error // - filtering the Schema is not currently supported // TODO: apply ISchemaFilter - context.Schema.Filter.AllowXXX return visitor; } private class Visitor : INodeVisitor { private readonly IAuthorizationEvaluator _evaluator; private bool _validate; public Visitor(IAuthorizationEvaluator evaluator) { _evaluator = evaluator; } public async ValueTask EnterAsync(ASTNode node, ValidationContext context) { if ((node is GraphQLOperationDefinition astType && astType == context.Operation) || (node is GraphQLFragmentDefinition fragment && (context.GetRecursivelyReferencedFragments(context.Operation)?.Contains(fragment) ?? false))) { var type = context.TypeInfo.GetLastType(); await AuthorizeAsync(node, type, context).ConfigureAwait(false); _validate = true; } if (!_validate) return; if (node is GraphQLObjectField objectFieldAst && context.TypeInfo.GetArgument()?.ResolvedType?.GetNamedType() is IComplexGraphType argumentType) { var fieldType = argumentType.GetField(objectFieldAst.Name); await AuthorizeAsync(objectFieldAst, fieldType, context).ConfigureAwait(false); } if (node is GraphQLField fieldAst) { var fieldDef = context.TypeInfo.GetFieldDef(); if (fieldDef == null) return; // check target field await AuthorizeAsync(fieldAst, fieldDef, context).ConfigureAwait(false); // check returned graph type await AuthorizeAsync(fieldAst, fieldDef.ResolvedType?.GetNamedType(), context).ConfigureAwait(false); } if (node is GraphQLVariable variableRef) { if (context.TypeInfo.GetArgument()?.ResolvedType?.GetNamedType() is not IComplexGraphType variableType) return; await AuthorizeAsync(variableRef, variableType, context).ConfigureAwait(false); // Check each supplied field in the variable that exists in the variable type. // If some supplied field does not exist in the variable type then some other // validation rule should check that but here we should just ignore that // "unknown" field. if (context.Variables != null && context.Variables.TryGetValue(variableRef.Name.StringValue, out object? input) && //ISSUE:allocation input is Dictionary fieldsValues) { foreach (var field in variableType.Fields) { if (fieldsValues.ContainsKey(field.Name)) { await AuthorizeAsync(variableRef, field, context).ConfigureAwait(false); } } } } } public ValueTask LeaveAsync(ASTNode node, ValidationContext context) { if (node is GraphQLOperationDefinition || node is GraphQLFragmentDefinition) _validate = false; return default; } public async ValueTask AuthorizeAsync(ASTNode? node, IMetadataReader? provider, ValidationContext context) { if (provider == null || !provider.IsAuthorizationRequired()) return; var result = await _evaluator.Evaluate(context.User, context.UserContext, context.Variables, provider.GetPolicies()).ConfigureAwait(false); if (result.Succeeded) return; string errors = string.Join("\n", result.Errors); context.ReportError(new ValidationError( context.Document.Source, "authorization", $"You are not authorized to run this {context.Operation.Operation.ToString().ToLower()}.\n{errors}", node == null ? Array.Empty() : new ASTNode[] { node })); } } } ================================================ FILE: src/GraphQL.Authorization/Extensions/AuthorizationGraphQLBuilderExtensions.cs ================================================ using GraphQL.Authorization; using GraphQL.DI; namespace GraphQL; /// /// Authorization extension methods for . /// public static class AuthorizationGraphQLBuilderExtensions { /// /// Registers and within the /// dependency injection framework and configures the validation rule to be added to the list of validation rules /// within and /// upon document execution. Configures authorization settings with the specified configuration delegate. /// public static IGraphQLBuilder AddAuthorization(this IGraphQLBuilder builder, Action configure) { builder.Services.TryRegister(ServiceLifetime.Singleton); builder.AddValidationRule(true); builder.Services.Configure(configure); return builder; } /// public static IGraphQLBuilder AddAuthorization(this IGraphQLBuilder builder, Action configure) { builder.Services.TryRegister(ServiceLifetime.Singleton); builder.AddValidationRule(true); builder.Services.Configure(configure); return builder; } } ================================================ FILE: src/GraphQL.Authorization/GraphQL.Authorization.csproj ================================================ netstandard2.0 A toolset for authorizing access to graph types for GraphQL.NET GraphQL;json;api ================================================ FILE: src/GraphQL.Authorization/IAuthorizationEvaluator.cs ================================================ using System.Security.Claims; namespace GraphQL.Authorization; /// /// Interface to evaluate the authorization result. /// public interface IAuthorizationEvaluator { /// /// Evaluates authorization result. /// /// Represents the current user. /// Arbitrary user defined context represented as dictionary. /// Represents a readonly dictionary of variables to an executed document. /// A set of policies names to authorize. /// Task Evaluate( ClaimsPrincipal? principal, IDictionary? userContext, Inputs? variables, IEnumerable? requiredPolicies); } ================================================ FILE: src/GraphQL.Authorization/IAuthorizationPolicy.cs ================================================ namespace GraphQL.Authorization; /// /// Policy is a named set of . /// public interface IAuthorizationPolicy { /// /// Returns all requirements of this policy. /// IEnumerable Requirements { get; } } ================================================ FILE: src/GraphQL.Authorization/Requirements/AuthenticatedUserRequirement.cs ================================================ namespace GraphQL.Authorization; /// /// Implements an which requires that /// current user from must be authenticated. /// public class AuthenticatedUserRequirement : IAuthorizationRequirement { internal static readonly AuthenticatedUserRequirement Instance = new(); /// public Task Authorize(AuthorizationContext context) { if (context.User == null || !context.User.Identities.Any(x => x.IsAuthenticated)) { context.ReportError("An authenticated user is required."); } return Task.CompletedTask; } } ================================================ FILE: src/GraphQL.Authorization/Requirements/ClaimAuthorizationRequirement.cs ================================================ namespace GraphQL.Authorization; /// /// Implements an which requires an instance of the specified /// claim type, and, if allowed values are specified, the claim value must be any of the allowed values. /// public class ClaimAuthorizationRequirement : IAuthorizationRequirement { /// /// Creates a new instance of with /// the specified claim type. /// public ClaimAuthorizationRequirement(string claimType) : this(claimType, (IEnumerable?)null, null) { } /// /// Creates a new instance of with /// the specified claim type and optional list of claim values, which, if present, /// the claim must match. /// public ClaimAuthorizationRequirement(string claimType, IEnumerable allowedValues) : this(claimType, allowedValues, null) { } /// /// Creates a new instance of with /// the specified claim type and optional list of claim values, which, if present, /// the claim must match. /// public ClaimAuthorizationRequirement(string claimType, params string[] allowedValues) : this(claimType, allowedValues, null) { } /// /// Creates a new instance of with /// the specified claim type and optional list of claim values, which, if present, /// the claim must match. Additional argument /// specifies the set of displayed claim values that will be used to generate an /// error message if the requirement is not met. /// public ClaimAuthorizationRequirement(string claimType, IEnumerable? allowedValues, IEnumerable? displayValues) { ClaimType = claimType ?? throw new ArgumentNullException(nameof(claimType)); AllowedValues = allowedValues ?? Enumerable.Empty(); DisplayValues = displayValues; } /// /// Claim type that claims principal from should have. /// public string ClaimType { get; } /// /// List of claim values, which, if present, the claim must match. /// public IEnumerable AllowedValues { get; } /// /// Specifies the set of displayed claim values that will be used /// to generate an error message if the requirement is not met. /// If null then values from are used. /// public IEnumerable? DisplayValues { get; } /// public Task Authorize(AuthorizationContext context) { bool found = false; if (context.User != null) { if (AllowedValues == null || !AllowedValues.Any()) { found = context.User.Claims.Any( claim => string.Equals(claim.Type, ClaimType, StringComparison.OrdinalIgnoreCase)); } else { found = context.User.Claims.Any( claim => string.Equals(claim.Type, ClaimType, StringComparison.OrdinalIgnoreCase) && AllowedValues.Contains(claim.Value, StringComparer.Ordinal)); } } if (!found) { if (AllowedValues != null && AllowedValues.Any()) { string values = string.Join(", ", DisplayValues ?? AllowedValues); context.ReportError($"Required claim '{ClaimType}' with any value of '{values}' is not present."); } else { context.ReportError($"Required claim '{ClaimType}' is not present."); } } return Task.CompletedTask; } } ================================================ FILE: src/GraphQL.Authorization/Requirements/IAuthorizationRequirement.cs ================================================ namespace GraphQL.Authorization; /// /// Represents an authorization requirement. /// One of the requirements in . /// public interface IAuthorizationRequirement { /// /// Execute requirement. If the requirement is not met then this method /// should call . /// Task Authorize(AuthorizationContext context); } ================================================ FILE: src/GraphQL.Authorization.ApiTests/ApiApprovalTests.cs ================================================ using PublicApiGenerator; namespace GraphQL.Authorization.ApiTests; /// /// See more info about API approval tests here . /// public class ApiApprovalTests { [Theory] [InlineData(typeof(IAuthorizationRequirement))] public void public_api_should_not_change_unintentionally(Type type) { string publicApi = type.Assembly.GeneratePublicApi(new ApiGeneratorOptions { IncludeAssemblyAttributes = false, }); // See: https://shouldly.readthedocs.io/en/latest/assertions/shouldMatchApproved.html // Note: If the AssemblyName.approved.txt file doesn't match the latest publicApi value, // this call will try to launch a diff tool to help you out but that can fail on // your machine if a diff tool isn't configured/setup. publicApi.ShouldMatchApproved(options => options.NoDiff().WithFilenameGenerator((_, _, fileType, fileExtension) => $"{type.Assembly.GetName().Name}.{fileType}.{fileExtension}")); } } ================================================ FILE: src/GraphQL.Authorization.ApiTests/GraphQL.Authorization.ApiTests.csproj ================================================ net8.0 ================================================ FILE: src/GraphQL.Authorization.ApiTests/GraphQL.Authorization.approved.txt ================================================ namespace GraphQL.Authorization { public class AuthenticatedUserRequirement : GraphQL.Authorization.IAuthorizationRequirement { public AuthenticatedUserRequirement() { } public System.Threading.Tasks.Task Authorize(GraphQL.Authorization.AuthorizationContext context) { } } public class AuthorizationContext { public AuthorizationContext() { } public System.Collections.Generic.IEnumerable Errors { get; } public bool HasErrors { get; } public System.Security.Claims.ClaimsPrincipal? User { get; set; } public System.Collections.Generic.IDictionary? UserContext { get; set; } public GraphQL.Inputs? Variables { get; set; } public void ReportError(string error) { } } public class AuthorizationEvaluator : GraphQL.Authorization.IAuthorizationEvaluator { public AuthorizationEvaluator(GraphQL.Authorization.AuthorizationSettings settings) { } public System.Threading.Tasks.Task Evaluate(System.Security.Claims.ClaimsPrincipal? principal, System.Collections.Generic.IDictionary? userContext, GraphQL.Inputs? variables, System.Collections.Generic.IEnumerable? requiredPolicies) { } } public class AuthorizationPolicy : GraphQL.Authorization.IAuthorizationPolicy { public AuthorizationPolicy(System.Collections.Generic.IEnumerable requirements) { } public System.Collections.Generic.IEnumerable Requirements { get; } } public class AuthorizationPolicyBuilder { public AuthorizationPolicyBuilder() { } public GraphQL.Authorization.AuthorizationPolicyBuilder AddRequirement(GraphQL.Authorization.IAuthorizationRequirement requirement) { } public GraphQL.Authorization.AuthorizationPolicy Build() { } public GraphQL.Authorization.AuthorizationPolicyBuilder RequireAuthenticatedUser() { } public GraphQL.Authorization.AuthorizationPolicyBuilder RequireClaim(string claimType) { } public GraphQL.Authorization.AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues) { } public GraphQL.Authorization.AuthorizationPolicyBuilder RequireClaim(string claimType, System.Collections.Generic.IEnumerable? allowedValues, System.Collections.Generic.IEnumerable? displayValues) { } } public class AuthorizationResult { public AuthorizationResult() { } public System.Collections.Generic.IEnumerable? Errors { get; } public bool Succeeded { get; } public static GraphQL.Authorization.AuthorizationResult Fail(System.Collections.Generic.IEnumerable errors) { } public static GraphQL.Authorization.AuthorizationResult Success() { } } public class AuthorizationSettings { public AuthorizationSettings() { } public System.Collections.Generic.IEnumerable Policies { get; } public void AddPolicy(string name, GraphQL.Authorization.IAuthorizationPolicy policy) { } public void AddPolicy(string name, System.Action configure) { } public System.Collections.Generic.IEnumerable GetPolicies(System.Collections.Generic.IEnumerable policies) { } public GraphQL.Authorization.IAuthorizationPolicy? GetPolicy(string name) { } } public class AuthorizationValidationRule : GraphQL.Validation.ValidationRuleBase { public AuthorizationValidationRule(GraphQL.Authorization.IAuthorizationEvaluator evaluator) { } public override System.Threading.Tasks.ValueTask GetPreNodeVisitorAsync(GraphQL.Validation.ValidationContext context) { } } public class ClaimAuthorizationRequirement : GraphQL.Authorization.IAuthorizationRequirement { public ClaimAuthorizationRequirement(string claimType) { } public ClaimAuthorizationRequirement(string claimType, System.Collections.Generic.IEnumerable allowedValues) { } public ClaimAuthorizationRequirement(string claimType, params string[] allowedValues) { } public ClaimAuthorizationRequirement(string claimType, System.Collections.Generic.IEnumerable? allowedValues, System.Collections.Generic.IEnumerable? displayValues) { } public System.Collections.Generic.IEnumerable AllowedValues { get; } public string ClaimType { get; } public System.Collections.Generic.IEnumerable? DisplayValues { get; } public System.Threading.Tasks.Task Authorize(GraphQL.Authorization.AuthorizationContext context) { } } public interface IAuthorizationEvaluator { System.Threading.Tasks.Task Evaluate(System.Security.Claims.ClaimsPrincipal? principal, System.Collections.Generic.IDictionary? userContext, GraphQL.Inputs? variables, System.Collections.Generic.IEnumerable? requiredPolicies); } public interface IAuthorizationPolicy { System.Collections.Generic.IEnumerable Requirements { get; } } public interface IAuthorizationRequirement { System.Threading.Tasks.Task Authorize(GraphQL.Authorization.AuthorizationContext context); } } namespace GraphQL { public static class AuthorizationGraphQLBuilderExtensions { public static GraphQL.DI.IGraphQLBuilder AddAuthorization(this GraphQL.DI.IGraphQLBuilder builder, System.Action configure) { } public static GraphQL.DI.IGraphQLBuilder AddAuthorization(this GraphQL.DI.IGraphQLBuilder builder, System.Action configure) { } } } ================================================ FILE: src/GraphQL.Authorization.Tests/AuthenticatedUserRequirementTests.cs ================================================ namespace GraphQL.Authorization.Tests; public class AuthenticatedUserRequirementTests { [Fact] public async Task produces_error_when_not_authenticated() { var req = new AuthenticatedUserRequirement(); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal() }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeTrue(); context.Errors.Single().ShouldBe("An authenticated user is required."); } [Fact] public async Task no_errors_when_authenticated() { var req = new AuthenticatedUserRequirement(); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal("jwt") }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeFalse(); } } ================================================ FILE: src/GraphQL.Authorization.Tests/AuthorizationEvaluatorTests.cs ================================================ namespace GraphQL.Authorization.Tests; public class AuthorizationEvaluatorTests { private readonly AuthorizationEvaluator _evaluator; private readonly AuthorizationSettings _settings; public AuthorizationEvaluatorTests() { _settings = new AuthorizationSettings(); _evaluator = new AuthorizationEvaluator(_settings); } [Fact] public async Task fails_with_null_principal() { _settings.AddPolicy("MyPolicy", builder => builder.RequireClaim("Admin")); var result = await _evaluator.Evaluate( null, null, null, new[] { "MyPolicy" } ).ConfigureAwait(false); result.Succeeded.ShouldBeFalse(); } [Fact] public async Task fails_when_missing_claim() { _settings.AddPolicy("MyPolicy", builder => builder.RequireClaim("Admin")); var result = await _evaluator.Evaluate( ValidationTestBase.CreatePrincipal(), null, null, new[] { "MyPolicy" } ).ConfigureAwait(false); result.Succeeded.ShouldBeFalse(); } [Fact] public async Task fails_when_missing_policy() { _settings.AddPolicy("MyPolicy", builder => builder.RequireClaim("Admin")); var result = await _evaluator.Evaluate( ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }), null, null, new[] { "PolicyDoesNotExist" } ).ConfigureAwait(false); result.Succeeded.ShouldBeFalse(); } [Fact] public async Task succeeds_when_policy_applied() { _settings.AddPolicy("MyPolicy", builder => builder.RequireClaim("Admin")); var result = await _evaluator.Evaluate( ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }), null, null, new[] { "MyPolicy" } ).ConfigureAwait(false); result.Succeeded.ShouldBeTrue(); } [Fact] public async Task succeeds_with_claim_value() { _settings.AddPolicy("MyPolicy", builder => builder.RequireClaim("Admin", "true")); var result = await _evaluator.Evaluate( ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }), null, null, new[] { "MyPolicy" } ).ConfigureAwait(false); result.Succeeded.ShouldBeTrue(); } [Fact] public async Task succeeds_when_null_policies() { _settings.AddPolicy("MyPolicy", builder => builder.RequireClaim("Admin")); var result = await _evaluator.Evaluate( ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }), null, null, null ).ConfigureAwait(false); result.Succeeded.ShouldBeTrue(); } [Fact] public async Task succeeds_when_empty_policies() { _settings.AddPolicy("MyPolicy", _ => { }); var result = await _evaluator.Evaluate( ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }), null, null, Array.Empty() ).ConfigureAwait(false); result.Succeeded.ShouldBeTrue(); } [Fact] public async Task succeeds_when_null_principal() { var result = await _evaluator.Evaluate( null, null, null, null ).ConfigureAwait(false); result.Succeeded.ShouldBeTrue(); } } ================================================ FILE: src/GraphQL.Authorization.Tests/AuthorizationSchemaBuilderTests.cs ================================================ using GraphQL.Types; namespace GraphQL.Authorization.Tests; public class AuthorizationSchemaBuilderTests { [Fact] public void can_set_policy_from_authorize_attribute() { string defs = """ type Query { post(id: ID!): String } """; var schema = Schema.For(defs, builder => builder.Types.Include()); schema.Initialize(); var query = (IObjectGraphType)schema.AllTypes["Query"]!; query.IsAuthorizationRequired().ShouldBeTrue(); query.GetPolicies()!.Single().ShouldBe("ClassPolicy"); var field = query.Fields.Single(x => x.Name == "post"); field.IsAuthorizationRequired().ShouldBeTrue(); field.GetPolicies()!.Single().ShouldBe("FieldPolicy"); } [GraphQLMetadata("Query")] [Authorize("ClassPolicy")] public class QueryWithAttributes { [Authorize("FieldPolicy")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "test")] public string Post(string id) => ""; } } ================================================ FILE: src/GraphQL.Authorization.Tests/AuthorizationSettingsTests.cs ================================================ namespace GraphQL.Authorization.Tests; public class AuthorizationSettingsTests { private readonly AuthorizationSettings _settings; public AuthorizationSettingsTests() { _settings = new AuthorizationSettings(); } [Fact] public void can_add_a_claim_policy() { _settings.AddPolicy("MyPolicy", builder => builder.RequireClaim("Admin")); _settings.Policies.Count().ShouldBe(1); var policy = _settings.Policies.Single(); policy.Requirements.Single().ShouldBeOfType(); } } ================================================ FILE: src/GraphQL.Authorization.Tests/AuthorizationValidationRuleTests.cs ================================================ using GraphQL.Types; using GraphQL.Types.Relay.DataObjects; namespace GraphQL.Authorization.Tests; public class AuthorizationValidationRuleTests : ValidationTestBase { [Fact] public void class_policy_success() { Settings.AddPolicy("ClassPolicy", builder => builder.RequireClaim("admin")); Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.Query = "query { post }"; config.Schema = BasicSchema(); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void class_policy_fail() { Settings.AddPolicy("ClassPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query { post }"; config.Schema = BasicSchema(); }); } [Fact] public void field_policy_success() { Settings.AddPolicy("ClassPolicy", builder => builder.RequireClaim("admin")); Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.Query = "query { post }"; config.Schema = BasicSchema(); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void field_policy_fail() { Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query { post }"; config.Schema = BasicSchema(); }); } [Fact] public void nested_type_policy_success() { Settings.AddPolicy("PostPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.Query = "query { post }"; config.Schema = NestedSchema(); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void nested_type_policy_fail() { Settings.AddPolicy("PostPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query { post }"; config.Schema = NestedSchema(); }); } [Fact] public void nested_type_list_policy_fail() { Settings.AddPolicy("PostPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query { posts }"; config.Schema = NestedSchema(); }); } // https://github.com/graphql-dotnet/authorization/issues/5 [Theory] [InlineData("c", "query p { posts } query c { comment }")] [InlineData(null, "query c { comment } query p { posts }")] public void issue5_should_pass(string operationName, string query) { Settings.AddPolicy("PostPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.OperationName = operationName; config.Query = query; config.Schema = NestedSchema(); }); } // https://github.com/graphql-dotnet/authorization/issues/5 [Theory] [InlineData("query a { article { id } } query b { article { ...frag } } fragment frag on Article { content }")] [InlineData("query a { article { ...frag1 author } } query b { article { ...frag2 } } fragment frag1 on Article { id } fragment frag2 on Article { content }")] public void issue5_with_fragment_should_pass(string query) { Settings.AddPolicy("AdminPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.Query = query; config.Schema = TypedSchema(); }); } // https://github.com/graphql-dotnet/authorization/issues/5 [Fact] public void issue5_with_fragment_should_fail() { Settings.AddPolicy("AdminPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query a { article { ...frag } } query b { article { ...frag } } fragment frag on Article { content }"; config.Schema = TypedSchema(); config.ValidateResult = result => _ = result.Errors.Single(x => x.Message == $"You are not authorized to run this query.\nRequired claim 'admin' is not present."); }); } [Fact] public void nested_fragment_should_fail() { Settings.AddPolicy("AdminPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query a { article { ...frag } } query b { article { ...frag } } fragment frag on Article { ...frag2 } fragment frag2 on Article { content }"; config.Schema = TypedSchema(); config.ValidateResult = result => _ = result.Errors.Single(x => x.Message == $"You are not authorized to run this query.\nRequired claim 'admin' is not present."); }); } [Fact] public void nested_type_list_non_null_policy_fail() { Settings.AddPolicy("PostPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query { postsNonNull }"; config.Schema = NestedSchema(); }); } [Fact] public void passes_with_claim_on_input_type() { Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.Query = """query { author(input: { name: "Quinn" }) }"""; config.Schema = TypedSchema(); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void fails_on_missing_claim_on_input_type() { Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = """query { author(input: { name: "Quinn" }) }"""; config.Schema = TypedSchema(); }); } [Fact] public void passes_with_multiple_policies_on_field_and_single_on_input_type() { Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); Settings.AddPolicy("AdminPolicy", builder => builder.RequireClaim("admin")); Settings.AddPolicy("ConfidentialPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.Query = """query { author(input: { name: "Quinn" }) project(input: { name: "TEST" }) }"""; config.Schema = TypedSchema(); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void Issue61() { ShouldPassRule(config => { config.Query = "query { unknown(obj: {id: 7}) }"; config.Schema = TypedSchema(); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void passes_with_claim_on_variable_type() { Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldPassRule(config => { config.Query = "query Author($input: AuthorInputType!) { author(input: $input) }"; config.Schema = TypedSchema(); config.Variables = new Inputs(new Dictionary() { { "input", new Dictionary{ { "name","Quinn" } } } }); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void passes_with_claim_on_variable_type_without_inputs_but_fails_later_either() { Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.ValidateResult = result => { // no auth error on null inputs, only INVALID_VALUE result.Errors.Count.ShouldBe(1); result.Errors[0].Code.ShouldBe("INVALID_VALUE"); result.Errors[0].Message.ShouldBe("Variable '$input' is invalid. No value provided for a non-null variable."); }; config.Query = "query Author($input: AuthorInputType!) { author(input: $input) }"; config.Schema = TypedSchema(); config.Variables = null; config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void fails_on_missing_claim_on_variable_type() { Settings.AddPolicy("FieldPolicy", builder => builder.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query Author($input: AuthorInputType!) { author(input: $input) }"; config.Schema = TypedSchema(); config.Variables = new Inputs(new Dictionary() { { "input", new Dictionary{ { "name","Quinn" } } } }); }); } [Fact] public void passes_with_policy_on_connection_type() { Settings.AddPolicy("ConnectionPolicy", _ => _.RequireClaim("admin")); ShouldPassRule(config => { config.Query = "query { posts { items { id } } }"; config.Schema = TypedSchema(); config.User = CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }); }); } [Fact] public void fails_on_missing_claim_on_connection_type() { Settings.AddPolicy("ConnectionPolicy", _ => _.RequireClaim("admin")); ShouldFailRule(config => { config.Query = "query { posts { items { id } } }"; config.Schema = TypedSchema(); config.User = CreatePrincipal(); }); } private static ISchema BasicSchema() { string defs = """ type Query { post(id: ID!): String } """; return Schema.For(defs, builder => builder.Types.Include()); } [GraphQLMetadata("Query")] [Authorize("ClassPolicy")] public class BasicQueryWithAttributes { [Authorize("FieldPolicy")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "test")] public string Post(string id) => ""; } private static ISchema NestedSchema() { string defs = """ type Query { post(id: ID!): Post posts: [Post] postsNonNull: [Post!]! comment: String } type Post { id: ID! } """; return Schema.For(defs, builder => { builder.Types.Include(); builder.Types.Include(); }); } [GraphQLMetadata("Query")] public class NestedQueryWithAttributes { [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "test")] public Post? Post(string id) => null; public IEnumerable? Posts() => null; public IEnumerable? PostsNonNull() => null; public string? Comment() => null; } [Authorize("PostPolicy")] public class Post { public string? Id { get; set; } } public class PostGraphType : ObjectGraphType { public PostGraphType() { Field(p => p.Id); } } public class Article { public string? Id { get; set; } public string? Author { get; set; } public string? Content { get; set; } } public class ArticleGraphType : ObjectGraphType
{ public ArticleGraphType() { Field(p => p.Id); Field(p => p.Author); Field(p => p.Content).AuthorizeWithPolicy("AdminPolicy"); } } public class Author { public string? Name { get; set; } } private static ISchema TypedSchema() { var query = new ObjectGraphType(); query.Field("author") .Arguments(new QueryArguments(new QueryArgument { Name = "input" })) .Resolve(_ => "testing"); query.Connection("posts") .AuthorizeWithPolicy("ConnectionPolicy") .Resolve(_ => new Connection()); query.Field("project") .Arguments(new QueryArguments(new QueryArgument { Name = "input" })) .Resolve(_ => "testing").AuthorizeWithPolicy("AdminPolicy").AuthorizeWithPolicy("ConfidentialPolicy"); query.Field("article") .Resolve(_ => null); return new Schema { Query = query }; } public class AuthorInputType : InputObjectGraphType { public AuthorInputType() { Name = "AuthorInputType"; Field(x => x.Name).AuthorizeWithPolicy("FieldPolicy"); } } } ================================================ FILE: src/GraphQL.Authorization.Tests/ClaimAuthorizationRequirementTests.cs ================================================ namespace GraphQL.Authorization.Tests; public class ClaimAuthorizationRequirementTests { [Fact] public async Task produces_error_when_missing_claim_ignoring_value() { var req = new ClaimAuthorizationRequirement("Admin"); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal() }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeTrue(); context.Errors.Single().ShouldBe("Required claim 'Admin' is not present."); } [Fact] public async Task produces_error_when_missing_claim_with_single_value() { var req = new ClaimAuthorizationRequirement("Admin", "true"); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal() }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeTrue(); context.Errors.Single().ShouldBe("Required claim 'Admin' with any value of 'true' is not present."); } [Fact] public async Task produces_error_when_missing_claim_with_multiple_values() { var req = new ClaimAuthorizationRequirement("Admin", "true", "maybe"); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal() }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeTrue(); context.Errors.Single().ShouldBe("Required claim 'Admin' with any value of 'true, maybe' is not present."); } [Fact] public async Task succeeds_when_claim_with_ignoring_value() { var req = new ClaimAuthorizationRequirement("Admin"); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }) }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeFalse(); } [Fact] public async Task succeeds_when_claim_with_single_value() { var req = new ClaimAuthorizationRequirement("Admin", "true"); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "true" } }) }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeFalse(); } [Fact] public async Task succeeds_when_claim_with_multiple_values() { var req = new ClaimAuthorizationRequirement("Admin", "true", "maybe"); var context = new AuthorizationContext { User = ValidationTestBase.CreatePrincipal(claims: new Dictionary { { "Admin", "maybe" } }) }; await req.Authorize(context).ConfigureAwait(false); context.HasErrors.ShouldBeFalse(); } } ================================================ FILE: src/GraphQL.Authorization.Tests/GraphQL.Authorization.Tests.csproj ================================================ net8.0 net8.0;net7.0;net6.0;net5.0;netcoreapp3.1 8.0.0 ================================================ FILE: src/GraphQL.Authorization.Tests/ValidationTestBase.cs ================================================ using System.Security.Claims; using GraphQL.Execution; using GraphQL.Validation; using GraphQLParser; namespace GraphQL.Authorization.Tests; public class ValidationTestBase { public ValidationTestBase() { Settings = new AuthorizationSettings(); Rule = new AuthorizationValidationRule(new AuthorizationEvaluator(Settings)); } protected AuthorizationValidationRule Rule { get; } protected AuthorizationSettings Settings { get; } protected void ShouldPassRule(Action configure) { var config = new ValidationTestConfig(); config.Rules.Add(Rule); configure(config); config.Rules.Any().ShouldBeTrue("Must provide at least one rule to validate against."); config.Schema.Initialize(); var result = Validate(config); string message = ""; if (result.Errors?.Any() == true) { message = string.Join(", ", result.Errors.Select(x => x.Message)); } result.IsValid.ShouldBeTrue(message); config.ValidateResult(result); } protected void ShouldFailRule(Action configure) { var config = new ValidationTestConfig(); config.Rules.Add(Rule); configure(config); config.Rules.Any().ShouldBeTrue("Must provide at least one rule to validate against."); config.Schema.Initialize(); var result = Validate(config); result.IsValid.ShouldBeFalse("Expected validation errors though there were none."); config.ValidateResult(result); } private static IValidationResult Validate(ValidationTestConfig config) { var documentBuilder = new GraphQLDocumentBuilder(); var document = documentBuilder.Build(config.Query); var validator = new DocumentValidator(); return validator.ValidateAsync(new ValidationOptions { Schema = config.Schema, Document = document, Operation = document.OperationWithName(config.OperationName) ?? throw new InvalidOperationException("Could not find specified operation"), Rules = config.Rules, Variables = config.Variables ?? Inputs.Empty, User = config.User }).GetAwaiter().GetResult(); } internal static ClaimsPrincipal CreatePrincipal(string? authenticationType = null, IDictionary? claims = null) { var claimsList = new List(); if (claims != null) { foreach (var c in claims) claimsList.Add(new Claim(c.Key, c.Value)); } return new ClaimsPrincipal(new ClaimsIdentity(claimsList, authenticationType)); } } ================================================ FILE: src/GraphQL.Authorization.Tests/ValidationTestConfig.cs ================================================ using System.Security.Claims; using GraphQL.Types; using GraphQL.Validation; namespace GraphQL.Authorization.Tests; public class ValidationTestConfig { public string OperationName { get; set; } = null!; public string Query { get; set; } = null!; public ISchema Schema { get; set; } = null!; public List Rules { get; set; } = new(); public ClaimsPrincipal? User { get; set; } public Inputs? Variables { get; set; } public Action ValidateResult = _ => { }; } ================================================ FILE: src/GraphQL.Authorization.sln ================================================ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33205.214 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Authorization", "GraphQL.Authorization\GraphQL.Authorization.csproj", "{21771B0C-0FCE-41DA-BF0B-67D70368BB00}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Authorization.Tests", "GraphQL.Authorization.Tests\GraphQL.Authorization.Tests.csproj", "{C482F1AF-6E9D-4D1C-96D7-3C0206312B66}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harness", "Harness\Harness.csproj", "{DEA32495-F7AA-45F0-8AF6-DE4FB089994F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solution Items", ".Solution Items", "{F1263413-6A34-42F2-ABA7-E38AB3DCB548}" ProjectSection(SolutionItems) = preProject ..\.editorconfig = ..\.editorconfig ..\.gitignore = ..\.gitignore Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets ..\LICENSE.md = ..\LICENSE.md ..\README.md = ..\README.md Tests.props = Tests.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{84514A09-9BB4-4C85-8A8E-92AF5AA26445}" ProjectSection(SolutionItems) = preProject ..\.github\codecov.yaml = ..\.github\codecov.yaml ..\.github\dependabot.yml = ..\.github\dependabot.yml ..\.github\FUNDING.yml = ..\.github\FUNDING.yml ..\.github\labeler.yml = ..\.github\labeler.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C1216C18-FAAE-46CA-95EB-B2202FBC994A}" ProjectSection(SolutionItems) = preProject ..\.github\workflows\build.yml = ..\.github\workflows\build.yml ..\.github\workflows\codeql-analysis.yml = ..\.github\workflows\codeql-analysis.yml ..\.github\workflows\format.yml = ..\.github\workflows\format.yml ..\.github\workflows\label.yml = ..\.github\workflows\label.yml ..\.github\workflows\publish.yml = ..\.github\workflows\publish.yml ..\.github\workflows\test.yml = ..\.github\workflows\test.yml ..\.github\workflows\wipcheck.yml = ..\.github\workflows\wipcheck.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicSample", "BasicSample\BasicSample.csproj", "{49174102-8BD4-4193-8CB6-026881BF1B6B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Authorization.ApiTests", "GraphQL.Authorization.ApiTests\GraphQL.Authorization.ApiTests.csproj", "{0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{A5C2458F-F60E-4340-ADF6-C8DF6A6601AF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{70F571C2-0255-457D-BAAA-3BFDC60B6BC9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Debug|Any CPU.Build.0 = Debug|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Debug|x64.ActiveCfg = Debug|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Debug|x64.Build.0 = Debug|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Debug|x86.ActiveCfg = Debug|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Debug|x86.Build.0 = Debug|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Release|Any CPU.ActiveCfg = Release|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Release|Any CPU.Build.0 = Release|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Release|x64.ActiveCfg = Release|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Release|x64.Build.0 = Release|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Release|x86.ActiveCfg = Release|Any CPU {21771B0C-0FCE-41DA-BF0B-67D70368BB00}.Release|x86.Build.0 = Release|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Debug|Any CPU.Build.0 = Debug|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Debug|x64.ActiveCfg = Debug|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Debug|x64.Build.0 = Debug|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Debug|x86.ActiveCfg = Debug|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Debug|x86.Build.0 = Debug|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Release|Any CPU.ActiveCfg = Release|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Release|Any CPU.Build.0 = Release|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Release|x64.ActiveCfg = Release|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Release|x64.Build.0 = Release|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Release|x86.ActiveCfg = Release|Any CPU {C482F1AF-6E9D-4D1C-96D7-3C0206312B66}.Release|x86.Build.0 = Release|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Debug|x64.ActiveCfg = Debug|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Debug|x64.Build.0 = Debug|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Debug|x86.ActiveCfg = Debug|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Debug|x86.Build.0 = Debug|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Release|Any CPU.Build.0 = Release|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Release|x64.ActiveCfg = Release|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Release|x64.Build.0 = Release|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Release|x86.ActiveCfg = Release|Any CPU {DEA32495-F7AA-45F0-8AF6-DE4FB089994F}.Release|x86.Build.0 = Release|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Debug|Any CPU.Build.0 = Debug|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Debug|x64.ActiveCfg = Debug|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Debug|x64.Build.0 = Debug|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Debug|x86.ActiveCfg = Debug|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Debug|x86.Build.0 = Debug|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Release|Any CPU.ActiveCfg = Release|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Release|Any CPU.Build.0 = Release|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Release|x64.ActiveCfg = Release|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Release|x64.Build.0 = Release|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Release|x86.ActiveCfg = Release|Any CPU {49174102-8BD4-4193-8CB6-026881BF1B6B}.Release|x86.Build.0 = Release|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Debug|x64.ActiveCfg = Debug|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Debug|x64.Build.0 = Debug|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Debug|x86.ActiveCfg = Debug|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Debug|x86.Build.0 = Debug|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Release|Any CPU.Build.0 = Release|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Release|x64.ActiveCfg = Release|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Release|x64.Build.0 = Release|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Release|x86.ActiveCfg = Release|Any CPU {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {C482F1AF-6E9D-4D1C-96D7-3C0206312B66} = {70F571C2-0255-457D-BAAA-3BFDC60B6BC9} {DEA32495-F7AA-45F0-8AF6-DE4FB089994F} = {A5C2458F-F60E-4340-ADF6-C8DF6A6601AF} {C1216C18-FAAE-46CA-95EB-B2202FBC994A} = {84514A09-9BB4-4C85-8A8E-92AF5AA26445} {49174102-8BD4-4193-8CB6-026881BF1B6B} = {A5C2458F-F60E-4340-ADF6-C8DF6A6601AF} {0A74FD44-76DD-4FD1-BFA1-8A0B950021E0} = {70F571C2-0255-457D-BAAA-3BFDC60B6BC9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B582AABA-5414-45C5-920C-1A00B5B1FDB4} EndGlobalSection EndGlobal ================================================ FILE: src/Harness/GraphQL.cs ================================================ namespace GraphQL; /// /// CLR type to map to the 'Query' graph type. /// public class Query { /// /// Resolver for 'Query.viewer' field. /// [Authorize("AdminPolicy")] public User Viewer() => new() { Id = Guid.NewGuid().ToString(), Name = "Quinn" }; /// /// Resolver for 'Query.users' field. /// public List Users() => new() { new User { Id = Guid.NewGuid().ToString(), Name = "Quinn" } }; /// /// Resolver for 'Query.guest' field. /// public string Guest() => "guest42"; } /// /// CLR type to map to the 'User' graph type. /// public class User { /// /// Resolver for 'User.id' field. Just a simple property. /// public string? Id { get; set; } /// /// Resolver for 'User.name' field. Just a simple property. /// public string? Name { get; set; } } ================================================ FILE: src/Harness/Harness.csproj ================================================ net8.0 Example ASP.NET Core project to demonstrate GraphQL.NET Authorization setup false ================================================ FILE: src/Harness/Program.cs ================================================ using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpContextAccessor(); builder.Services.TryAddSingleton(_ => { const string definitions = """ type User { id: ID name: String } type Query { viewer: User users: [User] guest: String } """; var schema = Schema.For(definitions, builder => builder.Types.Include()); schema.AllTypes["User"]!.AuthorizeWithPolicy("AdminPolicy"); return schema; }); // Claims principal must look something like this to allow access. // var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("role", "Admin") })); builder.Services.AddGraphQL(builder => builder .AddSystemTextJson() .ConfigureExecutionOptions(opt => { opt.Root = new Query(); // opt.User = user; // User property has already been initialized. Uncomment to play with ClaimsPrincipal. }) .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true) .AddAuthorization(settings => settings.AddPolicy("AdminPolicy", p => p.RequireClaim("role", "Admin")))); var app = builder.Build(); if (app.Environment.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseGraphQL(); app.UseGraphQLGraphiQL(); app.UseGraphQLPlayground(options: new GraphQL.Server.Ui.Playground.PlaygroundOptions { SchemaPollingEnabled = false }); app.Run(); ================================================ FILE: src/Harness/Properties/launchSettings.json ================================================ { "profiles": { "Harness [GraphiQL]": { "commandName": "Project", "launchBrowser": true, "launchUrl": "ui/graphiql", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5000" }, "Harness [Playground]": { "commandName": "Project", "launchBrowser": true, "launchUrl": "ui/playground", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5000" } } } ================================================ FILE: src/Harness/appsettings.Development.json ================================================ { "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: src/Harness/appsettings.json ================================================ { "Logging": { "Debug": { "LogLevel": { "Default": "Warning" } }, "Console": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } } } } ================================================ FILE: src/Tests.props ================================================ $(NoWarn);IDE1006;CS0618 false false all runtime; build; native; contentfiles; analyzers; buildtransitive Always ================================================ FILE: src/xunit.runner.json ================================================ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "diagnosticMessages": true, "longRunningTestSeconds": 10 }