GithubHelp home page GithubHelp logo

kianmeng / absinthe_permission Goto Github PK

View Code? Open in Web Editor NEW

This project forked from b-erdem/absinthe_permission

0.0 2.0 0.0 49 KB

Fine-grained Permission/Policy Checker Middleware for Absinthe

License: MIT License

Elixir 100.00%

absinthe_permission's Introduction

AbsinthePermission

Fine-grained Permission/Policy Checker Middleware for Absinthe Queries/Mutations/Subscriptions

This module allows to define fine-grained permissions for queries, mutations and subscriptions by leveraging Absinthe's meta field.

This module defines 3 types policies:

  1. Simple permission checks. It only checks if a user has specified permission or not.
  2. Policy checks prior to run query, nutation or subscription.
  3. Policy checks after operation.

1: Simple Permission Checks

For type 1 policies definition is simple. You just provide permission name on query and that's it. It'll run before executing the operation.

Example:

...
query do
  ...
  field(:get_todo_list, list_of(:todo)) do
    meta(required_permission: "can_view_todo_list")
  end
  ...
end
...

2: Pre-op Permission/Policy Check.

There are a few common ways to check if a user is allowed to do the operation before running the operation. One of them is checking given input values to see if the user is allowed to do operation. For instance you have an app like Jira. And a user wants to change the status of a ticket. And let's say only project managers can change a ticket status as CLOSED. In this case when a request comes in with {..., state: "CLOSED"}, then you'd want to check state parameter if it's CLOSED or not. Then check if the user has this permission: can_close_ticket.

With this module you can define this policy without writing any code:

mutation do
  ...
  field(:update_ticket, :ticket) do
    arg(:id, :integer)
    arg(:detail, :string)
    arg(:state, :string)
    
    meta(
      pre_op_policies: [
        [
          state: "CLOSED",
          required_permission: "can_close_ticket"
        ]
      ]
    )
  end
  ...
end

In some other cases, it's not enough to check input values to allow or deny a user. For some operations you'd want to check the remote object before doing any operation on it. Let's say, there's another permission for updating description of a "CLOSED" ticket. In this case before allowing a user to change the description of a ticket, you first fetch it from remote and check it if its state is "CLOSED" or not. And you allow or deny.

Here is how to add a new policy to updateTicket mutation:

mutation do
  ...
  field(:update_ticket, :ticket) do
    arg(:id, :integer)
    arg(:detail, :string)
    arg(:state, :string)
          
    meta(   
      pre_op_policies: [
        [
          state: "CLOSED",
          required_permission: "can_close_ticket"
        ],
        [
          remote_context: [
            config: [fetcher_key: :my_db, remote_key: :id, input_key: :id],
            fields: [state: "CLOSED"],
            extras: [model: Ticket]
          ],
          required_permission: "can_update_closed_ticket_detail"
        ]
      ]
    )
  end
  ...
end

These are a few examples what can be done with pre operation policies. And even more, you're not limited to use only one of them for a policy. You can combine them in a policy. Local context and remote context together.

For remote_context there are a few additional fields. Explanation is below.

2: Post-op Permission/Policy Check.

And there are some cases which you'd want to have policies after operation runs. For instance nullifying some fields in the response. Example: Everyone can get a ticket's details. And there's assignee field on it. assignee field has email field. And you don't want to everyone see this field. You can define this policy on email field:

...
object :assignee do
  field(:id, :integer)
  field(:name, :string)
  ...
  field(:email, :string) do
    meta(
      post_op_policies: [
        [required_permission: "can_view_email"]
      ]
    )
  end
end
...

Installation

def deps do
  [
    {:absinthe_permission, "~> 0.1.0"}
  ]
end

Usage

Register the Middleware

Add AbsinthePermission.Middleware.HasPermission to your Absinthe Schema:

def middleware(middleware, _field, _object) do
      [AbsinthePermission.Middleware.HasPermission] ++ middleware ++ [AbsinthePermission.Middleware.HasPermission]
    end

This middleware expects current_user and permissions in top level of Absinthe context. permissions list should be list of permission names: ["perm1", "perm2", ...]

Defining Policies

Simple Permission Check Definition.

Only required parameter is required_permission.

query do
  field(:get_todo_list, list_of(:todo)) do
    meta(required_permission: "some_permission_name")
    ...
  end
end

Pre-op Policy Definition

Key name for defining pre-op policies is pre_op_policies. More than one policy definition can be provided.

A policy is a keyword list. Any key inside of it will correspond to input names(except remote_context and user_context). Example:

mutation do
  field(:update_ticket, :ticket) do
    arg(:id, :integer)
    arg(:state, :string)

    meta(
      pre_op_policies: [
        [
          state: "CLOSED",
          required_permission: "can_close_ticket"
        ],
        [
          state: "DONE",
          required_permission: "can_move_ticket_to_done"
        ]
      ]
    )
  end
end

In this example, PolicyChecker will check if state key in input values matches to given value in the policy("CLOSED"). Policies accept more than one check. So you can put additional checks inside of it.

Additionally, you can provide remote_context to pre-op policies.

remote_context: Requires config, extras and fields definitions.

config: Requires fetcher_key, remote_key, input_key.

PolicyChecker needs these config values, because if you define a remote_context, it doesn't know where to get the object. So you need to put the fetcher you want to use in your application environment under absinthe_permission, :fetchers key.

Example:

...
config :absinthe_permission, :fetchers,
  todo_db: {MyFetcher, :fetch},
  api1: {HttpFetcher, :fetch},
  api2: &Api2Fetcher.fetch/5
...

Fetchers in config can be a {module, fun} or just &fun/5. PolicyChecker sends fetchers 5 parameters: %{key: key, value: val}, policy, input_parameters, absinthe_context, extras. Fetcher should return {:ok, object}. If you need some specific key or values for your fetcher you can put inside of extras key. You can have more than fetchers for different operations.

fields: Fields and their values that needs to compared against remote object after we fetch it. For example, if remote object has state field on it, and you can put this key and value you want to under fields key.

mutation do
  field :delete_todo, :todo do
    meta(
      pre_op_policies: [
        [ 
          remote_context: [
            config: [fetcher_key: :todo_db, remote_key: input_key: :id],
            fields: [creator__id: {:current_user_id, :neq}],
            extras: [model: :todo],
            required_permission: "can_delete_other_users_todo"
            ]
        ],
        [ 
          required_permission: "can_delete_any_todo"
        ]
      ]
    )
        
    arg(:id, :integer)
    ...
  end
end

Please check test for more examples.

absinthe_permission's People

Contributors

b-erdem avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.