diff --git a/config/config-sample.ini b/config/config-sample.ini index 030e70e..0b92cb7 100644 --- a/config/config-sample.ini +++ b/config/config-sample.ini @@ -83,6 +83,7 @@ password = password database = ska-db [ldap] +enabled = 0 ; Address to connect to LDAP server host = ldaps://ldap.example.com:636 ; Use StartTLS for connection security (recommended if using ldap:// instead diff --git a/core.php b/core.php index 618c15b..45acbb5 100644 --- a/core.php +++ b/core.php @@ -35,10 +35,12 @@ require('ldap.php'); require('email.php'); -$ldap_options = array(); -$ldap_options[LDAP_OPT_PROTOCOL_VERSION] = 3; -$ldap_options[LDAP_OPT_REFERRALS] = !empty($config['ldap']['follow_referrals']); -$ldap = new LDAP($config['ldap']['host'], $config['ldap']['starttls'], $config['ldap']['bind_dn'], $config['ldap']['bind_password'], $ldap_options); +if ($config['ldap']['enabled'] == 1) { + $ldap_options = array(); + $ldap_options[LDAP_OPT_PROTOCOL_VERSION] = 3; + $ldap_options[LDAP_OPT_REFERRALS] = !empty($config['ldap']['follow_referrals']); + $ldap = new LDAP($config['ldap']['host'], $config['ldap']['starttls'], $config['ldap']['bind_dn'], $config['ldap']['bind_password'], $ldap_options); +} setup_database(); $relative_frontend_base_url = (string)parse_url($config['web']['baseurl'], PHP_URL_PATH); diff --git a/migrations/004.php b/migrations/004.php new file mode 100644 index 0000000..9f088b7 --- /dev/null +++ b/migrations/004.php @@ -0,0 +1,159 @@ +store_result()) { + $res->free(); + } + } while ($database->more_results() && $database->next_result()); +} + +$this->database->autocommit(FALSE); + +$result = $this->database->query(" + SELECT uid FROM user WHERE uid = 'keys-sync' +"); +if ($result) { + if($result->num_rows === 0) { + $result->close(); + $result = $this->database->multi_query(" + INSERT INTO entity SET type = 'user'; + INSERT INTO user SET entity_id = ( + SELECT LAST_INSERT_ID() + ), uid = 'keys-sync', name = 'Synchronization script', email = '', auth_realm = 'local', admin = 1; + "); + free_results($this->database); + } else { + $result->close(); + $this->database->query(" + UPDATE user SET auth_realm = 'local', active = 1 WHERE uid = 'keys-sync'; + "); + } +} + + +$this->database->multi_query(" +CREATE TABLE `entity_event_2` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `entity_id` int(10) unsigned NOT NULL, + `actor_id` int(10) unsigned, + `date` datetime NOT NULL, + `details` mediumtext NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_entity_event_entity_id` (`entity_id`), + KEY `FK_entity_event_actor_id` (`actor_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT entity_event_2 SELECT * FROM entity_event; + +DROP TABLE entity_event; +RENAME TABLE entity_event_2 TO entity_event; + +ALTER TABLE `entity_event` + ADD CONSTRAINT `FK_entity_event_actor_id` FOREIGN KEY (`actor_id`) REFERENCES `entity` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `FK_entity_event_entity_id` FOREIGN KEY (`entity_id`) REFERENCES `entity` (`id`) ON DELETE CASCADE; +"); +free_results($this->database); + + +$this->database->multi_query(" +CREATE TABLE `group_event_2` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `group` int(10) unsigned NOT NULL, + `entity_id` int(10) unsigned, + `date` datetime NOT NULL, + `details` mediumtext NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_group_event_group` (`group`), + KEY `FK_group_event_entity` (`entity_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT; + +INSERT group_event_2 SELECT * FROM group_event; + +DROP TABLE group_event; +RENAME TABLE group_event_2 TO group_event; + +ALTER TABLE `group_event` + ADD CONSTRAINT `FK_group_event_entity` FOREIGN KEY (`entity_id`) REFERENCES `entity` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `FK_group_event_group` FOREIGN KEY (`group`) REFERENCES `group` (`entity_id`) ON DELETE CASCADE; +"); +free_results($this->database); + + +$this->database->multi_query(" +CREATE TABLE `group_member_2` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `group` int(10) unsigned NOT NULL, + `entity_id` int(10) unsigned NOT NULL, + `add_date` datetime NOT NULL, + `added_by` int(10) unsigned, + PRIMARY KEY (`id`), + UNIQUE KEY `group_entity_id` (`group`, `entity_id`), + KEY `FK_group_member_entity` (`entity_id`), + KEY `FK_group_member_entity_2` (`added_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT; + +INSERT group_member_2 SELECT * FROM group_member; + +DROP TABLE group_member; +RENAME TABLE group_member_2 TO group_member; + +ALTER TABLE `group_member` + ADD CONSTRAINT `FK_group_member_entity` FOREIGN KEY (`entity_id`) REFERENCES `entity` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `FK_group_member_entity_2` FOREIGN KEY (`added_by`) REFERENCES `entity` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `FK_group_member_group` FOREIGN KEY (`group`) REFERENCES `group` (`entity_id`) ON DELETE CASCADE +"); +free_results($this->database); + + +$this->database->multi_query(" +CREATE TABLE `server_event_2` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `server_id` int(10) unsigned NOT NULL, + `actor_id` int(10) unsigned, + `date` datetime NOT NULL, + `details` mediumtext NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_server_log_server` (`server_id`), + KEY `FK_server_event_actor_id` (`actor_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT server_event_2 SELECT * FROM server_event; + +DROP TABLE server_event; +RENAME TABLE server_event_2 TO server_event; + +ALTER TABLE `server_event` + ADD CONSTRAINT `FK_server_event_actor_id` FOREIGN KEY (`actor_id`) REFERENCES `entity` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `FK_server_log_server` FOREIGN KEY (`server_id`) REFERENCES `server` (`id`) ON DELETE CASCADE; +"); +free_results($this->database); + + +$this->database->multi_query(" +CREATE TABLE `server_note_2` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `server_id` int(10) unsigned NOT NULL, + `entity_id` int(10) unsigned, + `date` datetime NOT NULL, + `note` mediumtext NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_server_note_server` (`server_id`), + KEY `FK_server_note_user` (`entity_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT server_note_2 SELECT * FROM server_note; + +DROP TABLE server_note; +RENAME TABLE server_note_2 TO server_note; + +ALTER TABLE `server_note` + ADD CONSTRAINT `FK_server_note_entity` FOREIGN KEY (`entity_id`) REFERENCES `entity` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `FK_server_note_server` FOREIGN KEY (`server_id`) REFERENCES `server` (`id`) ON DELETE CASCADE +"); +free_results($this->database); + +$this->database->commit(); + +$this->database->autocommit(TRUE); diff --git a/model/migrationdirectory.php b/model/migrationdirectory.php index fabbc84..dd6a60c 100644 --- a/model/migrationdirectory.php +++ b/model/migrationdirectory.php @@ -22,7 +22,7 @@ class MigrationDirectory extends DBDirectory { /** * Increment this constant to activate a new migration from the migrations directory */ - const LAST_MIGRATION = 3; + const LAST_MIGRATION = 4; public function __construct() { parent::__construct(); diff --git a/model/user.php b/model/user.php index 6b7774e..deba94c 100644 --- a/model/user.php +++ b/model/user.php @@ -66,6 +66,23 @@ public function update() { } } + /** + * Delete the given user. + */ + public function delete() { + if(is_null($this->entity_id)) throw new BadMethodCallException('User must be in directory before it can be removed'); + $stmt = $this->database->prepare("DELETE FROM entity WHERE id = ?"); + $stmt->bind_param('d', $this->entity_id); + $stmt->execute(); + $stmt->close(); + $stmt = $this->database->prepare("DELETE FROM user WHERE entity_id = ?"); + $stmt->bind_param('d', $this->entity_id); + $stmt->execute(); + $stmt->close(); + + $this->sync_remote_access(); + } + /** * Magic getter method - if superior field requested, return User object of user's superior * @param string $field to retrieve diff --git a/model/userdirectory.php b/model/userdirectory.php index 8227b6b..5ff6b82 100644 --- a/model/userdirectory.php +++ b/model/userdirectory.php @@ -45,13 +45,22 @@ public function add_user(User $user) { $user_active = $user->active; $user_admin = $user->admin; $user_email = $user->email; - $stmt = $this->database->prepare("INSERT INTO entity SET type = 'user'"); - $stmt->execute(); - $user->entity_id = $stmt->insert_id; - $stmt = $this->database->prepare("INSERT INTO user SET entity_id = ?, uid = ?, name = ?, email = ?, active = ?, admin = ?"); - $stmt->bind_param('dsssdd', $user->entity_id, $user_id, $user_name, $user_email, $user_active, $user_admin); - $stmt->execute(); - $stmt->close(); + try { + $stmt = $this->database->prepare("INSERT INTO entity SET type = 'user'"); + $stmt->execute(); + $user->entity_id = $stmt->insert_id; + $stmt = $this->database->prepare("INSERT INTO user SET entity_id = ?, uid = ?, name = ?, email = ?, active = ?, admin = ?, auth_realm = ?"); + $stmt->bind_param('dsssdds', $user->entity_id, $user_id, $user_name, $user_email, $user_active, $user_admin, $user->auth_realm); + $stmt->execute(); + $stmt->close(); + } catch(mysqli_sql_exception $e) { + if($e->getCode() == 1062) { + // Duplicate entry + throw new UserAlreadyExistsException("User {$user->uid} already exists"); + } else { + throw $e; + } + } } /** @@ -82,6 +91,8 @@ public function get_user_by_id($id) { * @throws UserNotFoundException if no user with that uid exists */ public function get_user_by_uid($uid) { + global $config; + $ldap_enabled = $config['ldap']['enabled']; if(isset($this->cache_uid[$uid])) { return $this->cache_uid[$uid]; } @@ -93,11 +104,16 @@ public function get_user_by_uid($uid) { $user = new User($row['entity_id'], $row); $this->cache_uid[$uid] = $user; } else { - $user = new User; - $user->uid = $uid; - $this->cache_uid[$uid] = $user; - $user->get_details_from_ldap(); - $this->add_user($user); + if ($ldap_enabled == 1) { + $user = new User; + $user->uid = $uid; + $this->cache_uid[$uid] = $user; + $user->auth_realm = 'LDAP'; + $user->get_details_from_ldap(); + $this->add_user($user); + } else { + throw new UserNotFoundException('User does not exist.'); + } } $stmt->close(); return $user; @@ -148,3 +164,4 @@ public function list_users($include = array(), $filter = array()) { } class UserNotFoundException extends Exception {} +class UserAlreadyExistsException extends Exception {} diff --git a/requesthandler.php b/requesthandler.php index 5b921bf..cc60cd0 100644 --- a/requesthandler.php +++ b/requesthandler.php @@ -20,12 +20,6 @@ ob_start(); set_exception_handler('exception_handler'); -if(isset($_SERVER['PHP_AUTH_USER'])) { - $active_user = $user_dir->get_user_by_uid($_SERVER['PHP_AUTH_USER']); -} else { - throw new Exception("Not logged in."); -} - // Work out where we are on the server $base_path = dirname(__FILE__); $base_url = dirname($_SERVER['SCRIPT_NAME']); @@ -33,6 +27,17 @@ $relative_request_url = preg_replace('/^'.preg_quote($base_url, '/').'/', '/', $request_url); $absolute_request_url = 'http'.(isset($_SERVER['HTTPS']) ? 's' : '').'://'.$_SERVER['HTTP_HOST'].$request_url; +if(isset($_SERVER['PHP_AUTH_USER'])) { + try { + $active_user = $user_dir->get_user_by_uid($_SERVER['PHP_AUTH_USER']); + } catch(UserNotFoundException $ex) { + require('views/error403.php'); + die; + } +} else { + throw new Exception("Not logged in."); +} + if(empty($config['web']['enabled'])) { require('views/error503.php'); die; @@ -40,6 +45,7 @@ if(!$active_user->active) { require('views/error403.php'); + die; } if(!empty($_POST)) { @@ -62,10 +68,11 @@ if(isset($router->view)) { $view = path_join($base_path, 'views', $router->view.'.php'); if(file_exists($view)) { - if($active_user->auth_realm == 'LDAP' || $router->public) { + if($active_user->auth_realm == 'LDAP' || $active_user->auth_realm == 'local' || $router->public) { require($view); } else { require('views/error403.php'); + die; } } else { throw new Exception("View file $view missing."); diff --git a/scripts/ldap_update.php b/scripts/ldap_update.php index 6f8668f..076fca6 100755 --- a/scripts/ldap_update.php +++ b/scripts/ldap_update.php @@ -29,22 +29,28 @@ $active_user->uid = 'keys-sync'; $active_user->name = 'Synchronization script'; $active_user->email = ''; + $active_user->auth_realm = 'local'; $active_user->active = 1; $active_user->admin = 1; $active_user->developer = 0; $user_dir->add_user($active_user); } -try { - $sysgrp = $group_dir->get_group_by_name($config['ldap']['admin_group_cn']); -} catch(GroupNotFoundException $e) { - $sysgrp = new Group; - $sysgrp->name = $config['ldap']['admin_group_cn']; - $sysgrp->system = 1; - $group_dir->add_group($sysgrp); +$ldap_enabled = $config['ldap']['enabled']; + +if($ldap_enabled == 1) { + try { + $sysgrp = $group_dir->get_group_by_name($config['ldap']['admin_group_cn']); + } catch(GroupNotFoundException $e) { + $sysgrp = new Group; + $sysgrp->name = $config['ldap']['admin_group_cn']; + $sysgrp->system = 1; + $group_dir->add_group($sysgrp); + } } + foreach($users as $user) { - if($user->auth_realm == 'LDAP') { + if($user->auth_realm == 'LDAP' && $ldap_enabled == 1) { $active = $user->active; try { $user->get_details_from_ldap(); diff --git a/scripts/sync.php b/scripts/sync.php index 208818b..1390e32 100755 --- a/scripts/sync.php +++ b/scripts/sync.php @@ -69,6 +69,7 @@ $active_user->uid = 'keys-sync'; $active_user->name = 'Synchronization script'; $active_user->email = ''; + $active_user->auth_realm = 'local'; $active_user->active = 1; $active_user->admin = 1; $active_user->developer = 0; diff --git a/templates/functions.php b/templates/functions.php index aaf4184..758d395 100644 --- a/templates/functions.php +++ b/templates/functions.php @@ -85,7 +85,11 @@ function show_event($event) { group->name) ?> + actor->uid)) { ?> + removed + actor->uid) ?> + date) ?> diff --git a/templates/group.php b/templates/group.php index cd0a6d9..569c116 100644 --- a/templates/group.php +++ b/templates/group.php @@ -82,7 +82,7 @@ break; } ?> - Added on add_date) ?> by added_by->uid) ?> + Added on add_date) ?> by added_by->uid)) { ?>removedadded_by->uid) ?> get('group')->system) { ?> diff --git a/templates/server.php b/templates/server.php index 2c493d7..7e11bb9 100644 --- a/templates/server.php +++ b/templates/server.php @@ -451,7 +451,7 @@
get('output_formatter')->comment_format($note->note), ESC_NONE)?>
diff --git a/templates/user.php b/templates/user.php index a4f8d7e..c7f18c7 100644 --- a/templates/user.php +++ b/templates/user.php @@ -217,6 +217,17 @@ + get('user')->auth_realm == 'local' && $this->get('admin')) { ?> +

User managment

+
+ get('active_user')->get_csrf_field(), ESC_NONE) ?> +
+
+ +
+
+
+ get('user')->auth_realm == 'LDAP') { ?>
diff --git a/templates/users.php b/templates/users.php index 072303a..1d13ad0 100644 --- a/templates/users.php +++ b/templates/users.php @@ -16,21 +16,59 @@ ## ?>

Users

- - - - - - - - - - get('users') as $user) { ?> - active) out(' class="text-muted"', ESC_NONE) ?>> - - - - - - -
UsernameFull namePublic keys
uid)?>name)?>list_public_keys())))?>
+get('admin')) { ?> + + + + + +
+
+

User list

+

get('users')); out(number_format($total).' user'.($total == 1 ? '' : 's').' found')?>

