softwear 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/softwear/application.js +13 -0
  5. data/app/assets/javascripts/softwear/error_utils.js.coffee +82 -0
  6. data/app/assets/javascripts/softwear/modals.js.coffee +171 -0
  7. data/app/assets/stylesheets/softwear/application.css +33 -0
  8. data/app/controllers/softwear/application_controller.rb +4 -0
  9. data/app/controllers/softwear/error_reports_controller.rb +37 -0
  10. data/app/helpers/softwear/application_helper.rb +4 -0
  11. data/app/helpers/softwear/emails_helper.rb +9 -0
  12. data/app/mailers/error_report_mailer.rb +53 -0
  13. data/app/views/error_report_mailer/send_report.html.erb +30 -0
  14. data/app/views/layouts/softwear/application.html.erb +14 -0
  15. data/app/views/softwear/errors/_error.html.erb +35 -0
  16. data/app/views/softwear/errors/internal_server_error.html.erb +6 -0
  17. data/app/views/softwear/errors/internal_server_error.js.erb +10 -0
  18. data/bin/rails +12 -0
  19. data/config/routes.rb +3 -0
  20. data/lib/softwear/auth/belongs_to_user.rb +43 -0
  21. data/lib/softwear/auth/controller.rb +43 -0
  22. data/lib/softwear/auth/helper.rb +35 -0
  23. data/lib/softwear/auth/model.rb +16 -0
  24. data/lib/softwear/auth/spec.rb +62 -0
  25. data/lib/softwear/auth/standard_model.rb +498 -0
  26. data/lib/softwear/auth/stubbed_model.rb +130 -0
  27. data/lib/softwear/auth/token_authentication.rb +72 -0
  28. data/lib/softwear/engine.rb +5 -0
  29. data/lib/softwear/error_catcher.rb +65 -0
  30. data/lib/softwear/library/api_controller.rb +169 -0
  31. data/lib/softwear/library/capistrano.rb +94 -0
  32. data/lib/softwear/library/controller_authentication.rb +145 -0
  33. data/lib/softwear/library/enqueue.rb +87 -0
  34. data/lib/softwear/library/spec.rb +127 -0
  35. data/lib/softwear/library/tcp_server.rb +107 -0
  36. data/lib/softwear/version.rb +3 -0
  37. data/lib/softwear.rb +107 -0
  38. metadata +172 -0
