diff --git a/composer.json b/composer.json index 94e2c35..f180aa1 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "erusev/parsedown": "~1.4", "erusev/parsedown-extra": "dev-master", "jbroadway/urlify": "1.1.3", - "vlucas/valitron": "dev-master" + "vlucas/valitron": "dev-master", + "laminas/laminas-permissions-acl": "^2.7" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 4993e69..49a8761 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b2dd4b8a1c943c5e407add9f1b9104ea", + "content-hash": "87094a87b3a795ce73c299e4535358fb", "packages": [ { "name": "erusev/parsedown", @@ -159,6 +159,111 @@ ], "time": "2019-06-13T18:30:56+00:00" }, + { + "name": "laminas/laminas-permissions-acl", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-permissions-acl.git", + "reference": "624567fe376a70e0bfb5aa8217d5afa13b9d6e61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-permissions-acl/zipball/624567fe376a70e0bfb5aa8217d5afa13b9d6e61", + "reference": "624567fe376a70e0bfb5aa8217d5afa13b9d6e61", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-permissions-acl": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5" + }, + "suggest": { + "laminas/laminas-servicemanager": "To support Laminas\\Permissions\\Acl\\Assertion\\AssertionManager plugin manager usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Permissions\\Acl\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides a lightweight and flexible access control list (ACL) implementation for privileges management", + "homepage": "https://laminas.dev", + "keywords": [ + "acl", + "laminas" + ], + "time": "2019-12-31T17:37:23+00:00" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "fcd87520e4943d968557803919523772475e8ea3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/fcd87520e4943d968557803919523772475e8ea3", + "reference": "fcd87520e4943d968557803919523772475e8ea3", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev", + "dev-develop": "1.1.x-dev" + }, + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "time": "2020-05-20T16:45:56+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -686,16 +791,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.17.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" + "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", - "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", + "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", "shasum": "" }, "require": { @@ -708,6 +813,10 @@ "extra": { "branch-alias": { "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -740,7 +849,7 @@ "polyfill", "portable" ], - "time": "2020-05-12T16:14:59+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/yaml", diff --git a/media/live/hostinger-1.png b/media/live/hostinger-1.png new file mode 100644 index 0000000..8768d5c Binary files /dev/null and b/media/live/hostinger-1.png differ diff --git a/media/live/hostinger-2.png b/media/live/hostinger-2.png new file mode 100644 index 0000000..8768d5c Binary files /dev/null and b/media/live/hostinger-2.png differ diff --git a/media/live/hostinger.png b/media/live/hostinger.png new file mode 100644 index 0000000..8768d5c Binary files /dev/null and b/media/live/hostinger.png differ diff --git a/media/live/logo.png b/media/live/logo.png new file mode 100644 index 0000000..b5b44e2 Binary files /dev/null and b/media/live/logo.png differ diff --git a/media/original/hostinger-1.png b/media/original/hostinger-1.png new file mode 100644 index 0000000..8768d5c Binary files /dev/null and b/media/original/hostinger-1.png differ diff --git a/media/original/hostinger-2.png b/media/original/hostinger-2.png new file mode 100644 index 0000000..8768d5c Binary files /dev/null and b/media/original/hostinger-2.png differ diff --git a/media/original/hostinger.png b/media/original/hostinger.png new file mode 100644 index 0000000..8768d5c Binary files /dev/null and b/media/original/hostinger.png differ diff --git a/media/original/logo.png b/media/original/logo.png new file mode 100644 index 0000000..165e05e Binary files /dev/null and b/media/original/logo.png differ diff --git a/media/thumbs/hostinger-1.png b/media/thumbs/hostinger-1.png new file mode 100644 index 0000000..693c1a9 Binary files /dev/null and b/media/thumbs/hostinger-1.png differ diff --git a/media/thumbs/hostinger-2.png b/media/thumbs/hostinger-2.png new file mode 100644 index 0000000..693c1a9 Binary files /dev/null and b/media/thumbs/hostinger-2.png differ diff --git a/media/thumbs/hostinger.png b/media/thumbs/hostinger.png new file mode 100644 index 0000000..693c1a9 Binary files /dev/null and b/media/thumbs/hostinger.png differ diff --git a/media/thumbs/logo.png b/media/thumbs/logo.png new file mode 100644 index 0000000..8b3ae82 Binary files /dev/null and b/media/thumbs/logo.png differ diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index cf426aa..39376e4 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -231,11 +231,9 @@ class SettingsController extends Controller } /* add the users for navigation */ - $user = new User(); - $users = $user->getUsers(); $route = $request->getAttribute('route'); - return $this->render($response, 'settings/themes.twig', array('settings' => $userSettings, 'themes' => $themedata, 'users' => $users, 'route' => $route->getName() )); + return $this->render($response, 'settings/themes.twig', array('settings' => $userSettings, 'themes' => $themedata, 'route' => $route->getName() )); } public function showPlugins($request, $response, $args) @@ -300,11 +298,9 @@ class SettingsController extends Controller } } - $user = new User(); - $users = $user->getUsers(); $route = $request->getAttribute('route'); - return $this->render($response, 'settings/plugins.twig', array('settings' => $userSettings, 'plugins' => $plugins, 'users' => $users, 'route' => $route->getName() )); + return $this->render($response, 'settings/plugins.twig', array('settings' => $userSettings, 'plugins' => $plugins, 'route' => $route->getName() )); } /************************************* @@ -482,10 +478,13 @@ class SettingsController extends Controller } } - private function validateInput($objectType, $objectName, $userInput, $validate) + private function validateInput($objectType, $objectName, $userInput, $validate, $originalSettings = NULL) { - /* fetch the original settings from the folder (plugin or theme) to get the field definitions */ - $originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName); + if(!$originalSettings) + { + # fetch the original settings from the folder (plugin or theme) to get the field definitions + $originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName); + } # images get special treatment $imageFieldDefinitions = array(); @@ -557,7 +556,7 @@ class SettingsController extends Controller # initiate image processor with standard image sizes $processImages = new ProcessImage($userSettings['images']); - if(!$processImages->checkFolders()) + if(!$processImages->checkFolders('images')) { $this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.'); return false; @@ -591,10 +590,57 @@ class SettingsController extends Controller /*********************** ** USER MANAGEMENT ** ***********************/ + + public function showAccount($request, $response, $args) + { + $username = $_SESSION['user']; + + $validate = new Validation(); + + if($validate->username($username)) + { + # get settings + $settings = $this->c->get('settings'); + + # get user with userdata + $user = new User(); + $userdata = $user->getSecureUser($username); + + # instantiate field-builder + $fieldsModel = new Fields(); + + # get the field-definitions + $fieldDefinitions = $this->getUserFields($_SESSION['role']); + + # prepare userdata for field-builder + $userSettings['user'][$username] = $userdata; + + # generate the input form + $userform = $fieldsModel->getFields($userSettings, 'user', $username, $fieldDefinitions); + + $route = $request->getAttribute('route'); + + return $this->render($response, 'settings/user.twig', array( + 'settings' => $settings, + 'usersettings' => $userSettings, // needed for image url in form, will overwrite settings for field-template + 'userform' => $userform, // field model, needed to generate frontend-field + 'userdata' => $userdata, // needed to fill form with data +# 'userrole' => false, // not needed ? +# 'username' => $args['username'], // not needed ? + 'route' => $route->getName() // needed to set link active + )); + + return $this->render($response, 'settings/user.twig', array('settings' => $settings, 'usersettings' => $userSettings, 'userdata' => $userdata, 'userrole' => false, 'username' => $username, 'userform' => $userform, 'route' => $route->getName() )); + } + + $this->c->flash->addMessage('error', 'User does not exists'); + return $response->withRedirect($this->c->router->pathFor('home')); + } public function showUser($request, $response, $args) { - if($_SESSION['role'] == 'editor' && $_SESSION['user'] !== $args['username']) + # if user has no rights to watch userlist, then only show his user-entry + if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view') && $_SESSION['user'] !== $args['username'] ) { return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] )); } @@ -603,20 +649,77 @@ class SettingsController extends Controller if($validate->username($args['username'])) { - $user = new User(); - $users = $user->getUsers(); - $userrole = $user->getUserroles(); - $userdata = $user->getUser($args['username']); + # get settings $settings = $this->c->get('settings'); + + # get user with userdata + $user = new User(); + $userdata = $user->getSecureUser($args['username']); + + $username = $userdata['username']; - if($userdata) - { - return $this->render($response, 'settings/user.twig', array('settings' => $settings, 'users' => $users, 'userdata' => $userdata, 'userrole' => $userrole, 'username' => $args['username'] )); - } + # instantiate field-builder + $fieldsModel = new Fields(); + + # get the field-definitions + $fieldDefinitions = $this->getUserFields($userdata['userrole']); + + # prepare userdata for field-builder + $userSettings['user'][$username] = $userdata; + + # generate the input form + $userform = $fieldsModel->getFields($userSettings, 'user', $username, $fieldDefinitions); + + $route = $request->getAttribute('route'); + + return $this->render($response, 'settings/user.twig', array( + 'settings' => $settings, + 'usersettings' => $userSettings, // needed for image url in form, will overwrite settings for field-template + 'userform' => $userform, // field model, needed to generate frontend-field + 'userdata' => $userdata, // needed to fill form with data +# 'userrole' => false, // not needed ? +# 'username' => $args['username'], // not needed ? + 'route' => $route->getName() // needed to set link active + )); } $this->c->flash->addMessage('error', 'User does not exists'); - return $response->withRedirect($this->c->router->pathFor('user.list')); + return $response->withRedirect($this->c->router->pathFor('user.account')); + } + + private function getUserFields($role) + { + $fields = []; + $fields['username'] = ['label' => 'Username (read only)', 'type' => 'text', 'readonly' => true]; + $fields['image'] = ['label' => 'Profile-Image', 'type' => 'image']; + $fields['description'] = ['label' => 'Author-Description (Markdown)', 'type' => 'textarea']; + $fields['firstname'] = ['label' => 'First Name', 'type' => 'text']; + $fields['lastname'] = ['label' => 'Last Name', 'type' => 'text']; + $fields['email'] = ['label' => 'E-Mail', 'type' => 'text', 'required' => true]; + $fields['userrole'] = ['label' => 'Role', 'type' => 'text', 'readonly' => true]; + $fields['password'] = ['label' => 'Actual Password', 'type' => 'password']; + $fields['newpassword'] = ['label' => 'New Password', 'type' => 'password']; + + # dispatch fields; + + # change admin stuff + if($_SESSION['role'] == 'administrator') + { + $userroles = $this->c->acl->getRoles(); + $options = []; + + # we need associative array to make select-field with key/value work + foreach($userroles as $userrole) + { + $options[$userrole] = $userrole; + } + + $fields['userrole'] = ['label' => 'Role', 'type' => 'select', 'options' => $options]; + } + + $userform = []; + $userform['forms']['fields'] = $fields; + return $userform; } public function listUser($request, $response) @@ -632,7 +735,7 @@ class SettingsController extends Controller $userdata[] = $user->getUser($username); } - return $this->render($response, 'settings/userlist.twig', array('settings' => $settings, 'users' => $users, 'userdata' => $userdata, 'route' => $route->getName() )); + return $this->render($response, 'settings/userlist.twig', array('settings' => $settings, 'users' => $users, 'userdata' => $userdata, 'route' => $route->getName() )); } public function newUser($request, $response, $args) @@ -663,12 +766,18 @@ class SettingsController extends Controller $params = $request->getParams(); $user = new User(); - $userroles = $user->getUserroles(); $validate = new Validation(); + $userroles = $this->c->acl->getRoles(); if($validate->newUser($params, $userroles)) { - $userdata = array('username' => $params['username'], 'firstname' => $params['firstname'], 'lastname' => $params['lastname'], 'email' => $params['email'], 'userrole' => $params['userrole'], 'password' => $params['password']); + $userdata = array( + 'username' => $params['username'], + 'firstname' => $params['firstname'], + 'lastname' => $params['lastname'], + 'email' => $params['email'], + 'userrole' => $params['userrole'], + 'password' => $params['password']); $user->createUser($userdata); @@ -683,6 +792,7 @@ class SettingsController extends Controller public function updateUser($request, $response, $args) { + if($request->isPost()) { $referer = $request->getHeader('HTTP_REFERER'); @@ -697,44 +807,73 @@ class SettingsController extends Controller } $params = $request->getParams(); + $userdata = $params['user']; $user = new User(); - $userroles = $user->getUserroles(); $validate = new Validation(); - - /* non admins have different update rights */ - if($_SESSION['role'] !== 'administrator') + $userroles = $this->c->acl->getRoles(); + + print_r($params); + die(); + + # check if user is allowed to view (edit) userlist and other users + if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view')) { - /* if an editor tries to update other userdata than its own */ - if($_SESSION['user'] !== $params['username']) + # if an editor tries to update other userdata than its own */ + if($_SESSION['user'] !== $userdata['username']) { - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] )); + return $response->withRedirect($this->c->router->pathFor('user.account')); } - /* non admins cannot change his userrole */ - $params['userrole'] = $_SESSION['role']; + # non admins cannot change his userrole, so set it to session-value + $userdata['userrole'] = $_SESSION['role']; } - - if($validate->existingUser($params, $userroles)) + + # validate standard fields for users + if($validate->existingUser($userdata, $userroles)) { - $userdata = array('username' => $params['username'], 'firstname' => $params['firstname'], 'lastname' => $params['lastname'], 'email' => $params['email'], 'userrole' => $params['userrole']); - - if(empty($params['password']) AND empty($params['newpassword'])) + # validate custom input fields and return images + $userfields = $this->getUserFields($userdata['userrole']); + $imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields); + + if(!isset($_SESSION['errors']) && !empty($imageFields)) { - $user->updateUser($userdata); - $this->c->flash->addMessage('info', 'Saved all changes'); - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']])); + $images = $request->getUploadedFiles(); + + if(isset($images['user'])) + { + # set image size + $settings = $this->c->get('settings'); + $settings['images']['live'] = ['width' => 500, 'height' => 500]; + $userdata = $this->saveImages($imageFields, $userdata, $settings, $images['user']); + } } - elseif($validate->newPassword($params)) + + # check for errors and redirect to path, if errors found */ + if(isset($_SESSION['errors'])) + { + $this->c->flash->addMessage('error', 'Please correct the errors'); + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); + } + + if(empty($userdata['password']) AND empty($userdata['newpassword'])) { - $userdata['password'] = $params['newpassword']; $user->updateUser($userdata); $this->c->flash->addMessage('info', 'Saved all changes'); - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']])); + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); + } + elseif($validate->newPassword($userdata)) + { + $userdata['password'] = $userdata['newpassword']; + unset($userdata['newpassword']); + + $user->updateUser($userdata); + $this->c->flash->addMessage('info', 'Saved all changes'); + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); } } $this->c->flash->addMessage('error', 'Please correct your input'); - return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']])); + return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $userdata['username']])); } } diff --git a/system/Events/OnResourcesLoaded.php b/system/Events/OnResourcesLoaded.php new file mode 100644 index 0000000..339a358 --- /dev/null +++ b/system/Events/OnResourcesLoaded.php @@ -0,0 +1,14 @@ +router = $router; + $this->acl = $acl; + $this->resource = $resource; + $this->privilege = $privilege; + } + + public function __invoke(Request $request, Response $response, $next) + { + if(!isset($_SESSION['login'])) + { + return $response->withRedirect($this->router->pathFor('auth.show')); + } + + if(!$this->acl->isAllowed($_SESSION['role'], $this->resource, $this->privilege )) + { + # redirect to frontend startpage + # alternatively return an error and show an error page. + return $response->withRedirect($this->router->pathFor('home')); + } + + return $next($request, $response); + } +} \ No newline at end of file diff --git a/system/Models/Fields.php b/system/Models/Fields.php index c38a8db..828f0ff 100644 --- a/system/Models/Fields.php +++ b/system/Models/Fields.php @@ -78,7 +78,7 @@ class Fields } } elseif($field->getType() == "checkbox") - { + { # checkboxes need a special treatment, because field does not exist in settings if unchecked by user if(isset($userSettings[$objectType][$objectName][$fieldName])) { diff --git a/system/Models/User.php b/system/Models/User.php index 66be810..6241154 100644 --- a/system/Models/User.php +++ b/system/Models/User.php @@ -29,6 +29,13 @@ class User extends WriteYaml $user = $this->getYaml('settings/users', $username . '.yaml'); return $user; } + + public function getSecureUser($username) + { + $user = $this->getYaml('settings/users', $username . '.yaml'); + unset($user['password']); + return $user; + } public function createUser($params) { @@ -91,11 +98,13 @@ class User extends WriteYaml } } + /* replaced by ACL public function getUserroles() { return array('administrator', 'editor'); } - + */ + public function login($username) { $user = $this->getUser($username); diff --git a/system/Routes/Web.php b/system/Routes/Web.php index e3d227b..713e7ae 100644 --- a/system/Routes/Web.php +++ b/system/Routes/Web.php @@ -9,6 +9,7 @@ use Typemill\Controllers\ContentBackendController; use Typemill\Middleware\RedirectIfUnauthenticated; use Typemill\Middleware\RedirectIfAuthenticated; use Typemill\Middleware\RedirectIfNoAdmin; +use Typemill\Middleware\accessController; if($settings['settings']['setup']) { @@ -35,37 +36,40 @@ $app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->a $app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings'])); $app->get('/tm/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); -$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); -$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); -$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); -$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); -$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); -$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); -$app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new accessController($container['router'], $container['acl'], 'settings', 'view')); +$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new accessController($container['router'], $container['acl'], 'settings', 'update')); +$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new accessController($container['router'], $container['acl'], 'themes', 'view')); +$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new accessController($container['router'], $container['acl'], 'themes', 'update')); -$app->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new RedirectIfNoAdmin($container['router'], $container['flash'])); +$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new accessController($container['router'], $container['acl'], 'plugins', 'view')); +$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new accessController($container['router'], $container['acl'], 'plugins', 'update')); +$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new accessController($container['router'], $container['acl'], 'users', 'create')); +$app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new accessController($container['router'], $container['acl'], 'user', 'create')); +$app->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new accessController($container['router'], $container['acl'], 'user', 'update')); +$app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new accessController($container['router'], $container['acl'], 'user', 'delete')); +$app->get('/tm/user/account', SettingsController::class . ':showAccount')->setName('user.account')->add(new accessController($container['router'], $container['acl'], 'user', 'view')); +$app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new accessController($container['router'], $container['acl'], 'user', 'view')); +$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new accessController($container['router'], $container['acl'], 'userlist', 'view')); -$app->get('/tm/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); -$app->get('/tm/content[/{params:.*}]', ContentBackendController::class . ':showEmpty')->setName('content.empty')->add(new RedirectIfUnauthenticated($container['router'], $container['flash'])); +$app->get('/tm/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new accessController($container['router'], $container['acl'], 'content', 'view')); +$app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new accessController($container['router'], $container['acl'], 'content', 'view')); +$app->get('/tm/content[/{params:.*}]', ContentBackendController::class . ':showEmpty')->setName('content.empty')->add(new accessController($container['router'], $container['acl'], 'content', 'view')); foreach($routes as $pluginRoute) { - $method = $pluginRoute['httpMethod']; - $route = $pluginRoute['route']; - $class = $pluginRoute['class']; + $method = $pluginRoute['httpMethod']; + $route = $pluginRoute['route']; + $class = $pluginRoute['class']; + $resource = isset($pluginRoute['resource']) ? $pluginRoute['resource'] : NULL; + $privilege = isset($pluginRoute['privilege']) ? $pluginRoute['privilege'] : NULL; if(isset($pluginRoute['name'])) { - $app->{$method}($route, $class)->setName($pluginRoute['name']); + $app->{$method}($route, $class)->setName($pluginRoute['name'])->add(new accessController($container['router'], $container['acl'], $resource, $privilege)); } else { - $app->{$method}($route, $class); + $app->{$method}($route, $class)->add(new accessController($container['router'], $container['acl'], $resource, $privilege)); } } diff --git a/system/Settings.php b/system/Settings.php index 95ead28..7e3bc78 100644 --- a/system/Settings.php +++ b/system/Settings.php @@ -2,6 +2,10 @@ namespace Typemill; +use Laminas\Permissions\Acl\Acl; +use Laminas\Permissions\Acl\Role\GenericRole as Role; +use Laminas\Permissions\Acl\Resource\GenericResource as Resource; + class Settings { public static function loadSettings() @@ -182,4 +186,61 @@ class Settings $yaml->updateYaml('settings', 'settings.yaml', $settings); } } -} + + public static function loadResources() + { + return ['content', + 'user', + 'userlist', + 'settings', + 'themes', + 'plugins']; + } + + public static function loadRolesAndPermissions() + { + $guest['name'] = 'guest'; + $guest['inherits'] = NULL; + $guest['permissions'] = []; + + $member['name'] = 'member'; + $member['inherits'] = 'guest'; + $member['permissions'] = ['user' => ['view','update','delete']]; + + $author['name'] = 'author'; + $author['inherits'] = 'member'; + $author['permissions'] = ['content' => ['view','create', 'update', 'delete']]; + + $editor['name'] = 'editor'; + $editor['inherits'] = 'author'; + $editor['permissions'] = ['content' => ['publish', 'depublish']]; + + return [$guest, $member, $author, $editor]; + } + + public static function createAcl($roles, $resources) + { + $acl = new Acl(); + + foreach($resources as $resource) + { + $acl->addResource(new Resource($resource)); + } + + # add administrator role + $acl->addRole(new Role('administrator')); + $acl->allow('administrator'); + + # add all other roles dynamically + foreach($roles as $role) + { + $acl->addRole(new Role($role['name']), $role['inherits']); + foreach($role['permissions'] as $resource => $permissions) + { + $acl->allow($role['name'], $resource, $permissions); + } + } + + return $acl; + } +} \ No newline at end of file diff --git a/system/author/css/style.css b/system/author/css/style.css index 5e37db2..8101f69 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -67,9 +67,12 @@ a, a:link, a:visited, a:focus, a:hover, a:active, .link, button, .button, .tab-b .hover-bg-tm-green:hover{ background:#66b0a3; } -.hover-b--tm-green:hover{ +.hover-b--tm-green:hover,.hover-b--tm-green.active{ border-color:#66b0a3; } +.hover-bg-white.active{ + background-color: #fff; +} .w-100{ width:100%!important; } diff --git a/system/author/layouts/layout.twig b/system/author/layouts/layout.twig index 1eb8101..05d56dc 100644 --- a/system/author/layouts/layout.twig +++ b/system/author/layouts/layout.twig @@ -41,6 +41,22 @@ power-off + + + + + + + + + + + + + + + + {{ assets.renderSvg() }} diff --git a/system/author/partials/aside.twig b/system/author/partials/aside.twig index a82d368..43e50f8 100644 --- a/system/author/partials/aside.twig +++ b/system/author/partials/aside.twig @@ -1,19 +1,12 @@ \ No newline at end of file diff --git a/system/author/partials/fields.twig b/system/author/partials/fields.twig index 121eb2e..6238d3e 100644 --- a/system/author/partials/fields.twig +++ b/system/author/partials/fields.twig @@ -1,5 +1,5 @@ -
- +
+
- {% endblock %} diff --git a/system/system.php b/system/system.php index ed5b424..77e9f04 100644 --- a/system/system.php +++ b/system/system.php @@ -3,6 +3,8 @@ use Typemill\Events\OnSettingsLoaded; use Typemill\Events\OnPluginsLoaded; use Typemill\Events\OnSessionSegmentsLoaded; +use Typemill\Events\OnRolesPermissionsLoaded; +use Typemill\Events\OnResourcesLoaded; /**************************** * HIDE ERRORS BY DEFAULT * @@ -109,6 +111,32 @@ $dispatcher->dispatch('onPluginsLoaded', new OnPluginsLoaded($pluginNames)); # dispatch settings event and get all setting-updates from plugins $dispatcher->dispatch('onSettingsLoaded', new OnSettingsLoaded($settings))->getData(); + +/********************************** +* LOAD ROLES AND PERMISSIONS * +**********************************/ + +# load roles and permissions +$rolesAndPermissions = Typemill\Settings::loadRolesAndPermissions(); + +# dispatch roles so plugins can enhance them +$rolesAndPermissions = $dispatcher->dispatch('onRolesPermissionsLoaded', new OnRolesPermissionsLoaded($rolesAndPermissions))->getData(); + +# load resources +$resources = Typemill\Settings::loadResources(); + +# dispatch roles so plugins can enhance them +$resources = $dispatcher->dispatch('onResourcesLoaded', new OnResourcesLoaded($resources))->getData(); + +# create acl-object +$acl = Typemill\Settings::createAcl($rolesAndPermissions, $resources); + +# add acl to container +$container['acl'] = function($c) use ($acl) +{ + return $acl; +}; + /****************************** * ADD DISPATCHER TO CONTAINER * ******************************/