kankri 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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +68 -0
- data/Rakefile +7 -0
- data/kankri.gemspec +31 -0
- data/lib/kankri.rb +44 -0
- data/lib/kankri/exceptions.rb +7 -0
- data/lib/kankri/password_check.rb +34 -0
- data/lib/kankri/privilege_set.rb +87 -0
- data/lib/kankri/privilege_subject.rb +17 -0
- data/lib/kankri/simple_authenticator.rb +78 -0
- data/lib/kankri/version.rb +3 -0
- data/spec/kankri_spec.rb +55 -0
- data/spec/password_check_spec.rb +29 -0
- data/spec/privilege_set_spec.rb +65 -0
- data/spec/privilege_subject_spec.rb +28 -0
- data/spec/simple_authenticator_spec.rb +111 -0
- data/spec/spec_helper.rb +11 -0
- metadata +143 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 136760abbcab49dfdada252777f2e0597e0167d3
|
|
4
|
+
data.tar.gz: c9da45c3e30a82533f71840801b2c8ab35907125
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 122cf49d0d79df39cbc7a3b7dda429c9081f785e8047690ea88c1528f97c3e55d6b24980c9bcc6a42f3cdab289d883e4d22cd7be7e2bed006572c8888302f106
|
|
7
|
+
data.tar.gz: 12e15471a892db8476ef77b235a03f5ea4c619900a90390749fabe4dba521a3dbe409206ac03a53d184a6e1454537fbd57ecbe82861cf977a644a17f60f6d414
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Matt Windsor
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Kankri
|
|
2
|
+
|
|
3
|
+
**Kankri** is an exceptionally basic authentication system for Ruby. It's intended for small projects that don't need database authentication, ACLs or other such things. It has no runtime dependencies other than Ruby 2.0.
|
|
4
|
+
|
|
5
|
+
It takes in a hash mapping usernames (strings or symbols) to passwords (strings) as well as a hash mapping *privilege keys* (strings or symbols) to the lists of *privileges* (strings or symbols) the user has on those keys. It's a bit like ACL... ish.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
gem 'kankri'
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install kankri
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
**Health Warning:** Don't use Kankri for mission-critical authentication. It's both very simple and also very early in development and, although it has some RSpecs to make sure it isn't doing something stupid, it certainly isn't a replacement for a decent authentication system.
|
|
24
|
+
|
|
25
|
+
Once kankri is installed, you can get an *authenticator* by doing this:
|
|
26
|
+
|
|
27
|
+
require 'kankri'
|
|
28
|
+
auth = Kankri.authenticator_from_hash(
|
|
29
|
+
username: {
|
|
30
|
+
password: 'foo',
|
|
31
|
+
privileges: {
|
|
32
|
+
key_one: :all, # Grants all privileges
|
|
33
|
+
key_two: [:priv_one, :priv_two, :priv_three], # Grants some privileges
|
|
34
|
+
key_three: [] # Grants no privileges
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
With an authenticator, you can get the *privilege set* for a user by logging in with **authenticate**:
|
|
40
|
+
|
|
41
|
+
privs = auth.authenticate(:username, 'foo')
|
|
42
|
+
|
|
43
|
+
And then you can check for privileges using that privilege set:
|
|
44
|
+
|
|
45
|
+
privs.has?(:key_one, :priv_one) #=> true
|
|
46
|
+
privs.has?(:key_one, :priv_four) #=> true
|
|
47
|
+
privs.has?(:key_two, :priv_one) #=> true
|
|
48
|
+
privs.has?(:key_two, :priv_four) #=> false
|
|
49
|
+
privs.has?(:key_three, :priv_one) #=> true
|
|
50
|
+
privs.has?(:key_three, :priv_four) #=> false
|
|
51
|
+
|
|
52
|
+
You can also use **#require**, which is like **#has?** but raises a Kankri::InsufficientPrivilegeError on failure and returns nil on success.
|
|
53
|
+
|
|
54
|
+
You can include Kankri::PrivilegeSubject into a class, which will give it two new methods (**#can?** and **#fail_if_cannot**). These take a privilege set and a privilege, and call **#has?** and **#require** respectively on that set, passing it the class's **#privilege_key** and the requested privilege.
|
|
55
|
+
|
|
56
|
+
## Todo
|
|
57
|
+
|
|
58
|
+
1. Password hashes instead of plaintext
|
|
59
|
+
2. More comprehensive testing
|
|
60
|
+
3. Better documentation?
|
|
61
|
+
|
|
62
|
+
## Contributing
|
|
63
|
+
|
|
64
|
+
1. Fork it
|
|
65
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
66
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
67
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
68
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/kankri.gemspec
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'kankri/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'kankri'
|
|
8
|
+
spec.version = Kankri::VERSION
|
|
9
|
+
spec.authors = ['Matt Windsor']
|
|
10
|
+
spec.email = ['matt.windsor@ury.org.uk']
|
|
11
|
+
spec.description = %q{
|
|
12
|
+
Kankri is a library for quickly setting up basic authentication with
|
|
13
|
+
object-action privileges. It's intended to be used in projects which need
|
|
14
|
+
a simple auth system with no run-time requirements and little set-up. It
|
|
15
|
+
isn't intended for mission critical security.
|
|
16
|
+
}
|
|
17
|
+
spec.summary = 'Simple object-action privilege checking'
|
|
18
|
+
spec.homepage = 'https://github.com/CaptainHayashi/kankri'
|
|
19
|
+
spec.license = 'MIT'
|
|
20
|
+
|
|
21
|
+
spec.files = `git ls-files`.split($/)
|
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
24
|
+
spec.require_paths = ['lib']
|
|
25
|
+
|
|
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'
|
|
31
|
+
end
|
data/lib/kankri.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'kankri/password_check'
|
|
2
|
+
require 'kankri/privilege_set'
|
|
3
|
+
require 'kankri/privilege_subject'
|
|
4
|
+
require 'kankri/simple_authenticator'
|
|
5
|
+
require 'kankri/version'
|
|
6
|
+
|
|
7
|
+
# Main module for Kankri
|
|
8
|
+
#
|
|
9
|
+
# Kankri is a library for quickly setting up basic authentication with object-
|
|
10
|
+
# action privileges.
|
|
11
|
+
#
|
|
12
|
+
# It's intended to be used in projects which need a simple auth system with no
|
|
13
|
+
# run-time requirements and little set-up. It isn't intended for mission
|
|
14
|
+
# critical security.
|
|
15
|
+
module Kankri
|
|
16
|
+
# Creates an authenticator object from a hash
|
|
17
|
+
#
|
|
18
|
+
# The hash should map username strings to hashes with the following Symbol
|
|
19
|
+
# keys:
|
|
20
|
+
#
|
|
21
|
+
# - password: The plain-text password for the user.
|
|
22
|
+
# - privileges: A hash mapping String or Symbol 'privilege keys' to either
|
|
23
|
+
# the String 'all' or Symbol :all, meaning the user has all privileges
|
|
24
|
+
# for that key, or a list of String or Symbol privileges the user has for
|
|
25
|
+
# that key.
|
|
26
|
+
#
|
|
27
|
+
# @api public
|
|
28
|
+
# @example Create an authenticator from a hash of users.
|
|
29
|
+
# Kankri.authenticator_From_hash(
|
|
30
|
+
# admin: {
|
|
31
|
+
# password: 'hunter2',
|
|
32
|
+
# privileges: {
|
|
33
|
+
# foo: 'all',
|
|
34
|
+
# bar: ['abc', 'def', 'ghi'],
|
|
35
|
+
# baz: []
|
|
36
|
+
# }
|
|
37
|
+
# }
|
|
38
|
+
# )
|
|
39
|
+
#
|
|
40
|
+
# @return [Object] An authenticator for the given hash.
|
|
41
|
+
def self.authenticator_from_hash(hash)
|
|
42
|
+
SimpleAuthenticator.new(hash)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Kankri
|
|
2
|
+
# A method object that represents a check on username/password pairs
|
|
3
|
+
class PasswordCheck
|
|
4
|
+
def initialize(username, password, passwords)
|
|
5
|
+
@username = username
|
|
6
|
+
@password = password
|
|
7
|
+
@passwords = passwords
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def ok?
|
|
11
|
+
auth_present? && user_known? && password_match?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def auth_present?
|
|
15
|
+
username_present? && password_present?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def username_present?
|
|
19
|
+
!(@username.nil? || @username.empty?)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def password_present?
|
|
23
|
+
!(@password.nil? || @password.empty?)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def password_match?
|
|
27
|
+
@passwords.fetch(@username) == @password
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def user_known?
|
|
31
|
+
@passwords.key?(@username)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'kankri/exceptions'
|
|
2
|
+
|
|
3
|
+
module Kankri
|
|
4
|
+
# Wrapper around a set of privileges a client has
|
|
5
|
+
class PrivilegeSet
|
|
6
|
+
# Initialises a privilege set.
|
|
7
|
+
#
|
|
8
|
+
# @api public
|
|
9
|
+
# @example Create a privilege set with no privileges.
|
|
10
|
+
# PrivilegeSet.new({})
|
|
11
|
+
# @example Create a privilege set with some privileges.
|
|
12
|
+
# PrivilegeSet.new({channel_set: [:get, :put]})
|
|
13
|
+
def initialize(privileges)
|
|
14
|
+
@privileges = privileges
|
|
15
|
+
symbolise_privileges
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Requires a certain privilege on a certain target
|
|
19
|
+
def require(target, privilege)
|
|
20
|
+
fail(InsufficientPrivilegeError) unless has?(target, privilege)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Checks to see if a certain privilege exists on a given target
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
# @example Check your privilege.
|
|
27
|
+
# privs.has?(:channel, :put)
|
|
28
|
+
# #=> false
|
|
29
|
+
#
|
|
30
|
+
# @param target [Symbol] The handler target the privilege is for.
|
|
31
|
+
# @param privilege [Symbol] The privilege (one of :get, :put, :post or
|
|
32
|
+
# :delete).
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean] true if the privileges are sufficient; false
|
|
35
|
+
# otherwise.
|
|
36
|
+
def has?(privilege, target)
|
|
37
|
+
PrivilegeChecker.new(target, privilege, @privileges).check?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def symbolise_privileges
|
|
43
|
+
@privileges = Hash[@privileges.map do |key, key_privs|
|
|
44
|
+
[key.to_sym, symbolise_privilege_list(key_privs)]
|
|
45
|
+
end]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def symbolise_privilege_list(privlist)
|
|
49
|
+
privlist.is_a?(Array) ? privlist.map(&:to_sym) : privlist.to_sym
|
|
50
|
+
end
|
|
51
|
+
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
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Kankri
|
|
2
|
+
# Mixin that allows classes to require privileges from a PrivilegeSet
|
|
3
|
+
#
|
|
4
|
+
# This expects the including class to define a method, 'privilege_key',
|
|
5
|
+
# which identifies the object in the privilege set.
|
|
6
|
+
module PrivilegeSubject
|
|
7
|
+
# Checks whether an operation can proceed on this privilege subject
|
|
8
|
+
def can?(operation, privilege_set)
|
|
9
|
+
privilege_set.has?(operation, privilege_key)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Fails if an operation cannot proceed on this model object
|
|
13
|
+
def fail_if_cannot(operation, privilege_set)
|
|
14
|
+
privilege_set.require(operation, privilege_key)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'kankri'
|
|
2
|
+
|
|
3
|
+
module Kankri
|
|
4
|
+
# An object that takes in a user hash and authenticates users
|
|
5
|
+
#
|
|
6
|
+
# This object holds user data in memory, including passwords. It is thus
|
|
7
|
+
# not secure for mission-critical applications.
|
|
8
|
+
class SimpleAuthenticator
|
|
9
|
+
# Makes hashing functions for users based on SHA256.
|
|
10
|
+
def self.sha256_hasher(usernames)
|
|
11
|
+
digest_hasher(usernames, Digest::SHA256)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Makes hashing functions for users based on a Digest implementation.
|
|
15
|
+
def self.digest_hasher(usernames, hasher)
|
|
16
|
+
Hash[
|
|
17
|
+
usernames.map do |username|
|
|
18
|
+
salt = SecureRandom.random_bytes
|
|
19
|
+
[username, ->(password) { hasher.digest(password + salt) } ]
|
|
20
|
+
end
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(users, hash_maker = nil)
|
|
25
|
+
hash_maker ||= self.class.method(:sha256_hasher)
|
|
26
|
+
@users = users
|
|
27
|
+
|
|
28
|
+
@hashers = hash_maker.call(@users.keys)
|
|
29
|
+
@passwords = passwords
|
|
30
|
+
@privilege_sets = privilege_sets
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def authenticate(username, password)
|
|
34
|
+
auth_fail unless auth_ok?(username.intern, password.to_s)
|
|
35
|
+
privileges_for(username.intern)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def privileges_for(username)
|
|
41
|
+
@privilege_sets[username]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Creates a hash mapping username symbols to their password strings
|
|
45
|
+
def passwords
|
|
46
|
+
transform_users do |name, entry|
|
|
47
|
+
plaintext = entry.fetch(:password).to_s
|
|
48
|
+
@hashers.fetch(name).call(plaintext)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Creates a hash mapping username symbols to their privilege sets
|
|
53
|
+
def privilege_sets
|
|
54
|
+
transform_users { |_, entry| PrivilegeSet.new(entry.fetch(:privileges)) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def transform_users
|
|
58
|
+
Hash[@users.map { |name, entry| [name.intern, (yield name, entry)] }]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def auth_fail
|
|
62
|
+
fail(Kankri::AuthenticationFailure)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def auth_ok?(username, password)
|
|
66
|
+
username_present?(username) && password_ok?(username, password)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def username_present?(username)
|
|
70
|
+
@hashers.key?(username) && @passwords.key?(username)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def password_ok?(username, password)
|
|
74
|
+
hashed_pass = @hashers.fetch(username).call(password)
|
|
75
|
+
PasswordCheck.new(username, hashed_pass, @passwords).ok?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/spec/kankri_spec.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'kankri'
|
|
3
|
+
|
|
4
|
+
describe Kankri do
|
|
5
|
+
describe '#authenticator_from_hash' do
|
|
6
|
+
let(:auth) { ->{ Kankri.authenticator_from_hash(hash) } }
|
|
7
|
+
context 'when given a valid hash of users' do
|
|
8
|
+
let(:hash) do
|
|
9
|
+
{
|
|
10
|
+
username: {
|
|
11
|
+
password: 'foo',
|
|
12
|
+
privileges: {
|
|
13
|
+
key_one: :all,
|
|
14
|
+
key_two: [:priv_one, :priv_two, :priv_three],
|
|
15
|
+
key_three: []
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
specify { expect(auth.call).to respond_to(:authenticate) }
|
|
22
|
+
end
|
|
23
|
+
context 'when given a hash with a user with no password' do
|
|
24
|
+
let(:hash) do
|
|
25
|
+
{
|
|
26
|
+
test: {
|
|
27
|
+
privileges: {
|
|
28
|
+
channel_set: ['get'],
|
|
29
|
+
channel: 'all'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
specify { expect { auth.call }.to raise_error }
|
|
36
|
+
end
|
|
37
|
+
context 'when given a hash with a user with no privileges' do
|
|
38
|
+
let(:hash) do
|
|
39
|
+
{
|
|
40
|
+
test: {
|
|
41
|
+
password: 'hunter2'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
specify { expect { auth.call }.to raise_error }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'when given something that is not a hash' do
|
|
50
|
+
let(:hash) { 'not a hash' }
|
|
51
|
+
|
|
52
|
+
specify { expect { auth.call }.to raise_error }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
describe Kankri::PasswordCheck do
|
|
3
|
+
let(:passwords) { { test: 'hunter2' } }
|
|
4
|
+
subject { ->(u, p) { Kankri::PasswordCheck.new(u, p, passwords) } }
|
|
5
|
+
|
|
6
|
+
describe '#ok?' do
|
|
7
|
+
context 'with a valid username and password' do
|
|
8
|
+
specify { expect(subject.call(:test, 'hunter2').ok?).to be_true }
|
|
9
|
+
end
|
|
10
|
+
context 'with a valid username and invalid password' do
|
|
11
|
+
specify { expect(subject.call(:test, 'nope').ok?).to be_false }
|
|
12
|
+
end
|
|
13
|
+
context 'with an invalid username and password' do
|
|
14
|
+
specify { expect(subject.call(:toast, 'nope').ok?).to be_false }
|
|
15
|
+
end
|
|
16
|
+
context 'with a valid username and blank password' do
|
|
17
|
+
specify { expect(subject.call(:test, '').ok?).to be_false }
|
|
18
|
+
end
|
|
19
|
+
context 'with a blank username and password' do
|
|
20
|
+
specify { expect(subject.call(:'', '').ok?).to be_false }
|
|
21
|
+
end
|
|
22
|
+
context 'with a valid username and nil password' do
|
|
23
|
+
specify { expect(subject.call(:test, nil).ok?).to be_false }
|
|
24
|
+
end
|
|
25
|
+
context 'with a nil username and password' do
|
|
26
|
+
specify { expect(subject.call(nil, nil).ok?).to be_false }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'kankri'
|
|
3
|
+
|
|
4
|
+
describe Kankri::PrivilegeSet do
|
|
5
|
+
subject do
|
|
6
|
+
Kankri::PrivilegeSet.new(
|
|
7
|
+
foo: [:get, :put],
|
|
8
|
+
bar: :all
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '#require' do
|
|
13
|
+
context 'when #has? returns true' do
|
|
14
|
+
it 'does nothing' do
|
|
15
|
+
allow(subject).to receive(:has?).and_return(true)
|
|
16
|
+
subject.require(:get, :foo)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
context 'when #has? returns false' do
|
|
20
|
+
it 'fails' do
|
|
21
|
+
allow(subject).to receive(:has?).and_return(false)
|
|
22
|
+
expect { subject.require(:get, :foo) }.to raise_error
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#has?' do
|
|
28
|
+
context 'when given a valid target' do
|
|
29
|
+
context 'and the privilege is directly in the PrivilegeSet' do
|
|
30
|
+
context 'and the privilege and target are both Symbols' do
|
|
31
|
+
specify { expect(subject.has?(:get, :foo)).to be_true }
|
|
32
|
+
end
|
|
33
|
+
context 'and the privilege and target are both Strings' do
|
|
34
|
+
specify { expect(subject.has?('get', 'foo')).to be_true }
|
|
35
|
+
end
|
|
36
|
+
context 'and the privilege is a Symbol and the target is a String' do
|
|
37
|
+
specify { expect(subject.has?(:get, 'foo')).to be_true }
|
|
38
|
+
end
|
|
39
|
+
context 'and the privilege is a String and the target is a Symbol' do
|
|
40
|
+
specify { expect(subject.has?('get', :foo)).to be_true }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
context 'and the target is covered by an :all' do
|
|
44
|
+
context 'and the privilege and target are both Symbols' do
|
|
45
|
+
specify { expect(subject.has?(:get, :bar)).to be_true }
|
|
46
|
+
end
|
|
47
|
+
context 'and the privilege and target are both Strings' do
|
|
48
|
+
specify { expect(subject.has?('get', 'bar')).to be_true }
|
|
49
|
+
end
|
|
50
|
+
context 'and the privilege is a Symbol and the target is a String' do
|
|
51
|
+
specify { expect(subject.has?(:get, 'bar')).to be_true }
|
|
52
|
+
end
|
|
53
|
+
context 'and the privilege is a String and the target is a Symbol' do
|
|
54
|
+
specify { expect(subject.has?('get', :bar)).to be_true }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
context 'and the privilege is not allowed for that target' do
|
|
58
|
+
specify { expect(subject.has?(:delete, :foo)).to be_false }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
context 'when given a target not in the PrivilegeSet' do
|
|
62
|
+
specify { expect(subject.has?(:get, :baz)).to be_false }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'kankri'
|
|
3
|
+
|
|
4
|
+
class MockPrivilegeSubject
|
|
5
|
+
include Kankri::PrivilegeSubject
|
|
6
|
+
|
|
7
|
+
def privilege_key
|
|
8
|
+
:fake_key
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe MockPrivilegeSubject do
|
|
13
|
+
let(:privilege_set) { double(:privilege_set) }
|
|
14
|
+
let(:operation) { double(:operation) }
|
|
15
|
+
|
|
16
|
+
{fail_if_cannot: :require, can?: :has?}.each do |subject_meth, set_meth|
|
|
17
|
+
describe "##{subject_meth}" do
|
|
18
|
+
context 'when given a valid privilege set and operation' do
|
|
19
|
+
it "calls ##{set_meth} on the privilege set with the handler target" do
|
|
20
|
+
expect(privilege_set).to receive(set_meth).once.with(
|
|
21
|
+
operation, subject.privilege_key
|
|
22
|
+
)
|
|
23
|
+
subject.send(subject_meth, operation, privilege_set)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'kankri'
|
|
3
|
+
|
|
4
|
+
describe Kankri::SimpleAuthenticator do
|
|
5
|
+
let(:config) do
|
|
6
|
+
{
|
|
7
|
+
test: {
|
|
8
|
+
password: 'hunter2',
|
|
9
|
+
privileges: {
|
|
10
|
+
channel_set: ['get'],
|
|
11
|
+
channel: 'all'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
let(:no_password) do
|
|
17
|
+
{
|
|
18
|
+
test: {
|
|
19
|
+
privileges: {
|
|
20
|
+
channel_set: ['get'],
|
|
21
|
+
channel: 'all'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
let(:no_privs) do
|
|
27
|
+
{
|
|
28
|
+
test: {
|
|
29
|
+
password: 'hunter2'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
subject { Kankri::SimpleAuthenticator.new(config) }
|
|
35
|
+
|
|
36
|
+
describe '#initialize' do
|
|
37
|
+
context 'when the config is valid' do
|
|
38
|
+
it 'succeeds' do
|
|
39
|
+
Kankri::SimpleAuthenticator.new(config)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
context 'when a user is missing a password' do
|
|
43
|
+
specify do
|
|
44
|
+
expect { Kankri::SimpleAuthenticator.new(no_password) }.to raise_error
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
context 'when a user is missing a privilege hash' do
|
|
48
|
+
specify do
|
|
49
|
+
expect { Kankri::SimpleAuthenticator.new(no_privs) }.to raise_error
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
context 'when the input is not a hash' do
|
|
53
|
+
specify do
|
|
54
|
+
expect { Kankri::SimpleAuthenticator.new('nope') }.to raise_error
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe '#authenticate' do
|
|
60
|
+
context 'when the user and password are valid strings' do
|
|
61
|
+
it 'returns a privilege set matching the config' do
|
|
62
|
+
privs = subject.authenticate('test', 'hunter2')
|
|
63
|
+
expect(privs.has?(:get, :channel_set)).to be_true
|
|
64
|
+
expect(privs.has?(:put, :channel_set)).to be_false
|
|
65
|
+
expect(privs.has?(:get, :channel)).to be_true
|
|
66
|
+
expect(privs.has?(:put, :channel)).to be_true
|
|
67
|
+
expect(privs.has?(:get, :player)).to be_false
|
|
68
|
+
expect(privs.has?(:put, :player)).to be_false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
context 'when the user and password are valid symbols' do
|
|
72
|
+
it 'returns a privilege set matching the config' do
|
|
73
|
+
privs = subject.authenticate(:test, :hunter2)
|
|
74
|
+
expect(privs.has?(:get, :channel_set)).to be_true
|
|
75
|
+
expect(privs.has?(:put, :channel_set)).to be_false
|
|
76
|
+
expect(privs.has?(:get, :channel)).to be_true
|
|
77
|
+
expect(privs.has?(:put, :channel)).to be_true
|
|
78
|
+
expect(privs.has?(:get, :player)).to be_false
|
|
79
|
+
expect(privs.has?(:put, :player)).to be_false
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
context 'when the user is not authorised' do
|
|
83
|
+
specify do
|
|
84
|
+
expect { subject.authenticate('wrong', 'hunter2') }.to raise_error(
|
|
85
|
+
Kankri::AuthenticationFailure
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
context 'when the password is incorrect' do
|
|
90
|
+
specify do
|
|
91
|
+
expect { subject.authenticate('test', 'wrong') }.to raise_error(
|
|
92
|
+
Kankri::AuthenticationFailure
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
context 'when the password is blank' do
|
|
97
|
+
specify do
|
|
98
|
+
expect { subject.authenticate('test', '') }.to raise_error(
|
|
99
|
+
Kankri::AuthenticationFailure
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
context 'when the username is blank' do
|
|
104
|
+
specify do
|
|
105
|
+
expect { subject.authenticate('', 'hunter2') }.to raise_error(
|
|
106
|
+
Kankri::AuthenticationFailure
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kankri
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matt Windsor
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2013-12-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.3'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ~>
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.3'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - '>='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - '>='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - '>='
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - '>='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: simplecov
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - '>='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: fuubar
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - '>='
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - '>='
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
description: "\n Kankri is a library for quickly setting up basic authentication
|
|
84
|
+
with\n object-action privileges. It's intended to be used in projects which
|
|
85
|
+
need\n a simple auth system with no run-time requirements and little set-up.
|
|
86
|
+
\ It\n isn't intended for mission critical security.\n "
|
|
87
|
+
email:
|
|
88
|
+
- matt.windsor@ury.org.uk
|
|
89
|
+
executables: []
|
|
90
|
+
extensions: []
|
|
91
|
+
extra_rdoc_files: []
|
|
92
|
+
files:
|
|
93
|
+
- .gitignore
|
|
94
|
+
- .rspec
|
|
95
|
+
- Gemfile
|
|
96
|
+
- LICENSE.txt
|
|
97
|
+
- README.md
|
|
98
|
+
- Rakefile
|
|
99
|
+
- kankri.gemspec
|
|
100
|
+
- lib/kankri.rb
|
|
101
|
+
- lib/kankri/exceptions.rb
|
|
102
|
+
- lib/kankri/password_check.rb
|
|
103
|
+
- lib/kankri/privilege_set.rb
|
|
104
|
+
- lib/kankri/privilege_subject.rb
|
|
105
|
+
- lib/kankri/simple_authenticator.rb
|
|
106
|
+
- lib/kankri/version.rb
|
|
107
|
+
- spec/kankri_spec.rb
|
|
108
|
+
- spec/password_check_spec.rb
|
|
109
|
+
- spec/privilege_set_spec.rb
|
|
110
|
+
- spec/privilege_subject_spec.rb
|
|
111
|
+
- spec/simple_authenticator_spec.rb
|
|
112
|
+
- spec/spec_helper.rb
|
|
113
|
+
homepage: https://github.com/CaptainHayashi/kankri
|
|
114
|
+
licenses:
|
|
115
|
+
- MIT
|
|
116
|
+
metadata: {}
|
|
117
|
+
post_install_message:
|
|
118
|
+
rdoc_options: []
|
|
119
|
+
require_paths:
|
|
120
|
+
- lib
|
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - '>='
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: '0'
|
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - '>='
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
requirements: []
|
|
132
|
+
rubyforge_project:
|
|
133
|
+
rubygems_version: 2.1.11
|
|
134
|
+
signing_key:
|
|
135
|
+
specification_version: 4
|
|
136
|
+
summary: Simple object-action privilege checking
|
|
137
|
+
test_files:
|
|
138
|
+
- spec/kankri_spec.rb
|
|
139
|
+
- spec/password_check_spec.rb
|
|
140
|
+
- spec/privilege_set_spec.rb
|
|
141
|
+
- spec/privilege_subject_spec.rb
|
|
142
|
+
- spec/simple_authenticator_spec.rb
|
|
143
|
+
- spec/spec_helper.rb
|