+ + + + + + + + + + get('users') as $user) { ?> + active) out(' class="text-muted"', ESC_NONE) ?>> + + + + + + +
UsernameFull namePublic keys
uid)?>name)?>list_public_keys())))?>
+
+ + get('admin')) { ?> +
+

Add user

+
+ get('active_user')->get_csrf_field(), ESC_NONE) ?> +
+ + +
+
+ + +
+
+ + +
+ Administrator

+ +
+
+ +
diff --git a/views/user.php b/views/user.php index f871308..1f59f50 100644 --- a/views/user.php +++ b/views/user.php @@ -48,9 +48,16 @@ } } elseif(isset($_POST['edit_user']) && $active_user->admin) { $user->force_disable = $_POST['force_disable']; - $user->get_details_from_ldap(); + if($active_user->auth_realm == 'LDAP' ) { + $user->get_details_from_ldap(); + } $user->update(); redirect('#settings'); +} elseif(isset($_POST['delete_user']) && $active_user->admin) { + if($user->auth_realm == 'local' && $user->uid != 'keys-sync' ) { + $user->delete(); + } + redirect('/users'); } else { $content = new PageSection('user'); $content->set('user', $user); diff --git a/views/users.php b/views/users.php index 72ddf8b..c129b79 100644 --- a/views/users.php +++ b/views/users.php @@ -15,10 +15,44 @@ ## limitations under the License. ## -$content = new PageSection('users'); -$content->set('users', $user_dir->list_users()); -$content->set('admin', $active_user->admin); +if(isset($_POST['add_user']) && $active_user->admin) { + $uid = trim($_POST['uid']); + $name = trim($_POST['name']); + $email = trim($_POST['email']); + + $user = new User; + $user->uid = $uid; + $user->name = $name; + $user->email = $email; + + $user->active = 1; + if (isset($_POST['admin']) && $_POST['admin'] === 'admin') { + $user->admin = 1; + } else { + $user->admin = 0; + } + $user->auth_realm = 'local'; + try { + $user_dir->add_user($user); + $alert = new UserAlert; + $alert->content = 'User \''.hesc($user->uid).'\' successfully created.'; + $alert->escaping = ESC_NONE; + $active_user->add_alert($alert); + } catch(UserAlreadyExistsException $e) { + $alert = new UserAlert; + $alert->content = 'User \''.hesc($user->uid).'\' is already known by SSH Key Authority.'; + $alert->escaping = ESC_NONE; + $alert->class = 'danger'; + $active_user->add_alert($alert); + } + redirect('#add'); +} else { + $content = new PageSection('users'); + $content->set('users', $user_dir->list_users()); + $content->set('admin', $active_user->admin); +} + $page = new PageSection('base'); $page->set('title', 'Users'); $page->set('content', $content);