cando 0.2.0 → 0.2.1
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/README.md +48 -0
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/cando.gemspec +2 -2
- data/lib/cando.rb +79 -6
- data/lib/models/role.rb +3 -0
- data/lib/models/user.rb +2 -2
- data/spec/cando_spec.rb +4 -10
- metadata +3 -3
data/README.md
CHANGED
@@ -68,6 +68,54 @@ to use those edit (or create) the `Rakefile` and include
|
|
68
68
|
rake cando:users # List users and their roles
|
69
69
|
|
70
70
|
### Using CanDo in your project's code
|
71
|
+
|
72
|
+
#### Api
|
73
|
+
Please see [the api documentation](http://rubydoc.info/gems/cando/CanDo) up to date documentation.
|
74
|
+
|
75
|
+
connect to db (usually called within init block):
|
76
|
+
|
77
|
+
CanDo.connect "mysql://user:passwd@host:port/database"
|
78
|
+
|
79
|
+
create a role; capabilities will be created if they don't exist yet:
|
80
|
+
|
81
|
+
define_role("role_name", ["capability1","capability2", "capability3", ...])
|
82
|
+
|
83
|
+
assign role(s) to a user: if no user with that id exist, a new one will
|
84
|
+
be created; if a role does not exist, an exception is raised. You can pass
|
85
|
+
in a role object or just role names; pass in empty array to remove all roles:
|
86
|
+
|
87
|
+
role3 = CanDo::Role.first
|
88
|
+
assign_roles("user1", ["role1", "role2", role3])
|
89
|
+
|
90
|
+
get user's capabilities; returns an array of strings:
|
91
|
+
|
92
|
+
capabilities("user1")
|
93
|
+
|
94
|
+
get user's roles; returns an array of strings:
|
95
|
+
|
96
|
+
roles("user1")
|
97
|
+
|
98
|
+
set default handler if `can("u","c"){ ... }` fails (usually called within init block):
|
99
|
+
|
100
|
+
CanDo.cannot_block { |user_urn, capability| raise "#{user_urn} is missing #{capability}" }
|
101
|
+
|
102
|
+
guard block by `can` function; will execute `cannot_block` if user is missing
|
103
|
+
this capability (see example below):
|
104
|
+
|
105
|
+
can("user1", :capability1) do
|
106
|
+
puts "woohoo"
|
107
|
+
end
|
108
|
+
|
109
|
+
use `can` function as an expression -- `cannot_block` will not be executed if
|
110
|
+
expressions resolves to `false`:
|
111
|
+
|
112
|
+
if can("user1", :capability1)
|
113
|
+
puts "woohoo"
|
114
|
+
else
|
115
|
+
puts ":("
|
116
|
+
end
|
117
|
+
|
118
|
+
#### Usage example
|
71
119
|
Using the CanDo in your code (working code with an empty database):
|
72
120
|
|
73
121
|
require 'cando'
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.1
|
data/cando.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "cando"
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Daniel Bornkessel"]
|
12
|
-
s.date = "2014-05-
|
12
|
+
s.date = "2014-05-27"
|
13
13
|
s.description = "CanDo is a small gem to implement a simple user access system based on users, roles & capabilites, where:\n\n each user can have 0, 1 or many roles\n each role can have 0, 1 or many capabilites\n\nUsers have capabilities by getting roles assigned (role == collection of capabilities). Within the code, the can helper method can be used to test whether a user has a certain capability or not (see below for a working code example)."
|
14
14
|
s.email = "daniel@soundcloud.com"
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/cando.rb
CHANGED
@@ -6,15 +6,41 @@ if File.basename($0) == "rake" # we are in a rake call: export our rake stuff
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module CanDo
|
9
|
+
# The provided cannot_block is not as expected
|
9
10
|
class ConfigCannotBlockError < RuntimeError; end
|
10
|
-
|
11
|
-
|
11
|
+
|
12
|
+
# CanDo.connect received an invalid or unsupported connection string
|
13
|
+
class ConfigDBError < RuntimeError; end
|
14
|
+
|
15
|
+
# Error while trying to connect to the database
|
16
|
+
class ConfigConnectionError < RuntimeError; end
|
17
|
+
|
18
|
+
# CanDo is not connected to a database but connection is needed
|
12
19
|
class DBNotConnected < RuntimeError; end
|
13
20
|
|
21
|
+
# Return current database connection
|
22
|
+
#
|
23
|
+
# raises DBNotConnected exception if CanDo is not connected to a db
|
14
24
|
def self.db
|
15
25
|
@db or raise DBNotConnected.new("CanDo is not connected to a database")
|
16
26
|
end
|
17
27
|
|
28
|
+
# Initializes CanDo
|
29
|
+
#
|
30
|
+
# This method should be called during boot up to configure CanDo with the db
|
31
|
+
# connection and the cannot_block:
|
32
|
+
#
|
33
|
+
# CanDo.init do
|
34
|
+
# # this will be executed if the user does not have the
|
35
|
+
# # asked-for capability (only applies if 'can' is passed a block)
|
36
|
+
# cannot_block do |user_urn, capability|
|
37
|
+
# raise "#{user_urn} can not #{capability} .. user capabilities are: #{capabilities(user_urn)}"
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# connect "mysql://cando_user:cando_passwd@host:port/database"
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# ...
|
18
44
|
def self.init(&block)
|
19
45
|
CanDo.instance_eval &block
|
20
46
|
|
@@ -32,8 +58,11 @@ module CanDo
|
|
32
58
|
end
|
33
59
|
|
34
60
|
|
35
|
-
#
|
36
|
-
#
|
61
|
+
# Block to be executed if <tt>can("user", :cap) { }</tt> will not be executed due to missing capability
|
62
|
+
#
|
63
|
+
# - the block needs to accept two arguments <tt>|user_urn, capability|</tt>
|
64
|
+
# - this function should be called in the init method (see there for an example).
|
65
|
+
# - if this block gets executed, it'll have the context of the <tt>can(...){ }</tt> call
|
37
66
|
def self.cannot_block(&block)
|
38
67
|
if !block
|
39
68
|
raise ConfigCannotBlockError.new("CanDo#cannot_block expects block")
|
@@ -45,9 +74,16 @@ module CanDo
|
|
45
74
|
@@cannot_block_proc = block
|
46
75
|
end
|
47
76
|
|
77
|
+
# Connect to database
|
78
|
+
#
|
79
|
+
# Pass in a connection string of the form
|
80
|
+
#
|
81
|
+
# mysql://user:passwd@host:port/database
|
82
|
+
#
|
83
|
+
# Raises CanDo::ConfigConnectionError or CanDo::ConfigDBError when problems occur
|
48
84
|
def self.connect(connection)
|
49
85
|
if connection =~ /sqlite/
|
50
|
-
raise
|
86
|
+
raise ConfigDBError.new("sqlite is not supported as it misses certain constraints")
|
51
87
|
end
|
52
88
|
|
53
89
|
begin
|
@@ -55,7 +91,7 @@ module CanDo
|
|
55
91
|
@db.test_connection
|
56
92
|
@db
|
57
93
|
rescue => e
|
58
|
-
raise
|
94
|
+
raise ConfigConnectionError.new(<<-EOF
|
59
95
|
Error connecting to database. Be sure to pass in a databse config like 'mysql://user:passwd@host/database':
|
60
96
|
#{e.message}
|
61
97
|
EOF
|
@@ -63,6 +99,26 @@ EOF
|
|
63
99
|
end
|
64
100
|
end
|
65
101
|
|
102
|
+
# Method to check whether a user has a certain capability
|
103
|
+
#
|
104
|
+
# It can be used in two manners:
|
105
|
+
#
|
106
|
+
# * pass it a block which is only executed if the user has the capability:
|
107
|
+
#
|
108
|
+
# can("user_urn", :capability) do
|
109
|
+
# puts "woohoo"
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# this will execute cannot_block if user is missing this capability.
|
113
|
+
#
|
114
|
+
# * use as an expression that return true or false/nil
|
115
|
+
#
|
116
|
+
# if can("user_urn", :capability) do
|
117
|
+
# puts "woohoo"
|
118
|
+
# else
|
119
|
+
# puts "epic fail"
|
120
|
+
# end
|
121
|
+
#
|
66
122
|
def can(user_urn, capability)
|
67
123
|
user = CanDo::User.find(:id => user_urn)
|
68
124
|
has_permission = user && user.can(capability)
|
@@ -78,14 +134,28 @@ EOF
|
|
78
134
|
has_permission
|
79
135
|
end
|
80
136
|
|
137
|
+
# Define or redefine a role
|
138
|
+
#
|
139
|
+
# Capabilities will be created if they don't exist yet
|
81
140
|
def define_role(role, capabilities)
|
82
141
|
CanDo::Role.define_role(role, capabilities)
|
83
142
|
end
|
84
143
|
|
144
|
+
# Assign role(s) to a user
|
145
|
+
#
|
146
|
+
# assign_roles("user_urn", ["role_name", role_object])
|
147
|
+
#
|
148
|
+
# - if no user with that id exist, a new one will be created
|
149
|
+
# - if a role does not exist, an CanDo::Role::UndefinedRole is raised
|
150
|
+
# - you can pass in a role object or just role names (see example above)
|
151
|
+
# - pass in an empty array to remove all roles from user
|
85
152
|
def assign_roles(user, roles)
|
86
153
|
CanDo::User.find_or_create(:id => user).assign_roles(roles)
|
87
154
|
end
|
88
155
|
|
156
|
+
# Get user's roles
|
157
|
+
#
|
158
|
+
# returns an array of strings
|
89
159
|
def roles(user)
|
90
160
|
user = CanDo::User.first(:id => user)
|
91
161
|
return [] unless user
|
@@ -93,6 +163,9 @@ EOF
|
|
93
163
|
user.roles.map(&:id)
|
94
164
|
end
|
95
165
|
|
166
|
+
# Get user's capabilities
|
167
|
+
#
|
168
|
+
# returns an array of strings
|
96
169
|
def capabilities(user)
|
97
170
|
user = CanDo::User.first(:id => user)
|
98
171
|
return [] unless user
|
data/lib/models/role.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
module CanDo
|
2
2
|
class Role < Sequel::Model(:cando_roles)
|
3
|
+
# This role does not exist
|
4
|
+
class UndefinedRole < Exception; end
|
5
|
+
|
3
6
|
many_to_many :users, :join_table => :cando_roles_users
|
4
7
|
many_to_many :capabilities, :join_table => :cando_capabilities_roles
|
5
8
|
unrestrict_primary_key
|
data/lib/models/user.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module CanDo
|
2
|
-
class UndefinedRole < Exception; end
|
3
2
|
class User < Sequel::Model(:cando_users)
|
3
|
+
|
4
4
|
many_to_many :roles, :join_table => :cando_roles_users
|
5
5
|
unrestrict_primary_key
|
6
6
|
|
@@ -22,7 +22,7 @@ module CanDo
|
|
22
22
|
rescue Sequel::UniqueConstraintViolation => e
|
23
23
|
puts "user already has role '#{r}'"
|
24
24
|
rescue Sequel::NoMatchingRow
|
25
|
-
raise UndefinedRole.new("Role '#{r}' does not exist")
|
25
|
+
raise Role::UndefinedRole.new("Role '#{r}' does not exist")
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
data/spec/cando_spec.rb
CHANGED
@@ -1,11 +1,5 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
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
3
|
describe "CanDo module methods" do
|
10
4
|
context "CanDo.cannot_block expects block accepting two parameters" do
|
11
5
|
it { expect{ CanDo.cannot_block }.to raise_error(CanDo::ConfigCannotBlockError) }
|
@@ -39,8 +33,8 @@ describe "CanDo module methods" do
|
|
39
33
|
end
|
40
34
|
|
41
35
|
context "CanDo.connect" do
|
42
|
-
it { expect{ CanDo.connect(nil) }.to raise_error(CanDo::
|
43
|
-
it { expect{ CanDo.connect("sqlite::memory:") }.to raise_error(CanDo::
|
36
|
+
it { expect{ CanDo.connect(nil) }.to raise_error(CanDo::ConfigConnectionError) }
|
37
|
+
it { expect{ CanDo.connect("sqlite::memory:") }.to raise_error(CanDo::ConfigDBError) }
|
44
38
|
it { CanDo.connect(ENV['CANDO_TEST_DB']).test_connection be_true }
|
45
39
|
end
|
46
40
|
|
@@ -90,8 +84,8 @@ describe "CanDo module methods" do
|
|
90
84
|
end
|
91
85
|
|
92
86
|
context "invalid roles" do
|
93
|
-
it { expect{ assign_roles("user", ["non-existant-role"]) }.to raise_error(CanDo::UndefinedRole) }
|
94
|
-
it { expect{ assign_roles("user", [nil]) }.to raise_error(CanDo::UndefinedRole) }
|
87
|
+
it { expect{ assign_roles("user", ["non-existant-role"]) }.to raise_error(CanDo::Role::UndefinedRole) }
|
88
|
+
it { expect{ assign_roles("user", [nil]) }.to raise_error(CanDo::Role::UndefinedRole) }
|
95
89
|
end
|
96
90
|
|
97
91
|
context "standard situation" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cando
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-05-
|
12
|
+
date: 2014-05-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sequel
|
@@ -169,7 +169,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
169
169
|
version: '0'
|
170
170
|
segments:
|
171
171
|
- 0
|
172
|
-
hash:
|
172
|
+
hash: -614063614560370007
|
173
173
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
174
|
none: false
|
175
175
|
requirements:
|