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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 664c75489c53111c15f0624f918846a6e64c837e
4
- data.tar.gz: 82a5a8531f61133be195b32f7c0c431baf0c8d79
3
+ metadata.gz: 32f1f2b6604c7cda5393b06aad44f4ea84397a0d
4
+ data.tar.gz: 6e6dbbb843fae38ed80dc10034071da0b669d3ef
5
5
  SHA512:
6
- metadata.gz: dbb45889b53b0a7745f3cc42c5f41c270741369fcad73c957366859485f59313be4ffd7b2d2dc1064b30a6317a394a444c0b3fd8966979fbe47d3a4aa527df5a
7
- data.tar.gz: b605d49a0f8c94f2a8878095ea9a0319d782e1b174706a02f58da294f69e7d3eff393708b55eab09f69c6e3c8de874d77e74053f4b5697ae734fc723a55a3aaf
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
- The entity for which authentication is used must have the
47
- attribute `hashed_pass` to hold the generated hashed password.
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
- Prior to authenticating or logging in the user, retrieve them from the database and assign them to the instance variable of `@user`.
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
- #### Usage by features
60
+
61
+ #### Usage
53
62
 
54
63
  ###### Signup
55
- To create a user with a hashed password use the `hashed_password(password)` method for the password and store it as the user's attribute `hashed_pass`.
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 them in with the `login` method. Authentication is successful if the user exists and passwords match.
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]`. After the user is logged in the session start time is defined as `session[:session_start_time] = Time.now`. A flash message is also assigned as `flash[:success_notice] = flash_message`.
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?` method to determine whether the session has expired or not.
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
- <!-- ## Development
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
 
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/tachiban/commands/commands.rb"
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Tachiban
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -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.bindir = "exe"
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.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastjan Hribar
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-26 00:00:00.000000000 Z
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