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 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