mirror of
https://github.com/reactos/web.git
synced 2024-11-23 03:39:49 +00:00
[ROSADMIN] Add Mattermost plugin for admin panel
This commit is contained in:
parent
e6a9b171cd
commit
121dd77b1b
79
www/www.reactos.org/roslogin/admin/CurlHelper.php
Normal file
79
www/www.reactos.org/roslogin/admin/CurlHelper.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/*
|
||||
* PROJECT: RosLogin - A simple Self-Service and Single-Sign-On around an LDAP user directory
|
||||
* LICENSE: AGPL-3.0-or-later (https://spdx.org/licenses/AGPL-3.0-or-later)
|
||||
* PURPOSE: Curl Helper Class for RosAdmin
|
||||
* COPYRIGHT: Copyright 2020 Stanislav Motylkov (x86corez@gmail.com)
|
||||
*/
|
||||
|
||||
class CurlHelper
|
||||
{
|
||||
//
|
||||
// MEMBER VARIABLES
|
||||
//
|
||||
private $_last_error;
|
||||
private $_custom_headers;
|
||||
|
||||
//
|
||||
// PRIVATE FUNCTIONS
|
||||
//
|
||||
|
||||
private function perform($url, $content_type, $content_data)
|
||||
{
|
||||
$headers = $this->_custom_headers;
|
||||
if (!empty($content_type))
|
||||
$headers[] = "Content-Type: {$content_type}";
|
||||
|
||||
$ver = phpversion();
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, 'utf-8');
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
|
||||
curl_setopt($ch, CURLOPT_FAILONERROR, 0);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "PHP/{$ver} RosAdmin Curl");
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
if (!empty($content_type))
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $content_data);
|
||||
$data = curl_exec($ch);
|
||||
if (!$data)
|
||||
$this->_last_error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
return $data;
|
||||
}
|
||||
|
||||
//
|
||||
// PUBLIC FUNCTIONS
|
||||
//
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->_last_error = null;
|
||||
$this->_custom_headers = array();
|
||||
}
|
||||
|
||||
public function get($url)
|
||||
{
|
||||
return $this->perform($url, '', '');
|
||||
}
|
||||
|
||||
public function post($url, $content_type, $content_data)
|
||||
{
|
||||
return $this->perform($url, $content_type, $content_data);
|
||||
}
|
||||
|
||||
public function setHeaders($headers)
|
||||
{
|
||||
$this->_custom_headers = $headers;
|
||||
}
|
||||
|
||||
public function getLastError()
|
||||
{
|
||||
$error = $this->_last_error;
|
||||
$this->_last_error = null;
|
||||
return $error;
|
||||
}
|
||||
|
||||
}
|
228
www/www.reactos.org/roslogin/admin/Mattermost.php
Normal file
228
www/www.reactos.org/roslogin/admin/Mattermost.php
Normal file
@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/*
|
||||
* PROJECT: RosLogin - A simple Self-Service and Single-Sign-On around an LDAP user directory
|
||||
* LICENSE: AGPL-3.0-or-later (https://spdx.org/licenses/AGPL-3.0-or-later)
|
||||
* PURPOSE: Mattermost communication with RosAdmin
|
||||
* COPYRIGHT: Copyright 2020 Stanislav Motylkov (x86corez@gmail.com)
|
||||
*/
|
||||
|
||||
class Mattermost
|
||||
{
|
||||
//
|
||||
// MEMBER VARIABLES
|
||||
//
|
||||
private $_ch;
|
||||
private $_url;
|
||||
private $_last_error;
|
||||
|
||||
//
|
||||
// PRIVATE FUNCTIONS
|
||||
//
|
||||
|
||||
/**
|
||||
* Is this error returned by Mattermost server?
|
||||
*/
|
||||
private function isError($json)
|
||||
{
|
||||
return (isset($json['message']) && isset($json['status_code']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates unique to the client temporary filename
|
||||
*/
|
||||
private function getTempFilename()
|
||||
{
|
||||
$uniq = md5($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']);
|
||||
return sys_get_temp_dir() . '/rosadmin_mattermost_' . substr($uniq, 0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads error message that was saved earlier
|
||||
* and removes it from disk
|
||||
*/
|
||||
private function restoreError()
|
||||
{
|
||||
$filename = Mattermost::getTempFilename();
|
||||
if (!file_exists($filename))
|
||||
return null;
|
||||
$error = file_get_contents($filename);
|
||||
unlink($filename);
|
||||
return $error;
|
||||
}
|
||||
|
||||
//
|
||||
// PUBLIC FUNCTIONS
|
||||
//
|
||||
|
||||
public function __construct($curl_helper, $url, $token)
|
||||
{
|
||||
$this->_ch = $curl_helper;
|
||||
$this->_url = $url;
|
||||
$this->_ch->setHeaders(array("Authorization: Bearer {$token}"));
|
||||
$this->_last_error = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user ID
|
||||
*/
|
||||
public function getUserByName($name)
|
||||
{
|
||||
/* Mattermost usernames are lower-case */
|
||||
$name = urlencode(strtolower($name));
|
||||
$json = $this->_ch->get("{$this->_url}/api/v4/users/username/{$name}");
|
||||
if (!$json)
|
||||
{
|
||||
$this->_last_error = $this->_ch->getLastError();
|
||||
return null;
|
||||
}
|
||||
$json = json_decode($json, true);
|
||||
if (Mattermost::isError($json))
|
||||
{
|
||||
$this->_last_error = $json['message'];
|
||||
if ($json['status_code'] == 404)
|
||||
$this->_last_error = 'User is not registered in Mattermost';
|
||||
return null;
|
||||
}
|
||||
else if (!isset($json['id']) && empty($this->_last_error))
|
||||
{
|
||||
/* Request was successful, but response is not recognized */
|
||||
$this->_last_error = 'Cannot connect to Mattermost';
|
||||
return null;
|
||||
}
|
||||
return $json['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user sessions
|
||||
*/
|
||||
public function getSessionsById($user_id)
|
||||
{
|
||||
$user_id = urlencode($user_id);
|
||||
$json = $this->_ch->get("{$this->_url}/api/v4/users/{$user_id}/sessions");
|
||||
if (!$json)
|
||||
{
|
||||
$this->_last_error = $this->_ch->getLastError();
|
||||
return null;
|
||||
}
|
||||
$json = json_decode($json, true);
|
||||
if (Mattermost::isError($json))
|
||||
{
|
||||
$this->_last_error = $json['message'];
|
||||
return null;
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user audits
|
||||
*/
|
||||
public function getAuditsById($user_id)
|
||||
{
|
||||
$user_id = urlencode($user_id);
|
||||
$json = $this->_ch->get("{$this->_url}/api/v4/users/{$user_id}/audits");
|
||||
if (!$json)
|
||||
{
|
||||
$this->_last_error = $this->_ch->getLastError();
|
||||
return null;
|
||||
}
|
||||
$json = json_decode($json, true);
|
||||
if (Mattermost::isError($json))
|
||||
{
|
||||
$this->_last_error = $json['message'];
|
||||
return null;
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes user session
|
||||
*/
|
||||
public function revokeSession($user_id, $session_id)
|
||||
{
|
||||
$user_id = urlencode($user_id);
|
||||
$json = $this->_ch->post(
|
||||
"{$this->_url}/api/v4/users/{$user_id}/sessions/revoke",
|
||||
"application/json",
|
||||
json_encode(array('session_id' => $session_id)));
|
||||
if (!$json)
|
||||
{
|
||||
$this->_last_error = $this->_ch->getLastError();
|
||||
return null;
|
||||
}
|
||||
$json = json_decode($json, true);
|
||||
if (Mattermost::isError($json))
|
||||
{
|
||||
$this->_last_error = $json['message'];
|
||||
return null;
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes all user sessions
|
||||
*/
|
||||
public function revokeAllSessions($user_id)
|
||||
{
|
||||
$user_id = urlencode($user_id);
|
||||
$json = $this->_ch->post(
|
||||
"{$this->_url}/api/v4/users/{$user_id}/sessions/revoke/all",
|
||||
"application/json",
|
||||
"{}");
|
||||
if (!$json)
|
||||
{
|
||||
$this->_last_error = $this->_ch->getLastError();
|
||||
return null;
|
||||
}
|
||||
$json = json_decode($json, true);
|
||||
if (Mattermost::isError($json))
|
||||
{
|
||||
$this->_last_error = $json['message'];
|
||||
return null;
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all IP addresses associated with a session
|
||||
* using audit information
|
||||
*/
|
||||
public function getSessionAddresses($audits, $session_id)
|
||||
{
|
||||
$ips = array();
|
||||
for ($i = 0; $i < count($audits); $i++)
|
||||
{
|
||||
$e = $audits[$i];
|
||||
if ($e['session_id'] === $session_id)
|
||||
{
|
||||
$ip = $e['ip_address'];
|
||||
if (!in_array($ip, $ips, true))
|
||||
$ips[] = $ip;
|
||||
}
|
||||
}
|
||||
return $ips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes error message to disk to display it later
|
||||
*/
|
||||
public function rememberError()
|
||||
{
|
||||
$error = $this->getLastError();
|
||||
$filename = Mattermost::getTempFilename();
|
||||
file_put_contents($filename, $error);
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets textual description of last encountered error
|
||||
*/
|
||||
public function getLastError()
|
||||
{
|
||||
$error = $this->_last_error;
|
||||
$this->_last_error = null;
|
||||
if (is_null($error))
|
||||
$error = Mattermost::restoreError();
|
||||
return $error;
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
private $_is_mod;
|
||||
private $_is_admin;
|
||||
|
||||
public $mm;
|
||||
|
||||
//
|
||||
// PRIVATE FUNCTIONS
|
||||
//
|
||||
@ -43,6 +45,8 @@
|
||||
// Let's hope this never happens
|
||||
if ($this->_userinfo['banned'])
|
||||
redirect_to('/roslogin/?p=login');
|
||||
|
||||
$this->mm = new Mattermost(new CurlHelper(), ROSLOGIN_MATTERMOST_URL, ROSLOGIN_MATTERMOST_TOKEN);
|
||||
}
|
||||
|
||||
|
||||
|
36
www/www.reactos.org/roslogin/admin/actions/RevokeAction.php
Normal file
36
www/www.reactos.org/roslogin/admin/actions/RevokeAction.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/*
|
||||
* PROJECT: RosLogin - A simple Self-Service and Single-Sign-On around an LDAP user directory
|
||||
* LICENSE: AGPL-3.0-or-later (https://spdx.org/licenses/AGPL-3.0-or-later)
|
||||
* PURPOSE: Revoking a user session
|
||||
* COPYRIGHT: Copyright 2020 Stanislav Motylkov (x86corez@gmail.com)
|
||||
*/
|
||||
|
||||
class RevokeAction
|
||||
{
|
||||
public function perform($ra)
|
||||
{
|
||||
if (!array_key_exists('username', $_POST)
|
||||
|| !array_key_exists('user_id', $_POST)
|
||||
|| !array_key_exists('session_id', $_POST))
|
||||
{
|
||||
throw new RuntimeException("Necessary information not specified");
|
||||
}
|
||||
|
||||
$username = $_POST['username'];
|
||||
$user_id = $_POST['user_id'];
|
||||
$session_id = $_POST['session_id'];
|
||||
$data = [
|
||||
'username' => $username,
|
||||
];
|
||||
|
||||
$result = $ra->mm->revokeSession($user_id, $session_id);
|
||||
if (!$result || $result['status'] != "OK")
|
||||
{
|
||||
$ra->mm->rememberError();
|
||||
redirect_to("?p=user&revoke_problem=1&" . http_build_query($data));
|
||||
}
|
||||
else
|
||||
redirect_to("?p=user&revoke_ok=1&" . http_build_query($data));
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/*
|
||||
* PROJECT: RosLogin - A simple Self-Service and Single-Sign-On around an LDAP user directory
|
||||
* LICENSE: AGPL-3.0-or-later (https://spdx.org/licenses/AGPL-3.0-or-later)
|
||||
* PURPOSE: Revoking all user sessions
|
||||
* COPYRIGHT: Copyright 2020 Stanislav Motylkov (x86corez@gmail.com)
|
||||
*/
|
||||
|
||||
class RevokeAllAction
|
||||
{
|
||||
public function perform($ra)
|
||||
{
|
||||
if (!array_key_exists('username', $_POST)
|
||||
|| !array_key_exists('user_id', $_POST))
|
||||
{
|
||||
throw new RuntimeException("Necessary information not specified");
|
||||
}
|
||||
|
||||
$username = $_POST['username'];
|
||||
$user_id = $_POST['user_id'];
|
||||
$data = [
|
||||
'username' => $username,
|
||||
];
|
||||
|
||||
$result = $ra->mm->revokeAllSessions($user_id);
|
||||
if (!$result || $result['status'] != "OK")
|
||||
{
|
||||
$ra->mm->rememberError();
|
||||
redirect_to("?p=user&revoke_all_problem=1&" . http_build_query($data));
|
||||
}
|
||||
else
|
||||
redirect_to("?p=user&revoke_all_ok=1&" . http_build_query($data));
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@
|
||||
define("ROOT_PATH", "../../");
|
||||
require_once("../RosLogin.php");
|
||||
require_once("RosAdmin.php");
|
||||
require_once("CurlHelper.php");
|
||||
require_once("Mattermost.php");
|
||||
|
||||
require_once(ROOT_PATH . "rosweb/rosweb.php");
|
||||
$rw = new RosWeb();
|
||||
@ -65,6 +67,14 @@
|
||||
$action = new BanAction();
|
||||
break;
|
||||
|
||||
case "revoke":
|
||||
$action = new RevokeAction();
|
||||
break;
|
||||
|
||||
case "revoke_all":
|
||||
$action = new RevokeAllAction();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Invalid action");
|
||||
}
|
||||
|
@ -71,6 +71,39 @@
|
||||
$unbanned = array_key_exists("unbanned", $_GET);
|
||||
$was_not_banned = array_key_exists("was_not_banned", $_GET);
|
||||
|
||||
$revoke_ok = array_key_exists("revoke_ok", $_GET);
|
||||
$revoke_all_ok = array_key_exists("revoke_all_ok", $_GET);
|
||||
$revoke_problem = array_key_exists("revoke_problem", $_GET);
|
||||
$revoke_all_problem = array_key_exists("revoke_all_problem", $_GET);
|
||||
if ($revoke_problem || $revoke_all_problem)
|
||||
$revoke_error = $this->_ra->mm->getLastError();
|
||||
$chat_sessions = null;
|
||||
$chat_audits = null;
|
||||
$chat_user_id = $this->_ra->mm->getUserByName($_GET["username"]);
|
||||
if (!is_null($chat_user_id))
|
||||
{
|
||||
$chat_sessions = $this->_ra->mm->getSessionsById($chat_user_id);
|
||||
$chat_audits = $this->_ra->mm->getAuditsById($chat_user_id);
|
||||
}
|
||||
$chat_message = "";
|
||||
if (is_null($chat_user_id) || is_null($chat_sessions) || is_null($chat_audits))
|
||||
{
|
||||
$chat_message = $this->_ra->mm->getLastError();
|
||||
if ($chat_message != "User is not registered in Mattermost")
|
||||
{
|
||||
if (is_null($chat_user_id))
|
||||
$chat_message = "Failed to get user ID: {$chat_message}";
|
||||
else if (is_null($chat_sessions))
|
||||
$chat_message = "Failed to get user sessions: {$chat_message}";
|
||||
else if (is_null($chat_audits))
|
||||
$chat_message = "Failed to get user audits: {$chat_message}";
|
||||
}
|
||||
}
|
||||
else if (count($chat_sessions) == 0)
|
||||
{
|
||||
$chat_message = "User has no active Mattermost sessions";
|
||||
}
|
||||
|
||||
$user_message = "";
|
||||
if ($banned || $already_banned || $unbanned || $was_not_banned)
|
||||
{
|
||||
@ -83,6 +116,17 @@
|
||||
else if ($was_not_banned)
|
||||
$user_message = "User was not banned";
|
||||
}
|
||||
else if ($revoke_ok || $revoke_all_ok || $revoke_problem || $revoke_all_problem)
|
||||
{
|
||||
if ($revoke_ok)
|
||||
$user_message = "User session has been revoked";
|
||||
else if ($revoke_all_ok)
|
||||
$user_message = "All user sessions has been revoked";
|
||||
else if ($revoke_problem)
|
||||
$user_message = "Failed to revoke user session: {$revoke_error}";
|
||||
else if ($revoke_all_problem)
|
||||
$user_message = "Failed to revoke all user sessions: {$revoke_error}";
|
||||
}
|
||||
|
||||
if (!empty($user_message))
|
||||
{
|
||||
@ -140,6 +184,60 @@
|
||||
</form>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="lead center col-md-9">Mattermost Sessions</p>
|
||||
<?php
|
||||
if (!empty($chat_message))
|
||||
{
|
||||
?>
|
||||
<div class="col-md-10 col-md-offset-1"><?php echo $chat_message; ?></div>
|
||||
<?php
|
||||
}
|
||||
else
|
||||
{
|
||||
?>
|
||||
<table class="col-md-offset-1 table table-striped table-bordered">
|
||||
<tr>
|
||||
<th>
|
||||
<form class="form-horizontal" method="post">
|
||||
<input type="hidden" name="username" value="<?php echo $username; ?>">
|
||||
<input type="hidden" name="user_id" value="<?php echo $chat_user_id; ?>">
|
||||
<button type="submit" name="a" value="revoke_all" class="btn btn-danger">Revoke All</button>
|
||||
</form>
|
||||
</th>
|
||||
<th>Token</th>
|
||||
<th>IP Addresses</th>
|
||||
<th>User Agent</th>
|
||||
</tr>
|
||||
<?php
|
||||
for ($i = 0; $i < count($chat_sessions); $i++)
|
||||
{
|
||||
$e = $chat_sessions[$i];
|
||||
$ua = $e['props']['os'];
|
||||
if (empty($ua))
|
||||
$ua = $e['props']['platform'];
|
||||
if (!empty($ua))
|
||||
$ua .= ' - ';
|
||||
$ua .= $e['props']['browser'];
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<form class="form-horizontal" method="post">
|
||||
<input type="hidden" name="username" value="<?php echo $username; ?>">
|
||||
<input type="hidden" name="user_id" value="<?php echo $chat_user_id; ?>">
|
||||
<input type="hidden" name="session_id" value="<?php echo $e['id']; ?>">
|
||||
<button type="submit" name="a" value="revoke" class="btn btn-warning">Revoke</button>
|
||||
</form>
|
||||
</td>
|
||||
<td><? echo $e['token']; ?></td>
|
||||
<td><? echo implode('<br>', $this->_ra->mm->getSessionAddresses($chat_audits, $e['id'])); ?></td>
|
||||
<td><? echo htmlspecialchars($ua); ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,3 +32,5 @@
|
||||
// Admin panel settings
|
||||
define("ROSLOGIN_ADMIN_GROUP", "LDAP Administrators");
|
||||
define("ROSLOGIN_MODERATOR_GROUP", "Moderators");
|
||||
define("ROSLOGIN_MATTERMOST_URL", "https://chat.reactos.org");
|
||||
define("ROSLOGIN_MATTERMOST_TOKEN", "YOUR_MATTERMOST_TOKEN_HERE");
|
||||
|
Loading…
Reference in New Issue
Block a user