mno-enterprise-core 3.1.4 → 3.2.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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/mno_enterprise/logo-intuit.png +0 -0
  3. data/app/assets/images/mno_enterprise/main-logo.png +0 -0
  4. data/app/assets/stylesheets/mno_enterprise/mail.css +1 -4
  5. data/app/controllers/mno_enterprise/application_controller.rb +1 -1
  6. data/app/helpers/mno_enterprise/application_helper.rb +7 -7
  7. data/app/models/mno_enterprise/app.rb +12 -3
  8. data/app/models/mno_enterprise/app_answer.rb +13 -0
  9. data/app/models/mno_enterprise/app_comment.rb +10 -0
  10. data/app/models/mno_enterprise/app_feedback.rb +7 -0
  11. data/app/models/mno_enterprise/app_question.rb +9 -0
  12. data/app/models/mno_enterprise/app_review.rb +7 -0
  13. data/app/models/mno_enterprise/base_resource.rb +6 -1
  14. data/app/models/mno_enterprise/identity.rb +24 -0
  15. data/app/models/mno_enterprise/impac/alert.rb +10 -0
  16. data/app/models/mno_enterprise/impac/dashboard.rb +10 -11
  17. data/app/models/mno_enterprise/impac/kpi.rb +4 -2
  18. data/app/models/mno_enterprise/impac/widget.rb +4 -1
  19. data/app/models/mno_enterprise/team.rb +1 -33
  20. data/app/models/mno_enterprise/user.rb +109 -28
  21. data/app/views/system_notifications/email-change.html.erb +27 -0
  22. data/app/views/system_notifications/email-change.text.erb +10 -0
  23. data/app/views/system_notifications/password-change.html.erb +25 -0
  24. data/app/views/system_notifications/password-change.text.erb +7 -0
  25. data/app/views/system_notifications/reconfirmation-instructions.html.erb +7 -5
  26. data/app/views/system_notifications/reconfirmation-instructions.text.erb +3 -2
  27. data/config/initializers/config.rb +5 -0
  28. data/config/locales/models/user/en.yml +5 -0
  29. data/config/locales/templates/components/en.yml +9 -0
  30. data/config/locales/templates/dashboard/en.yml +14 -0
  31. data/config/locales/templates/dashboard/marketplace/en.yml +111 -8
  32. data/config/locales/templates/dashboard/organization/en.yml +1 -0
  33. data/config/locales/templates/impac/dock/en.yml +28 -0
  34. data/config/locales/views/auth/shared/en.yml +1 -2
  35. data/config/locales/views/webhook/o_auth/providers/en.yml +11 -6
  36. data/lib/devise/hooks/lockable.rb +13 -0
  37. data/lib/devise/models/remote_authenticatable.rb +31 -5
  38. data/lib/generators/mno_enterprise/install/install_generator.rb +7 -0
  39. data/lib/generators/mno_enterprise/install/templates/Procfile +1 -1
  40. data/lib/generators/mno_enterprise/install/templates/Procfile.dev +1 -1
  41. data/lib/generators/mno_enterprise/install/templates/config/application.yml +17 -0
  42. data/lib/generators/mno_enterprise/install/templates/config/initializers/mno_enterprise.rb +8 -3
  43. data/lib/generators/mno_enterprise/install/templates/config/newrelic.yml +46 -0
  44. data/lib/generators/mno_enterprise/install/templates/config/puma.rb +56 -0
  45. data/lib/generators/mno_enterprise/install/templates/config/settings/production.yml +1 -1
  46. data/lib/generators/mno_enterprise/install/templates/config/settings/uat.yml +1 -1
  47. data/lib/generators/mno_enterprise/install/templates/config/settings.yml +21 -0
  48. data/lib/generators/mno_enterprise/install/templates/stylesheets/variables.less +3 -0
  49. data/lib/her_extension/model/associations/association_proxy.rb +10 -6
  50. data/lib/mno_enterprise/concerns/controllers/auth/confirmations_controller.rb +1 -1
  51. data/lib/mno_enterprise/concerns/controllers/auth/omniauth_callbacks_controller.rb +203 -0
  52. data/lib/mno_enterprise/concerns/models/ability.rb +28 -0
  53. data/lib/mno_enterprise/concerns/models/app_instance.rb +22 -4
  54. data/lib/mno_enterprise/concerns/models/intercom_user.rb +28 -0
  55. data/lib/mno_enterprise/concerns/models/organization.rb +14 -1
  56. data/lib/mno_enterprise/concerns/models/team.rb +67 -0
  57. data/lib/mno_enterprise/core.rb +22 -3
  58. data/lib/mno_enterprise/impac_client.rb +19 -0
  59. data/lib/mno_enterprise/mail_adapters/mandrill_adapter.rb +4 -1
  60. data/lib/mno_enterprise/testing_support/factories/app_review.rb +51 -0
  61. data/lib/mno_enterprise/testing_support/factories/apps.rb +12 -10
  62. data/lib/mno_enterprise/testing_support/factories/identity.rb +9 -0
  63. data/lib/mno_enterprise/testing_support/factories/impac/alerts.rb +18 -0
  64. data/lib/mno_enterprise/testing_support/factories/impac/kpis.rb +5 -9
  65. data/lib/mno_enterprise/testing_support/factories/users.rb +4 -0
  66. data/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb +39 -0
  67. data/lib/mno_enterprise/testing_support/organizations_shared_helpers.rb +2 -1
  68. data/lib/mno_enterprise/testing_support/shared_examples/jpi_v1_admin.rb +38 -0
  69. data/lib/mno_enterprise/version.rb +1 -1
  70. data/spec/lib/devise/model/remote_authenticable_spec.rb +76 -0
  71. data/spec/lib/mno_enterprise/impac_client_spec.rb +31 -0
  72. data/spec/mno_enterprise_spec.rb +7 -8
  73. data/spec/models/mno_enterprise/app_spec.rb +1 -1
  74. data/spec/models/mno_enterprise/identity_spec.rb +39 -0
  75. data/spec/models/mno_enterprise/organization_spec.rb +19 -2
  76. data/spec/models/mno_enterprise/user_spec.rb +238 -0
  77. metadata +86 -22
@@ -3,24 +3,28 @@ module Her
3
3
  module Model
4
4
  module Associations
5
5
  class AssociationProxy < (ActiveSupport.const_defined?('ProxyObject') ? ActiveSupport::ProxyObject : ActiveSupport::BasicObject)
6
-
6
+
7
7
  install_proxy_methods :association,
8
8
  :build, :create, :update, :destroy, :where, :find, :all, :assign_nested_attributes, :reload, :order, :order_by, :limit, :skip
9
-
10
-
9
+
10
+ # Returns true if the association has been loaded, otherwise false.
11
+ def loaded?
12
+ !!association.instance_variable_get('@cached_result')
13
+ end
14
+
11
15
  def method_missing(name, *args, &block)
12
16
  if :object_id == name # avoid redefining object_id
13
17
  return association.fetch.object_id
14
18
  end
15
-
19
+
16
20
  # Check if a class scope has previously been defined
17
21
  begin
18
22
  if Relation.scopes.keys.grep(::Regexp.new(name.to_s)).any?
19
23
  return self.association.send(name,*args,&block)
20
24
  end
21
- rescue ::NoMethodError => e
25
+ rescue ::NoMethodError => e
22
26
  end
23
-
27
+
24
28
  # create a proxy to the fetched object's method
25
29
  # https://github.com/remiprev/her/pull/377
26
30
  AssociationProxy.install_proxy_methods 'association.fetch', name
@@ -98,7 +98,7 @@ module MnoEnterprise::Concerns::Controllers::Auth::ConfirmationsController
98
98
  sign_in resource, bypass: true
99
99
  set_flash_message(:notice, :confirmed) if is_flashing_format?
100
100
  yield(:success,resource) if block_given?
101
- MnoEnterprise::EventLogger.info('user_confirm', resource.id, 'User confirmed', nil, resource)
101
+ MnoEnterprise::EventLogger.info('user_confirm', resource.id, 'User confirmed', resource)
102
102
  respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource, new_user: true) }
103
103
  else
104
104
  yield(:error,resource) if block_given?
@@ -0,0 +1,203 @@
1
+ # This controller is used to handle the authentication (+creation) of external
2
+ # users via OpenID (e.g: QuickBooks OpenID)
3
+ #
4
+ # When users click on the "sign in with <provider>" button, they get redirected
5
+ # to the authorize endpoint (/users/auth/:provider - e.g: /users/auth/intuit).
6
+ # The action (handled by parent controller OmniauthCallbacksController) prepares
7
+ # the callback url then redirects the user to the OpenID provider (E.g: Intuit)
8
+ # for authentication.
9
+ #
10
+ # Once authentication has been performed at the OpenID provider level (e.g: Intuit)
11
+ # the user gets redirected to the callback endpoint (/users/auth/:provider/callback)
12
+ # The provider parameter in the url (E.g: intuit) gets automatically redirected to a
13
+ # controller action with the same name (handled by parent controller OmniauthCallbacksController)
14
+ # as you can see below with intuit.
15
+ #
16
+ # Then provider specific action then handles the (creation +) authentication of the user.
17
+ # Also, it automatically adds the right applications to the user dashboard (e.g: QuickBooks for
18
+ # Intuit)
19
+ #
20
+ # Intuit:
21
+ # --------
22
+ # For intuit, the authorize endpoint is be bypassed when the user clicks "try Maestrano" from
23
+ # the Intuit marketplace. The user automatically lands on the callback endpoints with a parameter
24
+ # in the url called 'qb_initiated'. This parameter is used to automatically trigger the retrieval
25
+ # of the oauth token in the background via javascript (by storing the temporary grant url in session)
26
+ #
27
+ # On Intuit, it is also possible to directly choose one of the apps proposed by Maestrano (E.g: 'SugarCRM
28
+ # by Maestrano'). In this case, an 'app' attribute containing the app nid (named id - e.g: 'sugarcrm') is
29
+ # added to the url parameters. The action then setup the app automatically (along with QuickBooks).
30
+ #
31
+ #
32
+ module MnoEnterprise::Concerns::Controllers::Auth::OmniauthCallbacksController
33
+ extend ActiveSupport::Concern
34
+
35
+ #==================================================================
36
+ # Included methods
37
+ #==================================================================
38
+ included do
39
+ skip_filter :verify_authenticity_token, only: [:intuit]
40
+
41
+ providers = Devise.omniauth_providers & %i(linkedin google facebook)
42
+
43
+ providers.each do |provider|
44
+ provides_callback_for provider
45
+ end
46
+ end
47
+
48
+ #==================================================================
49
+ # Class methods
50
+ #==================================================================
51
+ module ClassMethods
52
+ def provides_callback_for(provider)
53
+ class_eval %Q{
54
+ def #{provider}
55
+ auth = env["omniauth.auth"]
56
+ opts = { orga_on_create: create_orga_on_user_creation(auth.info.email) }
57
+
58
+ @user = MnoEnterprise::User.find_for_oauth(auth, opts, current_user)
59
+
60
+ if @user.persisted?
61
+ sign_in_and_redirect @user, event: :authentication
62
+ set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
63
+ else
64
+ session["devise.#{provider}_data"] = env["omniauth.auth"]
65
+ redirect_to new_user_registration_url
66
+ end
67
+ end
68
+ }
69
+ end
70
+ end
71
+
72
+ #==================================================================
73
+ # Instance methods
74
+ #==================================================================
75
+ # GET|POST /users/auth/:action/callback
76
+ def intuit
77
+ auth = request.env['omniauth.auth']
78
+ opts = {
79
+ orga_on_create: create_orga_on_user_creation(auth.info.email),
80
+ authorized_link_to_email: session['omniauth.intuit.authorized_link_to_email']
81
+ }
82
+
83
+ # Try to find via intuit
84
+ begin
85
+ @user = MnoEnterprise::User.find_for_oauth(auth, opts, current_user)
86
+ rescue SecurityError
87
+ # Intuit email is NOT a confirmed email. Therefore we need to ask the user to
88
+ # login the old fashion to make sure it is the right user!
89
+ session["omniauth.intuit.request_account_link"] = true
90
+ redirect_to new_user_session_path, notice: "Please sign in using your regular Maestrano account to confirm that you want to link it to your Intuit account"
91
+ return
92
+ end
93
+
94
+ # Cleanup any temporary omniauth.intuit session
95
+ cleanup_intuit_session
96
+
97
+ if @user && @user.persisted?
98
+ # Automatically adds a QuickBooks app (and any other app passed via :app param)
99
+ # to the user orga
100
+ # Only for new users for which an orga was created (not an invited user
101
+ # typically)
102
+ app_instances = setup_apps(@user,['quickbooks',params[:app]], oauth_keyset: params[:app])
103
+ qb_instance = app_instances.first
104
+
105
+ # On Intuit, Mno is configured to add qb_initiated=true if the user
106
+ # comes directly from apps.com (This is a different workflow from using
107
+ # the QuickBooks connect button because we're supposed to trigger the
108
+ # oauth workflow directly via javascript using directConnectToIntuit)
109
+ # Here we store in session the fact that we need to trigger an oauth
110
+ # workflow via directConnectToIntuit
111
+ # ----
112
+ # See layouts/partners/intuit for more info. The session param set
113
+ # below get reset in the view.
114
+ #
115
+ if params[:qb_initiated] && qb_instance && !qb_instance.oauth_keys_valid?
116
+ session[:qb_direct_connect_grant_url] = authorize_webhook_oauth_url(qb_instance.uid)
117
+ end
118
+
119
+ # The above methods trigger many different hooks which
120
+ # may impact the user (typically user workspace). It is safer
121
+ # to reload the user before continuing so that the picture
122
+ # is up to date
123
+ @user.reload
124
+
125
+ # Check whether we should redirect the user to a specific
126
+ # url
127
+ redirect_url = session.delete(:openid_previous_url) || MnoEnterprise.router.dashboard_path || main_app.root_path
128
+
129
+ sign_in @user
130
+ redirect_to redirect_url, event: :authentication
131
+
132
+ set_flash_message(:notice, :success, kind: "Intuit") if is_navigational_format?
133
+ else
134
+ session["devise.intuit_data"] = request.env["omniauth.auth"]
135
+ redirect_to home_url, "ng-controller" => "MnoSignupProcessCtrl", "ng-click" => "startProcess()"
136
+ end
137
+ end
138
+
139
+ #================================================
140
+ # Private methods
141
+ #================================================
142
+ private
143
+
144
+ def cleanup_intuit_session
145
+ session.delete("omniauth.intuit.passthru_email")
146
+ session.delete("omniauth.intuit.request_account_link")
147
+ end
148
+
149
+ # Whether to create an orga on user creation
150
+ def create_orga_on_user_creation(user_email = nil)
151
+ return false if user_email.blank?
152
+ return false if MnoEnterprise::User.exists?(email: user_email)
153
+
154
+ # First check previous url to see if the user
155
+ # was trying to accept an orga
156
+ if !session[:previous_url].blank? && (r = session[:previous_url].match(/\/orga_invites\/(\d+)\?token=(\w+)/))
157
+ invite_params = { id: r.captures[0].to_i, token: r.captures[1] }
158
+ return false if OrgInvite.where(invite_params).any?
159
+ end
160
+
161
+ # Get remaining invites via email address
162
+ return MnoEnterprise::OrgInvite.where(user_email: user_email).empty?
163
+ end
164
+
165
+ # Create or find the apps provided in argument
166
+ # Accept an array of app nid (named id - e.g: 'quickbooks')
167
+ # opts:
168
+ # oauth_keyset: If a oauth_keyset is provided then it will be added to the
169
+ # oauth_keys of any app that is oauth ready (QuickBooks for example)
170
+ #
171
+ # Return an array of app instances (found or created)
172
+ def setup_apps(user = nil, app_nids = [], opts = {})
173
+ return [] unless user
174
+ return [] unless (user.organizations.reload.count == 1)
175
+ return [] unless (org = user.organizations.first)
176
+ return [] unless MnoEnterprise::Ability.new(user).can?(:edit,org)
177
+
178
+ results = []
179
+
180
+ apps = MnoEnterprise::App.where('nid.in' => app_nids.compact)
181
+ existing = org.app_instances.active.index_by(&:app_id)
182
+
183
+ # For each app nid (which is not nil), try to find an existing instance or create one
184
+ apps.each do |app|
185
+ if (app_instance = existing[app.id])
186
+ results << app_instance
187
+ else
188
+ # Provision instance and add to results
189
+ app_instance = org.app_instances.create(product: app.nid)
190
+ results << app_instance
191
+ MnoEnterprise::EventLogger.info('app_add', user.id, 'App added', app_instance)
192
+ end
193
+
194
+ # Add oauth keyset if defined and app_instance is
195
+ # oauth ready and does not have a valid set of oauth keys
196
+ if app_instance && opts[:oauth_keyset].present? && !app_instance.oauth_keys_valid?
197
+ app_instance.oauth_keys = { keyset: opts[:oauth_keyset] }
198
+ app_instance.save
199
+ end
200
+ end
201
+ return results
202
+ end
203
+ end
@@ -109,6 +109,34 @@ module MnoEnterprise::Concerns::Models::Ability
109
109
  !!user.role(org) && ['Super Admin', 'Admin'].include?(user.role(org))
110
110
  end
111
111
  end
112
+
113
+ can :manage_dashboard, MnoEnterprise::Impac::Dashboard do |dashboard|
114
+ if dashboard.owner_type == "Organization"
115
+ # The current user is a member of the organization that owns the dashboard that has the kpi attached to
116
+ owner = MnoEnterprise::Organization.find(dashboard.owner_id)
117
+ owner && !!user.role(owner)
118
+ elsif dashboard.owner_type == "User"
119
+ # The current user is the owner of the dashboard that has the kpi attached to
120
+ dashboard.owner_id == user.id
121
+ else
122
+ false
123
+ end
124
+ end
125
+
126
+ can :manage_widget, MnoEnterprise::Impac::Widget do |widget|
127
+ dashboard = widget.dashboard
128
+ authorize! :manage_dashboard, dashboard
129
+ end
130
+
131
+ can :manage_kpi, MnoEnterprise::Impac::Kpi do |kpi|
132
+ dashboard = kpi.dashboard
133
+ authorize! :manage_dashboard, dashboard
134
+ end
135
+
136
+ can :manage_alert, MnoEnterprise::Impac::Alert do |alert|
137
+ kpi = alert.kpi
138
+ authorize! :manage_kpi, kpi
139
+ end
112
140
  end
113
141
 
114
142
  # Abilities for admin user
@@ -40,13 +40,13 @@ module MnoEnterprise::Concerns::Models::AppInstance
40
40
  included do
41
41
  attributes :id, :uid, :name, :status, :app_id, :created_at, :updated_at, :started_at, :stack, :owner_id,
42
42
  :owner_type, :terminated_at, :stopped_at, :billing_type, :autostop_at, :autostop_interval,
43
- :next_status, :soa_enabled, :oauth_keys_valid, :oauth_company
43
+ :next_status, :soa_enabled, :oauth_company, :oauth_keys, :oauth_keys_valid, :free_trial_end_at, :per_user_licence, :active_licences_count
44
44
 
45
45
  #==============================================================
46
46
  # Constants
47
47
  #==============================================================
48
- ACTIVE_STATUSES = [:running,:stopped,:staged,:provisioning,:starting,:stopping,:updating]
49
- TERMINATION_STATUSES = [:terminating,:terminated]
48
+ ACTIVE_STATUSES = [:running, :stopped, :staged, :provisioning, :starting, :stopping, :updating]
49
+ TERMINATION_STATUSES = [:terminating, :terminated]
50
50
 
51
51
  #==============================================================
52
52
  # Associations
@@ -55,7 +55,7 @@ module MnoEnterprise::Concerns::Models::AppInstance
55
55
  belongs_to :app, class_name: 'MnoEnterprise::App'
56
56
 
57
57
  # Define connector_stack?, cloud_stack? etc. methods
58
- [:cube,:cloud,:connector].each do |stackname|
58
+ [:cube, :cloud, :connector].each do |stackname|
59
59
  define_method("#{stackname}_stack?") do
60
60
  self.stack == stackname.to_s
61
61
  end
@@ -89,8 +89,26 @@ module MnoEnterprise::Concerns::Models::AppInstance
89
89
  ACTIVE_STATUSES.include?(self.status.to_sym)
90
90
  end
91
91
 
92
+ def per_user_licence?
93
+ self.per_user_licence
94
+ end
95
+
96
+ def under_free_trial?
97
+ self.under_free_trial
98
+ end
99
+
92
100
  def running?
93
101
  self.status == 'running'
94
102
  end
95
103
 
104
+ def to_audit_event
105
+ {
106
+ id: id,
107
+ uid: uid,
108
+ name: name,
109
+ app_nid: app ? app.nid : nil
110
+ }
111
+ end
112
+
113
+
96
114
  end
