Coder Social home page Coder Social logo

kan's Introduction

Kan

Build Status Backers on Open Collective Sponsors on Open Collective

Simple functional authorization library for ruby. Inspired by transproc and dry project

Table of context

Installation

Add this line to your application's Gemfile:

gem 'kan'

And then execute:

$ bundle

Or install it yourself as:

$ gem install kan

Usage

See User Documentation page

Basic Usage

Register abilities

class Post::Abilities
  include Kan::Abilities

  register('read') { |_, _| true }
  register('edit') { |user, post| user.id == post.user_id }
  register('delete') { |_, _| false }
end

Also, you can register more than one ability in one place and use string or symbol keys:

class Post::AdminAbilities
  include Kan::Abilities

  register(:read, :edit, :delete) { |user, _| user.admin? }
end

class Comments::Abilities
  include Kan::Abilities

  register('read') { |_, _| true }
  register('edit') { |user, _| user.admin? }

  register(:delete) do |user, comment|
    user.id == comment.user_id && comment.created_at < Time.now + TEN_MINUTES
  end
end

Check abilities

abilities = Kan::Application.new(
  post: Post::Abilities.new,
  comment: Comments::Abilities.new
)

abilities['post.read'].call(current_user, post) # => true
abilities['post.delete'].call(current_user, post) # => false
abilities['comment.delete'].call(current_user, post) # => false

Default ability block

By default Kan use proc { true } as a default ability block:

abilities['comment.invalid'].call(current_user, post) # => true

But you can rewrite it

admin_abilities = Kan::Application.new(
  post: Post::AdminAbilities.new(default_ability_block: proc { false }),
  comment: Comments::Abilities.new,
)

admin_abilities['post.delete'].call(current_user, post)  # => false
admin_abilities['post.delete'].call(admin_user, post)    # => true
admin_abilities['post.invalid'].call(current_user, post) # => false

List of abilities

You can provide array of abilities for each scope and Kan will return true if at least one ability return true:

global_abilities = Kan::Application.new(
  post: [Post::Abilities.new, Post::AdminAbilities.new],
  comment: Comments::Abilities.new
)

global_abilities['post.edit'].call(current_user, post) # => false
global_abilities['post.edit'].call(owner_user, post)   # => true
global_abilities['post.edit'].call(admin_user, post)   # => true

Aliases

You can use strings or symbols and then use it as name of ability

class Post::Abilities
  include Kan::Abilities

  register(:edit) { |_, _| true }
  register_alias(:correct, 'edit')
end

abilities = Kan::Application.new(
  post: Post::Abilities.new
)

abilities['post.correct'].call(current_user, post) # => true

Callback

