Draft: Granular PAT PoC

What does this MR do and why?

This is a PoC that shows we can use Grape annotations and GraphQL directives to perform granular PAT authorization for REST API endpoints and GraphQL queries and mutations.

This snippet shows the directives can be exported and used to perform early authorization checks in a future GitLab Auth Stack external auth reverse proxy server. It also shows they can be used for creating documentation and for static analysis in our CI pipelines.

How to setup

Select a project project with an issue issue.

  1. Create a PAT in the UI
  2. in the console, create token permissions
    pat = PersonalAccessToken.last
    pat.update!(granular: true)
    namespace = project.project_namespace
    pat.token_permissions.create(namespace: namespace, permissions: [:read_issues])
  3. Set PAT env var to your just created PAT token:
    PAT=glpat-xxx
  4. Verify REST endpoints to get issue details are working (replace project_id with project.id and issue_iid with issue.iid):
    curl http://gdk.test:3000/api/v4/projects/project_id/issues/issue_iid\?private_token\=$PAT
    output:
    => {"id":611,"iid":1,"project_id":19,"title":"My Issue",...
  5. Verify REST endpoints to update issue details are not working:
    curl http://gdk.test:3000/api/v4/projects/project_id/issues/issue_iid\?title\=My%20New%20Issue%20Title -X PUT -H "PRIVATE-TOKEN: $PAT"
    output:
    => Access denied: Your Personal Access Token lacks the required permissions: [admin_issues] for "my-group/my-subgroup/my-project".
  6. Verify GraphQL queries to lookup issue titles within the project are working (replace fullPath with project.full_path):
    curl http://gdk.test:3000/api/graphql -X POST \
      -H "PRIVATE-TOKEN: $PAT" \
      -H 'Content-Type: application/json' \
      -d '{"query":"{ project(fullPath: \"my-group/my-subgroup/my-project\") { issues { nodes { title } } } }"}'
    output:
    => {"data":{"project":{"issues":{"nodes":[{"title":"My Issue"}]}}}}
  7. Verify GraphQL mutations to update issue titles within the project are not working (replace fullPath with project.full_path and iid with issue.iid):
    curl http://localhost:3000/api/graphql -X POST \
      -H "PRIVATE-TOKEN: $PAT" \
      -H 'Content-Type: application/json' \
      -d '{"query":"mutation UpdateIssue($projectPath: ID\!, $iid: String\!, $title: String\!) { updateIssue(input: { projectPath: $projectPath, iid: $iid, title: $title }) { issue { iid title } errors } }","variables":{"projectPath":"my-group/my-subgroup/my-project","iid":"1","title":"My New Issue Title"}}'
    output:
    => {"errors":[{"message":"Access denied: Your Personal Access Token lacks the required permissions: [admin_issues] for \"my-group/my-subgroup/my-project\".","locations":[{"line":1,"column":75}],"path":["updateIssue"]}],"data":{"updateIssue":null}}
  8. Update the PAT permissions to include the :admin_issues permission
    Authz::TokenPermission.last.update(permissions: [:admin_issues])
  9. Execute the previous PUT request and verify the output is something like:
    {"id":611,"iid":1,"project_id":19,"title":"My New Issue Title",...
  10. Execute the previous mutation and verify the output is something like:
    {"data":{"updateIssue":{"issue":{"iid":"1", "title":"My New Issue Title"},"errors":[]}}}
Edited by Alex Buijs

Merge request reports

Loading