@@ -0,0 +1,28 @@
1
+ require 'openssl'
2
+
3
+ module MnoEnterprise::Concerns::Models::IntercomUser
4
+ extend ActiveSupport::Concern
5
+
6
+ #==================================================================
7
+ # Included methods
8
+ #==================================================================
9
+ # 'included do' causes the included code to be evaluated in the
10
+ # context where it is included rather than being executed in the module's context
11
+ included do
12
+ end
13
+
14
+ #==================================================================
15
+ # Class methods
16
+ #==================================================================
17
+ module ClassMethods
18
+ end
19
+
20
+ #==================================================================
21
+ # Instance methods
22
+ #==================================================================
23
+ # Return intercom user hash
24
+ # This is used in secure mode
25
+ def intercom_user_hash
26
+ OpenSSL::HMAC.hexdigest('sha256', MnoEnterprise.intercom_api_secret, (self.id || self.email).to_s) if MnoEnterprise.intercom_api_secret
27
+ end
28
+ end
@@ -51,6 +51,7 @@ module MnoEnterprise::Concerns::Models::Organization
51
51
  has_one :credit_card, class_name: 'MnoEnterprise::CreditCard'
52
52
  has_many :teams, class_name: 'MnoEnterprise::Team'
53
53
  has_many :dashboards, class_name: 'MnoEnterprise::Impac::Dashboard'
54
+ has_many :widgets, class_name: 'MnoEnterprise::Impac::Widget'
54
55
  has_one :raw_last_invoice, class_name: 'MnoEnterprise::Invoice', path: '/last_invoice'
55
56
  has_one :app_instances_sync, class_name: 'MnoEnterprise::AppInstancesSync'
56
57
  end
@@ -73,7 +74,7 @@ module MnoEnterprise::Concerns::Models::Organization
73
74
  # @params [Boolean] show_staged Also displayed staged invites (ie: not sent)
74
75
  def members(show_staged=false)
75
76
  invites = show_staged ? self.org_invites.active_or_staged : self.org_invites.active
76
- [self.users, invites].flatten
77
+ [self.users, invites.to_a].flatten
77
78
  end
78
79
 
79
80
  # Add a user to the organization with the provided role
@@ -102,4 +103,16 @@ module MnoEnterprise::Concerns::Models::Organization
102
103
  def update_user(user, role = 'Member')
103
104
  self.users.update(id: user.id, role: role)
104
105
  end
106
+
107
+ def to_audit_event
108
+ {
109
+ id: id,
110
+ uid: uid,
111
+ name: name
112
+ }
113
+ end
114
+
115
+ def payment_restriction
116
+ meta_data && meta_data['payment_restriction']
117
+ end
105
118
  end
@@ -0,0 +1,67 @@
1
+ # == Schema Information
2
+ #
3
+ # Endpoint:
4
+ # - /v1/teams
5
+ # - /v1/organizations/:organization_id/teams
6
+ #
7
+ # id :integer not null, primary key
8
+ # name :string(255)
9
+ # created_at :datetime not null
10
+ # updated_at :datetime not null
11
+ # organization_id :integer
12
+ #
13
+
14
+ module MnoEnterprise::Concerns::Models::Team
15
+ extend ActiveSupport::Concern
16
+
17
+ #==================================================================
18
+ # Included methods
19
+ #==================================================================
20
+ # 'included do' causes the included code to be evaluated in the
21
+ # context where it is included rather than being executed in the module's context
22
+ included do
23
+ attributes :id, :name, :organization_id
24
+
25
+ #=====================================
26
+ # Associations
27
+ #=====================================
28
+ belongs_to :organization, class_name: 'MnoEnterprise::Organization'
29
+ has_many :users, class_name: 'MnoEnterprise::User'
30
+ has_many :app_instances, class_name: 'MnoEnterprise::AppInstance'
31
+ end
32
+
33
+ #==================================================================
34
+ # Class methods
35
+ #==================================================================
36
+ module ClassMethods
37
+ # def some_class_method
38
+ # 'some text'
39
+ # end
40
+ end
41
+
42
+ #==================================================================
43
+ # Instance methods
44
+ #==================================================================
45
+ # Add a user to the team
46
+ # TODO: specs
47
+ def add_user(user)
48
+ self.users.create(id: user.id)
49
+ end
50
+
51
+ # Remove a user from the team
52
+ # TODO: specs
53
+ def remove_user(user)
54
+ self.users.destroy(id: user.id)
55
+ end
56
+
57
+ # Set the app_instance permissions of this team
58
+ # Accept a collection of hashes or an array of ids
59
+ # TODO: specs
60
+ def set_access_to(collection_or_array)
61
+ # Empty arrays do not seem to be passed in the request. Force value in this case
62
+ list = collection_or_array.empty? ? [""] : collection_or_array
63
+ self.put(data: { set_access_to: list })
64
+ self.reload
65
+ self
66
+ end
67
+ end
@@ -21,6 +21,7 @@ require "her_extension/model/associations/belongs_to_association"
21
21
  require "her_extension/middleware/mnoe_api_v1_parse_json"
