cando 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|