isaca-rails 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +102 -0
  4. data/app/assets/images/isaca/rails/geometric-1920-blue.png +0 -0
  5. data/app/assets/images/isaca/rails/isaca-logo.png +0 -0
  6. data/app/assets/javascript/isaca/rails/application.js +13 -0
  7. data/app/assets/stylesheets/isaca/rails/all.css +46 -0
  8. data/app/assets/stylesheets/isaca/rails/application.css +15 -0
  9. data/app/assets/stylesheets/isaca/rails/components/button.css +34 -0
  10. data/app/assets/stylesheets/isaca/rails/components/container.css +4 -0
  11. data/app/assets/stylesheets/isaca/rails/components/flash.css +18 -0
  12. data/app/assets/stylesheets/isaca/rails/components/form-control.css +7 -0
  13. data/app/assets/stylesheets/isaca/rails/sessions.css +96 -0
  14. data/app/assets/stylesheets/isaca/rails/user_consent.css +87 -0
  15. data/app/controllers/isaca/rails/application_controller.rb +5 -0
  16. data/app/controllers/isaca/rails/platform/administrators_controller.rb +68 -0
  17. data/app/controllers/isaca/rails/platform/application_controller.rb +10 -0
  18. data/app/controllers/isaca/rails/platform/claims_controller.rb +34 -0
  19. data/app/controllers/isaca/rails/sessions_controller.rb +56 -0
  20. data/app/controllers/isaca/rails/users_consent_controller.rb +24 -0
  21. data/app/controllers/isaca/rails/welcome_controller.rb +3 -0
  22. data/app/helpers/isaca/rails/application_helper.rb +48 -0
  23. data/app/helpers/isaca/rails/claims_helper.rb +13 -0
  24. data/app/models/session/sign_in/form_object.rb +28 -0
  25. data/app/models/user_consent/agreement/form_object.rb +33 -0
  26. data/app/views/isaca/rails/platform/administrators/_administrator.html.erb +6 -0
  27. data/app/views/isaca/rails/platform/administrators/_claims_form.html.erb +9 -0
  28. data/app/views/isaca/rails/platform/administrators/edit.html.erb +9 -0
  29. data/app/views/isaca/rails/platform/administrators/index.html.erb +15 -0
  30. data/app/views/isaca/rails/platform/administrators/new.html.erb +17 -0
  31. data/app/views/isaca/rails/platform/administrators/show.html.erb +29 -0
  32. data/app/views/isaca/rails/sessions/_form.html.erb +15 -0
  33. data/app/views/isaca/rails/sessions/new.html.erb +28 -0
  34. data/app/views/isaca/rails/sessions/shared/_links.html.erb +2 -0
  35. data/app/views/isaca/rails/users_consent/_form.html.erb +50 -0
  36. data/app/views/isaca/rails/users_consent/show.html.erb +21 -0
  37. data/app/views/isaca/rails/welcome/index.html.erb +81 -0
  38. data/app/views/layouts/isaca-rails.html.erb +23 -0
  39. data/config/application.rb +0 -0
  40. data/config/locales/isaca-rails.en.yml +25 -0
  41. data/config/routes.rb +2 -0
  42. data/lib/generators/isaca/rails/install/USAGE +24 -0
  43. data/lib/generators/isaca/rails/install/install_generator.rb +148 -0
  44. data/lib/generators/isaca/rails/install/templates/README +14 -0
  45. data/lib/generators/isaca/rails/install/templates/add_isaca_claims.rb.erb +10 -0
  46. data/lib/generators/isaca/rails/install/templates/add_isaca_to_existing_users.rb.erb +17 -0
  47. data/lib/generators/isaca/rails/install/templates/add_isaca_users.rb.erb +21 -0
  48. data/lib/generators/isaca/rails/install/templates/claim.rb.erb +13 -0
  49. data/lib/generators/isaca/rails/install/templates/isaca-rails.rb +4 -0
  50. data/lib/generators/isaca/rails/install/templates/isaca.rb +5 -0
  51. data/lib/generators/isaca/rails/install/templates/user.rb.erb +3 -0
  52. data/lib/isaca/rails/authentication.rb +166 -0
  53. data/lib/isaca/rails/authorization.rb +51 -0
  54. data/lib/isaca/rails/controller.rb +14 -0
  55. data/lib/isaca/rails/engine.rb +7 -0
  56. data/lib/isaca/rails/user.rb +16 -0
  57. data/lib/isaca/rails/version.rb +5 -0
  58. data/lib/isaca/rails.rb +83 -0
  59. data/lib/tasks/isaca/rails_tasks.rake +4 -0
  60. metadata +297 -0
