diff --git a/api/v1/portal/.htaccess b/api/v1/portal/.htaccess new file mode 100755 index 0000000000..c1d824da79 --- /dev/null +++ b/api/v1/portal/.htaccess @@ -0,0 +1,12 @@ +RewriteEngine On + +# Some hosts may require you to use the `RewriteBase` directive. +# If you need to use the `RewriteBase` directive, it should be the +# absolute physical path to the directory that contains this htaccess file. +# +# RewriteBase / + +RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}] + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^ index.php [QSA,L] \ No newline at end of file diff --git a/api/v1/portal/index.php b/api/v1/portal/index.php new file mode 100644 index 0000000000..57687bb851 --- /dev/null +++ b/api/v1/portal/index.php @@ -0,0 +1,38 @@ +run(); diff --git a/api/v1/portal/web.config b/api/v1/portal/web.config new file mode 100755 index 0000000000..be763f982b --- /dev/null +++ b/api/v1/portal/web.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/Espo/AclPortal/Email.php b/application/Espo/AclPortal/Email.php new file mode 100644 index 0000000000..9c7beabeb5 --- /dev/null +++ b/application/Espo/AclPortal/Email.php @@ -0,0 +1,71 @@ +checkEntity($user, $entity, $data, 'read')) { + return true; + } + + if ($data === false) { + return false; + } + if (is_object($data)) { + if ($data->read === false || $data->read === 'no') { + return false; + } + } + + if (!$entity->has('usersIds')) { + $entity->loadLinkMultipleField('users'); + } + $userIdList = $entity->get('usersIds'); + if (is_array($userIdList) && in_array($user->id, $userIdList)) { + return true; + } + return false; + } + + public function checkIsOwner(User $user, Entity $entity) + { + if ($user->id === $entity->get('createdById')) { + return true; + } + return false; + } +} + diff --git a/application/Espo/Core/Acl/Table.php b/application/Espo/Core/Acl/Table.php index 26620049a0..0d6723811f 100644 --- a/application/Espo/Core/Acl/Table.php +++ b/application/Espo/Core/Acl/Table.php @@ -114,6 +114,11 @@ class Table $this->cacheFilePath = 'data/cache/application/acl/' . $this->user->id . '.php'; } + protected function getUser() + { + return $this->user; + } + protected function getMetadata() { return $this->metadata; @@ -171,18 +176,15 @@ class Table private function load() { - $aclTableList = []; - $fieldTableList = []; - $valuePermissionLists = (object)[]; foreach ($this->valuePermissionList as $permission) { $valuePermissionLists->$permission = []; } if (!$this->user->isAdmin()) { - $userRoles = $this->user->get('roles'); + $roleList = $this->getRoleList(); - foreach ($userRoles as $role) { + foreach ($roleList as $role) { $aclTableList[] = $role->get('data'); $fieldTableList[] = $role->get('fieldData'); foreach ($this->valuePermissionList as $permission) { @@ -190,18 +192,6 @@ class Table } } - $teams = $this->user->get('teams'); - foreach ($teams as $team) { - $teamRoles = $team->get('roles'); - foreach ($teamRoles as $role) { - $aclTableList[] = $role->get('data'); - $fieldTableList[] = $role->get('fieldData'); - foreach ($this->valuePermissionList as $permission) { - $valuePermissionLists->{$permission}[] = $role->get($permission); - } - } - } - $aclTable = $this->mergeTableList($aclTableList); $fieldTable = $this->mergeFieldTableList($fieldTableList); @@ -211,7 +201,7 @@ class Table } else { $aclTable = (object) []; foreach ($this->getScopeList() as $scope) { - if ($this->metadata->get("scopes.{$scope}.acl") === 'boolean') { + if ($this->metadata->get("scopes.{$scope}.{$this->type}") === 'boolean') { $aclTable->$scope = true; } else { if ($this->metadata->get("scopes.{$scope}.entity")) { @@ -240,9 +230,9 @@ class Table if (!$this->user->isAdmin()) { foreach ($this->valuePermissionList as $permission) { - $this->data->$permission = $this->mergeValueList($valuePermissionLists->$permission, $this->metadata->get('app.acl.default.' . $permission, 'all')); - if ($this->metadata->get('app.acl.mandatory.' . $permission)) { - $this->data->$permission = $this->metadata->get('app.acl.mandatory.' . $permission); + $this->data->$permission = $this->mergeValueList($valuePermissionLists->$permission, $this->metadata->get('app.'.$this->type.'.default.' . $permission, 'all')); + if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) { + $this->data->$permission = $this->metadata->get('app.'.$this->type.'.mandatory.' . $permission); } } @@ -253,6 +243,26 @@ class Table } } + protected function getRoleList() + { + $roleList = []; + + $userRoleList = $this->getUser()->get('roles'); + foreach ($userRoleList as $role) { + $roleList[] = $role; + } + + $teamList = $this->getUser()->get('teams'); + foreach ($teamList as $team) { + $teamRoleList = $team->get('roles'); + foreach ($teamRoleList as $role) { + $roleList[] = $role; + } + } + + return $roleList; + } + public function getScopeForbiddenAttributeList($scope, $action = 'read', $thresholdLevel = 'no') { $key = $scope . '_'. $action . '_' . $thresholdLevel; @@ -372,7 +382,7 @@ class Table return; } - $data = $this->metadata->get('app.acl.default.scopeLevel', array()); + $data = $this->metadata->get('app.'.$this->type.'.default.scopeLevel', array()); foreach ($data as $scope => $item) { if (isset($table->$scope)) continue; @@ -383,11 +393,17 @@ class Table $table->$scope = $value; } - $fieldData = $this->metadata->get('app.acl.default.fieldLevel', array()); + $defaultFieldData = $this->metadata->get('app.'.$this->type.'.default.fieldLevel', array()); + + foreach ($this->getScopeList() as $scope) { + if (isset($table->$scope) && $table->$scope === false) continue; + if (!$this->getMetadata()->get('scopes.' . $scope . '.entity')) continue; + if (!$this->getMetadata()->get('scopes.' . $scope . '.acl')) continue; + if (!$this->getMetadata()->get('scopes.' . $scope . '.aclPortal')) continue; - foreach ($fieldData as $scope => $s) { $fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", [])); - foreach ($s as $field => $f) { + + foreach ($defaultFieldData as $field => $f) { if (!in_array($field, $fieldList)) continue; if (!isset($fieldTable->$scope)) { $fieldTable->$scope = (object) []; @@ -411,7 +427,7 @@ class Table $aclType = $this->defaultAclType; } if (!empty($aclType)) { - $defaultValue = $this->metadata->get('app.acl.scopeLevelTypesDefaults.' . $aclType, true); + $defaultValue = $this->metadata->get('app.'.$this->type.'.scopeLevelTypesDefaults.' . $aclType, $this->metadata->get('app.'.$this->type.'.scopeLevelTypesDefaults.record')); if (is_array($defaultValue)) { $defaultValue = (object) $defaultValue; } @@ -427,7 +443,7 @@ class Table return; } - $data = $this->metadata->get('app.acl.mandatory.scopeLevel', array()); + $data = $this->metadata->get('app.'.$this->type.'.mandatory.scopeLevel', array()); foreach ($data as $scope => $item) { $value = $item; @@ -437,14 +453,17 @@ class Table $table->$scope = $value; } - $fieldData = $this->metadata->get('app.acl.mandatory.fieldLevel', array()); + $mandatoryData = $this->metadata->get('app.'.$this->type.'.mandatory.fieldLevel', array()); + + foreach ($this->getScopeList() as $scope) { + if (isset($table->$scope) && $table->$scope === false) continue; + if (!$this->getMetadata()->get('scopes.' . $scope . '.entity')) continue; + if (!$this->getMetadata()->get('scopes.' . $scope . '.acl')) continue; + if (!$this->getMetadata()->get('scopes.' . $scope . '.aclPortal')) continue; - foreach ($fieldData as $scope => $s) { $fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", [])); - if (!isset($fieldTable->$scope)) { - $fieldTable->$scope = (object) []; - } - foreach ($s as $field => $f) { + + foreach ($mandatoryData as $field => $f) { if (!in_array($field, $fieldList)) continue; $fieldTable->$scope->$field = (object) []; foreach ($this->fieldActionList as $action) { diff --git a/application/Espo/Core/AclManager.php b/application/Espo/Core/AclManager.php index f83876190f..b57389ca0c 100644 --- a/application/Espo/Core/AclManager.php +++ b/application/Espo/Core/AclManager.php @@ -58,6 +58,11 @@ class AclManager return $this->container; } + protected function getMetadata() + { + return $this->metadata; + } + public function getImplementation($scope) { if (empty($this->implementationHashMap[$scope])) { diff --git a/application/Espo/Core/AclPortal/Table.php b/application/Espo/Core/AclPortal/Table.php index 88c050d8e3..5e4155b1e4 100644 --- a/application/Espo/Core/AclPortal/Table.php +++ b/application/Espo/Core/AclPortal/Table.php @@ -29,20 +29,69 @@ namespace Espo\Core\AclPortal; +use \Espo\Core\Exceptions\Error; + +use \Espo\ORM\Entity; +use \Espo\Entities\User; +use \Espo\Entities\Portal; + +use \Espo\Core\Utils\Config; +use \Espo\Core\Utils\Metadata; +use \Espo\Core\Utils\FieldManager; +use \Espo\Core\Utils\File\Manager as FileManager; + class Table extends \Espo\Core\Acl\Table { protected $type = 'aclPortal'; + protected $portal; + protected $defaultAclType = 'recordAllOwnNo'; protected $levelList = ['all', 'account', 'contact', 'own', 'no']; protected $valuePermissionList = []; + public function __construct(User $user, Portal $portal, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManager $fieldManager = null) + { + if (empty($portal)) { + throw new Error("No portal was passed to AclPortal\\Table constructor."); + } + $this->portal = $portal; + parent::__construct($user, $config, $fileManager, $metadata, $fieldManager); + } + + protected function getPortal() + { + return $this->portal; + } + + protected function initCacheFilePath() + { + $this->cacheFilePath = 'data/cache/application/acl-portal/'.$this->getPortal()->id.'/' . $this->getUser()->id . '.php'; + } + + protected function getRoleList() + { + $roleList = []; + + $userRoleList = $this->getUser()->get('portalRoles'); + foreach ($userRoleList as $role) { + $roleList[] = $role; + } + + $portalRoleList = $this->getPortal()->get('portalRoles'); + foreach ($portalRoleList as $role) { + $roleList[] = $role; + } + + return $roleList; + } + protected function getScopeWithAclList() { $scopeList = []; - $scopes = $this->metadata->get('scopes'); + $scopes = $this->getMetadata()->get('scopes'); foreach ($scopes as $scope => $d) { if (empty($d['acl'])) continue; if (empty($d['aclPortal'])) continue; diff --git a/application/Espo/Core/AclPortalManager.php b/application/Espo/Core/AclPortalManager.php index eb95fda133..e6e94745ca 100644 --- a/application/Espo/Core/AclPortalManager.php +++ b/application/Espo/Core/AclPortalManager.php @@ -29,6 +29,9 @@ namespace Espo\Core; +use \Espo\ORM\Entity; +use \Espo\Entities\User; +use \Espo\Core\Utils\Util; class AclPortalManager extends AclManager { @@ -41,7 +44,7 @@ class AclPortalManager extends AclManager $className = '\\Espo\\Custom\\AclPortal\\' . $normalizedName; if (!class_exists($className)) { - $moduleName = $this->metadata->getScopeModuleName($scope); + $moduleName = $this->getMetadata()->getScopeModuleName($scope); if ($moduleName) { $className = '\\Espo\\Modules\\' . $moduleName . '\\AclPortal\\' . $normalizedName; } else { @@ -67,6 +70,23 @@ class AclPortalManager extends AclManager return $this->implementationHashMap[$scope]; } + protected function getTable(User $user) + { + $key = spl_object_hash($user); + + if (empty($this->tableHashMap[$key])) { + $config = $this->getContainer()->get('config'); + $fileManager = $this->getContainer()->get('fileManager'); + $metadata = $this->getContainer()->get('metadata'); + $fieldManager = $this->getContainer()->get('fieldManager'); + $portal = $this->getContainer()->get('portal'); + + $this->tableHashMap[$key] = new $this->tableClassName($user, $portal, $config, $fileManager, $metadata, $fieldManager); + } + + return $this->tableHashMap[$key]; + } + public function checkReadOnlyAccount(User $user, $scope) { if ($user->isAdmin()) { diff --git a/application/Espo/Core/Application.php b/application/Espo/Core/Application.php index 57c9258ab7..b18d795644 100644 --- a/application/Espo/Core/Application.php +++ b/application/Espo/Core/Application.php @@ -33,22 +33,19 @@ class Application { private $metadata; - private $container; + protected $container; private $slim; private $auth; - /** - * Constructor - */ public function __construct() { - $this->initContainer(); - date_default_timezone_set('UTC'); - $GLOBALS['log'] = $this->container->get('log'); + $this->initContainer(); + + $GLOBALS['log'] = $this->getContainer()->get('log'); $this->initAutoloads(); } @@ -160,6 +157,11 @@ class Application return false; } + protected function createApiAuth($auth) + { + return new \Espo\Core\Utils\Api\Auth($auth); + } + protected function routeHooks() { $container = $this->getContainer(); @@ -171,9 +173,9 @@ class Application $container->get('output')->processError($e->getMessage(), $e->getCode()); } - $apiAuth = new \Espo\Core\Utils\Api\Auth($auth); - $this->getSlim()->add($apiAuth); + $apiAuth = $this->createApiAuth($auth); + $this->getSlim()->add($apiAuth); $this->getSlim()->hook('slim.before.dispatch', function () use ($slim, $container) { $route = $slim->router()->getCurrentRoute(); @@ -233,13 +235,19 @@ class Application }); } - protected function initRoutes() + protected function getRouteList() { $routes = new \Espo\Core\Utils\Route($this->getContainer()->get('config'), $this->getMetadata(), $this->getContainer()->get('fileManager')); - $crudList = array_keys( $this->getContainer()->get('config')->get('crud') ); - foreach ($routes->getAll() as $route) { + return $routes->getAll(); + } + + protected function initRoutes() + { + $crudList = array_keys($this->getContainer()->get('config')->get('crud')); + + foreach ($this->getRouteList() as $route) { $method = strtolower($route['method']); if (!in_array($method, $crudList)) { $GLOBALS['log']->error('Route: Method ['.$method.'] does not exist. Please check your route ['.$route['route'].']'); diff --git a/application/Espo/Core/ApplicationPortal.php b/application/Espo/Core/ApplicationPortal.php index 40830ab670..35bd6328d4 100644 --- a/application/Espo/Core/ApplicationPortal.php +++ b/application/Espo/Core/ApplicationPortal.php @@ -29,11 +29,77 @@ namespace Espo\Core; -class ApplicationPortal extends Appplication +use \Espo\Core\Exceptions\Error; + +class ApplicationPortal extends Application { + public function __construct($portalId) + { + date_default_timezone_set('UTC'); + + $this->initContainer(); + + if (empty($portalId)) { + throw new Error("Portal id was not passed to ApplicationPortal."); + } + + $portal = $this->getContainer()->get('entityManager')->getEntity('Portal', $portalId); + + if (!$portal) { + throw new NotFound(); + } + if (!$portal->get('isActive')) { + throw new Forbidden("Portal is not active."); + } + + $this->portal = $portal; + + $this->getContainer()->setPortal($portal); + + + $GLOBALS['log'] = $this->getContainer()->get('log'); + + $this->initAutoloads(); + } + + protected function getAuth() + { + if (empty($this->auth)) { + $this->auth = new \Espo\Core\Utils\AuthPortal($this->getContainer()); + } + return $this->auth; + } + + protected function getPortal() + { + return $this->portal; + } + protected function initContainer() { $this->container = new ContainerPortal(); } + + protected function getRouteList() + { + $routeList = parent::getRouteList(); + foreach ($routeList as $i => $route) { + if (isset($route['route'])) { + if ($route['route']{0} !== '/') { + $route['route'] = '/' . $route['route']; + } + $route['route'] = '/:portalId' . $route['route']; + } + $routeList[$i] = $route; + } + return $routeList; + } + + public function runClient() + { + $this->getContainer()->get('clientManager')->display(null, 'html/portal.html', array( + 'portalId' => $this->getPortal()->id + )); + } } diff --git a/application/Espo/Core/Container.php b/application/Espo/Core/Container.php index b478ae3f2d..c22e0957a9 100644 --- a/application/Espo/Core/Container.php +++ b/application/Espo/Core/Container.php @@ -40,7 +40,6 @@ class Container */ public function __construct() { - } public function get($name) @@ -51,6 +50,11 @@ class Container return $this->data[$name]; } + protected function set($name, $obj) + { + $this->data[$name] = $obj; + } + private function load($name) { $loadMethod = 'load' . ucfirst($name); @@ -304,9 +308,9 @@ class Container ); } - public function setUser($user) + public function setUser(\Espo\Entities\User $user) { - $this->data['user'] = $user; + $this->set('user', $user); } } diff --git a/application/Espo/Core/ContainerPortal.php b/application/Espo/Core/ContainerPortal.php index 48743df02e..a948914392 100644 --- a/application/Espo/Core/ContainerPortal.php +++ b/application/Espo/Core/ContainerPortal.php @@ -54,5 +54,10 @@ class ContainerPortal extends Container $this->get('user') ); } + + public function setPortal(\Espo\Entities\Portal $portal) + { + $this->set('portal', $portal); + } } diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index 4ee9396bbd..469a7712e1 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -439,6 +439,17 @@ class Base if ($this->getSeed()->hasAttribute('createdById')) { $d['createdById'] = $this->getUser()->id; } + + if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) { + $contactId = $this->getUser()->get('contactId'); + if ($contactId) { + $d[] = array( + 'parentType' => 'Contact', + 'parentId' => $contactId + ); + } + } + if (!empty($d)) { $result['whereClause'][] = array( 'OR' => $d @@ -466,6 +477,18 @@ class Base $this->setDistinct(true, $result); $d['accountsAccess.id'] = $accountIdList; } + if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) { + $d[] = array( + 'parentType' => 'Account', + 'parentId' => $accountIdList + ); + if ($contactId) { + $d[] = array( + 'parentType' => 'Contact', + 'parentId' => $contactId + ); + } + } } if ($contactId) { diff --git a/application/Espo/Core/Utils/Auth.php b/application/Espo/Core/Utils/Auth.php index 17ae916e3e..be1918876d 100644 --- a/application/Espo/Core/Utils/Auth.php +++ b/application/Espo/Core/Utils/Auth.php @@ -38,44 +38,51 @@ class Auth protected $authentication; - protected $config; - - protected $entityManager; - public function __construct(\Espo\Core\Container $container) { $this->container = $container; - $this->entityManager = $this->container->get('entityManager'); - $this->config = $this->container->get('config'); - - $authenticationMethod = $this->config->get('authenticationMethod', 'Espo'); + $authenticationMethod = $this->getConfig()->get('authenticationMethod', 'Espo'); $authenticationClassName = "\\Espo\\Core\\Utils\\Authentication\\" . $authenticationMethod; - $this->authentication = new $authenticationClassName($this->config, $this->entityManager, $this); + $this->authentication = new $authenticationClassName($this->getConfig(), $this->getEntityManager(), $this); - $this->request = $this->container->get('slim')->request(); + $this->request = $container->get('slim')->request(); + } + + protected function getContainer() + { + return $this->container; + } + + protected function getConfig() + { + return $this->getContainer()->get('config'); + } + + protected function getEntityManager() + { + return $this->getContainer()->get('entityManager'); } public function useNoAuth($isAdmin = false) { - $entityManager = $this->container->get('entityManager'); + $entityManager = $this->getContainer()->get('entityManager'); $user = $entityManager->getRepository('User')->get('system'); if (!$user) { - throw new Error('System user is not found'); + throw new Error("System user is not found"); } $user->set('isAdmin', $isAdmin); $entityManager->setUser($user); - $this->container->setUser($user); + $this->getContainer()->setUser($user); } + public function login($username, $password) { - $entityManager = $this->entityManager; - - $authToken = $entityManager->getRepository('AuthToken')->where(array('token' => $password))->findOne(); + $authToken = $this->getEntityManager()->getRepository('AuthToken')->where(array('token' => $password))->findOne(); $user = $this->authentication->login($username, $password, $authToken); @@ -84,12 +91,16 @@ class Auth $GLOBALS['log']->debug("AUTH: Trying to login as user '".$user->get('userName')."' which is not active."); return false; } - $entityManager->setUser($user); - $this->container->setUser($user); + if (!$user->isAdmin() && $user->get('isPortalUser')) { + $GLOBALS['log']->debug("AUTH: Trying to login to crm as a portal user '".$user->get('userName')."'."); + return false; + } + $this->getEntityManager()->setUser($user); + $this->getContainer()->setUser($user); if ($this->request->headers->get('HTTP_ESPO_AUTHORIZATION')) { if (!$authToken) { - $authToken = $entityManager->getEntity('AuthToken'); + $authToken = $this->getEntityManager()->getEntity('AuthToken'); $token = $this->createToken($user); $authToken->set('token', $token); $authToken->set('hash', $user->get('password')); @@ -98,7 +109,7 @@ class Auth } $authToken->set('lastAccess', date('Y-m-d H:i:s')); - $entityManager->saveEntity($authToken); + $this->getEntityManager()->saveEntity($authToken); $user->set('token', $authToken->get('token')); } @@ -113,11 +124,9 @@ class Auth public function destroyAuthToken($token) { - $entityManager = $this->container->get('entityManager'); - - $authToken = $entityManager->getRepository('AuthToken')->where(array('token' => $token))->findOne(); + $authToken = $this->getEntityManager()->getRepository('AuthToken')->where(array('token' => $token))->findOne(); if ($authToken) { - $entityManager->removeEntity($authToken); + $this->getEntityManager()->removeEntity($authToken); return true; } } diff --git a/application/Espo/Core/Utils/AuthPortal.php b/application/Espo/Core/Utils/AuthPortal.php new file mode 100644 index 0000000000..4cccbe5703 --- /dev/null +++ b/application/Espo/Core/Utils/AuthPortal.php @@ -0,0 +1,95 @@ +getContainer()->get('portal'); + } + + public function login($username, $password) + { + $authToken = $this->getEntityManager()->getRepository('AuthToken')->where(array('token' => $password))->findOne(); + + if ($authToken) { + if ($authToken->get('portalId') !== $this->getPortal()->id) { + $GLOBALS['log']->debug("AUTH: Trying to login to portal with a token not related to portal."); + return false; + } + } + + $user = $this->authentication->login($username, $password, $authToken); + + if ($user) { + if (!$user->isActive()) { + $GLOBALS['log']->debug("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is not active."); + return false; + } + if (!$user->isAdmin() && !$this->getEntityManager()->getRepository('Portal')->isRelated($this->getPortal(), 'users', $user)) { + $GLOBALS['log']->debug("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is not related to portal."); + return false; + } + + if (!$user->isAdmin() && !$user->get('isPortalUser')) { + $GLOBALS['log']->debug("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is not portal user."); + return false; + } + + $user->set('portalId', $this->getPortal()->id); + $this->getEntityManager()->setUser($user); + $this->getContainer()->setUser($user); + + if ($this->request->headers->get('HTTP_ESPO_AUTHORIZATION')) { + if (!$authToken) { + $authToken = $this->getEntityManager()->getEntity('AuthToken'); + $token = $this->createToken($user); + $authToken->set('token', $token); + $authToken->set('hash', $user->get('password')); + $authToken->set('ipAddress', $_SERVER['REMOTE_ADDR']); + $authToken->set('userId', $user->id); + $authToken->set('portalId', $this->getPortal()->id); + } + $authToken->set('lastAccess', date('Y-m-d H:i:s')); + + $this->getEntityManager()->saveEntity($authToken); + $user->set('token', $authToken->get('token')); + } + + return true; + } + } + +} + diff --git a/application/Espo/Core/Utils/Authentication/Base.php b/application/Espo/Core/Utils/Authentication/Base.php index 5eb7e72f72..9f70c50146 100644 --- a/application/Espo/Core/Utils/Authentication/Base.php +++ b/application/Espo/Core/Utils/Authentication/Base.php @@ -73,6 +73,5 @@ abstract class Base return $this->passwordHash; } - } diff --git a/application/Espo/Core/Utils/Authentication/Espo.php b/application/Espo/Core/Utils/Authentication/Espo.php index 1e7996ca88..9826502520 100644 --- a/application/Espo/Core/Utils/Authentication/Espo.php +++ b/application/Espo/Core/Utils/Authentication/Espo.php @@ -45,7 +45,7 @@ class Espo extends Base 'whereClause' => array( 'userName' => $username, 'password' => $hash - ), + ) )); return $user; diff --git a/application/Espo/Core/Utils/ClientManager.php b/application/Espo/Core/Utils/ClientManager.php index 1c671892eb..ecb0d10d60 100644 --- a/application/Espo/Core/Utils/ClientManager.php +++ b/application/Espo/Core/Utils/ClientManager.php @@ -45,10 +45,6 @@ class ClientManager { $this->config = $config; $this->themeManager = $themeManager; - - if ($this->config->get('isDeveloperMode')) { - $this->mainHtmlFilePath = $this->htmlFilePathForDeveloperMode; - } } protected function getThemeManager() @@ -61,16 +57,25 @@ class ClientManager return $this->config; } - public function display($runScript = null, $mainHtmlFilePath = null) + public function display($runScript = null, $htmlFilePath = null, $vars = array()) { if (is_null($runScript)) { $runScript = $this->runScript; } - if (is_null($mainHtmlFilePath)) { - $mainHtmlFilePath = $this->mainHtmlFilePath; + if (is_null($htmlFilePath)) { + $htmlFilePath = $this->mainHtmlFilePath; } - $html = file_get_contents($mainHtmlFilePath); + if ($this->getConfig()->get('isDeveloperMode')) { + if (file_exists('frontend/' . $htmlFilePath)) { + $htmlFilePath = 'frontend/' . $htmlFilePath; + } + } + + $html = file_get_contents($htmlFilePath); + foreach ($vars as $key => $value) { + $html = str_replace('{{'.$key.'}}', $value, $html); + } $html = str_replace('{{cacheTimestamp}}', $this->getConfig()->get('cacheTimestamp', 0), $html); $html = str_replace('{{useCache}}', $this->getConfig()->get('useCache') ? 'true' : 'false' , $html); $html = str_replace('{{stylesheet}}', $this->getThemeManager()->getStylesheet(), $html); diff --git a/application/Espo/Core/Utils/Route.php b/application/Espo/Core/Utils/Route.php index 364c60a1c9..d70a005bc3 100644 --- a/application/Espo/Core/Utils/Route.php +++ b/application/Espo/Core/Utils/Route.php @@ -28,6 +28,7 @@ ************************************************************************/ namespace Espo\Core\Utils; + class Route { protected $data = null; @@ -66,7 +67,6 @@ class Route return $this->metadata; } - public function get($key = '', $returns = null) { if (!isset($this->data)) { @@ -91,13 +91,11 @@ class Route return $lastRoute; } - public function getAll() { return $this->get(); } - protected function init() { if (file_exists($this->cacheFile) && $this->getConfig()->get('useCache')) { @@ -146,17 +144,14 @@ class Route return $currData; } - protected function addToData($data, $newData) { if (!is_array($newData)) { return $data; } - foreach($newData as $route) { - + foreach ($newData as $route) { $route['route'] = $this->adjustPath($route['route']); - $data[] = $route; } @@ -174,11 +169,10 @@ class Route { $routePath = trim($routePath); - if ( substr($routePath,0,1) != '/') { + if (substr($routePath,0,1) != '/') { return '/'.$routePath; } return $routePath; } - } \ No newline at end of file diff --git a/application/Espo/EntryPoints/Portal.php b/application/Espo/EntryPoints/Portal.php new file mode 100644 index 0000000000..9f7b240e6c --- /dev/null +++ b/application/Espo/EntryPoints/Portal.php @@ -0,0 +1,51 @@ +runClient(); + } +} + diff --git a/application/Espo/ORM/Repositories/RDB.php b/application/Espo/ORM/Repositories/RDB.php index fe7c352702..f22205ce76 100644 --- a/application/Espo/ORM/Repositories/RDB.php +++ b/application/Espo/ORM/Repositories/RDB.php @@ -28,6 +28,7 @@ ************************************************************************/ namespace Espo\ORM\Repositories; + use \Espo\ORM\EntityManager; use \Espo\ORM\EntityFactory; use \Espo\ORM\EntityCollection; diff --git a/application/Espo/Resources/metadata/app/acl.json b/application/Espo/Resources/metadata/app/acl.json index 964e2e0756..3393b64330 100644 --- a/application/Espo/Resources/metadata/app/acl.json +++ b/application/Espo/Resources/metadata/app/acl.json @@ -63,23 +63,9 @@ }, "scopeLevelTypesDefaults": { "boolean": true, - "recordAllTeamOwnNo": { - "read": "all", - "edit": "all", - "delete": "no" - }, - "recordAllOwnNo": { - "read": "all", - "edit": "all", - "delete": "no" - }, - "recordAllTeamNo": { - "read": "all", - "edit": "all", - "delete": "no" - }, - "recordAllNo": { + "record": { "read": "all", + "stream": "all", "edit": "all", "delete": "no" } diff --git a/application/Espo/Resources/metadata/app/aclPortal.json b/application/Espo/Resources/metadata/app/aclPortal.json index 80100715f4..dc6c83c599 100644 --- a/application/Espo/Resources/metadata/app/aclPortal.json +++ b/application/Espo/Resources/metadata/app/aclPortal.json @@ -7,11 +7,7 @@ "delete": "no", "stream": "no" }, - "Team": { - "read": "no", - "edit": "no", - "delete": "no" - }, + "Team": false, "Note": { "read": "own", "edit": "own", @@ -25,11 +21,7 @@ "delete": "own" }, "PhoneNumber": false, - "EmailAccount": { - "read": "own", - "edit": "own", - "delete": "own" - }, + "EmailAccount": false, "Role": false, "PortalRole": false, "EmailFilter": false, @@ -57,23 +49,9 @@ }, "scopeLevelTypesDefaults": { "boolean": false, - "recordAllAccountOwnNo": { - "read": "no", - "edit": "no", - "delete": "no" - }, - "recordAllOwnNo": { - "read": "no", - "edit": "no", - "delete": "no" - }, - "recordAllAccountNo": { - "read": "no", - "edit": "no", - "delete": "no" - }, - "recordAllNo": { + "record": { "read": "no", + "stream": "no", "edit": "no", "delete": "no" } diff --git a/application/Espo/Resources/metadata/entityDefs/AuthToken.json b/application/Espo/Resources/metadata/entityDefs/AuthToken.json index 129444f2b0..6510ef6296 100644 --- a/application/Espo/Resources/metadata/entityDefs/AuthToken.json +++ b/application/Espo/Resources/metadata/entityDefs/AuthToken.json @@ -17,6 +17,9 @@ "user": { "type": "link" }, + "portal": { + "type": "link" + }, "ipAddress": { "type": "varchar", "maxLength": "36" @@ -37,6 +40,10 @@ "user": { "type": "belongsTo", "entity": "User" + }, + "portal": { + "type": "belongsTo", + "entity": "Portal" } }, "collection": { diff --git a/application/Espo/SelectManagers/Email.php b/application/Espo/SelectManagers/Email.php index d75b9125a6..03301de0e2 100644 --- a/application/Espo/SelectManagers/Email.php +++ b/application/Espo/SelectManagers/Email.php @@ -121,6 +121,11 @@ class Email extends \Espo\Core\SelectManagers\Base $this->boolFilterOnlyMy($result); } + protected function accessPortalOnlyOwn(&$result) + { + $this->boolFilterOnlyMy($result); + } + protected function accessOnlyTeam(&$result) { $this->setDistinct(true, $result); @@ -135,6 +140,55 @@ class Email extends \Espo\Core\SelectManagers\Base ); } + protected function accessPortalOnlyAccount(&$result) + { + $this->setDistinct(true, $result); + $this->addLeftJoin(['users', 'usersAccess'], $result); + + $d = array( + 'usersAccess.id' => $this->getUser()->id + ); + + $accountIdList = $this->getUser()->getLinkMultipleIdList('accounts'); + if (count($accountIdList)) { + $d['accountId'] = $accountIdList; + } + + $contactId = $this->getUser()->get('contactId'); + if ($contactId) { + $d[] = array( + 'parentId' => $contactId, + 'parentType' => 'Contact' + ); + } + + $result['whereClause'][] = array( + 'OR' => $d + ); + } + + protected function accessPortalOnlyContact(&$result) + { + $this->setDistinct(true, $result); + $this->addLeftJoin(['users', 'usersAccess'], $result); + + $d = array( + 'usersAccess.id' => $this->getUser()->id + ); + + $contactId = $this->getUser()->get('contactId'); + if ($contactId) { + $d[] = array( + 'parentId' => $contactId, + 'parentType' => 'Contact' + ); + } + + $result['whereClause'][] = array( + 'OR' => $d + ); + } + protected function textFilter($textFilter, &$result) { $d = array(); diff --git a/frontend/client/src/acl-portal/email.js b/frontend/client/src/acl-portal/email.js new file mode 100644 index 0000000000..e7190dbd3b --- /dev/null +++ b/frontend/client/src/acl-portal/email.js @@ -0,0 +1,65 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://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. + ************************************************************************/ + +Espo.define('acl-portal/email', 'acl-portal', function (Dep) { + + return Dep.extend({ + + checkModelRead: function (model, data, precise) { + var result = this.checkModel(model, data, 'read', precise); + + if (result) { + return true; + } + + if (data === false) { + return false; + } + + var d = data || {}; + if (d.read === 'no') { + return false; + } + + if (model.has('usersIds')) { + if (~(model.get('usersIds') || []).indexOf(this.getUser().id)) { + return true; + } + } else { + if (precise) { + return null; + } + } + + return result; + } + + }); + +}); + diff --git a/frontend/client/src/app-portal.js b/frontend/client/src/app-portal.js new file mode 100644 index 0000000000..a910f9bcd6 --- /dev/null +++ b/frontend/client/src/app-portal.js @@ -0,0 +1,36 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://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. + ************************************************************************/ + +Espo.define('app-portal', ['app'], function (Dep) { + + return Dep.extend({ + + }); + +}); + diff --git a/frontend/client/src/app.js b/frontend/client/src/app.js index 4ba5d2b4c9..db47b78f29 100644 --- a/frontend/client/src/app.js +++ b/frontend/client/src/app.js @@ -543,6 +543,8 @@ Espo.define( }, Backbone.Events); + App.extend = Backbone.Router.extend; + return App; }); diff --git a/frontend/client/src/views/portal-role/record/table.js b/frontend/client/src/views/portal-role/record/table.js index 054d268723..f76d2ffe54 100644 --- a/frontend/client/src/views/portal-role/record/table.js +++ b/frontend/client/src/views/portal-role/record/table.js @@ -36,6 +36,7 @@ Espo.define('views/portal-role/record/table', 'views/role/record/table', functio 'recordAllContactOwnNo': ['all', 'contact', 'own', 'no'], 'recordAllAccountNo': ['all', 'account', 'no'], 'recordAllContactNo': ['all', 'contact', 'no'], + 'recordAllAccountContactNo': ['all', 'account', 'contact', 'no'], 'recordAllOwnNo': ['all', 'own', 'no'], 'recordAllNo': ['all', 'no'], 'record': ['all', 'own', 'no'] diff --git a/frontend/html/portal.html b/frontend/html/portal.html new file mode 100644 index 0000000000..a7268f64f1 --- /dev/null +++ b/frontend/html/portal.html @@ -0,0 +1,54 @@ + + + + EspoCRM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/html/portal.html b/html/portal.html new file mode 100644 index 0000000000..e0db903d7f --- /dev/null +++ b/html/portal.html @@ -0,0 +1,34 @@ + + + + EspoCRM + + + + + + + + + + + + +
+ + +