lesli 5.0.20 → 5.0.22

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +5 -0
  3. data/app/assets/images/lesli/brand/register-background.jpg +0 -0
  4. data/app/assets/stylesheets/lesli/application.css +191 -1
  5. data/app/helpers/lesli/assets_helper.rb +23 -59
  6. data/app/models/concerns/lesli/account_initializer.rb +79 -0
  7. data/app/models/concerns/lesli/user_activities.rb +165 -0
  8. data/app/models/concerns/lesli/user_extensions.rb +133 -0
  9. data/app/models/concerns/lesli/user_security.rb +220 -0
  10. data/app/models/lesli/user.rb +15 -15
  11. data/app/services/lesli/role_service.rb +4 -4
  12. data/app/views/lesli/abouts/welcome.html.erb +11 -11
  13. data/app/views/lesli/errors/unauthorized.html.erb +1 -1
  14. data/app/views/lesli/layouts/application-devise.html.erb +3 -6
  15. data/app/views/lesli/layouts/application-lesli.html.erb +4 -5
  16. data/app/views/lesli/layouts/application-public.html.erb +2 -16
  17. data/app/views/lesli/partials/_application-analytics.html.erb +4 -6
  18. data/app/views/lesli/partials/_application-head.html.erb +4 -4
  19. data/app/views/lesli/partials/{_application-lesli-scss.html.erb → _application-lesli-css.html.erb} +4 -10
  20. data/app/views/lesli/partials/{_application-data.html.erb → _application-lesli-data.html.erb} +4 -1
  21. data/app/views/lesli/partials/_application-lesli-header.html.erb +0 -15
  22. data/app/views/lesli/partials/_application-lesli-javascript.html.erb +3 -5
  23. data/db/seed/accounts.rb +1 -1
  24. data/db/seed/users.rb +1 -1
  25. data/db/seeds.rb +6 -6
  26. data/lib/lesli/engine.rb +3 -4
  27. data/lib/lesli/version.rb +2 -2
  28. data/lib/rspec/helpers/lesli_helper.rb +11 -0
  29. data/lib/tasks/lesli/controllers.rake +1 -1
  30. data/lib/tasks/lesli/db.rake +26 -26
  31. data/lib/tasks/lesli_tasks.rake +0 -2
  32. metadata +19 -41
  33. data/app/models/concerns/account_initializer.rb +0 -82
  34. data/app/models/concerns/user_activities.rb +0 -163
  35. data/app/models/concerns/user_extensions.rb +0 -152
  36. data/app/models/concerns/user_security.rb +0 -277
  37. data/app/operators/lesli/user_registration_operator.rb +0 -122
  38. data/app/views/lesli/partials/_application-lesli-chatbox.html.erb +0 -35
  39. data/app/views/lesli/partials/_application-lesli-panels.html.erb +0 -58
  40. data/app/views/lesli/partials/_application-public-footer.html.erb +0 -51
  41. data/app/views/lesli/partials/_application-public-javascript.html.erb +0 -49
  42. data/app/views/lesli/partials/_application-public-scss.html.erb +0 -35
  43. data/config/initializers/devise_rails_8_patch.rb +0 -8
