tachiban 0.3.0 → 0.5.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 +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
|
-
[](https://gitter.im/sebastjan-hribar/tachiban?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
3
|
+
[](https://gitter.im/sebastjan-hribar/tachiban?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](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
|