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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+