lesli 5.0.19 → 5.0.21
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/lesli/brand/register-background.jpg +0 -0
- data/app/assets/stylesheets/lesli/application.css +1 -0
- data/app/helpers/lesli/navigation_helper.rb +6 -0
- data/app/interfaces/lesli/responder_interface.rb +35 -19
- data/app/models/concerns/lesli/account_initializer.rb +79 -0
- data/app/models/concerns/lesli/user_extensions.rb +133 -0
- data/app/models/concerns/lesli/user_security.rb +220 -0
- data/app/models/lesli/role/action.rb +3 -2
- data/app/models/lesli/shared/dashboard.rb +2 -2
- data/app/models/lesli/user/session.rb +1 -1
- data/app/models/lesli/user.rb +15 -15
- data/app/operators/lesli/role_operator.rb +9 -4
- data/app/services/lesli/role/action_service.rb +2 -3
- data/app/services/lesli/role_service.rb +3 -3
- data/app/views/lesli/abouts/welcome.html.erb +8 -5
- data/app/views/lesli/apps/show.html.erb +21 -33
- data/app/views/lesli/errors/not_found.html.erb +32 -0
- data/app/views/lesli/errors/unauthorized.html.erb +48 -0
- data/app/views/lesli/layouts/application-devise.html.erb +2 -2
- data/app/views/lesli/layouts/application-lesli.html.erb +1 -2
- data/app/views/lesli/partials/_application-analytics.html.erb +2 -2
- data/app/views/lesli/partials/_application-data.html.erb +0 -1
- data/app/views/lesli/partials/_application-head.html.erb +4 -4
- data/app/views/lesli/partials/_application-lesli-header.html.erb +47 -67
- data/app/views/lesli/partials/_application-lesli-navigation.html.erb +23 -5
- data/config/importmap.rb +0 -10
- data/config/initializers/devise_rails_8_patch.rb +8 -0
- data/config/initializers/lesli.rb +27 -23
- data/db/migrate/v1/0000120310_create_lesli_role_privileges.rb +2 -2
- data/db/seed/accounts.rb +1 -1
- data/db/seed/users.rb +6 -4
- data/db/seeds.rb +6 -6
- data/lib/lesli/engine.rb +0 -14
- data/lib/lesli/routing.rb +1 -1
- data/lib/lesli/version.rb +2 -2
- data/lib/rspec/testers/request.rb +15 -6
- data/lib/scss/_apps.scss +93 -0
- data/lib/scss/application.scss +34 -0
- data/lib/tasks/lesli/controllers.rake +1 -1
- data/lib/tasks/lesli/db.rake +27 -27
- data/lib/tasks/lesli_tasks.rake +3 -0
- data/readme.md +9 -10
- metadata +33 -13
- data/app/models/concerns/account_initializer.rb +0 -83
- data/app/models/concerns/user_extensions.rb +0 -152
- data/app/models/concerns/user_security.rb +0 -276
- data/app/operators/lesli/user_registration_operator.rb +0 -122
- /data/app/models/concerns/{user_activities.rb → lesli/user_activities.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b77188611c1a03101fb76722bdee1160ab61b1b4697ebe969e00704ca45c8934
|
4
|
+
data.tar.gz: e25d29353f381df652227383dac086e0eda6b301438c93893b27899933409bc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 193238561d87e317c8c3c9eccca9a3a8594542c7e915a38a53c50dfcc1ed84487b565024357de99a4a4c3375932331ca7ad0291c48bbf6cf91ae568657403301
|
7
|
+
data.tar.gz: 259733aeb17b1a48d1f9274a1b4b0df88a252ce616eb1bcec94d91a281ad9314796a2c26ce212f3c85358abf629fdf5e7c577a9e8a462da99481a79fc9c00b33
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
body.lesli.apps.show .lesli-element-header{margin-bottom:5rem !important}body.lesli.apps.show .engines{gap:24px;margin-inline-end:auto;margin-inline-start:auto}body.lesli.apps.show .engines a{flex-basis:318px;text-align:center;padding:2rem .4rem 2.8rem;border:#fff 1px solid;border-radius:6px;background-color:#fff;transition:all ease-in-out .2s;box-shadow:rgba(9,30,66,.25) 0px 4px 8px -2px,rgba(9,30,66,.08) 0px 0px 0px 1px}body.lesli.apps.show .engines a:hover{border-color:var(--lesli-color-primary);transform:translateY(-2px)}body.lesli.apps.show .engines a.is-active{background-color:#f0f4ff;border-color:var(--lesli-color-primary)}body.lesli.apps.show .engines a svg{margin-right:.4rem;fill:var(--lesli-color-primary)}body.lesli.apps.show .engines a p{font-size:14}body.lesli.apps.show .engines a span{font-weight:600;font-size:20px}body.lesli.apps.show .engines a span,body.lesli.apps.show .engines a p{color:var(--lesli-color-primary)}
|
@@ -33,6 +33,12 @@ Building a better future, one line of code at a time.
|
|
33
33
|
module Lesli
|
34
34
|
module NavigationHelper
|
35
35
|
|
36
|
+
def navigation_partial
|
37
|
+
engine = lesli_engine[:code]
|
38
|
+
path = engine == "root" ? "partials/navigation" : "#{engine}/partials/navigation"
|
39
|
+
lookup_context.exists?(path, [], true) ? path : nil
|
40
|
+
end
|
41
|
+
|
36
42
|
# Prints a separator line
|
37
43
|
def navigation_separator
|
38
44
|
content_tag(:li) do
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Lesli
|
4
4
|
|
5
|
-
Copyright (c)
|
5
|
+
Copyright (c) 2025, Lesli Technologies, S. A.
|
6
6
|
|
7
7
|
This program is free software: you can redistribute it and/or modify
|
8
8
|
it under the terms of the GNU General Public License as published by
|
@@ -17,17 +17,17 @@ GNU General Public License for more details.
|
|
17
17
|
You should have received a copy of the GNU General Public License
|
18
18
|
along with this program. If not, see http://www.gnu.org/licenses/.
|
19
19
|
|
20
|
-
Lesli ·
|
20
|
+
Lesli · Ruby on Rails SaaS Development Framework.
|
21
21
|
|
22
|
-
Made with ♥ by
|
22
|
+
Made with ♥ by LesliTech
|
23
23
|
Building a better future, one line of code at a time.
|
24
24
|
|
25
25
|
@contact hello@lesli.tech
|
26
|
-
@website https://lesli.tech
|
26
|
+
@website https://www.lesli.tech
|
27
27
|
@license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
|
28
28
|
|
29
|
-
// · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
|
30
|
-
// ·
|
29
|
+
// · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
|
30
|
+
// ·
|
31
31
|
=end
|
32
32
|
|
33
33
|
module Lesli
|
@@ -72,27 +72,43 @@ module Lesli
|
|
72
72
|
end
|
73
73
|
|
74
74
|
# JSON not found response
|
75
|
-
def respond_with_not_found
|
76
|
-
|
77
|
-
|
78
|
-
|
75
|
+
def respond_with_not_found message=nil
|
76
|
+
|
77
|
+
@message = message || I18n.t("core.shared.messages_danger_not_found")
|
78
|
+
respond_to do |format|
|
79
|
+
format.json{ respond_with_http(404, { message: @message }) }
|
80
|
+
format.html{ render('lesli/errors/not_found', status: :not_found) }
|
81
|
+
end
|
79
82
|
end
|
80
83
|
|
81
84
|
# JSON not found response
|
82
85
|
def respond_with_unauthorized(detail = {})
|
83
|
-
error_object = {}
|
84
86
|
|
85
|
-
error_object
|
86
|
-
|
87
|
+
@error_object = {
|
88
|
+
error_role: nil,
|
89
|
+
error_detail: nil,
|
90
|
+
error_message: I18n.t("core.shared.view_text_unauthorized_request")
|
91
|
+
}
|
87
92
|
|
88
|
-
|
93
|
+
unless Rails.env.production?
|
94
|
+
@error_object[:error_detail] = detail unless detail.empty?
|
95
|
+
if current_user.present?
|
96
|
+
@error_object[:error_role] = "( #{current_user.lesliroles.map(&:name).join(', ')} )"
|
97
|
+
end
|
98
|
+
end
|
89
99
|
|
90
100
|
respond_to do |format|
|
91
|
-
format.json
|
92
|
-
format.html
|
93
|
-
|
94
|
-
# format.xlsx
|
95
|
-
#
|
101
|
+
format.json{ render(status: :unauthorized, json: @error_object) }
|
102
|
+
format.html{ render('lesli/errors/unauthorized', status: :unauthorized) }
|
103
|
+
|
104
|
+
# format.xlsx do
|
105
|
+
# if Rails.env.production?
|
106
|
+
# redirect_to "/401" # Or a specific Excel error download if applicable
|
107
|
+
# else
|
108
|
+
# # For development, you might still want a JSON response for debugging
|
109
|
+
# render status: :unauthorized, json: error_object.to_json
|
110
|
+
# end
|
111
|
+
# end
|
96
112
|
end
|
97
113
|
end
|
98
114
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
Lesli
|
4
|
+
|
5
|
+
Copyright (c) 2025, 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 LesliTech
|
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
|
+
module Lesli
|
34
|
+
module AccountInitializer
|
35
|
+
extend ActiveSupport::Concern
|
36
|
+
|
37
|
+
# initialize minimum resources needed for the account
|
38
|
+
def initialize_account
|
39
|
+
|
40
|
+
|
41
|
+
# create default roles for the new account
|
42
|
+
owner = self.roles
|
43
|
+
.create_with({ permission_level: 2147483647 })
|
44
|
+
.find_or_create_by(:name => "owner")
|
45
|
+
|
46
|
+
|
47
|
+
# platform administrator role
|
48
|
+
admin = self.roles
|
49
|
+
.create_with({ permission_level: 100000})
|
50
|
+
.find_or_create_by(name: "admin")
|
51
|
+
|
52
|
+
|
53
|
+
# access only to user profile
|
54
|
+
limited = self.roles
|
55
|
+
.create_with({ permission_level: 10, path_default: "/administration/profile" })
|
56
|
+
.find_or_create_by(name: "limited")
|
57
|
+
|
58
|
+
|
59
|
+
# Add base privileges to roles
|
60
|
+
Lesli::RoleOperator.new(owner).add_owner_actions
|
61
|
+
Lesli::RoleOperator.new(admin).add_owner_actions
|
62
|
+
Lesli::RoleOperator.new(limited).add_profile_actions
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# initialize engines for new accounts
|
68
|
+
def initialize_engines
|
69
|
+
|
70
|
+
LesliSystem.engines.each do |engine, data|
|
71
|
+
|
72
|
+
next if ["Lesli", "LesliBabel", "Root"].include?(engine)
|
73
|
+
|
74
|
+
# Create an associated account if the attribute is blank
|
75
|
+
engine.constantize::Account.create!(account: self)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
Lesli
|
4
|
+
|
5
|
+
Copyright (c) 2025, 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 LesliTech
|
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
|
+
# User extension methods
|
34
|
+
# Custom methods that belongs to a instance user
|
35
|
+
module Lesli
|
36
|
+
module UserExtensions
|
37
|
+
extend ActiveSupport::Concern
|
38
|
+
|
39
|
+
# Set the user alias based on the full_name.
|
40
|
+
def set_alias
|
41
|
+
if self.alias.blank?
|
42
|
+
self.alias = full_name_initials()
|
43
|
+
self.save
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the user's full name if available, or their email as a fallback.
|
48
|
+
def full_name
|
49
|
+
if first_name.present?
|
50
|
+
[first_name, last_name.presence].compact.join(" ")
|
51
|
+
else
|
52
|
+
email
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Retrieves and returns the name initials of the user
|
57
|
+
# depending on the available information.
|
58
|
+
def full_name_initials
|
59
|
+
return "" if first_name.blank?
|
60
|
+
|
61
|
+
initials = first_name.strip[0]&.upcase || ""
|
62
|
+
initials += last_name.strip[0]&.upcase if last_name.present?
|
63
|
+
initials
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the local configuration for the user,
|
67
|
+
# if there is no locale the default local of the platform will be returned
|
68
|
+
def locale
|
69
|
+
user_locale = self.settings.find_by(name: "locale")
|
70
|
+
|
71
|
+
# return the desire locale by the user
|
72
|
+
return user_locale.value.to_sym if user_locale
|
73
|
+
|
74
|
+
# create a desire locale if the record does not exist
|
75
|
+
self.settings.create_with(:value => I18n.locale).find_or_create_by(:name => "locale")
|
76
|
+
|
77
|
+
# reevaluate
|
78
|
+
self.locale()
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return a string with the names of all the roles assigned to the user
|
82
|
+
def role_names
|
83
|
+
self.lesliroles.pluck(:name).join(', ')
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
# Returns MFA settings configured by the user
|
90
|
+
def mfa_settings
|
91
|
+
mfa_enabled = self.settings.create_with(:value => false).find_or_create_by(:name => "mfa_enabled")
|
92
|
+
mfa_method = self.settings.create_with(:value => :email).find_or_create_by(:name => "mfa_method")
|
93
|
+
{
|
94
|
+
:enabled => mfa_enabled.nil? ? false : mfa_enabled.value == 't',
|
95
|
+
:method => mfa_method.nil? ? nil : mfa_method.value.to_sym
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [void]
|
100
|
+
# @description Register a new notification for the current user
|
101
|
+
# @param subject String Short notification description
|
102
|
+
# @param body String Long notification description
|
103
|
+
# @param url String Link to notified object
|
104
|
+
# @param category String Kind of notification: info, warning, danger, success.
|
105
|
+
def notification subject, body:nil, url:nil, category:"info"
|
106
|
+
Courier::Bell::Notification.new(self, subject, body:body, url:url, category:category)
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [void]
|
110
|
+
# @description Register a new notification for the current user
|
111
|
+
# @param subject String Short notification description
|
112
|
+
# @param body String Long notification description
|
113
|
+
# @param url String Link to notified object
|
114
|
+
# @param category String Kind of notification: info, warning, danger, success.
|
115
|
+
def notifications quantity=5, category:"info"
|
116
|
+
query = {
|
117
|
+
:pagination => {
|
118
|
+
:perPage => quantity,
|
119
|
+
:page => 1
|
120
|
+
}
|
121
|
+
}
|
122
|
+
Lesli::Courier.new(:lesli_bell, []).from(:notification_service, self, query).call(:index)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [CloudDriver::Calendar]
|
126
|
+
# @description Return the default calendar of the user if source_code is not provided.
|
127
|
+
# If source_code is provided the method return the specified source calendar.
|
128
|
+
def calendar source_code: :lesli
|
129
|
+
return Courier::Driver::Calendar.get_user_calendar(self, source_code: source_code, default: true) if source_code == :lesli
|
130
|
+
Courier::Driver::Calendar.get_user_calendar(self, source_code: source_code)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
Lesli
|
4
|
+
|
5
|
+
Copyright (c) 2025, 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 LesliTech
|
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 Lesli
|
37
|
+
module UserSecurity
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
|
40
|
+
def max_level_permission
|
41
|
+
|
42
|
+
# get the max level permission from roles assigned to the user
|
43
|
+
self.lesliroles.maximum(:permission_level) || 0
|
44
|
+
end
|
45
|
+
|
46
|
+
# check if user has roles with specific names
|
47
|
+
def has_roles? *roles
|
48
|
+
!roles.intersection(self.roles.map{ |r| r[:name] }).empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
# check the privilege cache to check if user is able
|
52
|
+
# to perform a specific action in a specific controller
|
53
|
+
def has_privileges_for?(controller, action)
|
54
|
+
begin
|
55
|
+
return self.privileges.where(
|
56
|
+
controller: controller,
|
57
|
+
action: action,
|
58
|
+
active: true
|
59
|
+
).exists?
|
60
|
+
rescue => exception
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if user has enough privilege to work with the given role
|
66
|
+
def can_work_with_role?(role_id)
|
67
|
+
|
68
|
+
# get the role if only id is given
|
69
|
+
role = self.account.roles.find_by(:id => role_id)
|
70
|
+
|
71
|
+
# false if role not found
|
72
|
+
return false if role.blank?
|
73
|
+
|
74
|
+
# not valid role without object levelpermission defined
|
75
|
+
return false if role.level_permission.blank?
|
76
|
+
|
77
|
+
# get the max level permission from the roles the user has assigned
|
78
|
+
user_role_max_level_permission = self.roles.map(&:level_permission).max()
|
79
|
+
|
80
|
+
# check if user can work with the level permission of the role is trying to modify
|
81
|
+
# Note: user only can assigned an level permission below the max of his own roles
|
82
|
+
# Current user cannot assign role if role to assign is the same of the greater role
|
83
|
+
# assigned to the current user
|
84
|
+
user_role_max_level_permission >= role.level_permission
|
85
|
+
end
|
86
|
+
|
87
|
+
# Checks configuration of all the roles assigned to the user
|
88
|
+
# if user has a role with "default path" to use as home to redirect after login
|
89
|
+
# IMPORTANT: This home path is used only the send the user after login, the user
|
90
|
+
# and the role are not limited by this configuration
|
91
|
+
def has_role_with_default_path?()
|
92
|
+
|
93
|
+
# get the roles that contains a path
|
94
|
+
role = self.roles.where.not(path_default: [nil, ""])
|
95
|
+
|
96
|
+
# here we must order the results descendant because we must
|
97
|
+
# keep the path of the hightest level permission role.
|
98
|
+
# Example: we should use the path of the admin role if user has
|
99
|
+
# admin & employee roles, also order by default_path, so we get first
|
100
|
+
# the roles with path in case the user has roles with the same level permission
|
101
|
+
role = role.order(level_permission: :desc).order(:path_default)
|
102
|
+
|
103
|
+
# get the first role found, due previously we sort in a descendant order
|
104
|
+
# the first role is going to be the one with highest level permission
|
105
|
+
# this is going to return nil if no role was found
|
106
|
+
default_path = role.first&.path_default || "/"
|
107
|
+
|
108
|
+
# if first loggin for account owner send him to the onboarding page
|
109
|
+
if self.account.onboarding? && self.has_roles?("owner")
|
110
|
+
default_path = "/onboarding"
|
111
|
+
end
|
112
|
+
|
113
|
+
default_path
|
114
|
+
end
|
115
|
+
|
116
|
+
# Checks configuration of all the roles assigned to the user
|
117
|
+
# if user has a role limited to a defined path
|
118
|
+
# if user has a high privilege role that overrides any other role configuration
|
119
|
+
def has_role_limited_to_path?()
|
120
|
+
|
121
|
+
# get the roles ordering in descendant mode because we must
|
122
|
+
# keep the path of the hightest level permission role.
|
123
|
+
# Example: we should use the path of the admin role if user has
|
124
|
+
# admin & employee roles, also order by default_path, so we get first
|
125
|
+
# the roles with path in case the user has roles with the same level permission
|
126
|
+
role = self.roles.order(level_permission: :desc).order(:path_default)
|
127
|
+
|
128
|
+
# get the first role found, due previously we sort in a descendant order
|
129
|
+
# the first role is going to be the one with highest level permission
|
130
|
+
# this is going to return nil if no role was found
|
131
|
+
role = role.first
|
132
|
+
|
133
|
+
# return the path of the role if is limited to a that specific path
|
134
|
+
return role.path_default if role.path_limited == true
|
135
|
+
|
136
|
+
# return nil if role has no limits
|
137
|
+
return nil
|
138
|
+
end
|
139
|
+
|
140
|
+
# Sets this user as inactive and removes complete access to the platform
|
141
|
+
def revoke_access
|
142
|
+
self.update(active: false)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Change user password forcing user to reset the password
|
146
|
+
def set_password_as_expired
|
147
|
+
self.update(password_expiration_at: Time.current)
|
148
|
+
end
|
149
|
+
|
150
|
+
# @description Change user password forcing user to reset the password
|
151
|
+
def set_password_for_reset
|
152
|
+
generate_password_reset_token()
|
153
|
+
end
|
154
|
+
|
155
|
+
def has_expired_password?
|
156
|
+
return false if self.password_expiration_at.blank?
|
157
|
+
return Time.current > self.password_expiration_at
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check if user has a confirmed telephone number
|
161
|
+
def has_telephone_confirmed?
|
162
|
+
!!self.telephone_confirmed_at
|
163
|
+
end
|
164
|
+
|
165
|
+
# Change user password forcing user to reset the password
|
166
|
+
def generate_password_reset_token
|
167
|
+
raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
|
168
|
+
|
169
|
+
self.password = raw
|
170
|
+
self.reset_password_token = enc
|
171
|
+
self.reset_password_sent_at = Time.now.utc
|
172
|
+
save(validate: false)
|
173
|
+
raw
|
174
|
+
end
|
175
|
+
|
176
|
+
# Generate a token to validate telephone number
|
177
|
+
def generate_telephone_token(length=4)
|
178
|
+
raw, enc = Devise.token_generator.create(
|
179
|
+
self.class,
|
180
|
+
:telephone_confirmation_token,
|
181
|
+
type:'number',
|
182
|
+
length:length
|
183
|
+
)
|
184
|
+
|
185
|
+
self.telephone_confirmation_token = enc
|
186
|
+
self.telephone_confirmation_sent_at = Time.now.utc
|
187
|
+
self.telephone_confirmed_at = nil
|
188
|
+
save(validate: false)
|
189
|
+
raw
|
190
|
+
end
|
191
|
+
|
192
|
+
# Mark telephone number as valid and confirmed
|
193
|
+
def confirm_telephone_number
|
194
|
+
self.telephone_confirmation_token = nil
|
195
|
+
self.telephone_confirmation_sent_at = nil
|
196
|
+
self.telephone_confirmed_at = Time.now.utc
|
197
|
+
save(validate: false)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Return a hash that contains all the abilities grouped by
|
201
|
+
# controller and define every action privilege. It also
|
202
|
+
# evaluate if the user has the ability no matter if is given
|
203
|
+
# to the user by role or by itself.
|
204
|
+
def abilities_by_controller
|
205
|
+
|
206
|
+
# Abilities hash where we will save all the privileges the user has to
|
207
|
+
abilities = {}
|
208
|
+
|
209
|
+
# We check all the privileges the user has in the cache table according to his roles
|
210
|
+
# and create a key per controller (with the full controller name) that contains an array of all the
|
211
|
+
# methods/actions with permission
|
212
|
+
# self.privileges.all.each do |privilege|
|
213
|
+
# abilities[privilege.controller] = [] if abilities[privilege.controller].nil?
|
214
|
+
# abilities[privilege.controller] << privilege.action
|
215
|
+
# end
|
216
|
+
|
217
|
+
abilities
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -34,14 +34,15 @@ module Lesli
|
|
34
34
|
class Role::Action < ApplicationLesliRecord
|
35
35
|
belongs_to :role
|
36
36
|
|
37
|
-
|
37
|
+
after_create :synchronize_privileges
|
38
|
+
after_update :synchronize_privileges
|
38
39
|
after_destroy :synchronize_privileges
|
39
40
|
|
40
41
|
belongs_to :action, class_name: "SystemController::Action"
|
41
42
|
belongs_to :system_controller_action, class_name: "SystemController::Action", foreign_key: "action_id"
|
42
43
|
|
43
44
|
def synchronize_privileges
|
44
|
-
Lesli::RoleOperator.new(self).synchronize
|
45
|
+
Lesli::RoleOperator.new(self.role, self).synchronize
|
45
46
|
end
|
46
47
|
|
47
48
|
def self.index current_user, query, role
|
@@ -34,13 +34,13 @@ module Lesli
|
|
34
34
|
module Shared
|
35
35
|
class DashboardFallback < ::Lesli::ApplicationLesliRecord
|
36
36
|
self.abstract_class = true
|
37
|
-
def self.
|
37
|
+
def self.initialize_account(account)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
base_class = "::LesliDashboard::Shared::Dashboard".safe_constantize || DashboardFallback
|
42
42
|
|
43
|
-
class Dashboard <
|
43
|
+
class Dashboard < base_class
|
44
44
|
self.abstract_class = true
|
45
45
|
end
|
46
46
|
end
|
data/app/models/lesli/user.rb
CHANGED
@@ -32,8 +32,8 @@ Building a better future, one line of code at a time.
|
|
32
32
|
|
33
33
|
module Lesli
|
34
34
|
class User < ApplicationLesliRecord
|
35
|
-
include UserSecurity
|
36
|
-
include UserExtensions
|
35
|
+
include Lesli::UserSecurity
|
36
|
+
include Lesli::UserExtensions
|
37
37
|
#include UserActivities
|
38
38
|
|
39
39
|
validates(:email,
|
@@ -70,7 +70,6 @@ module Lesli
|
|
70
70
|
has_many :sessions
|
71
71
|
has_many :activities #, class_name: "Lesli::Item::Activity"
|
72
72
|
|
73
|
-
|
74
73
|
|
75
74
|
has_many :shortcuts, class_name: "LesliShield::User::Shortcuts"
|
76
75
|
|
@@ -104,13 +103,24 @@ module Lesli
|
|
104
103
|
|
105
104
|
def after_create_user
|
106
105
|
self.activities.create(title: "create_user", description:"User created")
|
107
|
-
|
106
|
+
after_confirmation
|
108
107
|
after_account_assignation
|
109
108
|
end
|
110
109
|
|
111
110
|
|
111
|
+
def after_account_assignation
|
112
|
+
return unless self.account
|
113
|
+
|
114
|
+
#Courier::One::Firebase::User.sync_user(self)
|
115
|
+
# Lesli::Courier.new(:lesli_calendar).from(:calendar_service, self).create({
|
116
|
+
# name: "Personal Calendar",
|
117
|
+
# default: true
|
118
|
+
# })
|
119
|
+
end
|
120
|
+
|
121
|
+
|
112
122
|
# Initialize user settings and dependencies needed
|
113
|
-
def
|
123
|
+
def after_confirmation
|
114
124
|
return unless self.confirmed?
|
115
125
|
|
116
126
|
self.activities.create(title: "create_user", description:"User confirmed")
|
@@ -123,16 +133,6 @@ module Lesli
|
|
123
133
|
#self.settings.create_with(:value => :email).find_or_create_by(:name => "mfa_method")
|
124
134
|
end
|
125
135
|
|
126
|
-
def after_account_assignation
|
127
|
-
return unless self.account
|
128
|
-
|
129
|
-
#Courier::One::Firebase::User.sync_user(self)
|
130
|
-
# Lesli::Courier.new(:lesli_calendar).from(:calendar_service, self).create({
|
131
|
-
# name: "Personal Calendar",
|
132
|
-
# default: true
|
133
|
-
# })
|
134
|
-
end
|
135
|
-
|
136
136
|
|
137
137
|
def update_associated_services
|
138
138
|
if saved_change_to_first_name? || saved_change_to_last_name? || saved_change_to_telephone?
|