kankri 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG +6 -0
- data/Rakefile +7 -0
- data/kankri.gemspec +7 -4
- data/lib/kankri.rb +2 -1
- data/lib/kankri/exceptions.rb +1 -0
- data/lib/kankri/password_check.rb +80 -4
- data/lib/kankri/privilege_check.rb +104 -0
- data/lib/kankri/privilege_set.rb +43 -50
- data/lib/kankri/privilege_subject.rb +32 -2
- data/lib/kankri/simple_authenticator.rb +149 -8
- data/lib/kankri/version.rb +2 -1
- data/spec/privilege_subject_spec.rb +2 -1
- metadata +75 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a28b6708e74babd3eb76fbe143d1e9ddad83b7e
|
4
|
+
data.tar.gz: 589070377d1025cb55c66f96d9952bf4144789fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 338b0fb18ad4fdc91105e070412bfff3c750ffb60c01155ef53167d43d5a5e90a9efc751ab84d2ae12210aee0b71c726be83d52e05026247831fd132841bb9ce
|
7
|
+
data.tar.gz: 34d9fccd754ae9b75e05c6a56b9fd4e9f1046c3196417f32571c13d0f2417135697bb8ae68d7932298a91e88f8916860cd3b48edfd10543f01cb5f3504d0fe99
|
data/.gitignore
CHANGED
data/CHANGELOG
ADDED
data/Rakefile
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
4
|
+
require 'yardstick/rake/measurement'
|
5
|
+
require 'yardstick/rake/verify'
|
3
6
|
|
4
7
|
RSpec::Core::RakeTask.new
|
8
|
+
YARD::Rake::YardocTask.new
|
9
|
+
Yardstick::Rake::Verify.new
|
10
|
+
Yardstick::Rake::Measurement.new
|
5
11
|
|
6
12
|
task :default => :spec
|
13
|
+
task :doc => :yard
|
7
14
|
task :test => :spec
|
data/kankri.gemspec
CHANGED
@@ -24,8 +24,11 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
26
26
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
27
|
-
spec.add_development_dependency 'rake'
|
28
|
-
spec.add_development_dependency 'rspec'
|
29
|
-
spec.add_development_dependency 'simplecov'
|
30
|
-
spec.add_development_dependency 'fuubar'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10', '>= 10.1.1'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 2', '>= 2.14'
|
29
|
+
spec.add_development_dependency 'simplecov', '~> 0.8'
|
30
|
+
spec.add_development_dependency 'fuubar', '~> 1'
|
31
|
+
spec.add_development_dependency 'yard', '~> 0.8'
|
32
|
+
spec.add_development_dependency 'yardstick', '~> 0.9'
|
33
|
+
spec.add_development_dependency 'backports', '~> 3', '>= 3.3.5'
|
31
34
|
end
|
data/lib/kankri.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'kankri/password_check'
|
2
|
+
require 'kankri/privilege_check'
|
2
3
|
require 'kankri/privilege_set'
|
3
4
|
require 'kankri/privilege_subject'
|
4
5
|
require 'kankri/simple_authenticator'
|
@@ -26,7 +27,7 @@ module Kankri
|
|
26
27
|
#
|
27
28
|
# @api public
|
28
29
|
# @example Create an authenticator from a hash of users.
|
29
|
-
# Kankri.
|
30
|
+
# Kankri.authenticator_from_hash(
|
30
31
|
# admin: {
|
31
32
|
# password: 'hunter2',
|
32
33
|
# privileges: {
|
data/lib/kankri/exceptions.rb
CHANGED
@@ -1,34 +1,110 @@
|
|
1
1
|
module Kankri
|
2
2
|
# A method object that represents a check on username/password pairs
|
3
|
+
#
|
4
|
+
# This is a basic check based on string comparison, failing if either
|
5
|
+
# username or password are empty or nil. If used with a hashing
|
6
|
+
# authenticator, the hashing must be done before the password checking.
|
7
|
+
# Similarly, PasswordCheck does not convert to or from symbols; the
|
8
|
+
# authenticator must do this itself.
|
3
9
|
class PasswordCheck
|
10
|
+
# Creates a password check instance
|
11
|
+
#
|
12
|
+
# Passwords may be literal passwords, hashes or any other secret that
|
13
|
+
# can be compared by ==. Any hashing or other processing (such as type
|
14
|
+
# conversion) must be done by the Authenticator.
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
# @example Initialises a PasswordCheck.
|
18
|
+
# PasswordCheck.new('alf', 'hunter2', 'alf' => 'hunter2')
|
19
|
+
#
|
20
|
+
# @param username [Object] The username; this is typically a Symbol or a
|
21
|
+
# String.
|
22
|
+
# @param password [String] The password to check against the passwords
|
23
|
+
# database.
|
24
|
+
# @param passwords [Hash] The hash mapping usernames to their passwords.
|
4
25
|
def initialize(username, password, passwords)
|
5
26
|
@username = username
|
6
27
|
@password = password
|
7
28
|
@passwords = passwords
|
8
29
|
end
|
9
30
|
|
31
|
+
# Checks to see if the authentication credentials are correct
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
# @example Perform a successful authentication check.
|
35
|
+
# checker.ok?
|
36
|
+
# #=> true
|
37
|
+
# @example Perform an unsuccessful authentication check.
|
38
|
+
# checker.ok?
|
39
|
+
# #=> false
|
40
|
+
#
|
41
|
+
# @return [Boolean] True if the password matches; false otherwise.
|
10
42
|
def ok?
|
11
43
|
auth_present? && user_known? && password_match?
|
12
44
|
end
|
13
45
|
|
46
|
+
# Creates and runs a password check
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
# @example Check a correct password.
|
50
|
+
# PasswordCheck.check('alf', 'hunter2', 'alf' => 'hunter2')
|
51
|
+
# #=> true
|
52
|
+
# @example Check an incorrect password.
|
53
|
+
# PasswordCheck.check('alf', 'nope', 'alf' => 'hunter2')
|
54
|
+
# #=> false
|
55
|
+
#
|
56
|
+
# @param (see #initialize)
|
57
|
+
#
|
58
|
+
# @return (see #ok?)
|
59
|
+
def self.check(*args)
|
60
|
+
PasswordCheck.new(*args).ok?
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Checks to see if the authentication credentials are present and non-empty
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
# @return [Boolean] True if the credentials are present; false otherwise.
|
14
70
|
def auth_present?
|
15
71
|
username_present? && password_present?
|
16
72
|
end
|
17
73
|
|
74
|
+
# Checks to see if the username is present and non-empty
|
75
|
+
#
|
76
|
+
# @api private
|
77
|
+
#
|
78
|
+
# @return [Boolean] True if the username is present; false otherwise.
|
18
79
|
def username_present?
|
19
80
|
!(@username.nil? || @username.empty?)
|
20
81
|
end
|
21
82
|
|
83
|
+
# Checks to see if the password is present and non-empty
|
84
|
+
#
|
85
|
+
# @api private
|
86
|
+
#
|
87
|
+
# @return [Boolean] True if the password is present; false otherwise.
|
22
88
|
def password_present?
|
23
89
|
!(@password.nil? || @password.empty?)
|
24
90
|
end
|
25
91
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
92
|
+
# Checks to see if the user has a known password
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
#
|
96
|
+
# @return [Boolean] True if the user's password is known; false otherwise.
|
30
97
|
def user_known?
|
31
98
|
@passwords.key?(@username)
|
32
99
|
end
|
100
|
+
|
101
|
+
# Checks to see if the password matches that on record for the user
|
102
|
+
#
|
103
|
+
# @api private
|
104
|
+
#
|
105
|
+
# @return [Boolean] True if the password matches; false otherwise.
|
106
|
+
def password_match?
|
107
|
+
@passwords.fetch(@username) == @password
|
108
|
+
end
|
33
109
|
end
|
34
110
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Kankri
|
2
|
+
# A method object for checking privileges
|
3
|
+
#
|
4
|
+
# A PrivilegeChecker takes the target privilege key and required privilege,
|
5
|
+
# as well as the hash mapping privilege keys to their
|
6
|
+
class PrivilegeChecker
|
7
|
+
# Creates a PrivilegeChecker
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
# @example Initialise a passing privilege check.
|
11
|
+
# PrivilegeChecker.new(:foo, :bar, foo: [:bar])
|
12
|
+
# @example Initialise a failing privilege check.
|
13
|
+
# PrivilegeChecker.new(:foo, :bar, foo: [])
|
14
|
+
#
|
15
|
+
# @param target [Symbol] The privilege key that is the target of this
|
16
|
+
# privilege check.
|
17
|
+
# @param requisite [Symbol] The privilege required under the privilege
|
18
|
+
# key.
|
19
|
+
# @param privileges [Hash] A hash mapping privilege keys to their
|
20
|
+
# privilege lists.
|
21
|
+
def initialize(target, requisite, privileges)
|
22
|
+
@target = target
|
23
|
+
@requisite = requisite
|
24
|
+
@privileges = privileges
|
25
|
+
end
|
26
|
+
|
27
|
+
# Runs the privilege checker and checks the privilege
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
# @example Runs a passing privilege checker.
|
31
|
+
# checker.run
|
32
|
+
# #=> true
|
33
|
+
# @example Runs a failing privilege checker.
|
34
|
+
# checker.run
|
35
|
+
# #=> false
|
36
|
+
#
|
37
|
+
# @return [Boolean] True if the privilege is held by the privilege set
|
38
|
+
# for the target; false otherwise.
|
39
|
+
def valid?
|
40
|
+
target_in_privileges? && has_privilege?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates and runs a privilege checker
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
# @example Do a passing privilege check.
|
47
|
+
# PrivilegeChecker.check(:foo, :bar, foo: [:bar])
|
48
|
+
# #=> true
|
49
|
+
#
|
50
|
+
# @example Do a failing privilege check.
|
51
|
+
# PrivilegeChecker.check(:foo, :bar, foo: [])
|
52
|
+
# #=> false
|
53
|
+
#
|
54
|
+
# @param (see #initialize)
|
55
|
+
#
|
56
|
+
# @return (see #valid?)
|
57
|
+
def self.check(*args)
|
58
|
+
new(*args).valid?
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Checks to see if the target is in the privileges set
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
#
|
67
|
+
# @return [Boolean] True if the target key is in the privileges set;
|
68
|
+
# false otherwise.
|
69
|
+
def target_in_privileges?
|
70
|
+
@privileges.key?(@target)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Checks to see if the privilege request is satisfied for the target
|
74
|
+
#
|
75
|
+
# This assumes that the target exists in the privilege list.
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
#
|
79
|
+
# @return [Boolean] True if the privilege is held by the privilege set
|
80
|
+
# for the target; false otherwise.
|
81
|
+
def has_privilege?
|
82
|
+
has_all? || has_direct?
|
83
|
+
end
|
84
|
+
|
85
|
+
# Checks to see if the privilege request is satisfied by an 'all' clause
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
# @return [Boolean] True if this privilege set has all privileges for a
|
89
|
+
# target; false otherwise.
|
90
|
+
def has_all?
|
91
|
+
@privileges.fetch(@target) == :all
|
92
|
+
end
|
93
|
+
|
94
|
+
# Checks to see if the privilege request is satisfied directly
|
95
|
+
#
|
96
|
+
# @api private
|
97
|
+
#
|
98
|
+
# @return [Boolean] True if this privilege set explicitly has a certain
|
99
|
+
# privilege for a certain target; false otherwise.
|
100
|
+
def has_direct?
|
101
|
+
@privileges.fetch(@target).include?(@requisite)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/kankri/privilege_set.rb
CHANGED
@@ -1,21 +1,32 @@
|
|
1
1
|
require 'kankri/exceptions'
|
2
|
+
require 'kankri/privilege_check'
|
2
3
|
|
3
4
|
module Kankri
|
4
|
-
# Wrapper around a set of privileges a
|
5
|
+
# Wrapper around a set of privileges a user has
|
6
|
+
#
|
7
|
+
# The PrivilegeSet is the return value of an Authenticator, and represents
|
8
|
+
# the level of privilege the
|
5
9
|
class PrivilegeSet
|
6
|
-
# Initialises a privilege set
|
10
|
+
# Initialises a privilege set
|
7
11
|
#
|
8
12
|
# @api public
|
9
|
-
# @example
|
13
|
+
# @example Create a privilege set with no privileges.
|
10
14
|
# PrivilegeSet.new({})
|
11
|
-
# @example
|
15
|
+
# @example Create a privilege set with some privileges.
|
12
16
|
# PrivilegeSet.new({channel_set: [:get, :put]})
|
13
17
|
def initialize(privileges)
|
14
|
-
@privileges = privileges
|
15
|
-
symbolise_privileges
|
18
|
+
@privileges = symbolise_privileges(privileges)
|
16
19
|
end
|
17
20
|
|
18
21
|
# Requires a certain privilege on a certain target
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
# @example Check your privilege.
|
25
|
+
# privs.require(:channel, :put)
|
26
|
+
#
|
27
|
+
# @param (see #has?)
|
28
|
+
#
|
29
|
+
# @return [void]
|
19
30
|
def require(target, privilege)
|
20
31
|
fail(InsufficientPrivilegeError) unless has?(target, privilege)
|
21
32
|
end
|
@@ -23,65 +34,47 @@ module Kankri
|
|
23
34
|
# Checks to see if a certain privilege exists on a given target
|
24
35
|
#
|
25
36
|
# @api public
|
26
|
-
# @example
|
37
|
+
# @example Check your privilege.
|
27
38
|
# privs.has?(:channel, :put)
|
28
39
|
# #=> false
|
29
40
|
#
|
30
|
-
# @param target [Symbol]
|
31
|
-
# @param privilege [Symbol]
|
41
|
+
# @param target [Symbol] The handler target the privilege is for.
|
42
|
+
# @param privilege [Symbol] The privilege (one of :get, :put, :post or
|
32
43
|
# :delete).
|
33
44
|
#
|
34
|
-
# @return [Boolean]
|
45
|
+
# @return [Boolean] True if the privileges are sufficient; false
|
35
46
|
# otherwise.
|
36
47
|
def has?(privilege, target)
|
37
|
-
PrivilegeChecker.
|
48
|
+
PrivilegeChecker.check(target.to_sym, privilege.to_sym, @privileges)
|
38
49
|
end
|
39
50
|
|
40
51
|
private
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
53
|
+
# Converts the keys and values in a privileges hash into Symbols
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
#
|
57
|
+
# @param privileges [Hash] The privilege hash to symbolise.
|
58
|
+
#
|
59
|
+
# @return [Hash] The symbolised privileges set.
|
60
|
+
def symbolise_privileges(privileges)
|
61
|
+
Hash[
|
62
|
+
privileges.map do |key, key_privs|
|
63
|
+
[key.to_sym, symbolise_privilege_list(key_privs)]
|
64
|
+
end
|
65
|
+
]
|
46
66
|
end
|
47
67
|
|
68
|
+
# Converts a privilege list to Symbols
|
69
|
+
#
|
70
|
+
# If the privilege list is the String 'all', it will become :all.
|
71
|
+
# If it is an actual list, each privilege will be converted to a Symbol.
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
#
|
75
|
+
# @return [Object] The symbolised privilege list.
|
48
76
|
def symbolise_privilege_list(privlist)
|
49
77
|
privlist.is_a?(Array) ? privlist.map(&:to_sym) : privlist.to_sym
|
50
78
|
end
|
51
79
|
end
|
52
|
-
|
53
|
-
# A method object for checking privileges.
|
54
|
-
class PrivilegeChecker
|
55
|
-
def initialize(target, requisite, privileges)
|
56
|
-
@target = target.intern
|
57
|
-
@requisite = requisite.intern
|
58
|
-
@privileges = privileges
|
59
|
-
end
|
60
|
-
|
61
|
-
def check?
|
62
|
-
has_all? || has_direct?
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
# @return [Boolean] true if this privilege set has all privileges for a
|
68
|
-
# target.
|
69
|
-
def has_all?
|
70
|
-
@privileges[@target] == :all
|
71
|
-
end
|
72
|
-
|
73
|
-
# @return [Boolean] true if this privilege set explicitly has a certain
|
74
|
-
# privilege for a certain target.
|
75
|
-
def has_direct?
|
76
|
-
target_in_privileges? && requisite_in_target_privileges?
|
77
|
-
end
|
78
|
-
|
79
|
-
def target_in_privileges?
|
80
|
-
@privileges.key?(@target)
|
81
|
-
end
|
82
|
-
|
83
|
-
def requisite_in_target_privileges?
|
84
|
-
@privileges[@target].include?(@requisite)
|
85
|
-
end
|
86
|
-
end
|
87
80
|
end
|
@@ -4,12 +4,42 @@ module Kankri
|
|
4
4
|
# This expects the including class to define a method, 'privilege_key',
|
5
5
|
# which identifies the object in the privilege set.
|
6
6
|
module PrivilegeSubject
|
7
|
-
# Checks whether
|
7
|
+
# Checks whether a privilege is granted for this object
|
8
|
+
#
|
9
|
+
# This looks in the privilege set under the key returned by #privilege_key.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
# @example Checks for a privilege that is in the set for this object.
|
13
|
+
# subject.can?(:get, privilege_set)
|
14
|
+
# #=> true
|
15
|
+
#
|
16
|
+
# @example Checks for a privilege that is not in the set for this object.
|
17
|
+
# subject.can?(:get, privilege_set)
|
18
|
+
# #=> false
|
19
|
+
#
|
20
|
+
# @param operation [Object] The String or Symbol identifying the operation
|
21
|
+
# for which privileges are required.
|
22
|
+
#
|
23
|
+
# @param privilege_set [PrivilegeSet] The set of privileges that must
|
24
|
+
# contain the required privilege.
|
25
|
+
#
|
26
|
+
# @return [Boolean] True if the privileges are sufficient; false
|
27
|
+
# otherwise.
|
8
28
|
def can?(operation, privilege_set)
|
9
29
|
privilege_set.has?(operation, privilege_key)
|
10
30
|
end
|
11
31
|
|
12
|
-
#
|
32
|
+
# Raises an exception if a privilege is not granted for this object
|
33
|
+
#
|
34
|
+
# This looks in the privilege set under the key returned by #privilege_key.
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
# @example Requires a privilege to continue.
|
38
|
+
# subject.require(:get, privilege_set)
|
39
|
+
#
|
40
|
+
# @param (see #can?)
|
41
|
+
#
|
42
|
+
# @return [void]
|
13
43
|
def fail_if_cannot(operation, privilege_set)
|
14
44
|
privilege_set.require(operation, privilege_key)
|
15
45
|
end
|
@@ -6,21 +6,82 @@ module Kankri
|
|
6
6
|
# This object holds user data in memory, including passwords. It is thus
|
7
7
|
# not secure for mission-critical applications.
|
8
8
|
class SimpleAuthenticator
|
9
|
-
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
# Makes hashing functions for users based on SHA256
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
# @example Create a set of hashing functions for a given set of usernames
|
15
|
+
# SimpleAuthenticator::sha256_hasher(['alf', 'roy', 'busby'])
|
16
|
+
#
|
17
|
+
# @param usernames [Array] A list of usernames to form the keys of the
|
18
|
+
# hashing table.
|
19
|
+
#
|
20
|
+
# @return [Hash] A hash mapping usernames to functions that will take
|
21
|
+
# passwords and return their hashed equivalent.
|
10
22
|
def self.sha256_hasher(usernames)
|
11
23
|
digest_hasher(usernames, Digest::SHA256)
|
12
24
|
end
|
13
25
|
|
14
|
-
# Makes hashing functions for users based on a Digest implementation
|
26
|
+
# Makes hashing functions for users based on a Digest implementation
|
27
|
+
#
|
28
|
+
# Each hashing function uses a random salt value, which is stored inside
|
29
|
+
# the function and unique to the username.
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
# @example Create a set of hashing functions for a given set of usernames
|
33
|
+
# SimpleAuthenticator::digest_hasher(['joe', 'ron'], Digest::SHA256)
|
34
|
+
#
|
35
|
+
# @param usernames [Array] A list of usernames to form the keys of the
|
36
|
+
# hashing table.
|
37
|
+
# @param hasher [Digest] A Digest to use when hashing the user passwords
|
38
|
+
#
|
39
|
+
# @return [Hash] A hash mapping usernames to functions that will take
|
40
|
+
# passwords and return their hashed equivalent.
|
15
41
|
def self.digest_hasher(usernames, hasher)
|
16
42
|
Hash[
|
17
43
|
usernames.map do |username|
|
18
44
|
salt = SecureRandom.random_bytes
|
19
|
-
[username, ->(password) { hasher.digest(password + salt) }
|
45
|
+
[username, ->(password) { hasher.digest(password + salt) }]
|
20
46
|
end
|
21
47
|
]
|
22
48
|
end
|
23
49
|
|
50
|
+
# Initialises the SimpleAuthenticator
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
# @example Initialises the SimpleAuthenticator with a user hash.
|
54
|
+
# SimpleAuthenticator.new(
|
55
|
+
# admin: {
|
56
|
+
# password: 'hunter2',
|
57
|
+
# privileges: {
|
58
|
+
# foo: 'all',
|
59
|
+
# bar: ['abc', 'def', 'ghi'],
|
60
|
+
# baz: []
|
61
|
+
# }
|
62
|
+
# }
|
63
|
+
# )
|
64
|
+
# @example Initialises the SimpleAuthenticator with a custom hasher.
|
65
|
+
# SimpleAuthenticator.new(
|
66
|
+
# { admin: {
|
67
|
+
# password: 'hunter2',
|
68
|
+
# privileges: {
|
69
|
+
# foo: 'all',
|
70
|
+
# bar: ['abc', 'def', 'ghi'],
|
71
|
+
# baz: []
|
72
|
+
# }
|
73
|
+
# }
|
74
|
+
# }, hasher
|
75
|
+
# )
|
76
|
+
#
|
77
|
+
# @param users [String] A hash mapping usernames (which may be Strings or
|
78
|
+
# Symbols) to hashes containing a mapping from :password to the user's
|
79
|
+
# password, and from :privileges to a hash mapping privilege keys to
|
80
|
+
# privilege lists.
|
81
|
+
# @param hash_maker [Object] A callable that takes a list of usernames
|
82
|
+
# and returns a hash mapping the usernames to functions that hash
|
83
|
+
# passwords for those users. If nil, a sensible default hasher will be
|
84
|
+
# used.
|
24
85
|
def initialize(users, hash_maker = nil)
|
25
86
|
hash_maker ||= self.class.method(:sha256_hasher)
|
26
87
|
@users = users
|
@@ -30,6 +91,23 @@ module Kankri
|
|
30
91
|
@privilege_sets = privilege_sets
|
31
92
|
end
|
32
93
|
|
94
|
+
# Attempts to authenticate with the given username and password
|
95
|
+
#
|
96
|
+
# This will fail with an AuthenticationFailure exception if the credentials
|
97
|
+
# are invalid.
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
# @example Authenticates with a String username and password.
|
101
|
+
# auth.authenticate('joe_bloggs', 'hunter2')
|
102
|
+
# @example Authenticates with a Symbol username and String password.
|
103
|
+
# auth.authenticate(:joe_bloggs, 'hunter2')
|
104
|
+
#
|
105
|
+
# @param username [Object] The candidate username; this may be either a
|
106
|
+
# String or a Symbol, and will be normalised to a Symbol.
|
107
|
+
# @param username [Object] The candidate username; this may be either a
|
108
|
+
# String or a Symbol, and will be normalised to a String.
|
109
|
+
#
|
110
|
+
# @return [PrivilegeSet] The privilege set for the username
|
33
111
|
def authenticate(username, password)
|
34
112
|
auth_fail unless auth_ok?(username.intern, password.to_s)
|
35
113
|
privileges_for(username.intern)
|
@@ -37,11 +115,20 @@ module Kankri
|
|
37
115
|
|
38
116
|
private
|
39
117
|
|
40
|
-
|
41
|
-
|
42
|
-
|
118
|
+
# Returns the privilege set for the given username
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
#
|
122
|
+
# @param username [Object] The username whose privilege set is sought.
|
123
|
+
#
|
124
|
+
# @return [PrivilegeSet] The privilege set for the username
|
125
|
+
def_delegator :@privilege_sets, :fetch, :privileges_for
|
43
126
|
|
44
127
|
# Creates a hash mapping username symbols to their password strings
|
128
|
+
#
|
129
|
+
# @api private
|
130
|
+
#
|
131
|
+
# @return [Hash] A hash mapping usernames to passwords
|
45
132
|
def passwords
|
46
133
|
transform_users do |name, entry|
|
47
134
|
plaintext = entry.fetch(:password).to_s
|
@@ -50,29 +137,83 @@ module Kankri
|
|
50
137
|
end
|
51
138
|
|
52
139
|
# Creates a hash mapping username symbols to their privilege sets
|
140
|
+
#
|
141
|
+
# @api private
|
142
|
+
#
|
143
|
+
# @return [Hash] A hash mapping usernames to privilege sets
|
53
144
|
def privilege_sets
|
54
145
|
transform_users { |_, entry| PrivilegeSet.new(entry.fetch(:privileges)) }
|
55
146
|
end
|
56
147
|
|
148
|
+
# Creates a new Hash by modifying the entries of the user hash
|
149
|
+
#
|
150
|
+
# @api private
|
151
|
+
#
|
152
|
+
# @yieldparam name [Object] The username.
|
153
|
+
# @yieldparam entry [Hash] The user entry.
|
154
|
+
#
|
155
|
+
# @return [Hash] The hash mapping usernames to the yielded values.
|
57
156
|
def transform_users
|
58
157
|
Hash[@users.map { |name, entry| [name.intern, (yield name, entry)] }]
|
59
158
|
end
|
60
159
|
|
160
|
+
# Fails with an AuthenticationFailure exception
|
161
|
+
#
|
162
|
+
# @api private
|
163
|
+
#
|
164
|
+
# @return [void]
|
61
165
|
def auth_fail
|
62
166
|
fail(Kankri::AuthenticationFailure)
|
63
167
|
end
|
64
168
|
|
169
|
+
# Checks to see if given authentication credentials are OK
|
170
|
+
#
|
171
|
+
# @api private
|
172
|
+
#
|
173
|
+
# @param username [Object] The username of the authentication attempt.
|
174
|
+
# @param password [Object] The password of the authentication attempt.
|
175
|
+
#
|
176
|
+
# @return [Boolean] True if the authentication attempt fails; false
|
177
|
+
# otherwise.
|
65
178
|
def auth_ok?(username, password)
|
66
179
|
username_present?(username) && password_ok?(username, password)
|
67
180
|
end
|
68
181
|
|
182
|
+
# Checks to see if the username is in the system
|
183
|
+
#
|
184
|
+
# @api private
|
185
|
+
#
|
186
|
+
# @param username [Object] The username of the authentication attempt.
|
187
|
+
#
|
188
|
+
# @return [Boolean] True if the username exists in the authenticator;
|
189
|
+
# false otherwise.
|
69
190
|
def username_present?(username)
|
70
191
|
@hashers.key?(username) && @passwords.key?(username)
|
71
192
|
end
|
72
193
|
|
194
|
+
# Checks the password to see if it is correct for this username
|
195
|
+
#
|
196
|
+
# @api private
|
197
|
+
#
|
198
|
+
# @param username [Object] The username of the authentication attempt.
|
199
|
+
# @param password [Object] The password of the authentication attempt.
|
200
|
+
#
|
201
|
+
# @return [Boolean] True if the password is correct; false otherwise.
|
73
202
|
def password_ok?(username, password)
|
74
|
-
hashed_pass =
|
75
|
-
PasswordCheck.
|
203
|
+
hashed_pass = hashed_password(username, password)
|
204
|
+
PasswordCheck.check(username, hashed_pass, @passwords)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Applies the user's hashing function to the candidate password
|
208
|
+
#
|
209
|
+
# @api private
|
210
|
+
#
|
211
|
+
# @param username [Object] The username of the authentication attempt.
|
212
|
+
# @param password [Object] The password of the authentication attempt.
|
213
|
+
#
|
214
|
+
# @return [Object] The hashed password.
|
215
|
+
def hashed_password(username, password)
|
216
|
+
@hashers.fetch(username).call(password)
|
76
217
|
end
|
77
218
|
end
|
78
219
|
end
|
data/lib/kankri/version.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'kankri'
|
3
3
|
|
4
|
+
# A mock object for testing PrivilegeSubject
|
4
5
|
class MockPrivilegeSubject
|
5
6
|
include Kankri::PrivilegeSubject
|
6
7
|
|
@@ -13,7 +14,7 @@ describe MockPrivilegeSubject do
|
|
13
14
|
let(:privilege_set) { double(:privilege_set) }
|
14
15
|
let(:operation) { double(:operation) }
|
15
16
|
|
16
|
-
{fail_if_cannot: :require, can?: :has?}.each do |subject_meth, set_meth|
|
17
|
+
{ fail_if_cannot: :require, can?: :has? }.each do |subject_meth, set_meth|
|
17
18
|
describe "##{subject_meth}" do
|
18
19
|
context 'when given a valid privilege set and operation' do
|
19
20
|
it "calls ##{set_meth} on the privilege set with the handler target" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kankri
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Windsor
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,58 +28,118 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10'
|
31
34
|
- - '>='
|
32
35
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
36
|
+
version: 10.1.1
|
34
37
|
type: :development
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '10'
|
38
44
|
- - '>='
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
46
|
+
version: 10.1.1
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: rspec
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
44
50
|
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2'
|
45
54
|
- - '>='
|
46
55
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
56
|
+
version: '2.14'
|
48
57
|
type: :development
|
49
58
|
prerelease: false
|
50
59
|
version_requirements: !ruby/object:Gem::Requirement
|
51
60
|
requirements:
|
61
|
+
- - ~>
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '2'
|
52
64
|
- - '>='
|
53
65
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
66
|
+
version: '2.14'
|
55
67
|
- !ruby/object:Gem::Dependency
|
56
68
|
name: simplecov
|
57
69
|
requirement: !ruby/object:Gem::Requirement
|
58
70
|
requirements:
|
59
|
-
- -
|
71
|
+
- - ~>
|
60
72
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
73
|
+
version: '0.8'
|
62
74
|
type: :development
|
63
75
|
prerelease: false
|
64
76
|
version_requirements: !ruby/object:Gem::Requirement
|
65
77
|
requirements:
|
66
|
-
- -
|
78
|
+
- - ~>
|
67
79
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
80
|
+
version: '0.8'
|
69
81
|
- !ruby/object:Gem::Dependency
|
70
82
|
name: fuubar
|
71
83
|
requirement: !ruby/object:Gem::Requirement
|
72
84
|
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '1'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ~>
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '1'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: yard
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.8'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ~>
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0.8'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: yardstick
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ~>
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0.9'
|
116
|
+
type: :development
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ~>
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0.9'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: backports
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ~>
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '3'
|
73
130
|
- - '>='
|
74
131
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
132
|
+
version: 3.3.5
|
76
133
|
type: :development
|
77
134
|
prerelease: false
|
78
135
|
version_requirements: !ruby/object:Gem::Requirement
|
79
136
|
requirements:
|
137
|
+
- - ~>
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '3'
|
80
140
|
- - '>='
|
81
141
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
142
|
+
version: 3.3.5
|
83
143
|
description: "\n Kankri is a library for quickly setting up basic authentication
|
84
144
|
with\n object-action privileges. It's intended to be used in projects which
|
85
145
|
need\n a simple auth system with no run-time requirements and little set-up.
|
@@ -92,6 +152,7 @@ extra_rdoc_files: []
|
|
92
152
|
files:
|
93
153
|
- .gitignore
|
94
154
|
- .rspec
|
155
|
+
- CHANGELOG
|
95
156
|
- Gemfile
|
96
157
|
- LICENSE.txt
|
97
158
|
- README.md
|
@@ -100,6 +161,7 @@ files:
|
|
100
161
|
- lib/kankri.rb
|
101
162
|
- lib/kankri/exceptions.rb
|
102
163
|
- lib/kankri/password_check.rb
|
164
|
+
- lib/kankri/privilege_check.rb
|
103
165
|
- lib/kankri/privilege_set.rb
|
104
166
|
- lib/kankri/privilege_subject.rb
|
105
167
|
- lib/kankri/simple_authenticator.rb
|
@@ -141,3 +203,4 @@ test_files:
|
|
141
203
|
- spec/privilege_subject_spec.rb
|
142
204
|
- spec/simple_authenticator_spec.rb
|
143
205
|
- spec/spec_helper.rb
|
206
|
+
has_rdoc:
|