softwear-lib 1.4.3 → 1.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: 256a4d3e54b1b08a88522296a9061ddb43e25306
4
- data.tar.gz: 237c618746df70c045a9031fb7edcf8fca8423f5
3
+ metadata.gz: 07abbcd1086c6337273c1b6536c2f3ada2c817ca
4
+ data.tar.gz: afbd2b01e668364fd40d18e5f047a2297a824083
5
5
  SHA512:
6
- metadata.gz: 57f27e815f6aa1068ddad8ea29bb0db911dae1a066b7427302699810857bb55b50129a71bd52189c08ec9240044aeaed1260383e09c32b2d3f40655d0385d3b2
7
- data.tar.gz: 938338e81ee0c14780c375bba27033411405754fee92293a7d81e338db7f7dd2a603da9a2aea456079595c532845302c3c6de89ab065ffac9a0a2294233aea12
6
+ metadata.gz: 44716bcb3a8898364f4092328fc8c59217eb834768678204395dc469316d1b0b28ce27437f6df7ec650b7d15ecf5075ac004f4697d312611b2b77d16f97bee7f
7
+ data.tar.gz: 4d54bb27f4ece39a7132503fa59759eca15fc39c337d8d1f16f513cad1367d7bf5a10f5a6cd631432af9b612fb122c1e3c02886c2c0074a94f35b31a74da6bad
@@ -0,0 +1,32 @@
1
+ module Softwear
2
+ module Auth
3
+ module BelongsToUser
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def belongs_to_user_called(name, options = {})
8
+ foreign_key = "#{name}_id"
9
+
10
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
11
+ def #{name}
12
+ @#{name} ||= User.find(#{name}_id)
13
+ end
14
+
15
+ def #{name}=(new)
16
+ self.#{foreign_key} = new.id
17
+ @#{name} = new
18
+ end
19
+ RUBY
20
+ end
21
+
22
+ def belongs_to_user
23
+ belongs_to_user_called(:user)
24
+ end
25
+ end
26
+
27
+ included do
28
+ extend ClassMethods
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ module Softwear
2
+ module Auth
3
+ class Controller < ApplicationController
4
+ skip_before_filter :authenticate_user!, only: [:set_session_token, :clear_query_cache]
5
+
6
+ # ====================
7
+ # Comes from an img tag on softwear-hub to let an authorized app know that
8
+ # a user has signed in.
9
+ # ====================
10
+ def set_session_token
11
+ encrypted_token = params[:token]
12
+ redirect_to Figaro.env.softwear_hub_url and return if encrypted_token.blank?
13
+
14
+ Rails.logger.info "RECEIVED ENCRYPTED TOKEN: #{encrypted_token}"
15
+
16
+ decipher = OpenSSL::Cipher::AES.new(256, :CBC)
17
+ decipher.decrypt
18
+ decipher.key = Figaro.env.token_cipher_key
19
+ decipher.iv = Figaro.env.token_cipher_iv
20
+
21
+ session[:user_token] = decipher.update(Base64.urlsafe_decode64(encrypted_token)) + decipher.final
22
+
23
+ render inline: 'Done'
24
+ end
25
+
26
+ # ====================
27
+ # Comes from an img tag on softwear-hub when there has been a change to user
28
+ # attributes or roles and the cache should be cleared.
29
+ # ====================
30
+ def clear_query_cache
31
+ Softwear::Auth::Model.descendants.each do |user|
32
+ user.query_cache.clear
33
+ end
34
+
35
+ render inline: 'Done'
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ module Softwear
2
+ module Auth
3
+ module Helper
4
+ def profile_picture_of(user = nil, options = {})
5
+ options[:class] ||= ''
6
+ options[:class] += ' media-object img-circle profile-pic'
7
+ options[:alt] ||= "#{user.try(:full_name) || '(Unknown)'}'s Avatar"
8
+ options[:title] ||= user.try(:full_name) || 'Someone'
9
+
10
+ image_tag user.try(:profile_picture_url) || 'avatar/masarie.jpg', options
11
+ end
12
+
13
+ def auth_server_error_banner
14
+ return unless Softwear::Auth::Model.auth_server_down?
15
+
16
+ content_tag :div, class: 'alert alert-danger' do
17
+ content_tag :strong do
18
+ (Softwear::Auth::Model.auth_server_went_down_at || Time.now).strftime(
19
+ "WARNING: The authentication server is unreachable as of %I:%M%p. "\
20
+ "Some site features might not function properly."
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,408 @@
1
+ module Softwear
2
+ module Auth
3
+ class Model
4
+ include ActiveModel::Model
5
+ include ActiveModel::Conversion
6
+
7
+ class AccessDeniedError < StandardError
8
+ end
9
+ class InvalidCommandError < StandardError
10
+ end
11
+ class AuthServerError < StandardError
12
+ end
13
+ class AuthServerDown < StandardError
14
+ end
15
+
16
+ # ============================= CLASS METHODS ======================
17
+ class << self
18
+ attr_writer :query_cache
19
+ attr_accessor :total_query_cache
20
+ attr_writer :query_cache_expiry
21
+ alias_method :expire_query_cache_every, :query_cache_expiry=
22
+ attr_accessor :auth_server_went_down_at
23
+ attr_accessor :sent_auth_server_down_email
24
+ attr_accessor :time_before_down_email
25
+ alias_method :email_when_down_after, :time_before_down_email=
26
+
27
+ # ====================
28
+ # Returns true if the authentication server was unreachable for the previous query.
29
+ # ====================
30
+ def auth_server_down?
31
+ !!auth_server_went_down_at
32
+ end
33
+
34
+ # ====================
35
+ # The query cache takes message keys (such as "get 12") with response values straight from
36
+ # the server. So yes, this will cache error responses.
37
+ # You can clear this with <User Class>.query_cache.clear or <User Class>.query_cache = nil
38
+ # ====================
39
+ def query_cache
40
+ @query_cache ||= ThreadSafe::Cache.new
41
+ end
42
+
43
+ def query_cache_expiry
44
+ @query_cache_expiry || Figaro.env.query_cache_expiry.try(:to_f) || 1.hour
45
+ end
46
+
47
+ # ===================
48
+ # Override this in your subclasses! The mailer should have auth_server_down(time) and
49
+ # auth_server_up(time)
50
+ # ===================
51
+ def auth_server_down_mailer
52
+ # override me
53
+ end
54
+
55
+ # ======================================
56
+ def primary_key
57
+ :id
58
+ end
59
+
60
+ def base_class
61
+ self
62
+ end
63
+
64
+ def relation_delegate_class(*)
65
+ self
66
+ end
67
+
68
+ def unscoped
69
+ self
70
+ end
71
+
72
+ def new(*args)
73
+ if args.size == 3
74
+ assoc_class = args[2].owner.class.name
75
+ assoc_name = args[2].reflection.name
76
+ raise "Unsupported user association: #{assoc_class}##{assoc_name}. If this is a belongs_to "\
77
+ "association, you may have #{assoc_class} include Softwear::Auth::BelongsToUser and call "\
78
+ "`belongs_to_user_called :#{assoc_name}' instead of the traditional rails method."
79
+ else
80
+ super
81
+ end
82
+ end
83
+ # ======================================
84
+
85
+ # ====================
86
+ # Not a fully featured has_many - must specify foreign_key if the association doesn't match
87
+ # the model name.
88
+ # ====================
89
+ def has_many(assoc, options = {})
90
+ assoc = assoc.to_s
91
+
92
+ class_name = options[:class_name] || assoc.singularize.camelize
93
+ foreign_key = options[:foreign_key] || 'user_id'
94
+
95
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
96
+ def #{assoc}
97
+ #{class_name}.where(#{foreign_key}: id)
98
+ end
99
+ RUBY
100
+ end
101
+
102
+ def arel_table
103
+ @arel_table ||= Arel::Table.new(model_name.plural, self)
104
+ end
105
+
106
+ # ====================
107
+ # This is only used to record how long it takes to perform queries for development.
108
+ # ====================
109
+ def record(before, after, type, body)
110
+ ms = (after - before) * 1000
111
+ # The garbage in this string gives us the bold and color
112
+ Rails.logger.info " \033[1m\033[33m#{type} (#{'%.1f' % ms}ms)\033[0m #{body}"
113
+ end
114
+
115
+ # ====================
116
+ # Host of the auth server, from 'auth_server_endpoint' env variable.
117
+ # Defaults to localhost.
118
+ # ====================
119
+ def auth_server_host
120
+ endpoint = Figaro.env.auth_server_endpoint
121
+ if endpoint.blank?
122
+ 'localhost'
123
+ elsif endpoint.include?(':')
124
+ endpoint.split(':').first
125
+ else
126
+ endpoint
127
+ end
128
+ end
129
+
130
+ # ====================
131
+ # Port of the auth server, from 'auth_server_endpoint' env variable.
132
+ # Defaults to 2900.
133
+ # ====================
134
+ def auth_server_port
135
+ endpoint = Figaro.env.auth_server_endpoint
136
+ if endpoint.try(:include?, ':')
137
+ endpoint.split(':').last
138
+ else
139
+ 2900
140
+ end
141
+ end
142
+
143
+ def default_socket
144
+ @default_socket ||= TCPSocket.open(auth_server_host, auth_server_port)
145
+ end
146
+
147
+ # ====================
148
+ # Bare minimum query function - sends a message and returns the response, and
149
+ # handles a broken socket. #query and #force_query call this function.
150
+ # ====================
151
+ def raw_query(message)
152
+ begin
153
+ default_socket.puts message
154
+
155
+ rescue Errno::EPIPE => e
156
+ @default_socket = TCPSocket.open(auth_server_host, auth_server_port)
157
+ @default_socket.puts message
158
+ end
159
+
160
+ return default_socket.gets.chomp
161
+
162
+ rescue Errno::ECONNREFUSED => e
163
+ raise AuthServerDown, "Unable to connect to the authentication server."
164
+ end
165
+
166
+ # ====================
167
+ # Expires the query cache, setting a new expiration time as well as merging
168
+ # with the previous query cache, in case of an auth server outage.
169
+ # ====================
170
+ def expire_query_cache
171
+ before = Time.now
172
+ if total_query_cache
173
+ query_cache.each_pair do |key, value|
174
+ total_query_cache[key] = value
175
+ end
176
+ else
177
+ self.total_query_cache = query_cache.clone
178
+ end
179
+
180
+ query_cache.clear
181
+ query_cache['_expire_at'] = (query_cache_expiry || 1.hour).from_now
182
+ after = Time.now
183
+
184
+ record(before, after, "Authentication Expire Cache", "")
185
+ end
186
+
187
+ # ====================
188
+ # Queries the authentication server only if there isn't a cached response.
189
+ # Also keeps track of whether or not the server is reachable, and sends emails
190
+ # when the server goes down and back up.
191
+ # ====================
192
+ def query(message)
193
+ before = Time.now
194
+
195
+ expire_at = query_cache['_expire_at']
196
+ expire_query_cache if expire_at.blank? || Time.now > expire_at
197
+
198
+ if cached_response = query_cache[message]
199
+ response = cached_response
200
+ action = "Authentication Cache"
201
+ else
202
+ begin
203
+ response = raw_query(message)
204
+ action = "Authentication Query"
205
+ query_cache[message] = response
206
+
207
+ if auth_server_went_down_at
208
+ self.auth_server_went_down_at = nil
209
+
210
+ if sent_auth_server_down_email
211
+ self.sent_auth_server_down_email = false
212
+ if (mailer = auth_server_down_mailer) && mailer.respond_to?(:auth_server_up)
213
+ mailer.auth_server_up(Time.now).deliver_now
214
+ end
215
+ end
216
+ end
217
+
218
+ rescue AuthServerError => e
219
+ raise unless total_query_cache
220
+
221
+ old_response = total_query_cache[message]
222
+ if old_response
223
+ response = old_response
224
+ action = "Authentication Cache (due to error)"
225
+ Rails.logger.error "AUTHENTICATION: The authentication server encountered an error. "\
226
+ "You should probably check the auth server's logs. "\
227
+ "A cached response was used."
228
+ else
229
+ raise
230
+ end
231
+
232
+ rescue AuthServerDown => e
233
+ if auth_server_went_down_at.nil?
234
+ self.auth_server_went_down_at = Time.now
235
+ expire_query_cache
236
+
237
+ elsif auth_server_went_down_at > (time_before_down_email || 5.minutes).ago
238
+ unless sent_auth_server_down_email
239
+ self.sent_auth_server_down_email = true
240
+
241
+ if (mailer = auth_server_down_mailer) && mailer.respond_to?(:auth_server_down)
242
+ mailer.auth_server_down(auth_server_went_down_at).deliver_now
243
+ end
244
+ end
245
+ end
246
+
247
+ old_response = total_query_cache[message]
248
+ if old_response
249
+ response = old_response
250
+ action = "Authentication Cache (server down)"
251
+ else
252
+ raise AuthServerDown, "An uncached query was attempted, and the authentication server is down."
253
+ end
254
+ end
255
+ end
256
+ after = Time.now
257
+
258
+ record(before, after, action, message)
259
+ response
260
+ end
261
+
262
+ # ====================
263
+ # Runs a query through the server without error or cache checking.
264
+ # ====================
265
+ def force_query(message)
266
+ before = Time.now
267
+ response = raw_query(message)
268
+ after = Time.now
269
+
270
+ record(before, after, "Authentication Query (forced)", message)
271
+ response
272
+ end
273
+
274
+ # ====================
275
+ # Expects a response string returned from #query and raises an error for the
276
+ # following cases:
277
+ #
278
+ # - Access denied (AccessDeniedError)
279
+ # - Invalid command (bad query message) (InvalidCommandError)
280
+ # - Error on auth server's side (AuthServerError)
281
+ # ====================
282
+ def validate_response(response_string)
283
+ case response_string
284
+ when 'denied' then raise AccessDeniedError, "Denied"
285
+ when 'invalid' then raise InvalidCommandError, "Invalid command"
286
+ when 'sorry'
287
+ expire_query_cache
288
+ raise AuthServerError, "Authentication server encountered an error"
289
+ else
290
+ response_string
291
+ end
292
+ end
293
+
294
+ # ====================
295
+ # Finds a user with the given ID
296
+ # ====================
297
+ def find(target_id)
298
+ json = validate_response query "get #{target_id}"
299
+
300
+ if json == 'nosuchuser'
301
+ nil
302
+ else
303
+ object = new(JSON.parse(json))
304
+ object.instance_variable_set(:@persisted, true)
305
+ object
306
+ end
307
+ end
308
+
309
+ # ====================
310
+ # Returns an array of all registered users
311
+ # ====================
312
+ def all
313
+ json = validate_response query "all"
314
+
315
+ objects = JSON.parse(json).map(&method(:new))
316
+ objects.each { |u| u.instance_variable_set(:@persisted, true) }
317
+ objects
318
+ end
319
+
320
+ # ====================
321
+ # Given a valid signin token:
322
+ # Returns the authenticated user for the given token
323
+ # Given an invalid signin token:
324
+ # Returns false
325
+ # ====================
326
+ def auth(token)
327
+ response = validate_response query "auth #{Figaro.env.hub_app_name} #{token}"
328
+
329
+ return false unless response =~ /^yes .+$/
330
+
331
+ _yes, json = response.split(' ', 2)
332
+ object = new(JSON.parse(json))
333
+ object.instance_variable_set(:@persisted, true)
334
+ object
335
+ end
336
+
337
+ # ====================
338
+ # Overridable logger method used when recording query benchmarks
339
+ # ====================
340
+ def logger
341
+ Rails.logger
342
+ end
343
+ end
344
+
345
+ # ============================= INSTANCE METHODS ======================
346
+
347
+ REMOTE_ATTRIBUTES = [
348
+ :id, :email, :first_name, :last_name,
349
+ :profile_picture_url
350
+ ]
351
+ REMOTE_ATTRIBUTES.each(&method(:attr_accessor))
352
+
353
+ attr_reader :persisted
354
+ alias_method :persisted?, :persisted
355
+
356
+ # ====================
357
+ # Various class methods accessible on instances
358
+ def query(*a)
359
+ self.class.query(*a)
360
+ end
361
+ def raw_query(*a)
362
+ self.class.raw_query(*a)
363
+ end
364
+ def force_query(*a)
365
+ self.class.force_query(*a)
366
+ end
367
+ def logger
368
+ self.class.logger
369
+ end
370
+ # ====================
371
+
372
+ def initialize(attributes = {})
373
+ update_attributes(attributes)
374
+ end
375
+
376
+ def update_attributes(attributes={})
377
+ return if attributes.blank?
378
+ attributes = attributes.with_indifferent_access
379
+
380
+ REMOTE_ATTRIBUTES.each do |attr|
381
+ instance_variable_set("@#{attr}", attributes[attr])
382
+ end
383
+ end
384
+
385
+ def to_json
386
+ {
387
+ id: @id,
388
+ email: @email,
389
+ first_name: @first_name,
390
+ last_name: @last_name
391
+ }
392
+ .to_json
393
+ end
394
+
395
+ def reload
396
+ json = validate_response query "get #{id}"
397
+
398
+ update_attributes(JSON.parse(json))
399
+ @persisted = true
400
+ self
401
+ end
402
+
403
+ def full_name
404
+ "#{@first_name} #{@last_name}"
405
+ end
406
+ end
407
+ end
408
+ end
data/lib/softwear/lib.rb CHANGED
@@ -2,6 +2,11 @@ require "softwear/lib/version"
2
2
  require "softwear/lib/spec"
3
3
  require "softwear/lib/api_controller"
4
4
 
5
+ require "softwear/lib/controller_authentication"
6
+ require "softwear/auth/helper"
7
+ require "softwear/auth/model"
8
+ require "softwear/auth/belongs_to_user"
9
+
5
10
  module Softwear
6
11
  module Lib
7
12
  GEMFILE_OPENER = "# === BEGIN SOFTWEAR LIB GEMS === #"
@@ -0,0 +1,103 @@
1
+ module Softwear
2
+ module Lib
3
+ module ControllerAuthentication
4
+ extend ActiveSupport::Concern
5
+
6
+ class NotSignedInError < StandardError
7
+ end
8
+
9
+ included do
10
+ rescue_from NotSignedInError, with: :user_not_signed_in
11
+ rescue_from Softwear::Auth::Model::AuthServerDown, with: :auth_server_down
12
+
13
+ helper_method :current_user
14
+ helper_method :user_signed_in?
15
+
16
+ helper_method :destroy_user_session_path
17
+ helper_method :users_path
18
+ helper_method :user_path
19
+ helper_method :edit_user_path
20
+ end
21
+
22
+ def user_class
23
+ if Softwear::Auth::Model.descendants.size > 1
24
+ raise "More than one descendent of Softwear::Auth::Model is not supported."
25
+ elsif Softwear::Auth::Model.descendants.size == 0
26
+ raise "Please define a user model that extends Softwear::Auth::Model."
27
+ end
28
+ Softwear::Auth::Model.descendants.first
29
+ end
30
+
31
+ # ====================
32
+ # Action called when a NotSignedInError is raised.
33
+ # ====================
34
+ def user_not_signed_in
35
+ redirect_to Figaro.env.softwear_hub_url + "/users/sign_in?#{{return_to: request.original_url}.to_param}"
36
+ end
37
+
38
+ # ====================
39
+ # Action called when a NotSignedInError is raised.
40
+ # ====================
41
+ def auth_server_down(error)
42
+ respond_to do |format|
43
+ format.html do
44
+ render inline: \
45
+ "<h1>#{error.message}</h1><div>Not all site functions will work until the problem is resolved. "\
46
+ "<a href='javascripr' onclick='history.go(-1);return false;' class='btn btn-default'>Go back.</a></div>"
47
+ end
48
+
49
+ format.js do
50
+ render inline: "alert(\"#{error.message.gsub('"', '\"')}\");"
51
+ end
52
+ end
53
+ end
54
+
55
+ # ====================
56
+ # Drop this into a before_filter to require a user be signed in on every request -
57
+ # just like in Devise.
58
+ # ====================
59
+ def authenticate_user!
60
+ token = session[:user_token]
61
+
62
+ if token.blank?
63
+ raise NotSignedInError, "No token"
64
+ end
65
+
66
+ if user = user_class.auth(token)
67
+ @current_user = user
68
+ else
69
+ session[:user_token] = nil
70
+ raise NotSignedInError, "Invalid token"
71
+ end
72
+ end
73
+
74
+ def current_user
75
+ @current_user
76
+ end
77
+
78
+ def user_signed_in?
79
+ !@current_user.nil?
80
+ end
81
+
82
+ # -- url uelpers --
83
+
84
+ def destroy_user_session_path
85
+ Figaro.env.softwear_hub_url + "/users/sign_out"
86
+ end
87
+
88
+ def user_path(user)
89
+ user_id = user.is_a?(user_class) ? user.id : user
90
+ Figaro.env.softwear_hub_url + "/users/#{user_id}"
91
+ end
92
+
93
+ def edit_user_path(user)
94
+ user_id = user.is_a?(user_class) ? user.id : user
95
+ Figaro.env.softwear_hub_url + "/users/#{user_id}/edit"
96
+ end
97
+
98
+ def users_path
99
+ Figaro.env.softwear_hub_url + "/users"
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,103 @@
1
+ module Softwear
2
+ module Lib
3
+ module ControllerAuthentication
4
+ extend ActiveSupport::Concern
5
+
6
+ class NotSignedInError < StandardError
7
+ end
8
+
9
+ included do
10
+ rescue_from NotSignedInError, with: :user_not_signed_in
11
+ rescue_from Softwear::Auth::Model::AuthServerDown, with: :auth_server_down
12
+
13
+ helper_method :current_user
14
+ helper_method :user_signed_in?
15
+
16
+ helper_method :destroy_user_session_path
17
+ helper_method :users_path
18
+ helper_method :user_path
19
+ helper_method :edit_user_path
20
+ end
21
+
22
+ def user_class
23
+ if Softwear::Auth::Model.descendants.size > 1
24
+ raise "More than one descendent of Softwear::Auth::Model is not supported."
25
+ elsif Softwear::Auth::Model.descendants.size == 0
26
+ raise "Please define a user model that extends Softwear::Auth::Model."
27
+ end
28
+ Softwear::Auth::Model.descendants.first
29
+ end
30
+
31
+ # ====================
32
+ # Action called when a NotSignedInError is raised.
33
+ # ====================
34
+ def user_not_signed_in
35
+ redirect_to Figaro.env.softwear_hub_url + "/users/sign_in?#{{return_to: request.original_url}.to_param}"
36
+ end
37
+
38
+ # ====================
39
+ # Action called when a NotSignedInError is raised.
40
+ # ====================
41
+ def auth_server_down(error)
42
+ respond_to do |format|
43
+ format.html do
44
+ render inline: \
45
+ "<h1>#{error.message}</h1><div>Not all site functions will work until the problem is resolved. "\
46
+ "<a href='javascripr' onclick='history.go(-1);return false;' class='btn btn-default'>Go back.</a></div>"
47
+ end
48
+
49
+ format.js do
50
+ render inline: "alert(\"#{error.message.gsub('"', '\"')}\");"
51
+ end
52
+ end
53
+ end
54
+
55
+ # ====================
56
+ # Drop this into a before_filter to require a user be signed in on every request -
57
+ # just like in Devise.
58
+ # ====================
59
+ def authenticate_user!
60
+ token = session[:user_token]
61
+
62
+ if token.blank?
63
+ raise NotSignedInError, "No token"
64
+ end
65
+
66
+ if user = user_class.auth(token)
67
+ @current_user = user
68
+ else
69
+ session[:user_token] = nil
70
+ raise NotSignedInError, "Invalid token"
71
+ end
72
+ end
73
+
74
+ def current_user
75
+ @current_user
76
+ end
77
+
78
+ def user_signed_in?
79
+ !@current_user.nil?
80
+ end
81
+
82
+ # -- url uelpers --
83
+
84
+ def destroy_user_session_path
85
+ Figaro.env.softwear_hub_url + "/users/sign_out"
86
+ end
87
+
88
+ def user_path(user)
89
+ user_id = user.is_a?(user_class) ? user.id : user
90
+ Figaro.env.softwear_hub_url + "/users/#{user_id}"
91
+ end
92
+
93
+ def edit_user_path(user)
94
+ user_id = user.is_a?(user_class) ? user.id : user
95
+ Figaro.env.softwear_hub_url + "/users/#{user_id}/edit"
96
+ end
97
+
98
+ def users_path
99
+ Figaro.env.softwear_hub_url + "/users"
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,5 +1,5 @@
1
1
  module Softwear
2
2
  module Lib
3
- VERSION = "1.4.3"
3
+ VERSION = "1.5.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: softwear-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.3
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nigel Baillie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-15 00:00:00.000000000 Z
11
+ date: 2016-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -83,8 +83,14 @@ files:
83
83
  - Rakefile
84
84
  - bin/softwear
85
85
  - bin/softwear-deploy
86
+ - lib/softwear/auth/belongs_to_user.rb
87
+ - lib/softwear/auth/controller.rb
88
+ - lib/softwear/auth/helper.rb
89
+ - lib/softwear/auth/model.rb
86
90
  - lib/softwear/lib.rb
87
91
  - lib/softwear/lib/api_controller.rb
92
+ - lib/softwear/lib/authentication.rb
93
+ - lib/softwear/lib/controller_authentication.rb
88
94
  - lib/softwear/lib/spec.rb
89
95
  - lib/softwear/lib/version.rb
90
96
  - softwear-lib.gemspec