tachiban 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +94 -17
- data/bin/tachiban +2 -0
- data/lib/tachiban.rb +34 -0
- data/lib/tachiban/commands/commands.rb +45 -0
- data/lib/tachiban/policy_generator/policy_generator.rb +58 -0
- data/lib/tachiban/version.rb +1 -1
- data/tachiban.gemspec +3 -4
- metadata +18 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32f1f2b6604c7cda5393b06aad44f4ea84397a0d
|
4
|
+
data.tar.gz: 6e6dbbb843fae38ed80dc10034071da0b669d3ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee08ab9357babdfef72ad367eb31f2815a1dbfffed18d2c0e526b0d9b411edee1bfc1124d5cd02052677b08cd966a4e114ac55365fa9fd2d30440e72ed0edc7b
|
7
|
+
data.tar.gz: 98e960466e21300d6973b405d493ecba1cb691a58e85296775468390a0c9acebe678cd8ce31665071342c025b5ecf731fbcf1b839f75b9a6283f009a427c2230
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Tachiban
|
2
2
|
|
3
|
-
[![Join the chat at https://gitter.im/sebastjan-hribar/tachiban](https://badges.gitter.im/sebastjan-hribar/tachiban.svg)](https://gitter.im/sebastjan-hribar/tachiban?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
3
|
+
[![Join the chat at https://gitter.im/sebastjan-hribar/tachiban](https://badges.gitter.im/sebastjan-hribar/tachiban.svg)](https://gitter.im/sebastjan-hribar/tachiban?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Gem Version](https://badge.fury.io/rb/tachiban.svg)](https://badge.fury.io/rb/tachiban)
|
4
4
|
|
5
5
|
Tachiban (立ち番 - standing watch) provides simple authentication system for [Hanami web applications](http://hanamirb.org/) by using bcrypt for password hashing and
|
6
6
|
offers the following functionalities (with methods listed below
|
@@ -9,8 +9,10 @@ offers the following functionalities (with methods listed below
|
|
9
9
|
- Login
|
10
10
|
- Authentication
|
11
11
|
- Session handling
|
12
|
+
- Password reset
|
13
|
+
- Authorization
|
12
14
|
|
13
|
-
The Tachiban logic and code were extracted from a Hanami based web app using
|
15
|
+
The Tachiban logic (apart from the Authorization) and code were extracted from a Hanami based web app using
|
14
16
|
Hanami::Model and was also used in a Camping based web app using Active Record.
|
15
17
|
|
16
18
|
|
@@ -43,16 +45,27 @@ end
|
|
43
45
|
## Usage
|
44
46
|
|
45
47
|
#### Prerequisites
|
46
|
-
|
47
|
-
|
48
|
+
Prior to logging in or authenticating the user, retrieve the entity from the
|
49
|
+
database and assign it to the instance variable of `@user`.
|
50
|
+
|
51
|
+
The password reset methods require the user entity to have the following attributes:
|
52
|
+
**token** and **password_reset_sent_at (set as `Time.now`)**. The token can be used to compose
|
53
|
+
the password reset url and to later retrieve the correct user from
|
54
|
+
the database when the user visits the password reset url.
|
48
55
|
|
49
|
-
|
56
|
+
The **password_reset_sent_at** can be used to check the reset link validity.
|
50
57
|
|
58
|
+
The only prerequisite for the authorization is the attribute of **role** for the user entity.
|
51
59
|
|
52
|
-
|
60
|
+
|
61
|
+
#### Usage
|
53
62
|
|
54
63
|
###### Signup
|
55
|
-
|
64
|
+
The entity for which authentication is used must have the
|
65
|
+
attribute `hashed_pass` to hold the generated hashed password.
|
66
|
+
|
67
|
+
To create a user with a hashed password use the `hashed_password(password)`
|
68
|
+
method for the password and store it as the user's attribute `hashed_pass`.
|
56
69
|
|
57
70
|
*Example*
|
58
71
|
|
@@ -69,13 +82,18 @@ end
|
|
69
82
|
```
|
70
83
|
|
71
84
|
###### Login
|
72
|
-
To authenticate a user use the `authenticated?(input_password)` method and log
|
85
|
+
To authenticate a user use the `authenticated?(input_password)` method and log
|
86
|
+
them in with the `login` method. Authentication is successful if the user exists and passwords match.
|
73
87
|
|
74
|
-
The user is logged in by setting the user object as the `session[:current_user]`.
|
88
|
+
The user is logged in by setting the user object as the `session[:current_user]`.
|
89
|
+
After the user is logged in the session start time is defined as
|
90
|
+
`session[:session_start_time] = Time.now`. A flash message is also
|
91
|
+
assigned as `flash[:success_notice] = flash_message`.
|
75
92
|
|
76
|
-
The `session[:session_start_time]` is then used by the `session_expired?`
|
93
|
+
The `session[:session_start_time]` is then used by the `session_expired?`
|
94
|
+
method to determine whether the session has expired or not.
|
77
95
|
|
78
|
-
*Example*
|
96
|
+
*Example of session creation for an entity*
|
79
97
|
|
80
98
|
```ruby
|
81
99
|
# Create action for an entity session
|
@@ -88,8 +106,8 @@ login("You have been successfully logged in.") if authenticated?(password)
|
|
88
106
|
|
89
107
|
|
90
108
|
###### Authentication
|
91
|
-
To check whether the user is logged in use the `check_for_logged_in_user
|
92
|
-
` method.
|
109
|
+
To check whether the user is logged in use the `check_for_logged_in_user` method.
|
110
|
+
If the user is not logged in the `logout` method takes over.
|
93
111
|
|
94
112
|
|
95
113
|
###### Session handling
|
@@ -138,15 +156,74 @@ module Web
|
|
138
156
|
end
|
139
157
|
```
|
140
158
|
|
159
|
+
|
160
|
+
###### Password reset
|
161
|
+
The password reset feature provides a few simple methods to generate a
|
162
|
+
token, email subject and body. It is also possible to specify and
|
163
|
+
check the validity of the password reset url.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
token # => "YbRucc8YUlFJrYYp04eQKQ"
|
167
|
+
```
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
email_subject(SomeApp) # => "SomeApp -- password reset request"
|
171
|
+
```
|
172
|
+
|
173
|
+
|
174
|
+
Provide the base url, the token and the number and type of the time units
|
175
|
+
for the validity of the link.
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
body = email_body(base_url, url_token, 2, "hour")
|
179
|
+
# => "Visit this url to reset your password: http://localhost:2300/passwordupdate/asdasdasdaerwrw.
|
180
|
+
# The url will be valid for 2 hour(s).")
|
181
|
+
```
|
182
|
+
|
183
|
+
The link validity must me specified in seconds. The method compares the
|
184
|
+
current time with the time when the password reset link was sent increased
|
185
|
+
by the link validity: `Time.now > @user.password_reset_sent_at + link_validity`
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
password_reset_url_valid?(link_validity)
|
189
|
+
```
|
190
|
+
|
191
|
+
|
192
|
+
###### Authorization
|
193
|
+
Authorization support was setup as inspired by [this blog post](http://billpatrianakos.me/blog/2013/10/22/authorize-users-based-on-roles-and-permissions-without-a-gem/).
|
194
|
+
|
195
|
+
Authorization features support the generation of policy files for each controller where authorized roles are specified for each action.
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
tachiban -n mightyPoster -p post
|
199
|
+
```
|
200
|
+
The above CLI command will generate a policy file for the application mightyPoster (not the project) and the controller post. The file will be generated as `myProject/lib/mightyPoster/policies/PostPolicy.rb`
|
201
|
+
|
202
|
+
Each application would have its own `app/policies` folders.
|
203
|
+
|
204
|
+
**The command must be run in the project root folder.**
|
205
|
+
|
206
|
+
Once the file is generated the authorized roles variables in the initialize block for required actions need to be uncommneted and supplied with specific roles.
|
207
|
+
|
208
|
+
Then we can check if a user is authorized:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
authorized?(controller, role, action)
|
212
|
+
```
|
213
|
+
|
214
|
+
|
141
215
|
### ToDo
|
142
|
-
1. Add support for password reset and update.
|
143
|
-
2. Add support for level based authorizations.
|
144
216
|
|
145
|
-
|
217
|
+
- Add support for level based authorizations. [x]
|
218
|
+
- Add generators for adding authorization rules to existing policies.
|
219
|
+
- Add generators for entities with required attributes.
|
220
|
+
|
221
|
+
|
222
|
+
## Development
|
146
223
|
|
147
224
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
148
225
|
|
149
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
226
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
150
227
|
|
151
228
|
## Contributing
|
152
229
|
|
data/bin/tachiban
ADDED
data/lib/tachiban.rb
CHANGED
@@ -116,6 +116,40 @@ private
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
+
|
120
|
+
# ### Password reset ###
|
121
|
+
|
122
|
+
def token
|
123
|
+
SecureRandom.urlsafe_base64
|
124
|
+
end
|
125
|
+
|
126
|
+
def email_subject(app_name)
|
127
|
+
"#{app_name} -- password reset request"
|
128
|
+
end
|
129
|
+
|
130
|
+
def email_body(url, token, link_validity, time_unit)
|
131
|
+
"Visit this url to reset your password: #{url}#{token}. \n
|
132
|
+
The url will be valid for #{link_validity} #{time_unit}(s)."
|
133
|
+
end
|
134
|
+
|
135
|
+
# State the link_validity in seconds.
|
136
|
+
def password_reset_url_valid?(link_validity)
|
137
|
+
Time.now > @user.password_reset_sent_at + link_validity
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# ### Authorization ###
|
142
|
+
# The authorized? method checks if the specified user has the required role
|
143
|
+
# and permission to access the action. It returns true or false and
|
144
|
+
# provides the basis for further actions in either case.
|
145
|
+
#
|
146
|
+
# Example: redirect_to "/" unless authorized?
|
147
|
+
|
148
|
+
def authorized?(controller, role, action)
|
149
|
+
Object.const_get(controller.downcase.capitalize + "Policy").new(role).send("#{action.downcase}?")
|
150
|
+
end
|
151
|
+
|
152
|
+
|
119
153
|
end
|
120
154
|
end
|
121
155
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require_relative "../policy_generator/policy_generator.rb"
|
4
|
+
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
optparse = OptionParser.new do |opts|
|
8
|
+
opts.banner = "\nHanami authorization policy generator
|
9
|
+
Usage: tachiban -n myapp -p user
|
10
|
+
Flags:
|
11
|
+
\n"
|
12
|
+
|
13
|
+
opts.on("-n", "--app_name APP", "Specify the application name for the policy") do |app_name|
|
14
|
+
options[:app_name] = app_name
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on("-p", "--policy POLICY", "Specify the policy name") do |policy|
|
18
|
+
options[:policy] = policy
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-h", "--help", "Displays help") do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
begin
|
30
|
+
optparse.parse!
|
31
|
+
puts "Add flag -h or --help to see usage instructions." if options.empty?
|
32
|
+
mandatory = [:app_name, :policy]
|
33
|
+
missing = mandatory.select{ |arg| options[arg].nil? }
|
34
|
+
unless missing.empty?
|
35
|
+
raise OptionParser::MissingArgument.new(missing.join(', '))
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
39
|
+
puts $!.to_s
|
40
|
+
puts optparse
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
puts "Performing task with options: #{options.inspect}"
|
45
|
+
generate_policy("#{options[:app_name]}", "#{options[:policy]}") if options[:policy]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
# The generate_policy method creates the policy file for specified
|
4
|
+
# application and controller. By default all actions to check against
|
5
|
+
# are commented out.
|
6
|
+
# Uncomment the needed actions and define appropriate user role.
|
7
|
+
|
8
|
+
def generate_policy(app_name, controller_name)
|
9
|
+
app_name = app_name
|
10
|
+
controller = controller_name.downcase.capitalize
|
11
|
+
policy_txt = <<-TXT
|
12
|
+
class #{controller}Policy
|
13
|
+
def initialize(role)
|
14
|
+
@user_role = role
|
15
|
+
|
16
|
+
# Uncomment the required roles and add the
|
17
|
+
# appropriate user role to the @authorized_roles* array.
|
18
|
+
|
19
|
+
# @authorized_roles_for_new = []
|
20
|
+
# @authorized_roles_for_create = []
|
21
|
+
# @authorized_roles_for_show = []
|
22
|
+
# @authorized_roles_for_index = []
|
23
|
+
# @authorized_roles_for_edit = []
|
24
|
+
# @authorized_roles_for_update = []
|
25
|
+
# @authorized_roles_for_destroy = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def new?
|
29
|
+
@authorized_roles_for_new.include? @user_role
|
30
|
+
end
|
31
|
+
def create?
|
32
|
+
@authorized_roles_for_create.include? @user_role
|
33
|
+
end
|
34
|
+
def show?
|
35
|
+
@authorized_roles_for_show.include? @user_role
|
36
|
+
end
|
37
|
+
def index?
|
38
|
+
@authorized_roles_for_index.include? @user_role
|
39
|
+
end
|
40
|
+
def edit?
|
41
|
+
@authorized_roles_for_edit.include? @user_role
|
42
|
+
end
|
43
|
+
def update?
|
44
|
+
@authorized_roles_for_update.include? @user_role
|
45
|
+
end
|
46
|
+
def destroy?
|
47
|
+
@authorized_roles_for_destroy.include? @user_role
|
48
|
+
end
|
49
|
+
end
|
50
|
+
TXT
|
51
|
+
|
52
|
+
|
53
|
+
FileUtils.mkdir_p "lib/#{app_name}/policies" unless File.directory?("lib/#{app_name}/policies")
|
54
|
+
unless File.file?("lib/#{app_name}/policies/#{controller}Policy.rb")
|
55
|
+
File.open("lib/#{app_name}/policies/#{controller}Policy.rb", 'w') { |file| file.write(policy_txt) }
|
56
|
+
end
|
57
|
+
puts("Generated policy: lib/#{app_name}/policies/#{controller}Policy.rb") if File.file?("lib/#{app_name}/policies/#{controller}Policy.rb")
|
58
|
+
end
|
data/lib/tachiban/version.rb
CHANGED
data/tachiban.gemspec
CHANGED
@@ -14,8 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
-
spec.
|
18
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
18
|
spec.require_paths = ["lib"]
|
20
19
|
|
21
20
|
spec.add_development_dependency "bundler", "~> 1.11"
|
@@ -24,10 +23,10 @@ Gem::Specification.new do |spec|
|
|
24
23
|
spec.add_development_dependency "hanami-model", "~> 1.0"
|
25
24
|
spec.add_development_dependency "timecop", "0.8.1"
|
26
25
|
spec.add_development_dependency 'hanami-controller', "~> 1.0"
|
27
|
-
spec.add_development_dependency 'hanami-router'
|
26
|
+
spec.add_development_dependency 'hanami-router', "~> 1.0"
|
28
27
|
spec.add_development_dependency 'pry'
|
29
28
|
|
30
29
|
spec.add_runtime_dependency "bcrypt", "~> 3.1"
|
31
30
|
spec.add_runtime_dependency 'hanami-controller', "~> 1.0"
|
32
|
-
spec.add_runtime_dependency 'hanami-router'
|
31
|
+
spec.add_runtime_dependency 'hanami-router', "~> 1.0"
|
33
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tachiban
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastjan Hribar
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -98,16 +98,16 @@ dependencies:
|
|
98
98
|
name: hanami-router
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
101
|
+
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
103
|
+
version: '1.0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - "
|
108
|
+
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
110
|
+
version: '1.0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: pry
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -154,20 +154,23 @@ dependencies:
|
|
154
154
|
name: hanami-router
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
|
-
- - "
|
157
|
+
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
159
|
+
version: '1.0'
|
160
160
|
type: :runtime
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
|
-
- - "
|
164
|
+
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
166
|
+
version: '1.0'
|
167
167
|
description:
|
168
168
|
email:
|
169
169
|
- sebastjan.hribar@gmail.com
|
170
|
-
executables:
|
170
|
+
executables:
|
171
|
+
- console
|
172
|
+
- setup
|
173
|
+
- tachiban
|
171
174
|
extensions: []
|
172
175
|
extra_rdoc_files: []
|
173
176
|
files:
|
@@ -180,7 +183,10 @@ files:
|
|
180
183
|
- Rakefile
|
181
184
|
- bin/console
|
182
185
|
- bin/setup
|
186
|
+
- bin/tachiban
|
183
187
|
- lib/tachiban.rb
|
188
|
+
- lib/tachiban/commands/commands.rb
|
189
|
+
- lib/tachiban/policy_generator/policy_generator.rb
|
184
190
|
- lib/tachiban/version.rb
|
185
191
|
- tachiban.gemspec
|
186
192
|
homepage: https://github.com/sebastjan-hribar/tachiban
|