@@ -0,0 +1,130 @@
1
+ module Softwear
2
+ module Auth
3
+ # =======================================================================
4
+ # In development, unless ENV['AUTH_SERVER'] is set, Softwear::Auth::Model is
5
+ # set to this StubbedModel. The StubbedModel does not contact the auth_server,
6
+ # and instead retrieves user info from config/users.yml, which assumes the
7
+ # following format:
8
+ =begin
9
+ ---
10
+ signed_in: nigel@annarbortees.com
11
+
12
+ users:
13
+ admin@softwearcrm.com:
14
+ first_name: Admin
15
+ last_name: User
16
+ roles:
17
+ - sales_manager
18
+
19
+ nigel@annarbortees.com:
20
+ first_name: Nigel
21
+ last_name: Baillie
22
+ roles:
23
+ - developer
24
+ - admin
25
+
26
+ ricky@annarbortees.com:
27
+ first_name: Ricky
28
+ last_name: Winowiecki
29
+ roles:
30
+ - developer
31
+ - administrator
32
+ =end
33
+ # Changes in this .yml file will be reflected live, without restarting your
34
+ # server.
35
+ # =======================================================================
36
+ class StubbedModel < Softwear::Auth::StandardModel
37
+ class << self
38
+ def raw_query(m, *args)
39
+ return 'yes' if m =~ /^token/
40
+ raise %[Cannot perform auth server queries on stubbed auth model. (tried to send "#{m.split(/\s+/).first} ..." query)]
41
+ end
42
+
43
+ def users_yml_file
44
+ Rails.root.join('config', 'users.yml').to_s
45
+ end
46
+
47
+ def users_yml
48
+ if @users_yml
49
+ yml_mtime = File.mtime(users_yml_file)
50
+
51
+ if @users_yml_modified.nil? || yml_mtime > @users_yml_modified
52
+ @users_yml_modified = yml_mtime
53
+ @users_yml = nil
54
+ end
55
+ else
56
+ @users_yml_modified = File.mtime(users_yml_file)
57
+ end
58
+
59
+ if @users_yml.nil?
60
+ @users_yml = YAML.load(IO.read(users_yml_file)).with_indifferent_access
61
+ @users_yml[:users].to_a.each_with_index do |entry, i|
62
+ entry[1][:id] ||= i + 1
63
+ end
64
+ end
65
+ @users_yml
66
+ end
67
+
68
+ #
69
+ # Transforms
70
+ # ['email@email.com', { 'attr1' => 'val1', 'attr2' => 'val2' }]
71
+ # From the yml format
72
+ #
73
+ # Into
74
+ # { 'email' => 'email@email.com', 'attr1' => 'val1', 'attr2' => 'val2' }
75
+ #
76
+ def yml_entry(entry, id_if_default = nil)
77
+ attributes = {}.with_indifferent_access
78
+
79
+ if entry.nil?
80
+ entry = ['usernotfound@example.com', { first_name: 'Unknown', last_name: 'User', id: id_if_default || -1 }]
81
+ end
82
+
83
+ attributes[:email] = entry[0]
84
+ attributes.merge!(entry[1])
85
+ if attributes[:profile_picture]
86
+ attributes[:profile_picture_url] ||= "file://#{attributes[:profile_picture]}"
87
+ end
88
+
89
+ new(attributes).tap { |u| u.instance_variable_set(:@persisted, true) }
90
+ end
91
+
92
+ def find(target_id)
93
+ return nil if target_id.nil?
94
+ yml_entry users_yml[:users].to_a[target_id.to_i - 1], target_id.to_i
95
+ end
96
+
97
+ def all
98
+ users_yml[:users].to_a.map(&method(:yml_entry))
99
+ end
100
+
101
+ def of_role(*roles)
102
+ roles = Array(roles).map(&:to_s)
103
+ all.select { |u| !u.roles.nil? && roles.any? { |r| u.roles.include?(r) } }
104
+ end
105
+
106
+ def auth(_token, _appname = nil)
107
+ signed_in = users_yml[:signed_in]
108
+
109
+ yml_entry [signed_in, users_yml[:users][signed_in]] unless signed_in.blank?
110
+ end
111
+ end
112
+
113
+ def yml_entry(*args)
114
+ self.class.yml_entry(*args)
115
+ end
116
+ def users_yml(*args)
117
+ self.class.users_yml(*args)
118
+ end
119
+
120
+ def reload
121
+ update_attributes yml_entry users_yml[:users].to_a[id - 1]
122
+ self
123
+ end
124
+
125
+ def valid_password?
126
+ true
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,72 @@
1
+ module Softwear
2
+ module Auth
3
+ module TokenAuthentication
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ cattr_accessor :user_class
8
+ cattr_accessor :token_auth_options
9
+ end
10
+
11
+ def token_authenticate_user!
12
+ user_class = self.class.user_class || base_class.user_class || User
13
+ options = (self.class.token_auth_options || base_class.token_auth_options || {}).with_indifferent_access
14
+ params_options = (options[:params] || {}).with_indifferent_access
15
+ headers_options = (options[:headers] || {}).with_indifferent_access
16
+
17
+ email_param = params_options[:email] || 'user_email'
18
+ token_param = params_options[:authentication_token] || 'user_token'
19
+ email_header = headers_options[:email] || 'X-User-Email'
20
+ token_header = headers_options[:authentication_token] || 'X-User-Token'
21
+
22
+ email = params[email_param] || request.headers[email_header]
23
+ token = params[token_param] || request.headers[token_header]
24
+
25
+ return render_unauthorized if email.blank? || token.blank?
26
+
27
+ case user_class.query "token #{Figaro.env.hub_app_name} #{email} #{token}"
28
+ when 'no' then render_unauthorized
29
+ when 'invaild' then render_unauthorized
30
+ when 'sorry' then render_internal_server_error
31
+ when 'yes' then true
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def http_headers
38
+ Hash[
39
+ request.headers.each
40
+ .select { |h| h[0] =~ /^HTTP/ }
41
+ .map { |h| [h[0].gsub(/^HTTP_/, ''), h[1]] }
42
+ ]
43
+ end
44
+
45
+ def render_unauthorized
46
+ Rails.logger.error "#{self.class.name} Token authentication unauthorized request.\n"\
47
+ "Params: #{JSON.pretty_generate(params)}\n"\
48
+ "Headers: #{JSON.pretty_generate(http_headers)}"
49
+
50
+ respond_to do |format|
51
+ format.json do
52
+ render status: :unauthorized,
53
+ json: { error: "Invalid or missing credentials" }
54
+ end
55
+ end
56
+ end
57
+
58
+ def render_internal_server_error
59
+ Rails.logger.error "#{self.class.name} Token authentication request resulted in error.\n"\
60
+ "Params: #{JSON.pretty_generate(params)}\n"\
61
+ "Headers: #{JSON.pretty_generate(http_headers)}"
62
+
63
+ respond_to do |format|
64
+ format.json do
65
+ render status: :internal_server_error,
66
+ json: { error: "Authentication server broke" }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ module Softwear
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Softwear
4
+ end
5
+ end
@@ -0,0 +1,65 @@
1
+ module Softwear
2
+ module ErrorCatcher
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ rescue_from Exception, StandardError, with: :error_report_form unless Rails.env.test?
7
+ end
8
+
9
+ protected
10
+
11
+ def iterm_annotation(content)
12
+ puts "=======================================================\033]1337;AddAnnotation=#{content}\a"
13
+ end
14
+
15
+ def error_report_form(error)
16
+ time_of_error = Time.now.strftime("%x %I:%M %p")
17
+
18
+ iterm_annotation("Begin #{error.class.name} (#{time_of_error})")
19
+ Rails.logger.error "**** #{error.class.name}: #{error.message} ****\n\n"\
20
+ "\t#{error.backtrace.join("\n\t")}"
21
+ iterm_annotation("End #{error.class.name} (#{time_of_error})")
22
+
23
+ @error = error
24
+ @additional_info = gather_additional_info
25
+
26
+ begin
27
+ respond_to do |format|
28
+ format.html { render 'softwear/errors/internal_server_error', layout: layout_for_error, status: 500 }
29
+ format.js { render 'softwear/errors/internal_server_error', layout: layout_for_error, status: 500 }
30
+ format.json { render json: '{}', status: 500 }
31
+ end
32
+ rescue AbstractController::DoubleRenderError => e
33
+ Rails.logger.error "DOUBLE RENDER ERROR IN CONTROLLER ERROR CATCHER!!! #{e.message}"
34
+ end
35
+ end
36
+
37
+ def filter_params(params)
38
+ new_hash = {}
39
+ params.each do |key, value|
40
+ new_value = value
41
+
42
+ case key.to_s
43
+ when /cc_number/ then new_value = "<FILTERED>"
44
+ when /cc_cvc/ then new_value = "<FILTERED>"
45
+ when /password/ then new_value = "<FILTERED>"
46
+ end
47
+
48
+ new_hash[key] = new_value
49
+ end
50
+ new_hash
51
+ end
52
+
53
+ def gather_additional_info
54
+ JSON.pretty_generate(filter_params(params)) + "|||" +
55
+ instance_variables
56
+ .reject { |v| /^@_/ =~ v.to_s || %i(@view_renderer @output_buffer @view_flow @error).include?(v) }
57
+ .map { |v| "#{v}: #{instance_variable_get(v).inspect}" }
58
+ .join("|||")
59
+ end
60
+
61
+ def layout_for_error
62
+ current_user ? 'application' : 'no_overlay'
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,169 @@
1
+ require 'inherited_resources'
2
+
3
+ module Softwear
4
+ module Library
5
+ class ApiController < ActionController::Base
6
+ include ::InheritedResources::Actions
7
+ include ::InheritedResources::BaseHelpers
8
+ extend ::InheritedResources::ClassMethods
9
+ extend ::InheritedResources::UrlHelpers
10
+
11
+ skip_before_filter :authenticate_user!
12
+ skip_before_filter :verify_authenticity_token
13
+
14
+ before_filter :allow_origin
15
+
16
+ respond_to :json
17
+ self.responder = InheritedResources::Responder
18
+
19
+ self.class_attribute :resource_class, :instance_writer => false unless self.respond_to? :resource_class
20
+ self.class_attribute :parents_symbols, :resources_configuration, :instance_writer => false
21
+
22
+ def self.base_class
23
+ Softwear::Library::ApiController
24
+ end
25
+
26
+ def self.token_authenticate(user_class, options = {})
27
+ include Softwear::Auth::TokenAuthentication
28
+ self.user_class = user_class
29
+ self.token_auth_options = options
30
+ prepend_before_filter :token_authenticate_user!
31
+ end
32
+
33
+ def index(&block)
34
+ yield if block_given?
35
+
36
+ key_values = (permitted_attributes + [:id]).uniq.map do |a|
37
+ [a, params[a]] if params[a]
38
+ end
39
+ .compact
40
+
41
+ self.records ||= resource_class
42
+ self.records = records.where(Hash[key_values])
43
+
44
+ respond_to do |format|
45
+ format.json(&render_json(records))
46
+ end
47
+ end
48
+
49
+ def show
50
+ super do |format|
51
+ format.json(&render_json)
52
+ end
53
+ end
54
+
55
+ def update
56
+ self.record = resource_class.find(params[:id])
57
+
58
+ permitted_attributes.each do |a|
59
+ begin
60
+ record.send("#{a}=", params[a]) if params[a]
61
+ rescue ActiveRecord::AssociationTypeMismatch
62
+ # If you try to send "job" as an attribute to order, we're assuming it's
63
+ # not intentional. Send "job_attributes" or update the job model separately
64
+ # to actually update the job.
65
+ end
66
+ end
67
+
68
+ if record.save
69
+ respond_to { |format| format.json(&render_json) }
70
+ else
71
+ respond_to { |format| format.json(&render_errors) }
72
+ end
73
+ end
74
+
75
+ def create
76
+ create! do |success, failure|
77
+ success.json do
78
+ headers['Location'] = resource_url(record.id)
79
+ render_json(status: 201).call
80
+ end
81
+ failure.json(&render_errors)
82
+ end
83
+ end
84
+
85
+ def options
86
+ head(:ok) if request.request_method == 'OPTIONS'
87
+ end
88
+
89
+ protected
90
+
91
+ def base_class
92
+ self.class.base_class
93
+ end
94
+
95
+ def render_json(options = {})
96
+ proc do
97
+ if options.is_a?(Hash)
98
+ records = nil
99
+ else
100
+ records = options
101
+ options = {}
102
+ end
103
+ rendering = {
104
+ json: (records || record),
105
+ methods: (['id'] + permitted_attributes).uniq,
106
+ include: includes
107
+ }
108
+ .merge(options)
109
+
110
+ render rendering
111
+ end
112
+ end
113
+
114
+ def render_errors(options = {})
115
+ proc do
116
+ Rails.logger.error "API #{record.class.name} ERROR: #{record.errors.full_messages}"
117
+
118
+ rendering = {
119
+ json: { errors: record.errors },
120
+ status: :unprocessable_entity
121
+ }
122
+ .merge(options)
123
+
124
+ render rendering
125
+ end
126
+ end
127
+
128
+ def self.model_name
129
+ name.gsub('Api::', '').gsub('Controller', '').singularize
130
+ end
131
+
132
+ # Override this to specify the :include option of rendering json.
133
+ def includes
134
+ {}
135
+ end
136
+
137
+ def records
138
+ instance_variable_get("@#{resource_class.model_name.collection}")
139
+ end
140
+
141
+ def records=(r)
142
+ instance_variable_set("@#{resource_class.model_name.collection}", r)
143
+ end
144
+
145
+ def record
146
+ instance_variable_get("@#{resource_class.model_name.element}")
147
+ end
148
+
149
+ def record=(r)
150
+ instance_variable_set("@#{resource_class.model_name.element}", r)
151
+ end
152
+
153
+ def allow_origin
154
+ headers['Access-Control-Allow-Origin'] = '*'
155
+ headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE'
156
+ headers['Access-Control-Allow-Headers'] = 'X-Requested-With'
157
+ headers['Access-Control-Allow-Credentials'] = 'true'
158
+ end
159
+
160
+ def permitted_attributes
161
+ resource_class.column_names + ['state_event']
162
+ end
163
+
164
+ def permitted_params
165
+ { resource_class.model_name.element => permitted_attributes }
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,94 @@
1
+ module Softwear
2
+ module Library
3
+ def self.capistrano(context)
4
+ context.instance_eval do
5
+ Rake::Task["deploy:compile_assets"].clear
6
+
7
+ ruby = fetch(:rvm_ruby_string) || fetch(:rvm_ruby_version)
8
+
9
+ namespace :deploy do
10
+
11
+ desc 'Assure softwear-lib is up to date before deploying'
12
+ task :update_softwear_lib do
13
+ on roles(:app), in: :sequence do
14
+ execute "~/.rvm/bin/rvm #{ruby} do gem install --no-ri --no-rdoc softwear-lib"
15
+ end
16
+ end
17
+
18
+ desc 'Signal the running rails app to restart'
19
+ task :restart do
20
+ on roles([:web, :app]), in: :sequence, wait: 5 do
21
+ execute :mkdir, '-p', "#{release_path}/tmp"
22
+ execute :touch, release_path.join('tmp/restart.txt')
23
+ end
24
+ end
25
+
26
+ after :publishing, :restart_sidekiq_manager do
27
+ on roles(:sidekiq) do
28
+ execute 'sudo restart sidekiq-manager'
29
+ end
30
+ end
31
+
32
+ # This is more problematic than helpful
33
+ =begin
34
+ if !$LOAD_PATH.grep(/sunspot_solr/).empty? || fetch(:no_reindex)
35
+ before :publishing, :reindex_solr do
36
+ on roles(:db) do
37
+ with rails_env: fetch(:rails_env) || fetch(:stage) do
38
+ within(release_path) do
39
+ execute :rake, 'sunspot:solr:reindex'
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ =end
46
+
47
+ # This is no longer necessary
48
+ # before :updating, :update_softwear_lib
49
+ after :publishing, :restart
50
+
51
+ desc 'Compile assets'
52
+ task :compile_assets => [:set_rails_env] do
53
+ invoke 'deploy:assets:precompile_local'
54
+ invoke 'deploy:assets:backup_manifest'
55
+ end
56
+
57
+ namespace :assets do
58
+
59
+ desc "Precompile assets locally and then rsync to web servers"
60
+ task :precompile_local do
61
+ # compile assets locally
62
+ run_locally do
63
+ execute "RAILS_ENV=#{fetch(:stage)} bundle exec rake assets:precompile"
64
+ end
65
+
66
+ # rsync to each server
67
+ assets_dir = "public/#{fetch(:assets_prefix) || 'assets'}"
68
+ local_dir = "./#{assets_dir}/"
69
+ on roles( fetch(:assets_roles, [:web]) ) do
70
+ # this needs to be done outside run_locally in order for host to exist
71
+ remote_dir = "#{host.user}@#{host.hostname}:#{release_path}/#{assets_dir}"
72
+
73
+ execute "mkdir -p #{release_path}/#{assets_dir}"
74
+ run_locally { execute "rsync -av --delete #{local_dir} #{remote_dir}" }
75
+
76
+ if fetch(:asset_sync)
77
+ with rails_env: fetch(:rails_env) || fetch(:stage) do
78
+ within(release_path) do
79
+ with_rvm(fetch(:task_ruby_version)) { execute :rake, 'assets:sync' }
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ # clean up
86
+ run_locally { execute "rm -rf #{local_dir}" }
87
+ end
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,145 @@
1
+ module Softwear
2
+ module Library
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
+ # Assume there is a "User" model
27
+ begin
28
+ User.inspect
29
+ return User if User.ancestors.include?(Softwear::Auth::Model)
30
+ rescue NameError => _
31
+ end
32
+ raise "Please define a user model that extends Softwear::Auth::Model."
33
+ end
34
+ Softwear::Auth::Model.descendants.first
35
+ end
36
+
37
+ # ====================
38
+ # Action called when a NotSignedInError is raised.
39
+ # ====================
40
+ def user_not_signed_in
41
+ if Softwear::Auth::Model::STUBBED
42
+ self.user_token = "dummy-token"
43
+ authenticate_user!
44
+ else
45
+ redirect_to softwear_hub_url + "/users/sign_in?#{{return_to: request.original_url}.to_param}"
46
+ end
47
+ end
48
+
49
+ # ====================
50
+ # Action called when a NotSignedInError is raised.
51
+ # ====================
52
+ def auth_server_down(error)
53
+ respond_to do |format|
54
+ format.html do
55
+ render inline: \
56
+ "<div class='panel panel-danger'>"\
57
+ "<div class='panel-heading'>"\
58
+ "<h3 class='panel-title'>#{error.message}</h3>"\
59
+ "</div>"\
60
+ "<div class='panel-body'>"\
61
+ "Not all site functions will work until the problem is resolved. "\
62
+ "<a href='javascript' onclick='history.go(-1);return false;' class='btn btn-default'>Go back.</a>"\
63
+ "</div>"\
64
+ "</div>"
65
+ end
66
+
67
+ format.js do
68
+ render inline: "alert(\"#{error.message.gsub('"', '\"')}\");"
69
+ end
70
+ end
71
+ end
72
+
73
+ # ====================
74
+ # Drop this into a before_filter to require a user be signed in on every request -
75
+ # just like in Devise.
76
+ # ====================
77
+ def authenticate_user!
78
+ if user_token.blank?
79
+ raise NotSignedInError, "No token"
80
+ end
81
+
82
+ if user = user_class.auth(user_token)
83
+ @current_user = user
84
+ else
85
+ self.user_token = nil
86
+ raise NotSignedInError, "Invalid token"
87
+ end
88
+ end
89
+
90
+ def current_user
91
+ return @current_user if @current_user
92
+
93
+ if @current_user.nil? && !user_token.blank?
94
+ @current_user = user_class.auth(user_token)
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ def user_signed_in?
101
+ !!current_user
102
+ end
103
+
104
+ # -- url uelpers --
105
+
106
+ def softwear_hub_url
107
+ if Rails.env.production?
108
+ Figaro.env.softwear_hub_url || (raise "Please set softwear_hub_url in application.yml")
109
+ elsif Rails.env.test?
110
+ 'http://hub.example.com'
111
+ else
112
+ Figaro.env.softwear_hub_url || 'http://localhost:2995'
113
+ end
114
+ end
115
+
116
+ def destroy_user_session_path
117
+ softwear_hub_url + "/users/sign_out"
118
+ end
119
+
120
+ def user_path(user)
121
+ user_id = user.is_a?(user_class) ? user.id : user
122
+ softwear_hub_url + "/users/#{user_id}"
123
+ end
124
+
125
+ def edit_user_path(user)
126
+ user_id = user.is_a?(user_class) ? user.id : user
127
+ softwear_hub_url + "/users/#{user_id}/edit"
128
+ end
129
+
130
+ def users_path
131
+ softwear_hub_url + "/users"
132
+ end
133
+
134
+ private
135
+
136
+ def user_token
137
+ session[:user_token]
138
+ end
139
+
140
+ def user_token=(new_token)
141
+ session[:user_token] = new_token
142
+ end
143
+ end
144
+ end
145
+ end