@@ -1,163 +0,0 @@
1
- =begin
2
-
3
- Lesli
4
-
5
- Copyright (c) 2023, Lesli Technologies, S. A.
6
-
7
- This program is free software: you can redistribute it and/or modify
8
- it under the terms of the GNU General Public License as published by
9
- the Free Software Foundation, either version 3 of the License, or
10
- (at your option) any later version.
11
-
12
- This program is distributed in the hope that it will be useful,
13
- but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- GNU General Public License for more details.
16
-
17
- You should have received a copy of the GNU General Public License
18
- along with this program. If not, see http://www.gnu.org/licenses/.
19
-
20
- Lesli · Ruby on Rails SaaS development platform.
21
-
22
- Made with ♥ by https://www.lesli.tech
23
- Building a better future, one line of code at a time.
24
-
25
- @contact hello@lesli.tech
26
- @website https://www.lesli.tech
27
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
-
29
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
- // ·
31
- =end
32
-
33
-
34
- # User Activities methods
35
- # Methods related to cloudobject/activities and user_logs
36
- module UserActivities
37
- extend ActiveSupport::Concern
38
-
39
-
40
- # @return [void]
41
- # @description Register a new log for the current user
42
- # @param description String Details about the process
43
- # @param session String Current or active session id
44
- def log description, session=nil
45
- self.logs.create(session, description)
46
- end
47
-
48
- def log_activity_update(current_user, user, old_attributes, new_attributes)
49
- old_attributes.except!("id", "users_id", "created_at", "updated_at", "deleted_at")
50
- old_attributes.each do |key, value|
51
- if value != new_attributes[key]
52
- value_from = value
53
- value_to = new_attributes[key]
54
- value_from = Courier::Core::Date.to_string_datetime(value_from) if value_from.is_a?(Time) || value_from.is_a?(Date)
55
- value_to = Courier::Core::Date.to_string_datetime(value_to) if value_to.is_a?(Time) || value_to.is_a?(Date)
56
-
57
- user.activities.create!(
58
- assigned: current_user,
59
- category: "action_update",
60
- field_name: key,
61
- value_from: value_from,
62
- value_to: value_to
63
- )
64
- end
65
- end
66
- end
67
-
68
-
69
- # Define class methods from given block
70
- # This allows to inject class methods to the User model, so we can use:
71
- # User.log_activity_create
72
- class_methods do
73
-
74
-
75
- # @return [void]
76
- # @param current_user [::User] The user that created the user
77
- # @param [User] The user that was created
78
- # @description Creates an activity for this user indicating who created it.
79
- # Example
80
- # params = {...}
81
- # user = User.create(params)
82
- # User.log_activity_create(User.find(1), user)
83
- def log_activity_create(current_user, user)
84
- user.activities.create(
85
- assigned: current_user,
86
- category: "action_create"
87
- )
88
- end
89
-
90
-
91
- # @return [void]
92
- # @param current_user [::User] The user that created the user
93
- # @param user [User] The User that was created
94
- # @param old_attributes[Hash] The data of the record before update
95
- # @param new_attributes[Hash] The data of the record after update
96
- # @description Creates an activity for this user if someone changed any of this values
97
- # Example
98
- # user = User.find(1)
99
- # old_attributes = user.attributes.merge({detail_attributes: user.detail.attributes})
100
- # user.update(main_employee: User.find(33))
101
- # new_attributes = user.attributes.merge({detail_attributes: user.detail.attributes})
102
- # User.log_activity_update(User.find(1), user, old_attributes, new_attributes)
103
- def log_activity_update(current_user, user, old_attributes, new_attributes)
104
- old_attributes.except!("id", "users_id", "created_at", "updated_at", "deleted_at")
105
- old_attributes.each do |key, value|
106
- if value != new_attributes[key]
107
- value_from = value
108
- value_to = new_attributes[key]
109
- value_from = Courier::Core::Date.to_string_datetime(value_from) if value_from.is_a?(Time) || value_from.is_a?(Date)
110
- value_to = Courier::Core::Date.to_string_datetime(value_to) if value_to.is_a?(Time) || value_to.is_a?(Date)
111
-
112
- user.activities.create(
113
- owner: current_user,
114
- category: "action_update",
115
- field_name: key,
116
- value_from: value_from,
117
- value_to: value_to
118
- )
119
- end
120
- end
121
- end
122
-
123
-
124
- # @return [void]
125
- # @param current_user [::User] The user that created the user
126
- # @param user [User] The User that was created
127
- # @param user [Role] The Role assigned to the user
128
- # @description Creates an activity for this user if someone adds a new role
129
- # Example
130
- # user = User.find(1)
131
- # role = User.find(1)
132
- # User.log_activity_create_user_role(current_user, user, role)
133
- def log_activity_create_user_role(current_user, user, role)
134
- role_name = role.name
135
-
136
- user.activities.create(
137
- assigned: current_user,
138
- category: "action_create_user_role",
139
- value_to: role_name
140
- )
141
- end
142
-
143
-
144
- # @return [void]
145
- # @param current_user [::User] The user that created the user
146
- # @param user [User] The User that was created
147
- # @param user [Role] The Role assigned to the user
148
- # @description Creates an activity for this user if someone removes a role
149
- # Example
150
- # user = User.find(1)
151
- # role = User.find(1)
152
- # User.log_activity_destroy_user_role(current_user, user, role)
153
- def log_activity_destroy_user_role(current_user, user, role)
154
- role_name = role.name
155
-
156
- user.activities.create(
157
- assigned: current_user,
158
- category: "action_destroy_user_role",
159
- value_to: role_name
160
- )
161
- end
162
- end
163
- end
@@ -1,152 +0,0 @@
1
- =begin
2
-
3
- Lesli
4
-
5
- Copyright (c) 2023, Lesli Technologies, S. A.
6
-
7
- This program is free software: you can redistribute it and/or modify
8
- it under the terms of the GNU General Public License as published by
9
- the Free Software Foundation, either version 3 of the License, or
10
- (at your option) any later version.
11
-
12
- This program is distributed in the hope that it will be useful,
13
- but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- GNU General Public License for more details.
16
-
17
- You should have received a copy of the GNU General Public License
18
- along with this program. If not, see http://www.gnu.org/licenses/.
19
-
20
- Lesli · Your Smart Business Assistant.
21
-
22
- Made with ♥ by https://www.lesli.tech
23
- Building a better future, one line of code at a time.
24
-
25
- @contact hello@lesli.tech
26
- @website https://lesli.tech
27
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
-
29
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
- // ·
31
-
32
- =end
33
-
34
- # User extension methods
35
- # Custom methods that belongs to a instance user
36
- module UserExtensions
37
- extend ActiveSupport::Concern
38
-
39
-
40
- # @return [nil]
41
- # @description Set the user alias based on the full_name.
42
- # @example
43
- # puts current_user.full_name # John Doe
44
- # puts current_user.set_alias # John D.
45
- def set_alias
46
- if self.alias.blank?
47
- self.alias = full_name_initials()
48
- self.save
49
- end
50
- end
51
-
52
-
53
- # @return [void]
54
- # @description Register a new notification for the current user
55
- # @param subject String Short notification description
56
- # @param body String Long notification description
57
- # @param url String Link to notified object
58
- # @param category String Kind of notification: info, warning, danger, success.
59
- def notification subject, body:nil, url:nil, category:"info"
60
- Courier::Bell::Notification.new(self, subject, body:body, url:url, category:category)
61
- end
62
-
63
- # @return [void]
64
- # @description Register a new notification for the current user
65
- # @param subject String Short notification description
66
- # @param body String Long notification description
67
- # @param url String Link to notified object
68
- # @param category String Kind of notification: info, warning, danger, success.
69
- def notifications quantity=5, category:"info"
70
- query = {
71
- :pagination => {
72
- :perPage => quantity,
73
- :page => 1
74
- }
75
- }
76
- Lesli::Courier.new(:lesli_bell, []).from(:notification_service, self, query).call(:index)
77
- end
78
-
79
-
80
- # @return [CloudDriver::Calendar]
81
- # @description Return the default calendar of the user if source_code is not provided.
82
- # If source_code is provided the method return the specified source calendar.
83
- def calendar source_code: :lesli
84
- return Courier::Driver::Calendar.get_user_calendar(self, source_code: source_code, default: true) if source_code == :lesli
85
- Courier::Driver::Calendar.get_user_calendar(self, source_code: source_code)
86
- end
87
-
88
-
89
- # @return [String] The name of this user.
90
- # @description Retrieves and returns the name of the user depending on the available information.
91
- # The name can be a full name (first and last names), just the first name, or, in case the information
92
- # is not available, the email. This method currently is available if the the CloudLock engine exists,
93
- # otherwise, it returns *nil*
94
- # @example
95
- # my_user = current_user
96
- # puts my_user.name # can print John Doe
97
- # other_user = User.last
98
- # puts other_user.name # can print jane.smith@email.com
99
- def full_name
100
- self.first_name.blank? ? email : self.first_name + " " + self.last_name.to_s
101
- end
102
-
103
-
104
- # @return [String] The name initials of this user.
105
- # @description Retrieves and returns the name initials of the user depending on the available information.
106
- # @example
107
- # puts current_user.full_name_initials # would print JD
108
- def full_name_initials
109
- self.first_name.blank? ? "" : self.first_name[0].upcase + "" + (self.last_name.blank? ? "" : self.last_name[0].upcase)
110
- end
111
-
112
-
113
- # @return [String]
114
- # @description Returns the local configuration for the user if there is no locale the default local
115
- # of the platform will be returned
116
- # @example
117
- # locale = User.last.locle
118
- # will print something like: :es
119
- def locale
120
- user_locale = self.settings.find_by(name: "locale")
121
-
122
- # return the desire locale by the user
123
- return user_locale.value.to_sym if user_locale
124
-
125
- # create a desire locale if the record does not exist
126
- self.settings.create_with(:value => I18n.locale).find_or_create_by(:name => "locale")
127
-
128
- # reevaluate
129
- self.locale()
130
- end
131
-
132
-
133
- def role_names
134
- user_roles = self.lesliroles.map(&:name).join(", ")
135
- end
136
-
137
-
138
- # @return [void]
139
- # @description Returns MFA settings configured by the user
140
- # Example
141
- # user_mfa_settings = User.find(2).mfa_settings
142
- # puts user_mfa_settings
143
- # { :mfa_enabled => true, :mfa_method => "email"}
144
- def mfa_settings
145
- mfa_enabled = self.settings.create_with(:value => false).find_or_create_by(:name => "mfa_enabled")
146
- mfa_method = self.settings.create_with(:value => :email).find_or_create_by(:name => "mfa_method")
147
- {
148
- :enabled => mfa_enabled.nil? ? false : mfa_enabled.value == 't',
149
- :method => mfa_method.nil? ? nil : mfa_method.value.to_sym
150
- }
151
- end
152
- end
@@ -1,277 +0,0 @@
1
- =begin
2
-
3
- Lesli
4
-
5
- Copyright (c) 2023, Lesli Technologies, S. A.
6
-
7
- This program is free software: you can redistribute it and/or modify
8
- it under the terms of the GNU General Public License as published by
9
- the Free Software Foundation, either version 3 of the License, or
10
- (at your option) any later version.
11
-
12
- This program is distributed in the hope that it will be useful,
13
- but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- GNU General Public License for more details.
16
-
17
- You should have received a copy of the GNU General Public License
18
- along with this program. If not, see http://www.gnu.org/licenses/.
19
-
20
- Lesli · Ruby on Rails SaaS Development Framework.
21
-
22
- Made with ♥ by https://www.lesli.tech
23
- Building a better future, one line of code at a time.
24
-
25
- @contact hello@lesli.tech
26
- @website https://www.lesli.tech
27
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
-
29
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
- // ·
31
- =end
32
-
33
-
34
- # User extension methods
35
- # Custom methods that belongs to a instance user
36
- module UserSecurity
37
- extend ActiveSupport::Concern
38
-
39
-
40
- def max_object_level_permission
41
-
42
- # get the max object level permission from roles assigned to the user
43
- level = self.lesliroles.maximum(:permission_level)
44
-
45
- # if user has no roles assigned, we return the lowest role available
46
- # NOTE: This should not be possible due the user needs a role to login
47
- unless level
48
- return (self.account.roles.minimum(:permission_level))
49
- end
50
-
51
- # return the level found
52
- level
53
- end
54
-
55
- # @return [void]
56
- # @description After creating a user, creates the necessary resources for them to access the different engines.
57
- # @param *roles [String] One or more roles to be checked
58
- # check role of the user
59
- def has_roles? *roles
60
- !roles.intersection(self.roles.map{ |r| r[:name] }).empty?
61
- end
62
-
63
-
64
- # @return [Boolean]
65
- # @description Return true/false if a user has all the privileges to do an action based on a controllers list,
66
- # this validation includes the privileges that the user could have based on its roles and the privileges
67
- # that has been added to the specific user.
68
- # @examples
69
- # validate privileges on a controller with the same actions on each one
70
- # controllers = ["cloud_house/companies", "cloud_house/projects"]
71
- # actions = ["index", "update"]
72
- #
73
- # current_user.has_privileges?(controllers, actions)
74
- def has_privileges_for?(controller, action)
75
- begin
76
- return self.privileges.where(
77
- controller: controller,
78
- action: action,
79
- active: true
80
- ).exists?
81
- rescue => exception
82
- return false
83
- end
84
- end
85
-
86
-
87
- # @return [Hash]
88
- # @description Return a hash that contains all the abilities grouped by controller and define every action privilege. It also
89
- # evaluate if the user has the ability no matter if is given to the user by role or by itself.
90
- # @examples
91
- # current_user.abilities_by_controller
92
- def abilities_by_controller
93
-
94
- # Abilities hash where we will save all the privileges the user has to
95
- abilities = {}
96
-
97
- # We check all the privileges the user has in the cache table according to his roles
98
- # and create a key per controller (with the full controller name) that contains an array of all the
99
- # methods/actions with permission
100
- # self.privileges.all.each do |privilege|
101
- # abilities[privilege.controller] = [] if abilities[privilege.controller].nil?
102
- # abilities[privilege.controller] << privilege.action
103
- # end
104
-
105
- abilities
106
-
107
- end
108
-
109
-
110
- # @return Boolean
111
- # @description Check if user has enough privilege to work with the given role
112
- def can_work_with_role?(role)
113
-
114
- # get the role if only id is given
115
- role = self.account.roles.find_by(:id => role) unless role.class.name == "Lesli::Role"
116
-
117
- # false if role not found
118
- return false if role.blank?
119
-
120
- # not valid role without object levelpermission defined
121
- return false if role.object_level_permission.blank?
122
-
123
- # owner role can work with all the roles
124
- return true if !self.roles.find_by(name: 'owner').blank?
125
-
126
- # get the max object level permission from the roles the user has assigned
127
- user_role_level_max = self.roles.map(&:object_level_permission).max()
128
-
129
- # check if user can work with the object level permission of the role is trying to modify
130
- # Note: user only can assigned an object level permission below the max of his own roles
131
- # Current user cannot assign role if
132
- # role to assign has greater object level permission than the greater role assigned to the current user
133
- # role to assign is the same of the greater role assigned to the current user
134
- # current user is not sysadmin or owner
135
- return false if role.object_level_permission >= user_role_level_max
136
-
137
- # user can work with this role :)
138
- return true
139
-
140
- end
141
-
142
-
143
- # @return [nil,string]
144
- # @description Checks configuration of all the roles assigned to the user
145
- # if user has a role with "default path" to use as home to redirect after login
146
- # IMPORTANT: This home path is used only the send the user after login, the user
147
- # and the role are not limited by this configuration
148
- def has_role_with_default_path?()
149
-
150
- # get the roles that contains a path
151
- role = self.roles.where.not(path_default: [nil, ""])
152
-
153
- # here we must order the results descendant because we must
154
- # keep the path of the hightest object level permission role.
155
- # Example: we should use the path of the admin role if user has
156
- # admin & employee roles, also order by default_path, so we get first
157
- # the roles with path in case the user has roles with the same object level permission
158
- role = role.order(object_level_permission: :desc).order(:path_default)
159
-
160
- # get the first role found, due previously we sort in a descendant order
161
- # the first role is going to be the one with highest object level permission
162
- # this is going to return nil if no role was found
163
- default_path = role.first&.path_default || "/"
164
-
165
- # if first loggin for account owner send him to the onboarding page
166
- if self.account.onboarding? && self.has_roles?("owner")
167
- default_path = "/onboarding"
168
- end
169
-
170
- default_path
171
-
172
- end
173
-
174
-
175
- # @return [nil,string]
176
- # @description Checks configuration of all the roles assigned to the user
177
- # if user has a role limited to a defined path
178
- # if user has a high privilege role that overrides any other role configuration
179
- def has_role_limited_to_path?()
180
-
181
- # get the roles ordering in descendant mode because we must
182
- # keep the path of the hightest object level permission role.
183
- # Example: we should use the path of the admin role if user has
184
- # admin & employee roles, also order by default_path, so we get first
185
- # the roles with path in case the user has roles with the same object level permission
186
- role = self.lesliroles.order(object_level_permission: :desc).order(:path_default)
187
-
188
- # get the first role found, due previously we sort in a descendant order
189
- # the first role is going to be the one with highest object level permission
190
- # this is going to return nil if no role was found
191
- role = role.first
192
-
193
- # return the path of the role if is limited to a that specific path
194
- return role.path_default if role.path_limited == true
195
-
196
- # return nil if role has no limits
197
- return nil
198
- end
199
-
200
-
201
- # @return [void]
202
- # @description Sets this user as inactive and removes complete access to the platform from them
203
- # @example
204
- # old_user = User.last
205
- # old_user.revoke_access
206
- def revoke_access
207
- self.update(active: false)
208
- end
209
-
210
-
211
- # @return [void]
212
- # @description Change user password forcing user to reset the password
213
- def set_password_as_expired
214
- self.update(password_expiration_at: Time.current)
215
- end
216
-
217
-
218
- # @return [void]
219
- # @description Change user password forcing user to reset the password
220
- # @todo validate object level permission
221
- def password_reset
222
- pass = SecureRandom.hex(10)
223
- self.update(password: pass)
224
- pass
225
- end
226
-
227
-
228
- # @return [void]
229
- # @description After creating a user, creates the necessary resources for them to access the different engines.
230
- def has_expired_password?
231
- return false if self.password_expiration_at.blank?
232
- return Time.current > self.password_expiration_at
233
- end
234
-
235
-
236
- # @return String
237
- # @description Change user password forcing user to reset the password
238
- def generate_password_reset_token
239
- raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
240
-
241
- self.reset_password_token = enc
242
- self.reset_password_sent_at = Time.now.utc
243
- save(validate: false)
244
- raw
245
- end
246
-
247
-
248
- # @return [Boolean]
249
- # @description check if user has a confirmed telephone number
250
- def telephone_confirmed?
251
- !!self.telephone_confirmed_at
252
- end
253
-
254
-
255
- # @return String
256
- # @description Generate a token to validate telephone number
257
- def generate_telephone_token(length=4)
258
-
259
- raw, enc = Devise.token_generator.create(self.class, :telephone_confirmation_token, type:'number', length:length)
260
-
261
- self.telephone_confirmation_token = enc
262
- self.telephone_confirmation_sent_at = Time.now.utc
263
- self.telephone_confirmed_at = nil
264
- save(validate: false)
265
- raw
266
- end
267
-
268
-
269
- # @return String
270
- # @description Mark telephone number as valid and confirmed
271
- def confirm_telephone_number
272
- self.telephone_confirmation_token = nil
273
- self.telephone_confirmation_sent_at = nil
274
- self.telephone_confirmed_at = Time.now.utc
275
- save(validate: false)
276
- end
277
- end