22
22
  require "her_extension/middleware/mnoe_raise_error"
23
23
  require "faraday_middleware"
24
+ require "httparty"
24
25
  require "mno_enterprise/engine"
25
26
 
26
27
  require 'mno_enterprise/database_extendable'
@@ -29,8 +30,7 @@ require 'mno_enterprise/database_extendable'
29
30
  require 'config'
30
31
  require 'figaro'
31
32
 
32
- require "mandrill_client"
33
-
33
+ require 'mandrill_client'
34
34
  require 'accountingjs_serializer'
35
35
 
36
36
  module MnoEnterprise
@@ -52,7 +52,7 @@ module MnoEnterprise
52
52
  end
53
53
 
54
54
  def terms_url
55
- @terms_url || '#'
55
+ @terms_url || '/mnoe/terms'
56
56
  end
57
57
 
58
58
  def admin_path
@@ -213,6 +213,23 @@ module MnoEnterprise
213
213
  mattr_accessor :google_tag_container
214
214
  @@google_tag_container = nil
215
215
 
216
+ mattr_accessor :intercom_app_id
217
+ @@intercom_app_id = nil
218
+
219
+ mattr_accessor :intercom_api_secret
220
+ @@intercom_api_secret = nil
221
+
222
+ mattr_accessor :intercom_api_key
223
+ @@intercom_api_key = nil
224
+
225
+ mattr_accessor :intercom_token
226
+ @@intercom_token = nil
227
+
228
+ # Define if Intercom is enabled. Only if the gem intercom is present
229
+ def self.intercom_enabled?
230
+ defined?(::Intercom) && ((intercom_app_id && intercom_api_key) || intercom_token)
231
+ end
232
+
216
233
  #====================================
217
234
  # Layout & Styling
218
235
  #====================================
@@ -248,6 +265,8 @@ module MnoEnterprise
248
265
  @@style
249
266
  end
250
267
 
268
+
269
+
251
270
  # Default way to setup MnoEnterprise. Run rails generate mno-enterprise:install to create
252
271
  # a fresh initializer with all configuration values.
253
272
  def self.configure
@@ -0,0 +1,19 @@
1
+ module MnoEnterprise
2
+ class ImpacClient
3
+ include HTTParty
4
+
5
+ def self.host
6
+ "#{Settings.impac.protocol}://#{Settings.impac.host}"
7
+ end
8
+
9
+ def self.endpoint_url(endpoint, params)
10
+ "#{File.join(host,endpoint)}?#{params.to_query}"
11
+ end
12
+
13
+ def self.send_get(endpoint, params, opts={})
14
+ url = endpoint_url(endpoint, params)
15
+ get(url, opts)
16
+ end
17
+
18
+ end
19
+ end
@@ -16,7 +16,10 @@ module MnoEnterprise
16
16
  # Prepare message from args
17
17
  message = { from_name: from[:name], from_email: from[:email]}
18
18
  message[:to] = [to].flatten.map { |t| {name: t[:name], email: t[:email], type: (t[:type] || :to) } }
19
- message[:global_merge_vars] = vars.map { |k,v| {name: k.to_s, content: v} }
19
+
20
+ # Sanitize merge vars
21
+ full_sanitizer = Rails::Html::FullSanitizer.new
22
+ message[:global_merge_vars] = vars.map { |k,v| {name: k.to_s, content: full_sanitizer.sanitize(v)} }
20
23
 
21
24
  # Merge additional mandrill options
22
25
  message.merge!(opts)
