<?php
namespace App\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Component\Security\Core\Security;
class ApiVoter extends Voter
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
protected function supports($attribute, $subject) : bool
{
if(in_array($attribute, ['API_PASS_THROUGH'])) {
return true;
}
return false;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) : bool
{
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface) {
return false;
}
switch ($attribute) {
case 'API_PASS_THROUGH':
if($this->_isCommandAllowed($subject)) {
return $this->_isUserAllowed($subject, $user);
}
else {
return false;
}
break;
}
return false;
}
private function _commandsAllowed()
{
$cmds = [];
$cmds[] = 'accountdt/'; // POST
$cmds[] = 'aliasdt/'; // POST
$cmds[] = 'account/check/'; // GET user/domain
$cmds[] = 'account/checkwithalias/'; // GET user/domain
$cmds[] = 'account/checkpasswordreq/'; // GET password
$cmds[] = 'account/get/'; // GET user/domain
$cmds[] = 'account/getinprogress/'; // GET user/domain
$cmds[] = 'account/getquota/'; // GET user/domain
$cmds[] = 'account/getroledomains/'; // GET user/domain
$cmds[] = 'account/list/'; // GET filter/filter_params
$cmds[] = 'account/isinprogress/'; // GET src/dest
$cmds[] = 'account/saveextparameters'; // POST
$cmds[] = 'account/getimapdircount/'; // GET user/domain/directory
$cmds[] = 'alias/get'; // GET src/dest
$cmds[] = 'alias/getinprogress/'; // GET src/dest
$cmds[] = 'alias/isinprogress/'; // GET src/dest
$cmds[] = 'domain/list'; // GET no params
$cmds[] = 'domain/getaliases'; // GET domain
$cmds[] = 'logs/dtprocess';
$cmds[] = 'logs/dtprocessmaster';
$cmds[] = 'charts/';
$cmds[] = 'login_msg/userdismissed';
return $cmds;
}
private function _isCommandAllowed($cmdData)
{
$cmd = $cmdData['command'];
foreach($this->_commandsAllowed() as $c) {
if(preg_match('/^' . str_replace('/', '\/', $c) . '/', $cmd)) {
return true;
}
}
return false;
}
private function _isUserAllowed($cmdData, $user)
{
$cmd = $cmdData['command'];
if(array_key_exists('data', $cmdData)) {
$data = $cmdData['data'];
}
else {
$data = null;
}
$cmdParams = $this->_splitCmdParams($cmd);
if($this->security->isGranted('ROLE_ADMIN')) {
if($this->_checkAdminPerms($cmdParams[0], $cmdParams[1], $data, $user)) {
return true;
}
// else - check permissions for postmaster and user
}
if($this->security->isGranted('ROLE_POSTMASTER')) {
return $this->_checkPostmasterPerms($cmdParams[0], $cmdParams[1], $data, $user);
}
else {
if($this->security->isGranted('ROLE_POSTMASTER_MIN')) {
return $this->_checkPostmasterMinPerms($cmdParams[0], $cmdParams[1], $data, $user);
}
else {
if($this->security->isGranted('ROLE_GROUPMASTER')) {
return $this->_checkGroupmasterPerms($cmdParams[0], $cmdParams[1], $data, $user);
}
else {
if($this->security->isGranted('ROLE_USERMASTER')) {
return $this->_checkUsermasterPerms($cmdParams[0], $cmdParams[1], $data, $user);
}
else {
if($this->security->isGranted('ROLE_USER')) {
return $this->_checkUserPerms($cmdParams[0], $cmdParams[1], $data, $user->getEmail());
}
}
}
}
}
return false;
}
// returns command itself in ret[0] and params in ret[1]
private function _splitCmdParams($cmd)
{
$exploded = explode('/', $cmd);
$ret = [];
$ret[0] = '';
$ret[1] = '';
for($i = 0; $i < sizeof($exploded); $i++) {
if($i < 2) {
$ret[0] = $ret[0] . '/' . $exploded[$i];
}
else {
$ret[1] = $ret[1] . '/' . $exploded[$i];
}
}
$ret[0] = ltrim($ret[0], '/');
$ret[1] = ltrim($ret[1], '/');
return $ret;
}
// checks if user is permitted to execute the command
private function _checkUserPerms($cmd, $params, $postData, $userEmail)
{
$userSlash = str_replace('@', '/', $userEmail);
if($cmd == 'account/checkpasswordreq') {
return true;
}
elseif($cmd == 'account/get') {
// get allowed only for its own account
if($userSlash == $params) {
return true;
}
else {
return false;
}
}
elseif($cmd == 'account/saveextparameters') {
if($postData['userName'] . '/' . $postData['userDomain'] == $userSlash) {
return true;
}
return false;
}
elseif($cmd == 'account/getimapdircount') {
// demove dirname from parameters
$paramsArr = explode('/', $params);
if(count($paramsArr) != 3) {
return false;
}
$userDomainFromCmd = $paramsArr[0] . '/' . $paramsArr[1];
if($userSlash == $userDomainFromCmd) {
return true;
}
else {
return false;
}
}
elseif($cmd == 'login_msg/userdismissed') {
// demove msg_id from parameters
$paramsArr = explode('/', $params);
if(count($paramsArr) != 3) {
return false;
}
$userDomainFromCmd = $paramsArr[0] . '/' . $paramsArr[1];
if($userSlash == $userDomainFromCmd) {
return true;
}
else {
return false;
}
}
return false;
}
// checks if postmaster is permitted to execute the command
private function _checkPostmasterPerms($cmd, $params, $postData, $user)
{
// check domain
switch($cmd) {
case 'account/check':
case 'account/checkwithalias':
case 'account/get':
case 'account/getinprogress':
case 'account/getquota':
case 'account/getroledomains':
case 'account/isinprogress':
return $this->_checkDomain($params, $user->getDomains());
case 'account/getimapdircount':
// in $params is also directory, give only user/domain
return $this->_checkDomain(implode('/', array_slice(explode('/', $params), 0, 2)), $user->getDomains());
case 'account/checkpasswordreq':
case 'domain/list':
return true;
case 'account/saveextparameters':
return $this->_checkDomain($postData['userName'] . '/' . $postData['userDomain'], $user->getDomains());
case 'alias/get':
case 'alias/getinprogress':
case 'alias/isinprogress':
$srcDest = explode('/', $params);
return $this->_checkDomain(str_replace('@', '/', $srcDest[0]), $user->getDomains());
case 'logs/dtprocess':
// only for current logged in user
// in $params can be string filter/JSON_OBJECT
$filterArr = explode('/', $params);
if(count($filterArr) < 2) {
return false;
}
$filter = json_decode(urldecode($filterArr[1]));
if(!is_null($filter) && isset($filter->user)) {
if($filter->user == $user->getEmail()) {
return true;
}
else {
return false;
}
}
return false;
case 'logs/dtprocessmaster':
return false;
case 'account/list':
case 'accountdt/list':
case 'aliasdt/list':
// domains may be in POST
if(array_key_exists('filter', $postData)) {
$filter = json_decode($postData['filter'], true);
if(array_key_exists('domains', $filter)) {
$domains = $filter['domains'];
}
else {
$domains = $this->_getDomainsFromFilter($params);
}
}
else {
$domains = $this->_getDomainsFromFilter($params);
}
if(count($domains) == 0) {
return false;
}
$userDomains = $user->getDomains();
foreach($domains as $d) {
if($this->_checkDomain('x/' . $d, $userDomains) == false) {
return false;
}
}
return true;
default:
return false;
}
return false;
}
// checks if postmaster min is permitted to execute the command
private function _checkPostmasterMinPerms($cmd, $params, $postData, $user)
{
// check domain
switch($cmd) {
case 'account/check':
case 'account/checkwithalias':
case 'account/get':
case 'account/getinprogress':
case 'account/getquota':
case 'account/getroledomains':
case 'account/isinprogress':
return $this->_checkDomain($params, $user->getDomains());
case 'account/getimapdircount':
// in $params is also directory, give only user/domain
return $this->_checkDomain(implode('/', array_slice(explode('/', $params), 0, 2)), $user->getDomains());
case 'account/checkpasswordreq':
case 'domain/list':
return true;
case 'alias/get':
case 'alias/getinprogress':
case 'alias/isinprogress':
$srcDest = explode('/', $params);
return $this->_checkDomain(str_replace('@', '/', $srcDest[0]), $user->getDomains());
case 'logs/dtprocess':
// only for current logged in user
// in $params can be string filter/JSON_OBJECT
$filterArr = explode('/', $params);
if(count($filterArr) < 2) {
return false;
}
$filter = json_decode(urldecode($filterArr[1]));
if(!is_null($filter) && isset($filter->user)) {
if($filter->user == $user->getEmail()) {
return true;
}
else {
return false;
}
}
return false;
case 'logs/dtprocessmaster':
return false;
case 'account/list':
case 'accountdt/list':
case 'aliasdt/list':
// domains may be in POST
if(array_key_exists('filter', $postData)) {
$filter = json_decode($postData['filter'], true);
if(array_key_exists('domains', $filter)) {
$domains = $filter['domains'];
}
else {
$domains = $this->_getDomainsFromFilter($params);
}
}
else {
$domains = $this->_getDomainsFromFilter($params);
}
if(count($domains) == 0) {
return false;
}
$userDomains = $user->getDomains();
foreach($domains as $d) {
if($this->_checkDomain('x/' . $d, $userDomains) == false) {
return false;
}
}
return true;
default:
return false;
}
return false;
}
// checks if groupmaster is permitted to execute the command
private function _checkGroupmasterPerms($cmd, $params, $postData, $user)
{
// check domain
switch($cmd) {
case 'account/check':
case 'account/checkwithalias':
case 'account/get':
case 'account/getinprogress':
case 'account/getquota':
case 'account/getroledomains':
case 'account/isinprogress':
return $this->_checkDomain($params, $user->getDomains()) && $this->_checkAccount($params, $user->getAccounts());
case 'account/getimapdircount':
// in $params is also directory, give only user/domain
$cmdUserDomain = implode('/', array_slice(explode('/', $params), 0, 2));
return $this->_checkDomain($cmdUserDomain, $user->getDomains()) && $this->_checkAccount($cmdUserDomain, $user->getAccounts());
case 'account/checkpasswordreq':
case 'domain/list':
return true;
case 'account/saveextparameters':
return $this->_checkDomain($postData['userName'] . '/' . $postData['userDomain'], $user->getDomains())
&& $this->_checkAccount($postData['userName'] . '/' . $postData['userDomain'], $user->getAccounts())
;
case 'alias/get':
case 'alias/getinprogress':
case 'alias/isinprogress':
$srcDest = explode('/', $params);
return $this->_checkDomain(str_replace('@', '/', $srcDest[1]), $user->getDomains())
&& $this->_checkAccount(str_replace('@', '/', $srcDest[1]), $user->getAccounts())
;
case 'logs/dtprocess':
case 'logs/dtprocessmaster':
return false;
case 'account/list':
case 'accountdt/list':
case 'aliasdt/list':
// don't allow without parameter "groupmaster"
if($this->_getGroupmasterFromFilter($params) == "") {
return false;
}
$domains = $this->_getDomainsFromFilter($params);
if(count($domains) == 0) {
return false;
}
$userDomains = $user->getDomains();
foreach($domains as $d) {
if($this->_checkDomain('x/' . $d, $userDomains) == false) {
return false;
}
}
return true;
default:
return false;
}
return false;
}
// checks if usermaster is permitted to execute the command
private function _checkUsermasterPerms($cmd, $params, $postData, $user)
{
// check domain
switch($cmd) {
case 'account/check':
case 'account/checkwithalias':
case 'account/get':
case 'account/getinprogress':
case 'account/getquota':
case 'account/getroledomains':
case 'account/isinprogress':
return $this->_checkDomain($params, $user->getDomains()) && $this->_checkAccount($params, $user->getAccounts());
/*case 'domain/list':
return true;*/
case 'account/list':
case 'accountdt/list':
// don't allow without parameter "groupmaster"
if($this->_getGroupmasterFromFilter($params) == "") {
return false;
}
$domains = $this->_getDomainsFromFilter($params);
if(count($domains) == 0) {
return false;
}
$userDomains = $user->getDomains();
foreach($domains as $d) {
if($this->_checkDomain('x/' . $d, $userDomains) == false) {
return false;
}
}
return true;
default:
return false;
}
return false;
}
// checks if admin is permitted to execute the command
// (commands specific to admins, if returns false check also postmaster permissions)
private function _checkAdminPerms($cmd, $params, $postData, $user)
{
// check command
switch($cmd) {
case 'accountdt/rolestableall':
case 'accountdt/rolestableselected':
case 'accountdt/rolestoselected':
case 'accountdt/rolesfromselected':
case 'accountdt/list':
case 'account/list':
case 'aliasdt/list':
case 'domain/getaliases':
case 'logs/dtprocess':
case 'logs/dtprocessmaster':
case 'charts/loadavg':
case 'charts/freemem':
case 'charts/usedmem':
case 'charts/diskstats':
case 'charts/mailqueue':
case 'charts/connections':
return true;
default:
return false;
}
return false;
}
// checks if domain from $userAndDomain matches any domain from $domains
private function _checkDomain($userAndDomain, $domains)
{
$arr = explode('/', $userAndDomain);
if(sizeof($arr) != 2) {
return false;
}
foreach($domains as $d) {
if($arr[1] == $d) {
return true;
}
}
return false;
}
// checks if $userAndDomain matches user from $accounts
private function _checkAccount($userAndDomain, $accounts)
{
$arr = explode('/', $userAndDomain);
if(sizeof($arr) != 2) {
return false;
}
$user = implode('@', $arr);
foreach($accounts as $a) {
if($user == $a) {
return true;
}
}
return false;
}
// returns array of domains got from $params
private function _getDomainsFromFilter($params) : array
{
if(substr_count($params, 'filter/') == 0) {
return [];
}
$paramsArr = explode('/', $params);
for($i = 0; $i < count($paramsArr); $i++) {
if($paramsArr[$i] == 'filter') {
$js = json_decode(urldecode($paramsArr[$i+1]));
if($js == null) {
return [];
}
if(!property_exists($js, "domains")) {
return [];
}
return $js->domains;
}
}
return [];
}
// returns groupmaster from $params (or empty string if none)
private function _getGroupmasterFromFilter($params) : string
{
if(substr_count($params, 'filter/') == 0) {
return "";
}
$paramsArr = explode('/', urldecode($params));
for($i = 0; $i < count($paramsArr); $i++) {
if($paramsArr[$i] == 'filter') {
$js = json_decode(urldecode($paramsArr[$i+1]));
if($js == null) {
return "";
}
if(!property_exists($js, "groupmaster")) {
return "";
}
return $js->groupmaster;
}
}
return "";
}
}