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.
- checksums.yaml +4 -4
- data/app/assets/images/mno_enterprise/logo-intuit.png +0 -0
- data/app/assets/images/mno_enterprise/main-logo.png +0 -0
- data/app/assets/stylesheets/mno_enterprise/mail.css +1 -4
- data/app/controllers/mno_enterprise/application_controller.rb +1 -1
- data/app/helpers/mno_enterprise/application_helper.rb +7 -7
- data/app/models/mno_enterprise/app.rb +12 -3
- data/app/models/mno_enterprise/app_answer.rb +13 -0
- data/app/models/mno_enterprise/app_comment.rb +10 -0
- data/app/models/mno_enterprise/app_feedback.rb +7 -0
- data/app/models/mno_enterprise/app_question.rb +9 -0
- data/app/models/mno_enterprise/app_review.rb +7 -0
- data/app/models/mno_enterprise/base_resource.rb +6 -1
- data/app/models/mno_enterprise/identity.rb +24 -0
- data/app/models/mno_enterprise/impac/alert.rb +10 -0
- data/app/models/mno_enterprise/impac/dashboard.rb +10 -11
- data/app/models/mno_enterprise/impac/kpi.rb +4 -2
- data/app/models/mno_enterprise/impac/widget.rb +4 -1
- data/app/models/mno_enterprise/team.rb +1 -33
- data/app/models/mno_enterprise/user.rb +109 -28
- data/app/views/system_notifications/email-change.html.erb +27 -0
- data/app/views/system_notifications/email-change.text.erb +10 -0
- data/app/views/system_notifications/password-change.html.erb +25 -0
- data/app/views/system_notifications/password-change.text.erb +7 -0
- data/app/views/system_notifications/reconfirmation-instructions.html.erb +7 -5
- data/app/views/system_notifications/reconfirmation-instructions.text.erb +3 -2
- data/config/initializers/config.rb +5 -0
- data/config/locales/models/user/en.yml +5 -0
- data/config/locales/templates/components/en.yml +9 -0
- data/config/locales/templates/dashboard/en.yml +14 -0
- data/config/locales/templates/dashboard/marketplace/en.yml +111 -8
- data/config/locales/templates/dashboard/organization/en.yml +1 -0
- data/config/locales/templates/impac/dock/en.yml +28 -0
- data/config/locales/views/auth/shared/en.yml +1 -2
- data/config/locales/views/webhook/o_auth/providers/en.yml +11 -6
- data/lib/devise/hooks/lockable.rb +13 -0
- data/lib/devise/models/remote_authenticatable.rb +31 -5
- data/lib/generators/mno_enterprise/install/install_generator.rb +7 -0
- data/lib/generators/mno_enterprise/install/templates/Procfile +1 -1
- data/lib/generators/mno_enterprise/install/templates/Procfile.dev +1 -1
- data/lib/generators/mno_enterprise/install/templates/config/application.yml +17 -0
- data/lib/generators/mno_enterprise/install/templates/config/initializers/mno_enterprise.rb +8 -3
- data/lib/generators/mno_enterprise/install/templates/config/newrelic.yml +46 -0
- data/lib/generators/mno_enterprise/install/templates/config/puma.rb +56 -0
- data/lib/generators/mno_enterprise/install/templates/config/settings/production.yml +1 -1
- data/lib/generators/mno_enterprise/install/templates/config/settings/uat.yml +1 -1
- data/lib/generators/mno_enterprise/install/templates/config/settings.yml +21 -0
- data/lib/generators/mno_enterprise/install/templates/stylesheets/variables.less +3 -0
- data/lib/her_extension/model/associations/association_proxy.rb +10 -6
- data/lib/mno_enterprise/concerns/controllers/auth/confirmations_controller.rb +1 -1
- data/lib/mno_enterprise/concerns/controllers/auth/omniauth_callbacks_controller.rb +203 -0
- data/lib/mno_enterprise/concerns/models/ability.rb +28 -0
- data/lib/mno_enterprise/concerns/models/app_instance.rb +22 -4
- data/lib/mno_enterprise/concerns/models/intercom_user.rb +28 -0
- data/lib/mno_enterprise/concerns/models/organization.rb +14 -1
- data/lib/mno_enterprise/concerns/models/team.rb +67 -0
- data/lib/mno_enterprise/core.rb +22 -3
- data/lib/mno_enterprise/impac_client.rb +19 -0
- data/lib/mno_enterprise/mail_adapters/mandrill_adapter.rb +4 -1
- data/lib/mno_enterprise/testing_support/factories/app_review.rb +51 -0
- data/lib/mno_enterprise/testing_support/factories/apps.rb +12 -10
- data/lib/mno_enterprise/testing_support/factories/identity.rb +9 -0
- data/lib/mno_enterprise/testing_support/factories/impac/alerts.rb +18 -0
- data/lib/mno_enterprise/testing_support/factories/impac/kpis.rb +5 -9
- data/lib/mno_enterprise/testing_support/factories/users.rb +4 -0
- data/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb +39 -0
- data/lib/mno_enterprise/testing_support/organizations_shared_helpers.rb +2 -1
- data/lib/mno_enterprise/testing_support/shared_examples/jpi_v1_admin.rb +38 -0
- data/lib/mno_enterprise/version.rb +1 -1
- data/spec/lib/devise/model/remote_authenticable_spec.rb +76 -0
- data/spec/lib/mno_enterprise/impac_client_spec.rb +31 -0
- data/spec/mno_enterprise_spec.rb +7 -8
- data/spec/models/mno_enterprise/app_spec.rb +1 -1
- data/spec/models/mno_enterprise/identity_spec.rb +39 -0
- data/spec/models/mno_enterprise/organization_spec.rb +19 -2
- data/spec/models/mno_enterprise/user_spec.rb +238 -0
- 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',
|
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, :
|
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
|
49
|
-
TERMINATION_STATUSES = [:terminating
|
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
|
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
|
data/lib/mno_enterprise/core.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
27
|
-
|
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
|