cando 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ cache: bundler
5
+ before_script:
6
+ - mysql -e 'create database cando_test;'
7
+
8
+ env: CANDO_TEST_DB=mysql://travis@127.0.0.1/cando_test
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "sequel"
7
+ gem "rake"
8
+
9
+ # Add dependencies to develop your gem here.
10
+ # Include everything needed to run rake, tests, features, etc.
11
+ group :development do
12
+ gem "rspec", "~> 2.10.0"
13
+ gem "rdoc", "~> 3.12"
14
+ gem "bundler", "~> 1.0"
15
+ gem "jeweler", "~> 2.0.1"
16
+ gem "simplecov", ">= 0"
17
+ end
18
+
19
+ group :test do
20
+ gem "mysql"
21
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,79 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.3.6)
5
+ builder (3.2.2)
6
+ descendants_tracker (0.0.4)
7
+ thread_safe (~> 0.3, >= 0.3.1)
8
+ diff-lcs (1.1.3)
9
+ docile (1.1.3)
10
+ faraday (0.9.0)
11
+ multipart-post (>= 1.2, < 3)
12
+ git (1.2.6)
13
+ github_api (0.11.3)
14
+ addressable (~> 2.3)
15
+ descendants_tracker (~> 0.0.1)
16
+ faraday (~> 0.8, < 0.10)
17
+ hashie (>= 1.2)
18
+ multi_json (>= 1.7.5, < 2.0)
19
+ nokogiri (~> 1.6.0)
20
+ oauth2
21
+ hashie (2.1.1)
22
+ highline (1.6.21)
23
+ jeweler (2.0.1)
24
+ builder
25
+ bundler (>= 1.0)
26
+ git (>= 1.2.5)
27
+ github_api
28
+ highline (>= 1.6.15)
29
+ nokogiri (>= 1.5.10)
30
+ rake
31
+ rdoc
32
+ json (1.8.1)
33
+ jwt (0.1.13)
34
+ multi_json (>= 1.5)
35
+ mini_portile (0.5.3)
36
+ multi_json (1.10.0)
37
+ multi_xml (0.5.5)
38
+ multipart-post (2.0.0)
39
+ mysql (2.9.1)
40
+ nokogiri (1.6.1)
41
+ mini_portile (~> 0.5.0)
42
+ oauth2 (0.9.3)
43
+ faraday (>= 0.8, < 0.10)
44
+ jwt (~> 0.1.8)
45
+ multi_json (~> 1.3)
46
+ multi_xml (~> 0.5)
47
+ rack (~> 1.2)
48
+ rack (1.5.2)
49
+ rake (10.3.1)
50
+ rdoc (3.12.2)
51
+ json (~> 1.4)
52
+ rspec (2.10.0)
53
+ rspec-core (~> 2.10.0)
54
+ rspec-expectations (~> 2.10.0)
55
+ rspec-mocks (~> 2.10.0)
56
+ rspec-core (2.10.1)
57
+ rspec-expectations (2.10.0)
58
+ diff-lcs (~> 1.1.3)
59
+ rspec-mocks (2.10.1)
60
+ sequel (4.10.0)
61
+ simplecov (0.8.2)
62
+ docile (~> 1.1.0)
63
+ multi_json
64
+ simplecov-html (~> 0.8.0)
65
+ simplecov-html (0.8.0)
66
+ thread_safe (0.3.3)
67
+
68
+ PLATFORMS
69
+ ruby
70
+
71
+ DEPENDENCIES
72
+ bundler (~> 1.0)
73
+ jeweler (~> 2.0.1)
74
+ mysql
75
+ rake
76
+ rdoc (~> 3.12)
77
+ rspec (~> 2.10.0)
78
+ sequel
79
+ simplecov
data/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2014 Daniel Bornkessel
2
+
3
+ The MIT License (MIT)
4
+
5
+ Copyright © 2013 SoundCloud Ltd., Internal Tools Team
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+
25
+
data/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # CanDo [![Build Status](https://travis-ci.org/soundcloud/cando.svg?branch=master)](https://travis-ci.org/soundcloud/cando)
2
+
3
+ CanDo is a small gem to implement a simple user access system based on users, roles &
4
+ capabilites, where:
5
+
6
+ - each user can have 0, 1 or many roles
7
+ - each role can have 0, 1 or many capabilites
8
+
9
+ Users have capabilities by getting roles assigned (role == collection of
10
+ capabilities). Within the code, the `can` helper method can be used to test
11
+ whether a user has a certain capability or not (see below for a working code example).
12
+
13
+ ## Dependencies
14
+
15
+ CanDo depends on the following software:
16
+
17
+ * [sequel](http://sequel.jeremyevans.net)
18
+ * [rake](https://github.com/jimweirich/rake)
19
+
20
+
21
+ ## Installation and deployment
22
+
23
+ Download and install rake with the following.
24
+
25
+ gem install cando
26
+
27
+ ## Configuration and usage
28
+
29
+ ### Database setup & configuration
30
+ If you want to use an individual database for cando, create the db + credentials (adjust values):
31
+
32
+ CREATE DATABASE IF NOT EXISTS cando DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
33
+ GRANT ALL ON `cando`.* to 'cando_user'@'localhost' identified by 'cando_passwd';
34
+
35
+ Whenever you want to use a CanDo rake task, you need to set the database config via the env var `$CANDO_DB`:
36
+
37
+ export CANDO_DB=mysql://cando_user@cando_passwd@localhost/cando
38
+
39
+ for other dbs, [see the sequel
40
+ documentation](http://sequel.jeremyevans.net/rdoc/classes/Sequel.html#method-c-connect).
41
+ **Note that you will have to require the gem for your respective dbms, i.e. the
42
+ `mysql`-gem for mysql, the `sqlite3`-gem for sqlite, etc. **
43
+
44
+ ### Init cando
45
+ Cando provides a rake task to get you started. This will setup the necessary
46
+ tables (they are all prefixed with `cando` and thus should not interfere with
47
+ your database.
48
+
49
+ rake cando:init
50
+
51
+ ### Using rake tasks
52
+ Cando provides several useful rake tasks for easy cli-based operations. In order
53
+ to use those edit (or create) the `Rakefile` and include
54
+
55
+ require 'cando'
56
+
57
+ To get an overview, execute:
58
+
59
+ $ rake -T cando
60
+ rake cando:init # Initialize cando (creates schema and runs migration)
61
+
62
+ rake cando:list # List roles
63
+ rake cando:add # Add a new role (pass in role name and capabilities with role=<name> capabilities=<cap1>,<cap2>,...
64
+ rake cando:update # Update role (pass in role name and capabilities with role=<name> capabilities=<cap1>,<cap2>,...
65
+ rake cando:remove # Remove role (pass in role name with role=<name>)
66
+
67
+ rake cando:assign # Assign role to user (args: roles=<r1>,<r2>,<rn> user=<user_urn>)
68
+ rake cando:users # List users and their roles
69
+
70
+ ### Using CanDo in your project's code
71
+ Using the CanDo in your code (working code with an empty database):
72
+
73
+ require 'cando'
74
+ include CanDo
75
+
76
+ CanDo.init do
77
+ # if passed, this will be executed if the user does not have the
78
+ # asked-for capability (only applies if 'can' is passed a block)
79
+ cannot_block do |user_urn, capability|
80
+ raise "#{user_urn} can not #{capability}"
81
+ end
82
+
83
+ connect "mysql://cando_user:cando_passwd@host:port/database"
84
+ end
85
+
86
+ # if the role or a capability does not exist, it'll be created
87
+ define_role("r1", ["capability1", "capability3"])
88
+ define_role("r2", ["capability2"])
89
+
90
+ # if the user does not exist, he'll be created -- the roles must be available
91
+ assign_roles("user1", ["r1", "r2"])
92
+ assign_roles("user2", ["r1"])
93
+
94
+ # use 'can' block syntax
95
+ can("user1", :capability1) do
96
+ puts "user has capability1"
97
+ end
98
+
99
+ # this will raise an exception as declared in the init block
100
+ can("user1", :super_admin) do
101
+ puts "hey hoh" # this will not be printed
102
+ end
103
+
104
+ # when no block is given, 'can' returns true or false/nil
105
+ unless can("user2", :capability2)
106
+ puts "user does not have capability1"
107
+ end
108
+
109
+ ## Versioning
110
+ CanDo adheres to Semantic Versioning 2.0.0. If there is a violation of
111
+ this scheme, report it as a bug.Specifically, if a patch or minor version is
112
+ released and breaks backward compatibility, that version should be immediately
113
+ yanked and/or a new version should be immediately released that restores
114
+ compatibility. Any change that breaks the public API will only be introduced at
115
+ a major-version release. As a result of this policy, you can (and should)
116
+ specify any dependency on <project name> by using the Pessimistic Version
117
+ Constraint with two digits of precision.
118
+
119
+ ## Licensing
120
+
121
+ See the [LICENSE](LICENSE.md) file for details.
122
+
123
+ The MIT License (MIT)
124
+
125
+ Copyright &copy; 2014 Daniel Bornkessel
126
+
127
+ Permission is hereby granted, free of charge, to any person obtaining a copy
128
+ of this software and associated documentation files (the "Software"), to deal
129
+ in the Software without restriction, including without limitation the rights
130
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
131
+ copies of the Software, and to permit persons to whom the Software is
132
+ furnished to do so, subject to the following conditions:
133
+
134
+ The above copyright notice and this permission notice shall be included in
135
+ all copies or substantial portions of the Software.
136
+
137
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
138
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
139
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
140
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
141
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
142
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
143
+ THE SOFTWARE.
144
+
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rake'
4
+ require 'rubygems'
5
+ require 'bundler'
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+ require 'rake'
14
+
15
+ require 'jeweler'
16
+ Jeweler::Tasks.new do |gem|
17
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
18
+ gem.name = "cando"
19
+ gem.homepage = "http://github.com/kesselborn/cando"
20
+ gem.license = "MIT"
21
+ gem.summary = %Q{Simple roles helper}
22
+ gem.description = %Q{cando description}
23
+ gem.email = "daniel@soundcloud.com"
24
+ gem.authors = ["Daniel Bornkessel"]
25
+ # dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ desc "Code coverage detail"
36
+ task :simplecov do
37
+ ENV['COVERAGE'] = "true"
38
+ Rake::Task['spec'].execute
39
+ end
40
+
41
+ task :default => :spec
42
+
43
+ require 'rdoc/task'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "cando #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ rdoc.rdoc_files.include('contrib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/cando.gemspec ADDED
@@ -0,0 +1,75 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "cando"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Daniel Bornkessel"]
12
+ s.date = "2014-05-20"
13
+ s.description = "cando description"
14
+ s.email = "daniel@soundcloud.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ ".travis.yml",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.md",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "cando.gemspec",
30
+ "contrib/initial_schema.rb",
31
+ "lib/cando.rb",
32
+ "lib/models/capability.rb",
33
+ "lib/models/role.rb",
34
+ "lib/models/user.rb",
35
+ "lib/tasks/cando.rake",
36
+ "spec/cando_spec.rb",
37
+ "spec/spec_helper.rb"
38
+ ]
39
+ s.homepage = "http://github.com/kesselborn/cando"
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = "1.8.23"
43
+ s.summary = "Simple roles helper"
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<sequel>, [">= 0"])
50
+ s.add_runtime_dependency(%q<rake>, [">= 0"])
51
+ s.add_development_dependency(%q<rspec>, ["~> 2.10.0"])
52
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
53
+ s.add_development_dependency(%q<bundler>, ["~> 1.0"])
54
+ s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
55
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
56
+ else
57
+ s.add_dependency(%q<sequel>, [">= 0"])
58
+ s.add_dependency(%q<rake>, [">= 0"])
59
+ s.add_dependency(%q<rspec>, ["~> 2.10.0"])
60
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
61
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
62
+ s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
63
+ s.add_dependency(%q<simplecov>, [">= 0"])
64
+ end
65
+ else
66
+ s.add_dependency(%q<sequel>, [">= 0"])
67
+ s.add_dependency(%q<rake>, [">= 0"])
68
+ s.add_dependency(%q<rspec>, ["~> 2.10.0"])
69
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
70
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
71
+ s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
72
+ s.add_dependency(%q<simplecov>, [">= 0"])
73
+ end
74
+ end
75
+
@@ -0,0 +1,39 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table :cando_users do
4
+ String :id, :unique => true, :null => false
5
+ primary_key :id
6
+ end
7
+
8
+ create_table :cando_roles do
9
+ String :id, :unique => true, :null => false
10
+ primary_key :id
11
+ end
12
+
13
+ create_table :cando_capabilities do
14
+ String :id, :unique => true, :null => false
15
+ primary_key :id
16
+ end
17
+
18
+ # associations
19
+ create_table :cando_roles_users do
20
+ String :user_id
21
+ String :role_id
22
+ primary_key [:user_id, :role_id], :name => :ur_pk
23
+ end
24
+
25
+ create_table :cando_capabilities_roles do
26
+ String :role_id
27
+ String :capability_id
28
+ primary_key [:role_id, :capability_id], :name =>:rc_pk
29
+ end
30
+ end
31
+
32
+ down do
33
+ drop_table :cando_users
34
+ drop_table :cando_roles
35
+ drop_table :cando_capabilities
36
+ drop_table :cando_roles_users
37
+ drop_table :cando_capabilities_roles
38
+ end
39
+ end
data/lib/cando.rb ADDED
@@ -0,0 +1,88 @@
1
+ require 'sequel'
2
+
3
+ if File.basename($0) == "rake" # we are in a rake call: export our rake stuff
4
+ require 'rake'
5
+ import File.join(File.dirname(File.dirname(__FILE__)), "lib", "tasks", "cando.rake" )
6
+ end
7
+
8
+ module CanDo
9
+ class ConfigCannotBlockError < RuntimeError; end
10
+ class ConfigMysqlDBError < RuntimeError; end
11
+ class ConfigMysqlConnectionError < RuntimeError; end
12
+ class DBNotConnected < RuntimeError; end
13
+
14
+ def self.db
15
+ @db or raise DBNotConnected.new("CanDo is not connected to a database")
16
+ end
17
+
18
+ def self.init(&block)
19
+ CanDo.instance_eval &block
20
+
21
+ begin
22
+ Sequel::Model.db.test_connection
23
+ rescue ::Sequel::Error => e
24
+ raise DBNotConnected.new("No database connection established. Have you called 'connect' within the 'CanDo.init' block? Sequel error message is:\n#{e.message}")
25
+ end
26
+
27
+ Dir.glob(File.expand_path("#{__FILE__}/../models/*.rb")).each do |model|
28
+ require_relative model
29
+ end
30
+
31
+ Sequel::Model.db
32
+ end
33
+
34
+
35
+ # will be executed if `can(user_urn, :capability) { }` will not be executed
36
+ # due to missing capabilities
37
+ def self.cannot_block(&block)
38
+ if !block
39
+ raise ConfigCannotBlockError.new("CanDo#cannot_block expects block")
40
+ end
41
+ if block.arity != 2
42
+ raise ConfigCannotBlockError.new("CanDo#cannot_block expects block expecting two arguments |user_urn, capability|")
43
+ end
44
+
45
+ @@cannot_block_proc = block
46
+ end
47
+
48
+ def self.connect(connection)
49
+ if connection =~ /sqlite/
50
+ raise ConfigMysqlDBError.new("sqlite is not supported as it misses certain constraints")
51
+ end
52
+
53
+ begin
54
+ @db = Sequel.connect(connection)
55
+ @db.test_connection
56
+ @db
57
+ rescue => e
58
+ raise ConfigMysqlConnectionError.new(<<-EOF
59
+ Error connecting to database. Be sure to pass in a databse config like 'mysql://user:passwd@host/database':
60
+ #{e.message}
61
+ EOF
62
+ )
63
+ end
64
+ end
65
+
66
+ def can(user_urn, capability)
67
+ user = CanDo::User.find(:id => user_urn)
68
+ has_permission = user && user.can(capability)
69
+ if block_given?
70
+ if has_permission
71
+ return yield
72
+ end
73
+ if @@cannot_block_proc
74
+ @@cannot_block_proc.call(user_urn, capability)
75
+ end
76
+ end
77
+
78
+ has_permission
79
+ end
80
+
81
+ def define_role(role, capabilities)
82
+ CanDo::Role.define_role(role, capabilities)
83
+ end
84
+
85
+ def assign_roles(user, roles)
86
+ CanDo::User.find_or_create(:id => user).assign_roles(roles)
87
+ end
88
+ end
@@ -0,0 +1,14 @@
1
+ module CanDo
2
+ class Capability < Sequel::Model(:cando_capabilities)
3
+ many_to_many :roles, :join_table => :cando_capabilities_roles
4
+ unrestrict_primary_key
5
+
6
+ def self.cleanup
7
+ Capability.all do |cap|
8
+ if cap.roles.count == 0
9
+ cap.destroy
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ module CanDo
2
+ class Role < Sequel::Model(:cando_roles)
3
+ many_to_many :users, :join_table => :cando_roles_users
4
+ many_to_many :capabilities, :join_table => :cando_capabilities_roles
5
+ unrestrict_primary_key
6
+
7
+ def before_destroy
8
+ self.remove_all_capabilities
9
+ self.remove_all_users
10
+ Capability.cleanup
11
+ end
12
+
13
+ def self.define_role(name, capabilities)
14
+ role = Role.find_or_create(:id => name)
15
+ role.remove_all_capabilities
16
+ capabilities.each do |capability|
17
+ role.add_capability( Capability.find_or_create(:id => capability.to_s) )
18
+ end
19
+
20
+ role
21
+ end
22
+
23
+ def to_s
24
+ "#{id}\t#{capabilities.map(&:id).join(",")}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ module CanDo
2
+ class UndefinedRole < Exception; end
3
+ class User < Sequel::Model(:cando_users)
4
+ many_to_many :roles, :join_table => :cando_roles_users
5
+ unrestrict_primary_key
6
+
7
+ def capabilities
8
+ roles.inject([]){|a,role| a << role.capabilities}.flatten.uniq.map(&:id)
9
+ end
10
+
11
+ def can(capability)
12
+ capabilities.include?(capability.to_s)
13
+ end
14
+
15
+ def assign_roles(roles)
16
+ self.class.db.transaction do
17
+ self.remove_all_roles
18
+ roles.each do |r|
19
+ begin
20
+ role = r.is_a?(CanDo::Role) ? r : CanDo::Role.where(:id => r).first!
21
+ self.add_role(role)
22
+ rescue Sequel::UniqueConstraintViolation => e
23
+ puts "user already has role '#{r}'"
24
+ rescue Sequel::NoMatchingRow
25
+ raise UndefinedRole.new("Role '#{r}' does not exist")
26
+ end
27
+ end
28
+ end
29
+ self
30
+ end
31
+
32
+ def role_names
33
+ roles.map(&:id)
34
+ end
35
+
36
+ def to_s
37
+ "#{id}\t#{role_names.join(",")}\t#{capabilities.join(",")}"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,138 @@
1
+ require 'tmpdir'
2
+
3
+ CANDO_MIGRATION_DIR = "db/cando-migrations"
4
+ CANDO_SCHEMA = File.join(File.dirname(File.dirname(File.dirname(__FILE__))), "contrib", "initial_schema.rb")
5
+
6
+ namespace :cando do
7
+ desc "Initialize cando (creates schema and runs migration)"
8
+ task :init do
9
+ if Dir.glob("#{CANDO_MIGRATION_DIR}/*#{File.basename(CANDO_SCHEMA)}*").empty?
10
+ puts green("Copying cando schema to local cando migrations folder '#{CANDO_MIGRATION_DIR}'")
11
+ cando_schema_migration = File.join(CANDO_MIGRATION_DIR, "#{Time.now.strftime("%Y%m%d%H%M%S")}_#{File.basename(CANDO_SCHEMA)}")
12
+
13
+ FileUtils.mkdir_p(CANDO_MIGRATION_DIR) unless File.exists?(CANDO_MIGRATION_DIR)
14
+ FileUtils.cp(CANDO_SCHEMA, cando_schema_migration)
15
+ else
16
+ $stderr.puts red("skipping copying cando schema migration file: already exists")
17
+ end
18
+
19
+ Sequel::Migrator.run(connect_to_db, CANDO_MIGRATION_DIR, { allow_missing_migration_files: true} )
20
+
21
+ puts <<EOF
22
+ #{green("Success!")}
23
+
24
+ In order to add, update or remove a role, call
25
+
26
+ rake cando:add role=<rolename> capabilities=<cap1>,<cap2>,<cap3>
27
+ rake cando:update role=<rolename> capabilities=<cap1>,<cap2>,<cap3>
28
+ rake cando:remove role=<rolename>
29
+
30
+ When adding or updating a roles it doesn't matter whether the passed in capabilities
31
+ exist or not -- if not existant, they will be created automatically
32
+
33
+ For more cando rake tasks execute
34
+
35
+ rake -T cando
36
+
37
+ EOF
38
+
39
+ end
40
+
41
+ desc "Add a new role (pass in role name and capabilities with role=<name> capabilities=<cap1>,<cap2>,... )"
42
+ task :add do
43
+ add_role(false)
44
+ end
45
+
46
+ desc "Update role (pass in role name and capabilities with role=<name> capabilities=<cap1>,<cap2>,... )"
47
+ task :update do
48
+ add_role(true)
49
+ end
50
+
51
+
52
+ desc "Remove role (pass in role name with role=<name>)"
53
+ task :remove do
54
+ unless ENV['role']
55
+ $stderr.puts red("usage: rake cando:remove role=<rolename>")
56
+ exit 1
57
+ end
58
+
59
+ connect_to_db
60
+ unless r = CanDo::Role.find(:id => ENV['role'])
61
+ $stderr.puts red("role '#{args.role}' does not exist")
62
+ exit 1
63
+ end
64
+
65
+ r.destroy
66
+ end
67
+
68
+ desc "Assign role to user (args: roles=<r1>,<r2>,<rn> user=<user_urn>)"
69
+ task :assign do
70
+ roles = ENV['roles']
71
+ user_urn = ENV['user']
72
+
73
+ unless roles && user_urn
74
+ $stderr.puts red("usage: rake cando:assign user=<user_urn> roles=<role1>,<role2>,... ")
75
+ end
76
+
77
+ connect_to_db
78
+ include CanDo
79
+ assign_roles(user_urn, roles.split(","))
80
+ end
81
+
82
+ desc "List roles"
83
+ task :list do
84
+ connect_to_db
85
+ puts "ROLE\tCAPABILITIES"
86
+ CanDo::Role.all.each do |role|
87
+ puts role
88
+ end
89
+ end
90
+
91
+ desc "List users and their roles"
92
+ task :users do
93
+ connect_to_db
94
+ puts "USER_URN\tROLES\tCAPABILITIES"
95
+ CanDo::User.all.each do |user|
96
+ puts user
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ def green(text)
103
+ "\033[1;32;48m#{text}\033[m"
104
+ end
105
+
106
+ def red(text)
107
+ "\033[1;31;48m#{text}\033[m"
108
+ end
109
+
110
+ def add_role(force = false)
111
+ role = ENV['role']
112
+ capabilities = ENV['capabilities'] && ENV['capabilities'].split(",")
113
+
114
+ unless role && capabilities
115
+ puts red("usage: rake cando:add role=<rolename> capabilities=<cap1>,<cap2>,<cap3>")
116
+ exit 1
117
+ end
118
+
119
+ connect_to_db
120
+ if !force && CanDo::Role.find(:id => role)
121
+ puts red("Role '#{role}' already exists!")
122
+ puts "If you want to update '#{role}', please use 'rake cando:update'"
123
+ exit 1
124
+ end
125
+
126
+ include CanDo
127
+ define_role(role, capabilities)
128
+ end
129
+
130
+ def connect_to_db
131
+ unless ENV['CANDO_DB']
132
+ raise "Please pass in database config, e.g. CANDO_DB=mysql://user:passwd@host/database bundle exec rake cando:<command>"
133
+ end
134
+
135
+ CanDo.init do
136
+ connect(ENV['CANDO_DB'])
137
+ end
138
+ end
@@ -0,0 +1,126 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ #describe "Cando" do
4
+ # it "fails" do
5
+ # fail "hey buddy, you should probably rename this file and start specing for real"
6
+ # end
7
+ #end
8
+ #
9
+ describe "CanDo module methods" do
10
+ context "CanDo.cannot_block expects block accepting two parameters" do
11
+ it { expect{ CanDo.cannot_block }.to raise_error(CanDo::ConfigCannotBlockError) }
12
+ it { expect{ CanDo.cannot_block{|x| x} }.to raise_error(CanDo::ConfigCannotBlockError) }
13
+ it { expect{ CanDo.cannot_block{|x,y,z| x} }.to raise_error(CanDo::ConfigCannotBlockError) }
14
+
15
+ it { expect{ CanDo.cannot_block{|x,y| x} }.to_not raise_error(CanDo::ConfigCannotBlockError) }
16
+ end
17
+
18
+ context "CanDo.connect" do
19
+ it { expect{ CanDo.connect(nil) }.to raise_error(CanDo::ConfigMysqlConnectionError) }
20
+ it { expect{ CanDo.connect("sqlite::memory:") }.to raise_error(CanDo::ConfigMysqlDBError) }
21
+ it { CanDo.connect(ENV['CANDO_TEST_DB']).test_connection be_true }
22
+ end
23
+
24
+ context "CanDo.init" do
25
+ context "CanDo.cannot_block called from config" do
26
+ it { expect{ CanDo.init{ cannot_block }}.to raise_error(CanDo::ConfigCannotBlockError) }
27
+ end
28
+ end
29
+
30
+ context "CanDo exported methods" do
31
+ include CanDo
32
+
33
+ context "CanDo#define_role" do
34
+ context "empty role (no capabilities)" do
35
+ it { expect{ define_role("role", []) }.to change{CanDo::Role.count}.from(0).to(1) }
36
+ it { expect{ define_role("role", []) }.to change{CanDo::Capability.count}.by(0) }
37
+ end
38
+
39
+ context "standard situation" do
40
+ it { expect{ define_role("role", [:capability1, :capability2]) }.to change{CanDo::Role.count}.from(0).to(1) }
41
+ it { expect{ define_role("role", [:capability1, :capability2]) }.to change{CanDo::Capability.count}.from(0).to(2) }
42
+ end
43
+
44
+ context "duplication" do
45
+ before(:each) { define_role("role", [:capability]) }
46
+
47
+ it { expect{ define_role("role", [:new_capability]) }.to change{CanDo::Role.count}.by(0) }
48
+ it { expect{ define_role("new_role", [:capability]) }.to change{CanDo::Capability.count}.by(0) }
49
+ end
50
+
51
+ context "cleanup" do
52
+ before(:each) do
53
+ @role = define_role("role", [:capabilty])
54
+ end
55
+
56
+ it { expect{ @role.destroy }.to change{CanDo::Role.count}.from(1).to(0) }
57
+ it { expect{ @role.destroy }.to change{CanDo::Capability.count}.from(1).to(0) }
58
+ end
59
+ end
60
+
61
+ context "CanDo#assign_roles" do
62
+ include CanDo
63
+
64
+ context "user without roles" do
65
+ it { expect{ assign_roles("user", []) }.to change{CanDo::User.count}.from(0).to(1) }
66
+ it { expect{ assign_roles("user", []) }.to change{CanDo::Role.count}.by(0) }
67
+ end
68
+
69
+ context "invalid roles" do
70
+ it { expect{ assign_roles("user", ["non-existant-role"]) }.to raise_error(CanDo::UndefinedRole) }
71
+ it { expect{ assign_roles("user", [nil]) }.to raise_error(CanDo::UndefinedRole) }
72
+ end
73
+
74
+ context "standard situation" do
75
+ before(:each) do
76
+ @r1 = define_role("r1", [:c1, :c2])
77
+ @r2 = define_role("r2", [:c2, :c3])
78
+ end
79
+
80
+ it { expect{ assign_roles("user", ["r1"]) }.to change{CanDo::User.count}.from(0).to(1) }
81
+ it { expect{ assign_roles("user", ["r1","r2"]) }.to change{CanDo::User.count}.from(0).to(1) }
82
+
83
+ it { expect{ assign_roles("user", [@r1]) }.to change{CanDo::User.count}.from(0).to(1) }
84
+ it { expect{ assign_roles("user", ["r1",@r2]) }.to change{CanDo::User.count}.from(0).to(1) }
85
+ end
86
+
87
+ context "cleanup" do
88
+ before(:each) do
89
+ @role = define_role("role", [:capabilty])
90
+ @user = assign_roles("user", [@role])
91
+ end
92
+
93
+ it { expect{ @user.destroy }.to change{CanDo::User.count}.from(1).to(0) }
94
+ it { expect{ @user.destroy }.to change{CanDo::Role.count}.by(0) }
95
+ end
96
+ end
97
+
98
+ context "CanDo#can" do
99
+ include CanDo
100
+
101
+ before(:each) do
102
+ @role = define_role("role", [:capability1, :capabilty2])
103
+ @user = assign_roles("user", [@role])
104
+ end
105
+
106
+ context "CanDo#can" do
107
+ it { can("user", :capability1).should be_true }
108
+ it { can("user", :undefined_capability).should be_false }
109
+
110
+ it { expect{ |can| can("user", :capability1, &can) }.to yield_control }
111
+
112
+ context "CanDo.cannot_block" do
113
+ class DummyException < RuntimeError; end
114
+ before(:all) do
115
+ CanDo.cannot_block do |user, capabilty|
116
+ raise DummyException
117
+ end
118
+ end
119
+
120
+ it { expect{ can("user", :undefined_capability){} }.to raise_error(DummyException) }
121
+ it { expect{ can("user", :undefined_capability)}.to_not raise_error(DummyException) }
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,48 @@
1
+ require 'simplecov'
2
+ require 'cando'
3
+
4
+ module SimpleCov::Configuration
5
+ def clean_filters
6
+ @filters = []
7
+ end
8
+ end
9
+
10
+ SimpleCov.configure do
11
+ clean_filters
12
+ load_profile 'test_frameworks'
13
+ end
14
+
15
+ ENV["COVERAGE"] && SimpleCov.start do
16
+ add_filter "/.rvm/"
17
+ end
18
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
19
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
20
+
21
+ require 'rspec'
22
+ require 'cando'
23
+
24
+ # Requires supporting files with custom matchers and macros, etc,
25
+ # in ./support/ and its subdirectories.
26
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
27
+
28
+ ENV['CANDO_TEST_DB'] or raise "Please set CANDO_TEST_DB to a valid configuration similar to 'mysql://user:passwd@localhost/database'"
29
+
30
+ RSpec.configure do |config|
31
+ Sequel.extension :migration
32
+ migration = eval(File.read(File.join(File.dirname(File.dirname(__FILE__)), "contrib", "initial_schema.rb")))
33
+ db = nil
34
+
35
+ config.before(:suite) do
36
+ db = CanDo.init do
37
+ connect ENV['CANDO_TEST_DB']
38
+ end
39
+ end
40
+
41
+ config.before(:each) do
42
+ db.drop_table(*db.tables)
43
+ migration.apply(db, :up)
44
+ end
45
+ end
46
+
47
+ def connect
48
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cando
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Daniel Bornkessel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-05-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sequel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.10.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.10.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: rdoc
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '3.12'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '3.12'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bundler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '1.0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '1.0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: jeweler
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 2.0.1
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 2.0.1
110
+ - !ruby/object:Gem::Dependency
111
+ name: simplecov
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: cando description
127
+ email: daniel@soundcloud.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files:
131
+ - LICENSE.txt
132
+ - README.md
133
+ files:
134
+ - .document
135
+ - .rspec
136
+ - .travis.yml
137
+ - Gemfile
138
+ - Gemfile.lock
139
+ - LICENSE.txt
140
+ - README.md
141
+ - Rakefile
142
+ - VERSION
143
+ - cando.gemspec
144
+ - contrib/initial_schema.rb
145
+ - lib/cando.rb
146
+ - lib/models/capability.rb
147
+ - lib/models/role.rb
148
+ - lib/models/user.rb
149
+ - lib/tasks/cando.rake
150
+ - spec/cando_spec.rb
151
+ - spec/spec_helper.rb
152
+ homepage: http://github.com/kesselborn/cando
153
+ licenses:
154
+ - MIT
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ! '>='
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ segments:
166
+ - 0
167
+ hash: -3220119119017063207
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubyforge_project:
176
+ rubygems_version: 1.8.23
177
+ signing_key:
178
+ specification_version: 3
179
+ summary: Simple roles helper
180
+ test_files: []