Skip to content

Instantly share code, notes, and snippets.

@danieljharvey
Last active July 16, 2024 10:15
Show Gist options
  • Save danieljharvey/2718cf1a8fc340a5b21063bc5e843b36 to your computer and use it in GitHub Desktop.
Save danieljharvey/2718cf1a8fc340a5b21063bc5e843b36 to your computer and use it in GitHub Desktop.

Before / After comparison

Imagine the following data models for an internal API targeted at a company's own developers and support agents.

  • User: id, name, email, is_gov
  • UserActivityRecord: id, user_id, details, is_hidden
  • SupportTickets: id, assigned_agent_id

And these are the potential authorization considerations:

  • Two personas: Developers and Support Agents
    • Developers can generally view all user's information
    • Support agents can only view data for users whose tickets they have been assigned
  • Some user information is protected
    • PII fields (name, email) unless the API consumer has explicit access
    • Government user data is restricted unless the API consumer has explicit access

After

The authentication system would be simple. Four roles:

  • developer
  • support_agent
  • has_access_to_pii
  • has_access_to_gov

A user's roles are the result of all RolePredicates that return true.

kind: RolePredicate
version: v1
definition:
  name: has_access_to_pii
  condition:
    equals:
      left: sessionVariable: x-hasura-has-pii-access
      right: literal: true

kind: RolePredicate
version: v1
definition:
  name: has_access_to_gov 
  condition:
    equals:
      left: sessionVariable: x-hasura-has-gov-access
      right: literal: true

kind: RolePredicate
version: v1
definition:
  name: developer 
  condition:
    equals:
      left: sessionVariable: x-hasura-role 
      right: literal: developer

kind: RolePredicate
version: v1
definition:
  name: support_agent
  condition:
    equals:
      left: sessionVariable: x-hasura-role 
      right: literal: support_agent 

kind: TypePermissions
version: v2
definition:
  typeName: User
  permissions:
    # Rule 1: everybody can access `id` fields
    - roles: [developer, support_agent]
      output: 
        allowedFields: [id]

    # Rule 2: people with the a special role allows access to everything
    - roles: [has_access_to_pii]
      output:
        # we cumulatively build up access to fields, so these users can also see the `id`
        # field
        allowedFields: [name, dob]

kind: ModelPermissions
version: v2
definition:
  modelName: User
  permissions:
    # Rule 1: Developers are allowed to access all users by default
    - roles: [developer]
      select: null 

    # Rule 2: Support agents are allowed to access users they have support
    # tickets for.
    # we express this as a partially preset `where` clause on the model
    - roles: [support_agent]
      select:
        # row permissions, expressed as a boolean expression
        filter:
          fieldComparison:
             field: agent_id
             operator: _eq
             value: sessionVariable: x-hasura-agent-id

    # Rule 3: If a developer / support_agent does not have gov access,
    # don't allow them to access gov users.
    # This rule matches on the absence of a role, and we accumulate predicates
    # to add to the `where` clause
    # we may need to think about what to do where two comparison fields
    # conflict. `withoutRole` feels like it should be more important, like
    # `deny`, but need to think about this more. Or base it on ordering, and put
    # this entry first.
    - withoutRole: has_access_to_gov
      select:
        # row permissions, expressed as a boolean expression
        filter: 
          fieldComparison:
            field: is_gov 
            operator: _eq
            value: literal: false 

# the suggestion of "if I can access X, then I can access Y" makes sense
# and it's straightforward to take any predicate presets and use them on the
# nested object / object relationship.
kind: ModelPermissions
version: v2
definition:
  modelName: UserActivity
  # we look at the permissions for `user`, and use them to decide if we can
  # look at `UserActivity`
  inheritedPermissions:
    - relationship:
        name: user
        relatedObjectAllowed: true
  permissions:
    # nobody can see the hidden fields
    - roles: '*' 
      select:
        filter:
          fieldComparison:
            fieldName: is_hidden
            operator: _eq
            value: literal: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment