diff --git a/agents/ToplevelAgent.inc b/agents/ToplevelAgent.inc index c545975a..eee33450 100644 --- a/agents/ToplevelAgent.inc +++ b/agents/ToplevelAgent.inc @@ -265,6 +265,7 @@ /** * Run the Controller of this Agent and its SubAgents. * + * @throws AccessDeniedException * @throws IdNotFoundException * @throws ServiceUnavailableException * @throws DatamodelException @@ -292,6 +293,7 @@ /** * Run IntermediateAgent. * + * @throws AccessDeniedException * @throws ParamsNotValidException * @throws IdNotFoundException * @throws ServiceUnavailableException diff --git a/app/Controller.inc b/app/Controller.inc index 4403407b..24301a88 100644 --- a/app/Controller.inc +++ b/app/Controller.inc @@ -19,12 +19,30 @@ */ abstract class Controller extends \nre\core\Controller { + /** + * Required components + * + * @var array + */ + public $components = array('auth'); + /** + * Required models + * + * @var array + */ + public $models = array('users'); /** * Linker instance * * @var Linker */ protected $linker = null; + /** + * Data of currently logged in user if any + * + * @var array + */ + protected static $user = null; @@ -58,13 +76,19 @@ { parent::preFilter($request, $response); + // Check rights + $this->checkPermission(); + // Create linker $this->linker = new \nre\core\Linker($this->request); + + // Set userdata + $this->set('loggedUser', static::$user); } /** - * Prefilter that is executed after running the Controller. + * Postfilter that is executed after running the Controller. * * @param Request $request Current request * @param Response $response Current response @@ -72,9 +96,44 @@ public function postFilter(\nre\core\Request $request, \nre\core\Response $response) { parent::postFilter($request, $response); + } + + + + + /** + * Check user permissions. + * + * @throws AccessDeniedException + */ + private function checkPermission() + { + // Determine user + try { + $userId = $this->Auth->getUserId(); + if(!is_null($userId)) { + static::$user = $this->Users->getUserById($this->Auth->getUserId()); + } + } + catch(\nre\exceptions\IdNotFoundException $e) { + } - // Set title - $this->set('title', $this->request->getParam(1, 'intermediate')); + + // Determine permissions + $action = $this->request->getParam(2, 'action'); + if(!property_exists($this, 'permissions')) { + return; // Allow if nothing is specified + } + if(!array_key_exists($action, $this->permissions)) { + return; // Allow if Action is not specified + } + $permissions = $this->permissions[$action]; + + + // Check permissions + if(is_null(static::$user)) { + throw new \nre\exceptions\AccessDeniedException(); + } } } diff --git a/configs/AppConfig.inc b/configs/AppConfig.inc index 3467c3be..c4ef5929 100644 --- a/configs/AppConfig.inc +++ b/configs/AppConfig.inc @@ -69,9 +69,10 @@ * @var array */ public static $routes = array( - array('css/?(.*)', 'css/$1?layout=stylesheet', false), - array('users/(.+)', 'users/user/$1', false), - array('seminaries/(.+)', 'seminaries/seminary/$1', false) + array('css/?(.*)', 'css/$1?layout=stylesheet', false), + array('users/([^/]+)/(edit|delete)', 'users/$2/$1', true), + array('users/(?!(index|login|logout|create|edit|delete))', 'users/user/$1', true), + array('seminaries/(.+)', 'seminaries/seminary/$1', false) ); @@ -82,7 +83,8 @@ * @var array */ public static $reverseRoutes = array( - array('users/user/(.*)', 'users/$1', false), + array('users/user/(.*)', 'users/$1', true), + array('users/([^/]+)/(.*)', 'users/$2/$1', true), array('seminaries/seminary/(.*)', 'seminaries/$1', false) ); diff --git a/controllers/HtmlController.inc b/controllers/HtmlController.inc index 3c33d732..a2659898 100644 --- a/controllers/HtmlController.inc +++ b/controllers/HtmlController.inc @@ -17,7 +17,7 @@ * * @author Oliver Hanraths */ - class HtmlController extends \nre\core\Controller + class HtmlController extends \hhu\z\Controller { @@ -36,9 +36,6 @@ // Set content-type $this->response->addHeader("Content-type: text/html; charset=utf-8"); - - // Start session - session_start(); } @@ -51,6 +48,9 @@ { // Set the name of the current IntermediateAgent as page title $this->set('title', $this->request->getParam(1, 'intermediate')); + + // Set userdata + $this->set('loggedUser', static::$user); } } diff --git a/controllers/SeminariesController.inc b/controllers/SeminariesController.inc index 4f026f41..4a9581bf 100644 --- a/controllers/SeminariesController.inc +++ b/controllers/SeminariesController.inc @@ -25,6 +25,15 @@ * @var array */ public $models = array('seminaries', 'users'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array(), + 'seminary' => array() + ); diff --git a/controllers/UsersController.inc b/controllers/UsersController.inc index 0a3ce645..aea5ffbb 100644 --- a/controllers/UsersController.inc +++ b/controllers/UsersController.inc @@ -19,6 +19,19 @@ */ class UsersController extends \hhu\z\Controller { + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array(), + 'user' => array(), + 'create' => array(), + 'edit' => array(), + 'delete' => array() + ); + @@ -56,6 +69,153 @@ } + /** + * Action: login. + * + * Log in a user. + */ + public function login() + { + $username = ''; + + // Log the user in + if($this->request->getRequestMethod() == 'POST' && !empty($this->request->getPostParam('login'))) + { + $username = $this->request->getPostParam('username'); + $userId = $this->Users->login( + $username, + $this->request->getPostParam('password') + ); + + if(!is_null($userId)) + { + $this->Auth->setUserId($userId); + $user = $this->Users->getUserById($userId); + + $this->redirect($this->linker->link(array($user['url']), 1)); + } + } + + + // Pass data to view + $this->set('username', $username); + $this->set('failed', ($this->request->getRequestMethod() == 'POST')); + } + + + /** + * Action: logout. + * + * Log out a user. + */ + public function logout() + { + // Unset the currently logged in user + $this->Auth->setUserId(null); + + // Redirect + $this->redirect($this->linker->link(array())); + } + + + /** + * Action: create. + * + * Create a new user. + */ + public function create() + { + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('create'))) + { + // Create new user + $userId = $this->Users->createUser( + $this->request->getPostParam('username'), + $this->request->getPostParam('email'), + $this->request->getPostParam('password') + ); + + // Redirect to user + $user = $this->Users->getUserById($userId); + $this->redirect($this->linker->link(array($user['url']), 1)); + } + } + + + /** + * Action: edit. + * + * Edit a user. + * + * @throws IdNotFoundException + * @param string $userUrl URL-Username of an user + */ + public function edit($userUrl) + { + // User + $user = $this->Users->getUserByUrl($userUrl); + + // Check request method + if($this->request->getRequestMethod() == 'POST') + { + // Save changes + if(!empty($this->request->getPostParam('save'))) + { + // Edit user + $this->Users->editUser( + $user['id'], + $this->request->getPostParam('username'), + $this->request->getPostParam('email'), + $this->request->getPostParam('password') + ); + } + + + // Redirect to user + $this->redirect($this->linker->link(array($user['url']), 1)); + } + + + // Pass data to view + $this->set('user', $user); + } + + + /** + * Action: delete. + * + * Delete a user. + * + * @throws IdNotFoundException + * @param string $userUrl URL-Username of an user + */ + public function delete($userUrl) + { + // User + $user = $this->Users->getUserByUrl($userUrl); + + // Check request method + if($this->request->getRequestMethod() == 'POST') + { + // Check confirmation + if($this->request->getPostParam('delete') == 'delete') + { + // Delete user + $this->Users->deleteUser($user['id']); + + // Redirect to overview + $this->redirect($this->linker->link(null, 1)); + } + + // Redirect to entry + $this->redirect($this->linker->link(array('user', $user['url']), 1)); + } + + + // Show confirmation + $this->set('user', $user); + } + + } ?> diff --git a/locale/de_DE/LC_MESSAGES/The Legend of Z.mo b/locale/de_DE/LC_MESSAGES/The Legend of Z.mo index ec51389a..a8dd7d2c 100644 Binary files a/locale/de_DE/LC_MESSAGES/The Legend of Z.mo and b/locale/de_DE/LC_MESSAGES/The Legend of Z.mo differ diff --git a/locale/de_DE/LC_MESSAGES/The Legend of Z.po b/locale/de_DE/LC_MESSAGES/The Legend of Z.po index e216dbb5..0f2a598c 100644 --- a/locale/de_DE/LC_MESSAGES/The Legend of Z.po +++ b/locale/de_DE/LC_MESSAGES/The Legend of Z.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: The Legend of Z\n" -"POT-Creation-Date: 2014-01-19 22:56+0100\n" -"PO-Revision-Date: 2014-01-19 22:58+0100\n" +"POT-Creation-Date: 2014-01-22 16:28+0100\n" +"PO-Revision-Date: 2014-01-22 16:29+0100\n" "Last-Translator: \n" "Language-Team: \n" "Language: de_DE\n" @@ -15,8 +15,10 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SearchPath-0: ../../../views\n" -#: ../../../views/html/menu/index.tpl:2 ../../../views/html/users/user.tpl:1 -#: ../../../views/html/users/index.tpl:1 +#: ../../../views/html/menu/index.tpl:2 ../../../views/html/users/login.tpl:1 +#: ../../../views/html/users/user.tpl:1 ../../../views/html/users/index.tpl:1 +#: ../../../views/html/users/edit.tpl:1 ../../../views/html/users/delete.tpl:1 +#: ../../../views/html/users/create.tpl:1 msgid "Users" msgstr "Benutzer" @@ -26,10 +28,73 @@ msgstr "Benutzer" msgid "Seminaries" msgstr "Kurse" -#: ../../../views/html/users/user.tpl:4 ../../../views/html/users/index.tpl:7 +#: ../../../views/html/menu/index.tpl:5 ../../../views/html/users/login.tpl:2 +#: ../../../views/html/users/login.tpl:11 +msgid "Login" +msgstr "Login" + +#: ../../../views/html/menu/index.tpl:7 +msgid "Logout" +msgstr "Logout" + +#: ../../../views/html/users/login.tpl:6 ../../../views/html/users/login.tpl:7 +#: ../../../views/html/users/edit.tpl:6 ../../../views/html/users/edit.tpl:7 +#: ../../../views/html/users/create.tpl:6 +#: ../../../views/html/users/create.tpl:7 +msgid "Username" +msgstr "Benutzername" + +#: ../../../views/html/users/login.tpl:8 ../../../views/html/users/login.tpl:9 +#: ../../../views/html/users/edit.tpl:10 ../../../views/html/users/edit.tpl:11 +#: ../../../views/html/users/create.tpl:10 +#: ../../../views/html/users/create.tpl:11 +msgid "Password" +msgstr "Passwort" + +#: ../../../views/html/users/user.tpl:4 ../../../views/html/users/delete.tpl:2 +msgid "Delete user" +msgstr "Benutzer löschen" + +#: ../../../views/html/users/user.tpl:7 ../../../views/html/users/index.tpl:10 msgid "registered on" msgstr "registriert seit" +#: ../../../views/html/users/index.tpl:3 +msgid "Create new user" +msgstr "Neuen Benutzer erstellen" + +#: ../../../views/html/users/edit.tpl:2 +msgid "Edit user" +msgstr "Benutzer bearbeiten" + +#: ../../../views/html/users/edit.tpl:8 ../../../views/html/users/edit.tpl:9 +#: ../../../views/html/users/create.tpl:8 +#: ../../../views/html/users/create.tpl:9 +msgid "E‑Mail-Address" +msgstr "E‑Mail-Adresse" + +#: ../../../views/html/users/edit.tpl:13 +#: ../../../views/html/users/create.tpl:13 +msgid "create" +msgstr "erstellen" + +#: ../../../views/html/users/delete.tpl:4 +#, php-format +msgid "Should the user “%s” (%s) really be deleted?" +msgstr "Soll der Benutzer „%s“ (%s) wirklich gelöscht werden?" + +#: ../../../views/html/users/delete.tpl:6 +msgid "delete" +msgstr "löschen" + +#: ../../../views/html/users/delete.tpl:7 +msgid "cancel" +msgstr "abbrechen" + +#: ../../../views/html/users/create.tpl:2 +msgid "New user" +msgstr "Neuer Benutzer" + #: ../../../views/html/error/index.tpl:1 msgid "Error" msgstr "Fehler" diff --git a/models/UsersModel.inc b/models/UsersModel.inc index 2a9bb6a3..576bc1a2 100644 --- a/models/UsersModel.inc +++ b/models/UsersModel.inc @@ -100,6 +100,138 @@ return $user[0]; } + + /** + * Log a user in if its credentials are valid. + * + * @throws DatamodelException + * @param string $username The name of the user to log in + * @param string $password Plaintext password of the user to log in + */ + public function login($username, $password) + { + $data = $this->db->query('SELECT id, password FROM users WHERE username = ?', 's', $username); + if(!empty($data)) + { + $data = $data[0]; + if($this->verify($password, $data['password'])) { + return $data['id']; + } + } + + + return null; + } + + + /** + * Create a new user. + * + * @param string $username Username of the user to create + * @param string $email E‑Mail-Address of the user to create + * @param string $password Password of the user to create + * @return int ID of the newly created user + */ + public function createUser($username, $email, $password) + { + $this->db->query( + 'INSERT INTO users '. + '(username, url, email, password) '. + 'VALUES '. + '(?, ?, ?, ?)', + 'ssss', + $username, + \nre\core\Linker::createLinkParam($username), + $email, + $this->hash($password) + ); + + + return $this->db->getInsertId(); + } + + + /** + * Edit a user. + * + * @throws DatamodelException + * @param string $username New name of user + * @param string $email Changed e‑mail-address of user + * @param string $password Changed plaintext password of user + */ + public function editUser($userId, $username, $email, $password) + { + try { + // Update user data + $this->db->query( + 'UPDATE users '. + 'SET username = ?, email = ? '. + 'WHERE id = ?', + 'ssi', + $sername, $email, + $userId + ); + + // Set new password + if(!empty($password)) + { + $this->db->query( + 'UPDATE users '. + 'SET password = ? '. + 'WHERE id = ?', + 'si', + $this->hash($password), + $userId + ); + } + } + catch(Exception $e) { + $this->db->rollback(); + throw $e; + } + finally { + $this->db->setAutocommit(true); + } + } + + + /** + * Delete a user. + * + * @param int $userId ID of the user to delete + */ + public function deleteUser($userId) + { + $this->db->query('DELETE FROM users WHERE id = ?', 'i', $userId); + } + + + + + /** + * Hash a password. + * + * @param string $password Plaintext password + * @return string Hashed password + */ + private function hash($password) + { + return password_hash($password, PASSWORD_DEFAULT); + } + + + /** + * Verify a password. + * + * @param string $password Plaintext password to verify + * @param string $hash Hashed password to match with + * @return boolean Verified + */ + private function verify($password, $hash) + { + return password_verify($password, $hash); + } + } ?> diff --git a/views/html/html.tpl b/views/html/html.tpl index 0f46493c..a9f5c842 100644 --- a/views/html/html.tpl +++ b/views/html/html.tpl @@ -14,6 +14,11 @@ + +
+ +
+
diff --git a/views/html/menu/index.tpl b/views/html/menu/index.tpl index ad0fbe39..f9f8246c 100644 --- a/views/html/menu/index.tpl +++ b/views/html/menu/index.tpl @@ -1,4 +1,9 @@
  • ">
  • ">
  • + +
  • + +
  • +
    diff --git a/views/html/users/create.tpl b/views/html/users/create.tpl new file mode 100644 index 00000000..caffac41 --- /dev/null +++ b/views/html/users/create.tpl @@ -0,0 +1,14 @@ +

    +

    + +
    + + +
    + +
    + +
    + + + diff --git a/views/html/users/delete.tpl b/views/html/users/delete.tpl new file mode 100644 index 00000000..10f280ab --- /dev/null +++ b/views/html/users/delete.tpl @@ -0,0 +1,8 @@ +

    +

    + + +
    + + +
    diff --git a/views/html/users/edit.tpl b/views/html/users/edit.tpl new file mode 100644 index 00000000..d3d1c2e0 --- /dev/null +++ b/views/html/users/edit.tpl @@ -0,0 +1,14 @@ +

    +

    + +
    + + +
    + +
    + +
    + + + diff --git a/views/html/users/index.tpl b/views/html/users/index.tpl index 3b94e6a0..9a5b525e 100644 --- a/views/html/users/index.tpl +++ b/views/html/users/index.tpl @@ -1,4 +1,7 @@

    +
    • diff --git a/views/html/users/login.tpl b/views/html/users/login.tpl new file mode 100644 index 00000000..36c13da4 --- /dev/null +++ b/views/html/users/login.tpl @@ -0,0 +1,12 @@ +

      +

      + +
      +
      + +
      + +
      +
      + +
      diff --git a/controllers/components/empty b/views/html/users/logout.tpl similarity index 100% rename from controllers/components/empty rename to views/html/users/logout.tpl diff --git a/views/html/users/user.tpl b/views/html/users/user.tpl index 91591dd3..1e8466b5 100644 --- a/views/html/users/user.tpl +++ b/views/html/users/user.tpl @@ -1,5 +1,8 @@

      +