Skip to content

Commit

Permalink
Adding local user managment
Browse files Browse the repository at this point in the history
* keys-sync user becomes a local and not an ldap user
* local users are allowed to access site without 403
* LDAP can be dis/enabled in the configuration
* Added section to delete local user
* Added section to add local users
* Added graceful handling of deleted users in logs, nodes, ...

There are tables containing NOT NULL fields for actors
like the log table, which contains who did something.
When we remove a user, we don't want to remove that log
as the entity it is for is still alive. We instead want
to remove the actor (or set it to null) so that the
datbase stays consitent. The migrations in this patch
recreate every table which has an actor to allow NULL
fields. It also corrects a few constraints to set those
values to NULL on delete.
  • Loading branch information
mettke committed Apr 7, 2019
1 parent fe2eeab commit 4c00db0
Show file tree
Hide file tree
Showing 16 changed files with 361 additions and 56 deletions.
1 change: 1 addition & 0 deletions config/config-sample.ini
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,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
Expand Down
10 changes: 6 additions & 4 deletions core.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
159 changes: 159 additions & 0 deletions migrations/004.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php
$migration_name = 'Add local usermanagment';

function free_results($database) {
do {
if ($res = $database->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);
2 changes: 1 addition & 1 deletion model/migrationdirectory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
17 changes: 17 additions & 0 deletions model/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 30 additions & 12 deletions model/userdirectory.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,23 @@ 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();
$user->log(array('action' => 'User add'));
} catch(mysqli_sql_exception $e) {
if($e->getCode() == 1062) {
// Duplicate entry
throw new UserAlreadyExistsException("User {$user->uid} already exists");
} else {
throw $e;
}
}
}

/**
Expand Down Expand Up @@ -82,6 +92,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];
}
Expand All @@ -93,11 +105,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;
Expand Down Expand Up @@ -148,3 +165,4 @@ public function list_users($include = array(), $filter = array()) {
}

class UserNotFoundException extends Exception {}
class UserAlreadyExistsException extends Exception {}
21 changes: 14 additions & 7 deletions requesthandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,32 @@
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']);
$request_url = $_SERVER['REQUEST_URI'];
$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;
}

if(!$active_user->active) {
require('views/error403.php');
die;
}

if(!empty($_POST)) {
Expand All @@ -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.");
Expand Down
22 changes: 14 additions & 8 deletions scripts/ldap_update.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions scripts/sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions templates/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ function show_event($event) {
<a href="<?php outurl('/groups/'.urlencode($event->group->name))?>" class="group"><?php out($event->group->name) ?></a>
<?php } ?>
</td>
<?php if(is_null($event->actor->uid)) { ?>
<td>removed</td>
<?php } else { ?>
<td><a href="<?php outurl('/users/'.urlencode($event->actor->uid))?>" class="user"><?php out($event->actor->uid) ?></a></td>
<?php } ?>
<td><?php out($details, ESC_NONE) ?></td>
<td class="nowrap"><?php out($event->date) ?></td>
</tr>
Expand Down
Loading

0 comments on commit 4c00db0

Please sign in to comment.