Compare commits

...

12 Commits

Author SHA1 Message Date
Yuri Kuznetsov 66a6f70a30 v 2023-04-07 22:18:02 +03:00
Yuri Kuznetsov d7a629c5a3 int/float fix 2023-04-07 22:17:25 +03:00
Yuri Kuznetsov 506e96e333 float preventing same decimal mark and th separator 2023-04-07 17:38:06 +03:00
Yuri Kuznetsov 76a9177c46 v 2023-04-07 16:42:17 +03:00
Yuri Kuznetsov a8baac3f92 missing counterpart link warning 2023-04-07 16:40:57 +03:00
Yuri Kuznetsov 8d9d073c16 fix currency decimal places null 2023-04-07 10:10:36 +03:00
Yuri Kuznetsov 177ecdb70b relation issue msg 2023-04-06 20:40:48 +03:00
Yuri Kuznetsov 89ce80d5b2 fix empty type 2023-04-06 19:19:44 +03:00
Yuri Kuznetsov 7f07175bee v 2023-04-06 19:08:54 +03:00
Yuri Kuznetsov 8bca35934c error msg 2023-04-06 19:05:28 +03:00
Yuri Kuznetsov 65bef1df65 fix link-parent not-storable 2023-04-06 18:57:38 +03:00
Yuri Kuznetsov 922a2e835c 2fa portals 2023-04-06 15:31:28 +03:00
24 changed files with 230 additions and 40 deletions
@@ -55,7 +55,8 @@ class TwoFactorEmail
if (
!$this->user->isAdmin() &&
!$this->user->isRegular()
!$this->user->isRegular() &&
!$this->user->isPortal()
) {
throw new Forbidden();
}
@@ -55,7 +55,8 @@ class TwoFactorSms
if (
!$this->user->isAdmin() &&
!$this->user->isRegular()
!$this->user->isRegular() &&
!$this->user->isPortal()
) {
throw new Forbidden();
}
@@ -57,7 +57,8 @@ class UserSecurity
if (
!$this->user->isAdmin() &&
!$this->user->isRegular()
!$this->user->isRegular() &&
!$this->user->isPortal()
) {
throw new Forbidden();
}
@@ -41,6 +41,7 @@ use Espo\ORM\Defs\RelationDefs;
use Espo\ORM\Entity;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Metadata\Helper as MetadataHelper;
use LogicException;
class Converter
{
@@ -160,11 +161,11 @@ class Converter
);
}
foreach ($ormMetadata as $entityOrmMetadata) {
foreach ($ormMetadata as $entityType => $entityOrmMetadata) {
/** @var array<string, array<string, mixed>> $ormMetadata */
$ormMetadata = Util::merge(
$ormMetadata,
$this->createEntityTypesFromRelations($entityOrmMetadata)
$this->createEntityTypesFromRelations($entityType, $entityOrmMetadata)
);
/** @var array<string, array<string, mixed>> $ormMetadata */
@@ -846,7 +847,7 @@ class Converter
* @param array<string, mixed> $defs
* @return array<string, mixed>
*/
private function createEntityTypesFromRelations(array $defs): array
private function createEntityTypesFromRelations(string $entityType, array $defs): array
{
$result = [];
@@ -873,6 +874,11 @@ class Converter
],
];
if (!$relationDefs->hasMidKey()) {
throw new LogicException(
"Bad manyMany relation {$name} in {$entityType}. Might be not defined on the other side.");
}
$key1 = $relationDefs->getMidKey();
$key2 = $relationDefs->getForeignMidKey();
@@ -33,12 +33,15 @@ use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
use Espo\Core\Utils\Database\Orm\LinkConverter;
use Espo\Core\Utils\Log;
use Espo\ORM\Defs\RelationDefs as LinkDefs;
use Espo\ORM\Type\AttributeType;
use Espo\ORM\Type\RelationType;
class HasMany implements LinkConverter
{
public function __construct(private Log $log) {}
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
{
if (!$linkDefs->hasForeignRelationName() && $linkDefs->getParam('disabled')) {
@@ -52,9 +55,20 @@ class HasMany implements LinkConverter
$foreignRelationName = $linkDefs->getForeignRelationName();
$hasField = $linkDefs->getParam('hasField');
$type = $linkDefs->hasRelationshipName() ?
$type = RelationType::HAS_MANY;
/*$type = $linkDefs->hasRelationshipName() ?
RelationType::MANY_MANY : // Revise.
RelationType::HAS_MANY;
RelationType::HAS_MANY;*/
if ($linkDefs->hasRelationshipName()) {
$this->log->warning(
"Issue with the link '{$name}' in '{$entityType}' entity type. Might be the foreign link " .
"'{$foreignRelationName}' in '{$foreignEntityType}' entity type is missing. " .
"Remove the problem link manually.");
return EntityDefs::create();
}
return EntityDefs::create()
->withAttribute(
@@ -36,11 +36,13 @@ use Espo\Core\Utils\Database\Orm\LinkConverters\HasChildren;
use Espo\Core\Utils\Database\Orm\LinkConverters\HasMany;
use Espo\Core\Utils\Database\Orm\LinkConverters\HasOne;
use Espo\Core\Utils\Database\Orm\LinkConverters\ManyMany;
use Espo\Core\Utils\Log;
use Espo\Core\Utils\Util;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs\RelationDefs;
use Espo\ORM\Type\AttributeType;
use Espo\ORM\Type\RelationType;
use RuntimeException;
class RelationConverter
@@ -59,7 +61,8 @@ class RelationConverter
public function __construct(
private Metadata $metadata,
private InjectableFactory $injectableFactory
private InjectableFactory $injectableFactory,
private Log $log
) {}
/**
@@ -87,9 +90,15 @@ class RelationConverter
$params['relationName'] = $relationshipName;
}
$linkType = $params['type'];
$linkType = $params['type'] ?? null;
$foreignLinkType = $foreignParams ? $foreignParams['type'] : null;
if (!$linkType) {
$this->log->warning("Link {$entityType}.{$name} has no type.");
return null;
}
$params['hasField'] = (bool) $this->metadata
->get(['entityDefs', $entityType, 'fields', $name]);
@@ -204,6 +204,7 @@ return [
'useWebSocket' => false,
'webSocketMessager' => 'ZeroMQ',
'auth2FAMethodList' => ['Totp'],
'auth2FAInPortal' => false,
'personNameFormat' => 'firstLast',
'newNotificationCountInTitle' => false,
'pdfEngine' => 'Dompdf',
@@ -141,6 +141,7 @@
"auth2FA": "Enable 2-Factor Authentication",
"auth2FAForced": "Force regular users to set up 2FA",
"auth2FAMethodList": "Available 2FA methods",
"auth2FAInPortal": "Allow 2FA in portals",
"workingTimeCalendar": "Working Time Calendar",
"oidcClientId": "OIDC Client ID",
"oidcClientSecret": "OIDC Client Secret",
@@ -10,7 +10,7 @@
"label": "2-Factor Authentication",
"rows": [
[{"name": "auth2FA"}, {"name": "auth2FAMethodList"}],
[{"name": "auth2FAForced"}, false]
[{"name": "auth2FAForced"}, {"name": "auth2FAInPortal"}]
]
},
{
@@ -289,6 +289,9 @@
"auth2FAForced": {
"type": "bool"
},
"auth2FAInPortal": {
"type": "bool"
},
"passwordRecoveryDisabled": {
"type": "bool"
},
@@ -38,9 +38,7 @@
],
"filter": true,
"notCreatable": true,
"fieldDefs": {
"notStorable": true
},
"valueFactoryClassName": "Espo\\Core\\Field\\LinkParent\\LinkParentFactory",
"attributeExtractorClassName": "Espo\\Core\\Field\\LinkParent\\LinkParentAttributeExtractor"
}
@@ -76,7 +76,12 @@ class Service
throw new NotFound();
}
if (!$user->isAdmin() && !$user->isRegular()) {
$allow =
$user->isAdmin() ||
$user->isRegular() ||
$user->isPortal() && $this->config->get('auth2FAInPortal');
if (!$allow) {
throw new Forbidden();
}
@@ -116,7 +121,15 @@ class Service
throw new NotFound();
}
if (!$user->isAdmin() && !$user->isRegular()) {
$allow =
$this->config->get('auth2FA') &&
(
$user->isAdmin() ||
$user->isRegular() ||
$user->isPortal() && $this->config->get('auth2FAInPortal')
);
if (!$allow) {
throw new Forbidden();
}
@@ -178,7 +191,12 @@ class Service
throw new NotFound();
}
if (!$user->isAdmin() && !$user->isRegular()) {
$allow =
$user->isAdmin() ||
$user->isRegular() ||
$user->isPortal() && $this->config->get('auth2FAInPortal');
if (!$allow) {
throw new Forbidden();
}
@@ -219,7 +237,11 @@ class Service
if (
$userData->get('auth2FA') &&
$userData->get('auth2FAMethod') &&
($userData->isAttributeChanged('auth2FA') || $userData->isAttributeChanged('auth2FAMethod'))
($userData->isAttributeChanged('auth2FA') || $userData->isAttributeChanged('auth2FAMethod')) &&
(
!$user->isPortal() ||
$this->config->get('auth2FAInPortal')
)
) {
$auth2FAMethod = $userData->get('auth2FAMethod');
@@ -93,6 +93,10 @@ class EmailService
throw new Forbidden("2FA is not enabled.");
}
if ($this->user->isPortal() && !$this->config->get('auth2FAInPortal')) {
throw new Forbidden("2FA is not enabled in portals.");
}
$methodList = $this->config->get('auth2FAMethodList') ?? [];
if (!in_array(EmailLogin::NAME, $methodList)) {
@@ -93,6 +93,10 @@ class SmsService
throw new Forbidden("2FA is not enabled.");
}
if ($this->user->isPortal() && !$this->config->get('auth2FAInPortal')) {
throw new Forbidden("2FA is not enabled in portals.");
}
$methodList = $this->config->get('auth2FAMethodList') ?? [];
if (!in_array(SmsLogin::NAME, $methodList)) {
+2
View File
@@ -155,6 +155,7 @@ define('views/admin/authentication', ['views/settings/record/edit'], function (D
if (this.model.get('auth2FA')) {
this.showField('auth2FAForced');
this.showField('auth2FAMethodList');
this.showField('auth2FAInPortal');
this.setFieldRequired('auth2FAMethodList');
return;
@@ -162,6 +163,7 @@ define('views/admin/authentication', ['views/settings/record/edit'], function (D
this.hideField('auth2FAForced');
this.hideField('auth2FAMethodList');
this.hideField('auth2FAInPortal');
this.setFieldNotRequired('auth2FAMethodList');
},
+10 -5
View File
@@ -119,6 +119,12 @@ function (Dep, /** module:ui/select*/Select) {
showWarnings: false,
formulaMode: true,
};
if (this.decimalPlaces === null) {
this.autoNumericOptions.decimalPlaces = this.decimalPlacesRawValue;
this.autoNumericOptions.decimalPlacesRawValue = this.decimalPlacesRawValue;
this.autoNumericOptions.allowDecimalPadding = false;
}
},
getCurrencyFormat: function () {
@@ -149,13 +155,12 @@ function (Dep, /** module:ui/select*/Select) {
},
formatNumber: function (value) {
if (this.isReadMode()) {
return this.formatNumberDetail(value);
}
return this.formatNumberEdit(value);
return this.formatNumberDetail(value);
},
/**
* @todo Remove. Used in range.
*/
formatNumberEdit: function (value) {
let currencyDecimalPlaces = this.decimalPlaces;
+12 -5
View File
@@ -60,6 +60,14 @@ define('views/fields/float', ['views/fields/int'], function (Dep) {
else if (this.getConfig().has('decimalMark')) {
this.decimalMark = this.getConfig().get('decimalMark');
}
if (!this.decimalMark) {
this.decimalMark = '.';
}
if (this.decimalMark === this.thousandSeparator) {
this.thousandSeparator = '';
}
},
/**
@@ -90,13 +98,12 @@ define('views/fields/float', ['views/fields/int'], function (Dep) {
return value;
}
if (this.isReadMode()) {
return this.formatNumberDetail(value);
}
return this.formatNumberEdit(value);
return this.formatNumberDetail(value);
},
/**
* @todo Remove. Used in range.
*/
formatNumberEdit: function (value) {
if (value === null) {
return '';
+17 -6
View File
@@ -99,8 +99,16 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
* @protected
*/
setupAutoNumericOptions: function () {
let separator = (!this.disableFormatting ? this.thousandSeparator : null) || '';
let decimalCharacter = '.';
if (separator === '.') {
decimalCharacter = ',';
}
this.autoNumericOptions = {
digitGroupSeparator: (!this.disableFormatting ? this.thousandSeparator : null) || '',
digitGroupSeparator: separator,
decimalCharacter: decimalCharacter,
modifyValueOnWheel: false,
decimalPlaces: 0,
selectOnFocus: false,
@@ -162,6 +170,10 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
}
}
if (this.isEditMode()) {
data.value = this.model.get(this.name);
}
return data;
},
@@ -176,11 +188,7 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
return value;
}
if (this.isReadMode()) {
return this.formatNumberDetail(value);
}
return this.formatNumberEdit(value);
return this.formatNumberDetail(value);
},
formatNumberDetail: function (value) {
@@ -195,6 +203,9 @@ define('views/fields/int', ['views/fields/base', 'lib!autonumeric'], function (D
return stringValue;
},
/**
* @todo Remove. Used in range.
*/
formatNumberEdit: function (value) {
if (value === null) {
return '';
+9 -3
View File
@@ -51,10 +51,16 @@ define('views/user/record/detail', 'views/record/detail', function (Dep) {
}
}
let isPortalUser = this.model.isPortal() ||
this.model.id === this.getUser().id && this.getUser().isPortal();
if (
(this.model.id == this.getUser().id || this.getUser().isAdmin()) &&
(this.model.isRegular() || this.model.isAdmin()) &&
this.getConfig().get('auth2FA')
(this.model.id === this.getUser().id || this.getUser().isAdmin()) &&
this.getConfig().get('auth2FA') &&
(
(this.model.isRegular() || this.model.isAdmin()) ||
isPortalUser && this.getConfig().get('auth2FAInPortal')
)
) {
this.addButton({
name: 'viewSecurity',
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "espocrm",
"version": "7.4.0",
"version": "7.4.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "espocrm",
"version": "7.4.0",
"version": "7.4.3",
"hasInstallScript": true,
"license": "GPL-3.0",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "7.4.0",
"version": "7.4.3",
"description": "Open-source CRM.",
"repository": {
"type": "git",
+2
View File
@@ -0,0 +1,2 @@
{
}
@@ -0,0 +1,68 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
use Espo\Core\Container;
use Espo\Core\Utils\Metadata;
class AfterUpgrade
{
public function run(Container $container): void
{
$this->updateMetadata($container->get('metadata'));
}
private function updateMetadata(Metadata $metadata): void
{
$this->fixParent($metadata);
$metadata->save();
}
private function fixParent(Metadata $metadata): void
{
foreach ($metadata->get(['entityDefs']) as $scope => $defs) {
foreach ($metadata->get(['entityDefs', $scope, 'fields']) as $field => $fieldDefs) {
$custom = $metadata->getCustom('entityDefs', $scope);
if (!$custom) {
continue;
}
if (
($fieldDefs['type'] ?? null) === 'linkParent' &&
($fieldDefs['notStorable'] ?? false)
) {
if ($custom?->fields?->$field?->notStorable) {
$metadata->delete('entityDefs', $scope, "fields.{$field}.notStorable");
}
}
}
}
}
}
+24
View File
@@ -43,6 +43,30 @@ class AfterUpgrade
'length' => 24,
]);
$this->fixParent($metadata);
$metadata->save();
}
private function fixParent(Metadata $metadata): void
{
foreach ($metadata->get(['entityDefs']) as $scope => $defs) {
foreach ($metadata->get(['entityDefs', $scope, 'fields']) as $field => $fieldDefs) {
$custom = $metadata->getCustom('entityDefs', $scope);
if (!$custom) {
continue;
}
if (
($fieldDefs['type'] ?? null) === 'linkParent' &&
($fieldDefs['notStorable'] ?? false)
) {
if ($custom?->fields?->$field?->notStorable) {
$metadata->delete('entityDefs', $scope, "fields.{$field}.notStorable");
}
}
}
}
}
}