You can provide callable object (that respond to #call) that accepts ability_name and payload params to after_call_callback param of your ability:

admin_abilities = Kan::Application.new(
  post: Post::AdminAbilities.new(after_call_callback: -> (ability_name, payload) { ... }),
  comment: Comments::Abilities.new,
)

admin_abilities['post.read'].call(current_user, post) # => false

Your object will be executed after calling ability.

Contributing

Code and features

Bug reports and pull requests are welcome on GitHub at https://github.com/davydovanton/kan. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

Docs

Just send PR with changes in docs/ folder.

How to instal the project

Just clone repository and call:

$ bundle install
$ bundle exec rspec

Contributors

This project exists thanks to all the people who contribute.

Backers

Thank you to all our backers! 🙏 [Become a backer]

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Kan project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

kan's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

kan's Issues

Allow to use role classes, not only blocks

We need to add ability to extract role logic to classes. Something like this:

module Post
  module Roles
    class Anonymous
      def call(user, _)
        user.id.nil?
      end
    end
  end

  class AnonymousAbilities
    include Kan::Abilities

    role :anonymous, Roles::Anonymous

    register(:read, :edit, :delete) { false }
  end
end

Aliases for abilities

Something like this:

class Post::Abilities
  include Kan::Abilities

  register('read') { |_, _| true }
  register_alias('alias_read', 'read')
end

abilities = Kan::Application.new(post: Post::Abilities.new)
abilities['post.read'].call(current_user, post)       # => true
abilities['post.alias_read'].call(current_user, post) # => true

Allow to setup notification callback for all abilities

Let's try to create only after_call_callback for broadcasting notifications for any frameworks:

abilities = Kan::Application.new(
  post: Post::Abilities.new(after_call_callback: -> (ability_name, payload) { ... }),
  comment: Comments::Abilities.new
)

abilities['post.read'].call(current_user, post) # => true
abilities['post.delete'].call(current_user, post) # => false
abilities['comment.delete'].call(current_user, post) # => false

Syntax errors in the readme

Was looking at the README and seems that all the inline blocks are missing parens around the arguments, e.g.:

  register 'read' { |_, _| true }

should be:

  register('read') { |_, _| true }

PoC: plugins

We can use kan not only roles and user. Also, same interface is good for working with permissions, experiments and etc.

That's why we can start use plugin system for helping use kan with other areas.

I have no idea how it should look like, but we can start discussion and find what the best way for this.

My general points around plugin system:

  1. Easy integrate outside core code (plugin container can help here, I think)
  2. Ability to use different plugins with different instances of library

Also, we can move callbacks and notifications to plugins too

API ideas

Kan::Plugin.register(:roles) { Kan::Plugin::Roles }
Kan::Plugin.register(:custom) { CustomKanPlugin }


class Post::Abilities
  include Kan::Abilities
  extend Kan::Plugin[:roles]

  register('read') { |_, _| true }
  register('edit') { |user, post| user.id == post.user_id }
  register('delete') { |_, _| false }
end

Update user documentation

From reddit:

In the example "Also, you can register more than one ability in one place and use string or symbol keys:", I don't understand how these ablities are triggered -- do PostAbilities and AdminAbilities somehow both apply at once? Can you add to this example to show how you'd call the auth check, and what would be checked?

Application with default settings

Now we have a simple problem: how to set up default values for each abilities instance in one place?

I sugest to use something like this:

class ProjectAbilityApplication < Kan::Application
  default_options logger: MyLogger.new
end

After that we can initialize:

  • default application class
Kan::Application.new(
  comment: Comments::Abilities.new
) # => will use default Logger
  • custom application class
ProjectAbilityApplication.new(
  comment: Comments::Abilities.new
) # => will use default MyLogger
  • custom application class with other value
ProjectAbilityApplication.new(
  comment: Comments::Abilities.new(logger: MyOtherLogger.new)
) # => will use MyOtherLogger

WDYT?


UPD agter small talk with @apotonick: Maybe we need to use instance variable for settings and #call for generating a builder class 🤔

Kan::Application.new.call(
  comment: Comments::Abilities.new
) # => will use default Logger

Kan::Application.new(logger: MyLogger.new).call(
  comment: Comments::Abilities.new
) # will use MyLogger

Kan::Application.new(logger: MyLogger.new).call(
  comment: Comments::Abilities.new(logger: MyOtherLogger.new)
) # => will use MyOtherLogger

Role detector ability

What we want

Detect role value for specific scope:

abilities['post.role'].call(user)    # => :payed

Why

Sometimes you need to understand what type of user you have. For this you can put all logic to user entity, but it's place for can. Because we need to understand role and permission for action here

UPD

How to handle cases, when user map to 1+ role? For example, admin user which map to regular and admin role?

TODO

  • Raise error if user create scope.role check
  • Returns list of roles for current data

Add metainformation to kan role objects

It will be awesome to add some meta information to Kan::Role object. Something like this:

module User::Roles
  class Admin < Kan::Roles
    name :admin

    def call(user, _)
      logger.info # => allow to call kan application logger
    end
  end
end

And use it:

module Post
  class AnonymousAbilities
    include Kan::Abilities

    role :admin, User::Roles::Admin
    # or
    role User::Roles::Admin
  end
end

Scoping resources

Is it possible to scope resources for viewing? This way, we could scope relations for a given user. Something like:

class Post::Abilities
  include Kan::Abilities

  register_scope('index') do |user|
    if user.admin?
      posts_repo
    else
      posts_repo.exclude(draft: true)
    end
  end
end

Is it possible to do this already?

Metainformation for roles

It will be really important to setup any meta information to role for abilities list. Now we want to allow developers setup it and in next iteration use it in roles or other places.

What API I see for setup meta information:

module Post
  class AnonymousAbilities
    include Kan::Abilities

    role :anonymous, metainformation: :here, other: :information, Roles::Anonymous.new
    register(:read, :edit, :delete) { false }
  end

  class AdminAbilities
    include Kan::Abilities

    role :admin, weight: 2, version: 1, Roles::Admin.new
    register(:read, :edit, :delete) { |_, _| true }
  end
end

Proposal: Role system

Thanks, @apotonick for really good question:

https://twitter.com/apotonick/status/951359565198778368

I think we can define "role" for each abilities class and after that use it for detect right abilities for scope. For example:

module Post
  class AnonymousAbilities
    include Kan::Abilities

    role :anonymous do |user, _|
      user.id.nil?
    end

    register(:read) { |_, _| true }
    register(:edit, :delete) { |user, post| false }
  end

  class BaseAbilities
    include Kan::Abilities

    role :all do |_, _|
      true
    end

    register(:read) { |_, _| true }
    register(:edit, :delete) { |user, post| false }
  end


  class AuthorAbilities
    include Kan::Abilities

    role :author do |user, post|
      user.id == post.author_id
    end

    register(:read, :edit) { |_, _| true }
    register(:delete) { |_, _| false }
  end

  class AdminAbilities
    include Kan::Abilities

    role :admin do |user, _|
      user.admin?
    end

    register :read, :edit, :delete { |_, _| true }
  end
end

After that we can initialize kan object:

abilities = Kan::Application.new(
  post: [Post::AnonymousAbilities.new, Post::BaseAbilities.new, Post::AuthorAbilities.new, Post::AdminAbilities.new]
  comment: Comments::Abilities.new,
)

And now, we can check can user do something or not:

abilities['post.read'].call(anonymous, post) # => false
abilities['post.read'].call(regular, post)   # => true
abilities['post.read'].call(auther, post)    # => true
abilities['post.read'].call(admin, post)     # => true

abilities['post.edit'].call(anonymous, post) # => false
abilities['post.edit'].call(regular, post)   # => false
abilities['post.edit'].call(auther, post)    # => true
abilities['post.edit'].call(admin, post)     # => true

abilities['post.delete'].call(anonymous, post) # => false
abilities['post.delete'].call(regular, post)   # => false
abilities['post.delete'].call(auther, post)    # => false
abilities['post.delete'].call(admin, post)     # => true

WDYT?

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.