Skip to content

Commit 1f949f1

Browse files
SuiteCRM 8.3.0 Release
1 parent 2c1f3e0 commit 1f949f1

File tree

58 files changed

+1368
-87
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1368
-87
lines changed

Diff for: .env

+5
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,8 @@ LOCK_DSN=flock
6161
###> login throttling ###
6262
LOGIN_THROTTLING_MAX_ATTEMPTS=5
6363
###< login throttling ###
64+
65+
###> login throttling ###
66+
LOGIN_THROTTLING_IP_LOGIN_MAX_ATTEMPTS=50
67+
LOGIN_THROTTLING_INTERVAL="30 minutes"
68+
###< login throttling ###

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<img width="180px" height="41px" src="https://suitecrm.com/wp-content/uploads/2017/12/logo.png" align="right" />
33
</a>
44

5-
# SuiteCRM 8.3-beta
5+
# SuiteCRM 8.3.0
66

77
[![LICENSE](https://img.shields.io/github/license/suitecrm/suitecrm.svg)](https://github.com/salesagility/suitecrm/blob/hotfix/LICENSE.txt)
88
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/salesagility/SuiteCRM-Core/issues)

Diff for: VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.3-beta
1+
8.3.0

Diff for: config/core_services.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ services:
6262
$samlAutoCreateAttributesMap: '%saml.autocreate.attributes_map%'
6363
$logoutConfig: '%auth.logout%'
6464
$sessionExpiredConfig: '%auth.session-expired%'
65+
$adminOnlyModuleActions: '%system.admin_only_module_actions%'
6566
$navbarAdministrationOverrides: '%navbar.administration_override%'
6667
_instanceof:
6768
App\Process\Service\ProcessHandlerInterface:

Diff for: config/packages/security.php

+36-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
use App\Security\Saml\AppSamlAuthenticator;
3333
use App\Security\UserChecker;
3434
use Symfony\Component\DependencyInjection\Container;
35+
use Symfony\Component\DependencyInjection\Reference;
3536
use Symfony\Component\Ldap\Ldap;
37+
use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter;
3638

3739
/** @var $container Container */
3840
if (!isset($container)) {
@@ -44,7 +46,36 @@
4446
$env = $_ENV ?? [];
4547
$authType = $env['AUTH_TYPE'] ?? 'native';
4648

47-
$maxAttempts = (int)($env['LOGIN_THROTTLING_MAX_ATTEMPTS'] ?? 3);
49+
$maxAttempts = (int)($env['LOGIN_THROTTLING_MAX_ATTEMPTS'] ?? 5);
50+
$ipLoginMaxAttempts = (int)($env['LOGIN_THROTTLING_IP_LOGIN_MAX_ATTEMPTS'] ?? 50);
51+
$loginThrottlingInterval = (string)($env['LOGIN_THROTTLING_INTERVAL'] ?? '30 minutes');
52+
53+
$containerConfig->extension('framework', [
54+
'rate_limiter' => [
55+
// define 2 rate limiters (one for username+IP, the other for IP)
56+
'username_ip_login' => [
57+
'policy' => 'token_bucket',
58+
'limit' => $maxAttempts,
59+
'rate' => [ 'interval' => $loginThrottlingInterval ],
60+
],
61+
'ip_login' => [
62+
'policy' => 'sliding_window',
63+
'limit' => $ipLoginMaxAttempts,
64+
'interval' => $loginThrottlingInterval,
65+
],
66+
],
67+
]);
68+
69+
$containerConfig->services()->set('app.login_rate_limiter')
70+
->class(DefaultLoginRateLimiter::class)
71+
->args(
72+
[
73+
// 1st argument is the limiter for IP
74+
new Reference('limiter.ip_login'),
75+
// 2nd argument is the limiter for username+IP
76+
new Reference('limiter.username_ip_login'),
77+
]
78+
);
4879

4980
$baseFirewall = [
5081
'dev' => [
@@ -84,7 +115,7 @@
84115
'check_path' => 'app_login',
85116
],
86117
'login_throttling' => [
87-
'max_attempts' => $maxAttempts
118+
'limiter' => 'app.login_rate_limiter'
88119
],
89120
'logout' => [
90121
'path' => 'app_logout'
@@ -170,7 +201,7 @@
170201
'json_login_ldap' => $baseLdapConfig,
171202
'provider' => $baseLdapConfig['provider'],
172203
'login_throttling' => [
173-
'max_attempts' => $maxAttempts
204+
'limiter' => 'app.login_rate_limiter',
174205
],
175206
'logout' => [
176207
'path' => 'app_logout'
@@ -239,7 +270,7 @@
239270
'check_path' => 'native_auth_login',
240271
],
241272
'login_throttling' => [
242-
'max_attempts' => $maxAttempts,
273+
'limiter' => 'app.login_rate_limiter',
243274
],
244275
'logout' => [
245276
'path' => 'native_auth_logout'
@@ -255,7 +286,7 @@
255286
'check_path' => 'native_auth_login',
256287
],
257288
'login_throttling' => [
258-
'max_attempts' => $maxAttempts,
289+
'limiter' => 'app.login_rate_limiter',
259290
],
260291
'logout' => [
261292
'path' => 'native_auth_logout'
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
parameters:
2+
system.admin_only_module_actions:
3+
administration:
4+
'*': true
5+
users:
6+
'list': true
7+
'index': true
8+
'sugar-feed':
9+
'adminsettings': true
10+
'address-cache':
11+
'*': true
12+
maps:
13+
'geocoded_counts': true
14+
'geocoding_test': true
15+
'config': true
16+
external-oauth-connection:
17+
'*': true

Diff for: core/app/common/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "common",
3-
"version": "8.3-beta",
3+
"version": "8.3.0",
44
"peerDependencies": {
55
"@angular/common": "^12.1.0",
66
"@angular/core": "^12.1.0",

Diff for: core/app/core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "core",
3-
"version": "8.3-beta",
3+
"version": "8.3.0",
44
"peerDependencies": {
55
"@angular/common": "^12.1.0",
66
"@angular/core": "^12.1.0",

Diff for: core/app/core/src/lib/store/record-list/record-list.store.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ export class RecordListStore implements StateStore, DataSource<Record>, Selectio
777777
* @param {string} pageSizeConfig to use
778778
*/
779779
protected determinePageSize(pageSizePreference: any, pageSizeConfig: string): void {
780-
let size = 0;
780+
let size = 20;
781781

782782
if (pageSizePreference) {
783783
size = pageSizePreference;

Diff for: core/app/core/src/lib/views/login/components/login/login.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export class LoginUiComponent implements OnInit {
208208

209209
const errorMessage = httpError?.error?.error ?? '';
210210

211-
if (errorMessage === 'Too many failed login attempts, please try again in 1 minute.') {
211+
if (typeof errorMessage === 'string' && errorMessage.includes('Too many failed login attempts, please try again in')) {
212212
message = this.getTooManyFailedMessage(defaultTooManyFailedMessage);
213213
}
214214

Diff for: core/backend/Engine/LegacyHandler/AclHandler.php

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ public function checkAccess(
100100

101101
$hasAccess = ACLController::checkAccess($legacyName, $action, $isOwner, $type, $in_group);
102102

103+
if (check_default_module_access($legacyName) === false) {
104+
$hasAccess = false;
105+
}
106+
103107
$this->close();
104108

105109
return $hasAccess;

Diff for: core/backend/Install/Service/Installation/InstallActionHandler.php

+8
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ public function requiredAuthRole(): string
7777
return '';
7878
}
7979

80+
/**
81+
* @inheritDoc
82+
*/
83+
public function getRequiredACLs(Process $process): array
84+
{
85+
return [];
86+
}
87+
8088
/**
8189
* @inheritDoc
8290
*/

Diff for: core/backend/Languages/LegacyHandler/SetSessionLanguage.php

+8
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ public function requiredAuthRole(): string
100100
return 'ROLE_USER';
101101
}
102102

103+
/**
104+
* @inheritDoc
105+
*/
106+
public function getRequiredACLs(Process $process): array
107+
{
108+
return [];
109+
}
110+
103111
/**
104112
* @inheritDoc
105113
*/

Diff for: core/backend/Migrations/Version20230420135520.php

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
/**
3+
* SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
4+
* Copyright (C) 2023 SalesAgility Ltd.
5+
*
6+
* This program is free software; you can redistribute it and/or modify it under
7+
* the terms of the GNU Affero General Public License version 3 as published by the
8+
* Free Software Foundation with the addition of the following permission added
9+
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10+
* IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
11+
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12+
*
13+
* This program is distributed in the hope that it will be useful, but WITHOUT
14+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15+
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16+
* details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*
21+
* In accordance with Section 7(b) of the GNU Affero General Public License
22+
* version 3, these Appropriate Legal Notices must retain the display of the
23+
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
24+
* feasible for technical reasons, the Appropriate Legal Notices must display
25+
* the words "Supercharged by SuiteCRM".
26+
*/
27+
28+
declare(strict_types=1);
29+
30+
namespace App\Migrations;
31+
32+
use Doctrine\DBAL\Schema\Schema;
33+
use Psr\Log\LoggerInterface;
34+
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
35+
use Symfony\Component\DependencyInjection\ContainerInterface;
36+
37+
final class Version20230420135520 extends BaseMigration implements ContainerAwareInterface
38+
{
39+
use EnvHandlingMigrationTrait;
40+
41+
/**
42+
* @var ContainerInterface
43+
*/
44+
protected $container;
45+
46+
/**
47+
* @var LoggerInterface
48+
*/
49+
protected $upgradeLogger;
50+
51+
public function getDescription(): string
52+
{
53+
return 'Add login throttling interval to .env';
54+
}
55+
56+
public function isTransactional(): bool
57+
{
58+
return false;
59+
}
60+
61+
public function up(Schema $schema): void
62+
{
63+
$envFile = $this->getProjectDir() . "/.env";
64+
65+
if (!file_exists($envFile)) {
66+
return;
67+
}
68+
69+
$envContents = file_get_contents($envFile);
70+
71+
$this->addLoginThrottlingConfig($envContents, $envFile);
72+
73+
}
74+
75+
public function down(Schema $schema): void
76+
{
77+
}
78+
79+
/**
80+
* Check and add missing login throttling config
81+
* @param $envContents
82+
* @param string $envFile
83+
*/
84+
protected function addLoginThrottlingConfig(&$envContents, string $envFile): void
85+
{
86+
$properties = [
87+
'LOGIN_THROTTLING_IP_LOGIN_MAX_ATTEMPTS' => '50',
88+
'LOGIN_THROTTLING_INTERVAL' => '"30 minutes"'
89+
];
90+
91+
$wrapperStart = '###> login throttling ###';
92+
$wrapperEnd = '###< login throttling ###';
93+
94+
$propertiesToAdd = $this->getContentToAdd($envContents, $properties, $wrapperStart, $wrapperEnd);
95+
if (!empty($propertiesToAdd)) {
96+
$envContents .= $propertiesToAdd;
97+
file_put_contents($envFile, $envContents);
98+
$this->log('Added LOGIN_THROTTLING_INTERVAL to .env.');
99+
100+
return;
101+
}
102+
103+
$this->log('LOGIN_THROTTLING_INTERVAL already in .env, skipping.');
104+
}
105+
}

Diff for: core/backend/Module/LegacyHandler/Favorites/UpdateFavorite.php

+29
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ public function requiredAuthRole(): string
101101
return 'ROLE_USER';
102102
}
103103

104+
/**
105+
* @inheritDoc
106+
*/
107+
public function getRequiredACLs(Process $process): array
108+
{
109+
['favorite' => $favorite] = $process->getOptions();
110+
111+
$module = $favorite['attributes']['parent_type'] ?? '';
112+
$id = $favorite['attributes']['parent_id'] ?? '';
113+
114+
return [
115+
$module => [
116+
[
117+
'action' => 'view',
118+
'record' => $id
119+
]
120+
]
121+
];
122+
}
123+
104124
/**
105125
* @inheritDoc
106126
*/
@@ -121,6 +141,15 @@ public function validate(Process $process): void
121141
if (empty($process->getOptions())) {
122142
throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
123143
}
144+
145+
['favorite' => $favorite, 'action' => $action] = $process->getOptions();
146+
147+
$module = $favorite['attributes']['parent_type'] ?? '';
148+
$id = $favorite['attributes']['parent_id'] ?? '';
149+
150+
if (empty($module) || empty($id) || empty($action)) {
151+
throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
152+
}
124153
}
125154

126155
/**

Diff for: core/backend/Module/LegacyHandler/RecentlyViewed/AddRecentlyViewed.php

+29
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,25 @@ public function requiredAuthRole(): string
107107
return 'ROLE_USER';
108108
}
109109

110+
/**
111+
* @inheritDoc
112+
*/
113+
public function getRequiredACLs(Process $process): array
114+
{
115+
['recentlyViewed' => $recentlyViewed] = $process->getOptions();
116+
$itemId = $recentlyViewed['attributes']['item_id'] ?? '';
117+
$itemModule = $recentlyViewed['attributes']['module_name'] ?? '';
118+
119+
return [
120+
$itemModule => [
121+
[
122+
'action' => 'view',
123+
'record' => $itemId,
124+
]
125+
]
126+
];
127+
}
128+
110129
/**
111130
* @inheritDoc
112131
*/
@@ -127,6 +146,16 @@ public function validate(Process $process): void
127146
if (empty($process->getOptions())) {
128147
throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
129148
}
149+
150+
['recentlyViewed' => $recentlyViewed] = $process->getOptions();
151+
$itemModule = $recentlyViewed['attributes']['module_name'] ?? '';
152+
$itemModule = $this->moduleNameMapper->toLegacy($itemModule);
153+
$action = $recentlyViewed['attributes']['action'] ?? '';
154+
155+
156+
if (empty($itemModule) || empty($action)) {
157+
throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
158+
}
130159
}
131160

132161
/**

0 commit comments

Comments
 (0)