@@ -0,0 +1,17 @@
1
+ class AddIsaca<%= name.pluralize.camelize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ <%= "add_column #{model_symbol_string(name)}, :imis_id, :string" unless model_has_column? name, :imis_id %>
4
+ <%= "add_index #{model_symbol_string(name)}, :imis_id, unique: true" unless model_has_column? name, :imis_id %>
5
+ <%= "add_column #{model_symbol_string(name)}, :imis_expiration_date, :datetime" unless model_has_column? name, :imis_expiration_date %>
6
+ <%= "add_column #{model_symbol_string(name)}, :imis_member_type, :string" unless model_has_column? name, :imis_member_type %>
7
+ <%= "add_column #{model_symbol_string(name)}, :imis_enterprise_id, :string" unless model_has_column? name, :imis_enterprise_id %>
8
+ <%= "add_column #{model_symbol_string(name)}, :imis_active_member, :boolean" unless model_has_column? name, :imis_active_member %>
9
+ <%= "add_column #{model_symbol_string(name)}, :email, :string" unless model_has_column? name, :email %>
10
+ <%= "add_column #{model_symbol_string(name)}, :first_name, :string" unless model_has_column? name, :first_name %>
11
+ <%= "add_column #{model_symbol_string(name)}, :last_name, :string" unless model_has_column? name, :last_name %>
12
+ <%= "add_column #{model_symbol_string(name)}, :username, :string" unless model_has_column? name, :username %>
13
+ <%= "add_column #{model_symbol_string(name)}, :country, :string" unless model_has_column? name, :country %>
14
+ <%= "add_column #{model_symbol_string(name)}, :admin, :boolean, default: false, null: false" unless model_has_column? name, :admin %>
15
+ <%= "add_column #{model_symbol_string(name)}, :last_sign_in_at, :datetime" unless model_has_column? name, :last_sign_in_at %>
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ class AddIsaca<%= name.pluralize.camelize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table <%= model_symbol_string(name) %> do |t|
4
+ t.timestamps null: false
5
+ t.string :imis_id, null: false
6
+ t.datetime :imis_expiration_date
7
+ t.boolean :imis_active_member
8
+ t.string :imis_member_type
9
+ t.string :imis_enterprise_id
10
+ t.string :email
11
+ t.string :first_name
12
+ t.string :last_name
13
+ t.string :username
14
+ t.string :country
15
+ t.boolean :admin, default: false, null: false
16
+ t.datetime :last_sign_in_at
17
+ end
18
+
19
+ add_index <%= model_symbol_string(name) %>, :imis_id, unique: true
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ class Claim < ApplicationRecord
2
+ enum privilege: {
3
+ read_administrators: 100,
4
+ write_administrators: 101,
5
+ read_claims: 102,
6
+ write_claims: 103
7
+ }
8
+
9
+ belongs_to :<%= name.downcase.singularize.to_sym %>
10
+ validates :<%= name.downcase.singularize.to_sym %>, presence: true
11
+ validates :privilege, presence: true
12
+ validates_uniqueness_of :privilege, scope: :<%= name.downcase.singularize.to_sym %>, message: '<%= name.singularize.humanize %> already has privilege'
13
+ end
@@ -0,0 +1,4 @@
1
+ Isaca::Rails.configure do |config|
2
+ # config.user_model = ::User
3
+ # config.redirect_for_consent = true
4
+ end
@@ -0,0 +1,5 @@
1
+ Isaca.configure do |config|
2
+ config.url = 'http://isaca-dev-stage-sp-2420.centralus.cloudapp.azure.com:7777/isacaservices/Service1.svc'
3
+ config.secret_pass = ENV["ISACA_SECRET_PASS"]
4
+ config.user_agent = 'my_application'
5
+ end
@@ -0,0 +1,3 @@
1
+ class <%= name.singularize.classify %> < ApplicationRecord
2
+ include Isaca::Rails::User
3
+ end
@@ -0,0 +1,166 @@
1
+ module Isaca
2
+ module Rails
3
+ module Authentication
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper_method :current_isaca_user
8
+ helper_method :user_signed_in?
9
+ helper_method :isaca_requires_consent?
10
+ helper_method :redirect_for_consent?
11
+ end
12
+
13
+ # Checks to see if there is a current_isaca_user, if not it redirects to the new_session_path. This method is intended
14
+ # to be used with before_action.
15
+ #
16
+ # @return nil
17
+ def authenticate_isaca_user
18
+ if user_signed_in?
19
+ if request.path != user_consent_path && redirect_for_consent?
20
+ session[:after_sign_in_path] = request.fullpath if request.get?
21
+ flash.alert = t('isaca.rails.user_consent.consent_required')
22
+ redirect_to user_consent_path
23
+ end
24
+ else
25
+ session[:after_sign_in_path] = request.fullpath if request.get?
26
+ flash.alert = t('isaca.rails.sessions.sign_in_required')
27
+ redirect_to sign_in_path
28
+ end
29
+ end
30
+
31
+ # A helper method for referencing the user who is currently logged in.
32
+ #
33
+ # @return [ActiveModel::Model|nil]
34
+ def current_isaca_user
35
+ return @current_isaca_user if @current_isaca_user
36
+
37
+ begin
38
+ set_current_isaca_user if token_cookie_exists?
39
+ rescue Isaca::ServiceError => e
40
+ Rails.logger.warn("Error occurred while setting the current isaca user: #{e.message}")
41
+ end
42
+ end
43
+
44
+ # Method used to to login a user and set the token
45
+ #
46
+ # @param username [String] The user's username
47
+ # @param password [String] The user's password
48
+ #
49
+ # @return [Boolean] Whether or not the user's record was updated with the last_sign_in_at datetime
50
+ def authenticate(username, password)
51
+ session = Isaca::Request::AuthenticateUser.get(username, password)
52
+ raise Isaca::SessionError.new(session.value) unless session.is_valid?
53
+ isaca_sign_in(session.value)
54
+ current_isaca_user.update_attribute(:last_sign_in_at, DateTime.current)
55
+ end
56
+
57
+ # Destroys the user token and sets the current_isaca_user attribute to nil
58
+ #
59
+ # @param [Hash] params Optional
60
+ # @option params [String] token The session token to be deleted.
61
+ #
62
+ # @return nil
63
+ def isaca_sign_out(**params)
64
+ token = nil
65
+ params && params[:token] ? (token = params[:token]) : (token = cookies['Token'] if token_cookie_exists?)
66
+
67
+ if token && Isaca::Request::LogOut.get(token)
68
+ cookies.delete('Token', domain: :all) if token_cookie_exists?
69
+ @current_isaca_user = nil
70
+ reset_session
71
+ end
72
+ end
73
+
74
+ # Helper method to check and see if the current_isaca_user attribute exists
75
+ #
76
+ # @return [Boolean] The truthiness of the current_isaca_user attribute
77
+ def user_signed_in?
78
+ !current_isaca_user.nil?
79
+ end
80
+
81
+ def isaca_requires_consent?
82
+ user_signed_in? && !current_isaca_user.privacy
83
+ end
84
+
85
+ # Helper method to redirect to a saved path or fallback
86
+ #
87
+ # @param fallback [String] Path to visit if session[:after_sign_in_path] does not exist
88
+ def redirect_after_sign_in_or(fallback)
89
+ redirect_to(session[:after_sign_in_path] || fallback)
90
+ session.delete(:after_sign_in_path)
91
+ end
92
+
93
+ # Helper method used to check the conditions for redirecting for consent
94
+ #
95
+ # @return [Boolean] Whether or not a redirect is required
96
+ def redirect_for_consent?
97
+ isaca_requires_consent? && Isaca::Rails.configuration.redirect_for_consent
98
+ end
99
+
100
+ private
101
+
102
+ # Creates a 'Token' cookie
103
+ #
104
+ # @param token [String] - The user's session token
105
+ #
106
+ # @return [String] - Returns the provided token
107
+ def isaca_sign_in(token)
108
+ opts = ::Rails.env.production? ? {secure: true} : {secure: false}
109
+ cookies['Token'] = {value: token, domain: :all, httponly: true}.merge(opts)
110
+ end
111
+
112
+ # Method used to create or find a user model based on an existing 'Token' cookie
113
+ #
114
+ # @return [ActiveModel::Model]
115
+ #
116
+ # @raise [Isaca::ServiceError] An error can be raised by {Isaca::Request::GetUserDetailsByToken#get} or {Isaca::Request::GetUserByID#get}
117
+ def set_current_isaca_user
118
+ # Using the Token cookie we can fetch our users details from isaca
119
+ isaca_user = Isaca::Request::GetUserDetailsByToken.get(cookies['Token'])
120
+
121
+ # The GetUserDetailsByToken endpoint does not return everything we need, we need to supplement our attributes
122
+ # by fetching the GetUserByID endpoint as well.
123
+ membership = Isaca::Request::GetUserByID.get(isaca_user.imis_id)
124
+
125
+ # Set all the aggregated user data to a hash for user record creation or user record updating
126
+ attributes = {
127
+ imis_enterprise_id: isaca_user.enterprise_id,
128
+ first_name: isaca_user.first_name,
129
+ last_name: isaca_user.last_name,
130
+ email: isaca_user.email,
131
+ username: isaca_user.username,
132
+ country: isaca_user.country,
133
+ imis_member_type: membership.member_type,
134
+ imis_expiration_date: membership.expiration_date
135
+ }
136
+
137
+ # Since isaca-rails allows configurable user models, we need to pull the model type from the configuration.
138
+ klass = Isaca::Rails.configuration.user_model
139
+
140
+ # Fetch the first record with a matching imis id or initialize a new record with the given user data.
141
+ @current_isaca_user = klass.create_with(attributes).find_or_initialize_by(imis_id: isaca_user.imis_id)
142
+
143
+ # Update the old user record or save a new user record
144
+ if @current_isaca_user.new_record?
145
+ @current_isaca_user.save
146
+ else
147
+ @current_isaca_user.update_attributes(attributes)
148
+ end
149
+
150
+ # Temporal data so we do not need dedicated columns for this but we do need to associate it with the user
151
+ @current_isaca_user.privacy = isaca_user.privacy
152
+ @current_isaca_user.marketing = isaca_user.marketing
153
+
154
+ # Return the current isaca user
155
+ @current_isaca_user
156
+ end
157
+
158
+ # Checks to see if a 'Token' cookie exists
159
+ #
160
+ # @return [Boolean] Whether or not the `Token` cookie exists
161
+ def token_cookie_exists?
162
+ cookies && cookies.key?('Token')
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,51 @@
1
+ module Isaca
2
+ module Rails
3
+ module Authorization
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper_method :user_has_privilege?
8
+ end
9
+
10
+ def authorize_isaca_user
11
+ if current_isaca_user.admin?
12
+ if %w(index new show create update destroy).include?(action_name)
13
+ if %w(index show).include?(action_name)
14
+ behavior = 'read'
15
+ else
16
+ behavior = 'write'
17
+ end
18
+ else
19
+ if %w(GET).include?(request.method)
20
+ behavior = 'read'
21
+ else
22
+ behavior = 'write'
23
+ end
24
+ end
25
+
26
+ privilege = "#{behavior}_#{controller_name.underscore}".to_sym
27
+ unless user_has_privilege?(current_isaca_user, privilege)
28
+ redirect_to root_path, alert: "#{t('isaca.rails.claims.admin_required')} Missing claim: #{privilege}."
29
+ end
30
+ else
31
+ redirect_to root_path, alert: t('isaca.rails.claims.admin_required')
32
+ end
33
+ end
34
+
35
+ def user_has_privilege?(user, privilege)
36
+ user.claims.select {|c| c.privilege.to_sym == privilege}.any?
37
+ end
38
+
39
+ def claim_symbols(claim_params, state)
40
+ case state
41
+ when :destroyable
42
+ claim_params.select {|k,v| v == '0'}.keys.map(&:to_sym)
43
+ when :creatable
44
+ claim_params.select {|k,v| v == '1'}.keys.map(&:to_sym)
45
+ else
46
+ nil
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ require 'isaca/rails/authentication'
2
+ require 'isaca/rails/authorization'
3
+
4
+ # This class is meant to be included in Rails controllers where you want authentication/authorization
5
+ module Isaca
6
+ module Rails
7
+ module Controller
8
+ extend ActiveSupport::Concern
9
+
10
+ include Isaca::Rails::Authentication
11
+ include Isaca::Rails::Authorization
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module Isaca
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ config.assets.precompile += %w(isaca/rails/isaca-logo.png)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module Isaca
2
+ module Rails
3
+ module User
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :privacy, :marketing, :country
8
+
9
+ has_many :claims
10
+
11
+ validates :imis_id, presence: true, uniqueness: true
12
+ validates :imis_member_type, presence: true, inclusion: %w(student member non_member)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Isaca
2
+ module Rails
3
+ VERSION = '0.2.0'
4
+ end
5
+ end
@@ -0,0 +1,83 @@
1
+ require "isaca"
2
+ require "isaca/rails/engine"
3
+ require "isaca/rails/controller"
4
+ require "isaca/rails/user"
5
+
6
+ module Isaca
7
+ module Rails
8
+ class << self
9
+ attr_accessor :configuration
10
+ # @!attribute configuration
11
+ # @return [Isaca::Rails::Configuration] Object used to configure the engine
12
+ end
13
+
14
+
15
+ # Configuration block used to configure the engine.
16
+ #
17
+ # @yield [Isaca::Rails::Configuration]
18
+ #
19
+ # @example An example configuration
20
+ # Isaca::Rails.configure do |config|
21
+ # config.user_model = ::Person
22
+ # end
23
+ def self.configure
24
+ yield configuration
25
+ self.configuration
26
+ end
27
+
28
+ # Method used to fetch the configuration object
29
+ #
30
+ # @return [Isaca::Rails::Configuration]
31
+ def self.configuration
32
+ @configuration ||= Configuration.new
33
+ end
34
+
35
+ # Method used to reset configuration. Primarily required during testing.
36
+ #
37
+ # @return [Isaca::Rails::Configuration]
38
+ def self.reset
39
+ @configuration = Configuration.new
40
+ end
41
+
42
+ # Method used during testing to temporarily set configurations and then immediately reset the configuration
43
+ #
44
+ # @example An example configuration
45
+ # Isaca::Rails.test_configure do |config|
46
+ # config.user_model = ::Person
47
+ #
48
+ # assert_equal ::Person, Isaca::Rails.configuration.user_model
49
+ # end
50
+ #
51
+ # @return [Isaca::Rails::Configuration]
52
+ def self.test_configure
53
+ yield configuration
54
+ self.reset
55
+ end
56
+
57
+ class Configuration
58
+ # ActiveRecord class that represents the users of your application. Should return an ActiveRecord class.
59
+ #
60
+ # You can set the user_model in the configuration for this engine.
61
+ #
62
+ # Isaca::Rails.configure {|config| config.user_model = ::Person}
63
+ #
64
+ # Default `::User`
65
+ attr_accessor :user_model
66
+
67
+ # Whether or not users should be redirected and required to provide consent if they have not already
68
+ #
69
+ # Isaca::Rails.configure {|config| config.redirect_for_consent = ::Person}
70
+ #
71
+ # Default true
72
+ attr_accessor :redirect_for_consent
73
+
74
+ def initialize
75
+ @redirect_for_consent = true
76
+ end
77
+
78
+ def user_model
79
+ @user_model ||= ::User
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :isaca_rails do
3
+ # # Task goes here
4
+ # end