softwear-lib 1.4.3 → 1.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: 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