@@ -0,0 +1,51 @@
1
+ # Read about factories at https://github.com/thoughtbot/factory_girl
2
+
3
+ FactoryGirl.define do
4
+ factory :mno_enterprise_app_review, :class => 'AppReview' do
5
+
6
+ factory :app_review, class: MnoEnterprise::AppReview do
7
+ sequence(:id)
8
+ description 'Some Description'
9
+ status 'approved'
10
+ rating 3
11
+ app_id 'app-id'
12
+ app_name 'the app'
13
+ user_id 'usr-11'
14
+ user_name 'Jean Bon'
15
+ organization_id 'org-11'
16
+ organization_name 'Organization 11'
17
+ created_at 3.days.ago
18
+ updated_at 1.hour.ago
19
+ edited true
20
+ edited_by_name 'Jane Dale'
21
+ edited_by_admin_role 'admin'
22
+ edited_by_id 1
23
+
24
+ user_admin_role 'admin'
25
+ # Properly build the resource with Her
26
+ initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } }
27
+
28
+ factory :app_feedback, class: MnoEnterprise::AppFeedback do
29
+ type 'Feedback'
30
+ sequence(:description) { |n| "Feedback ##{n}" }
31
+ end
32
+
33
+ factory :app_question, class: MnoEnterprise::AppQuestion do
34
+ type 'Question'
35
+ sequence(:description) { |n| "Question ##{n}" }
36
+ end
37
+
38
+ factory :app_comment, class: MnoEnterprise::AppComment do
39
+ type 'Comment'
40
+ sequence(:description) { |n| "Comment ##{n}" }
41
+ feedback_id 'feedback-id'
42
+ end
43
+
44
+ factory :app_answer, class: MnoEnterprise::AppAnswer do
45
+ type 'Answer'
46
+ sequence(:description) { |n| "Answer ##{n}" }
47
+ question_id 'question-id'
48
+ end
49
+ end
50
+ end
51
+ end
@@ -5,7 +5,7 @@ FactoryGirl.define do
5
5
  sequence(:id) { |n| n }
6
6
  sequence(:name) { |n| "TestApp#{n}" }
7
7
  nid { name.parameterize }
8
-
8
+
9
9
  description "This is a description"
10
10
  created_at 1.day.ago
11
11
  updated_at 2.hours.ago
@@ -14,30 +14,32 @@ FactoryGirl.define do
14
14
  slug { "#{id}-myapp" }
15
15
  categories ["CRM"]
16
16
  tags ['Foo', 'Bar']
17
- key_benefits ['Super','Hyper','Good']
18
- key_features ['Super','Hyper','Good']
19
- testimonials [{text:'Bla', company:'Doe Pty Ltd', author: 'John'}]
17
+ key_benefits ['Super', 'Hyper', 'Good']
18
+ key_features ['Super', 'Hyper', 'Good']
19
+ testimonials [{text: 'Bla', company: 'Doe Pty Ltd', author: 'John'}]
20
20
  worldwide_usage 120000
21
21
  tiny_description "A great app"
22
22
  stack 'cube'
23
23
  terms_url "http://opensource.org/licenses/MIT"
24
24
  appinfo { {} }
25
+ average_rating { rand(1..5) }
25
26
  sequence(:rank) { |n| n }
26
- pricing_plans {{
27
- 'default' =>[{name: 'Monthly Plan', price: '20.0', currency: 'AUD', factor: '/month'}]
28
- }}
27
+ running_instances_count { rand(0..10) }
28
+ pricing_plans { {
29
+ 'default' => [{name: 'Monthly Plan', price: '20.0', currency: 'AUD', factor: '/month'}]
30
+ } }
29
31
 
30
32
  trait :cloud do
31
33
  stack 'cloud'
32
34
  end
33
-
35
+
34
36
  trait :connector do
35
37
  stack 'connector'
36
38
  end
37
-
39
+
38
40
  factory :cloud_app, traits: [:cloud]
39
41
  factory :connector_app, traits: [:connector]
40
-
42
+
41
43
  # Properly build the resource with Her
42
44
  initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } }
43
45
  end
@@ -0,0 +1,9 @@
1
+ FactoryGirl.define do
2
+ factory :identity, class: MnoEnterprise::Identity do
3
+ provider 'someprovider'
4
+ uid '123456'
5
+
6
+ # Properly build the resource with Her
7
+ initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } }
8
+ end
9
+ end