Skip to content
Snippets Groups Projects
Commit 39e86f03 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Implemented basic admin panel functionalities

parent b0d644d5
Branches
Tags
No related merge requests found
...@@ -2,3 +2,5 @@ composer.lock ...@@ -2,3 +2,5 @@ composer.lock
config.php config.php
logs/ logs/
vendor/ vendor/
client-icons/
/nbproject/
...@@ -124,4 +124,12 @@ interface DAO { ...@@ -124,4 +124,12 @@ interface DAO {
* @param type $token join token * @param type $token join token
*/ */
function deleteJoinRequest($token); function deleteJoinRequest($token);
/**
* CRUD methods for OAuth2Clients (used by admin interface).
*/
function getOAuth2Clients();
function createOAuth2Client($client);
function updateOAuth2Client($client);
function deleteOAuth2Client($clientId);
} }
...@@ -332,4 +332,150 @@ class MySQLDAO implements DAO { ...@@ -332,4 +332,150 @@ class MySQLDAO implements DAO {
$stmt->execute(); $stmt->execute();
} }
function getOAuth2Clients() {
$dbh = $this->getDBHandler();
// Load clients info
$queryClient = "SELECT id, name, icon, client, secret, redirect_url, scope FROM oauth2_client";
$stmtClients = $dbh->prepare($queryClient);
$stmtClients->execute();
$clientsMap = [];
foreach ($stmtClients->fetchAll() as $row) {
$client = new OAuth2Client();
$client->id = $row['id'];
$client->name = $row['name'];
$client->icon = $row['icon'];
$client->client = $row['client'];
$client->secret = $row['secret'];
$client->redirectUrl = $row['redirect_url'];
$client->scope = $row['scope'];
$clientsMap[$client->id] = $client;
}
// Load authentication methods info
$queryAuthNMethods = "SELECT client_id, auth_method FROM oauth2_client_auth_methods";
$stmtAuthNMethods = $dbh->prepare($queryAuthNMethods);
$stmtAuthNMethods->execute();
foreach ($stmtAuthNMethods->fetchAll() as $row) {
$id = $row['client_id'];
array_push($clientsMap[$id]->authMethods, $row['auth_method']);
}
$clients = [];
foreach ($clientsMap as $id => $client) {
array_push($clients, $client);
}
return $clients;
}
function createOAuth2Client($client) {
$dbh = $this->getDBHandler();
try {
$dbh->beginTransaction();
$stmt = $dbh->prepare("INSERT INTO `oauth2_client`(`name`, `icon`, `client`, `secret`, `redirect_url`, `scope`)"
. " VALUES(:name, :icon, :client, :secret, :redirect_url, :scope)");
$stmt->bindParam(':name', $client->name);
$stmt->bindParam(':icon', $client->icon);
$stmt->bindParam(':client', $client->client);
$stmt->bindParam(':secret', $client->secret);
$stmt->bindParam(':redirect_url', $client->redirectUrl);
$stmt->bindParam(':scope', $client->scope);
$stmt->execute();
$client->id = $dbh->lastInsertId();
foreach ($client->authMethods as $method) {
$stmt = $dbh->prepare("INSERT INTO `oauth2_client_auth_methods`(`client_id`, `auth_method`)"
. " VALUES(:client_id, :auth_method)");
$stmt->bindParam(':client_id', $client->id);
$stmt->bindParam(':auth_method', $method);
$stmt->execute();
}
$dbh->commit();
} catch (Exception $ex) {
$dbh->rollBack();
throw $ex;
}
return $client;
}
function updateOAuth2Client($client) {
$dbh = $this->getDBHandler();
try {
$dbh->beginTransaction();
$stmt = $dbh->prepare("UPDATE `oauth2_client` SET `name` = :name, `icon` = :icon, "
. " `client` = :client, `secret` = :secret, `redirect_url` = :redirect_url, `scope` = :scope "
. " WHERE id = :id");
$stmt->bindParam(':name', $client->name);
$stmt->bindParam(':icon', $client->icon);
$stmt->bindParam(':client', $client->client);
$stmt->bindParam(':secret', $client->secret);
$stmt->bindParam(':redirect_url', $client->redirectUrl);
$stmt->bindParam(':scope', $client->scope);
$stmt->bindParam(':id', $client->id);
$stmt->execute();
// Delete old authentication methods
$stmt = $dbh->prepare("DELETE FROM oauth2_client_auth_methods WHERE client_id = :id");
$stmt->bindParam(':id', $client->id);
$stmt->execute();
// Re-add authentication methods
foreach ($client->authMethods as $method) {
$stmt = $dbh->prepare("INSERT INTO `oauth2_client_auth_methods`(`client_id`, `auth_method`)"
. " VALUES(:client_id, :auth_method)");
$stmt->bindParam(':client_id', $client->id);
$stmt->bindParam(':auth_method', $method);
$stmt->execute();
}
$dbh->commit();
} catch (Exception $ex) {
$dbh->rollBack();
throw $ex;
}
return $client;
}
function deleteOAuth2Client($clientId) {
$dbh = $this->getDBHandler();
try {
$dbh->beginTransaction();
$stmt = $dbh->prepare("DELETE FROM `oauth2_client_auth_methods` WHERE client_id = :id");
$stmt->bindParam(':id', $clientId);
$stmt->execute();
$stmt = $dbh->prepare("DELETE FROM `oauth2_client` WHERE id = :id");
$stmt->bindParam(':id', $clientId);
$stmt->execute();
$dbh->commit();
} catch (Exception $ex) {
$dbh->rollBack();
throw $ex;
}
}
} }
<?php
/* ----------------------------------------------------------------------------
* INAF - National Institute for Astrophysics
* IRA - Radioastronomical Institute - Bologna
* OATS - Astronomical Observatory - Trieste
* ----------------------------------------------------------------------------
*
* Copyright (C) 2019 Istituto Nazionale di Astrofisica
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License Version 3 as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
namespace RAP;
/**
* Data model for storing information about a RAP client connecting using OAuth2.
*/
class OAuth2Client {
public $id;
public $name;
public $icon;
public $client;
public $secret;
public $redirectUrl;
public $scope;
// list of AuthN methods
public $authMethods = [];
}
<?php
/**
* Functionalities for the admin panel.
*/
//
function checkUser() {
startSession();
global $session;
if ($session->user === null) {
http_response_code(401);
die("You must be registered to perform this action");
}
// TODO: check is admin
}
Flight::route('GET /admin', function() {
global $VERSION;
Flight::render('admin/index.php', array('title' => 'Admin panel',
'version' => $VERSION));
});
Flight::route('GET /admin/oauth2_clients', function() {
checkUser();
global $dao;
$clients = $dao->getOAuth2Clients();
Flight::json($clients);
});
Flight::route('POST /admin/oauth2_clients', function() {
checkUser();
global $dao;
$client = $dao->createOAuth2Client(buildOAuth2ClientFromData());
Flight::json($client);
});
Flight::route('PUT /admin/oauth2_clients', function() {
checkUser();
global $dao;
$client = $dao->updateOAuth2Client(buildOAuth2ClientFromData());
Flight::json($client);
});
Flight::route('DELETE /admin/oauth2_clients/@id', function($id) {
checkUser();
global $dao;
$dao->deleteOAuth2Client($id);
// Return no content
Flight::halt(204);
});
function buildOAuth2ClientFromData() {
$data = Flight::request()->data;
$client = new \RAP\OAuth2Client();
if (isset($data)) {
if (isset($data['id'])) {
$client->id = $data['id'];
}
$client->name = $data['name'];
$client->icon = $data['icon'];
$client->client = $data['client'];
$client->secret = $data['secret'];
$client->redirectUrl = $data['redirectUrl'];
$client->scope = $data['scope'];
}
if (isset($data['authMethods'])) {
foreach ($data['authMethods'] as $method) {
array_push($client->authMethods, $method);
}
}
return $client;
}
...@@ -222,3 +222,5 @@ Flight::route('GET /register', function() { ...@@ -222,3 +222,5 @@ Flight::route('GET /register', function() {
$callbackHandler->manageLoginRedirect($user, $session); $callbackHandler->manageLoginRedirect($user, $session);
} }
}); });
include 'admin.php';
\ No newline at end of file
...@@ -32,7 +32,7 @@ Flight::route('GET /user', function() { ...@@ -32,7 +32,7 @@ Flight::route('GET /user', function() {
array_push($jsRes, $searchResult->userDisplayText); array_push($jsRes, $searchResult->userDisplayText);
} }
echo json_encode($jsRes); Flight::json($jsRes);
}); });
Flight::route('POST /join', function() { Flight::route('POST /join', function() {
......
...@@ -46,8 +46,7 @@ Flight::route('GET ' . $WS_PREFIX . '/user/@userId', function($userId) { ...@@ -46,8 +46,7 @@ Flight::route('GET ' . $WS_PREFIX . '/user/@userId', function($userId) {
$user = $dao->findUserById($userId); $user = $dao->findUserById($userId);
if ($user !== null) { if ($user !== null) {
header('Content-Type: application/json'); Flight::json($user);
echo json_encode($user);
} else { } else {
http_response_code(404); http_response_code(404);
die("User not found"); die("User not found");
...@@ -63,7 +62,7 @@ Flight::route('GET ' . $WS_PREFIX . '/user', function() { ...@@ -63,7 +62,7 @@ Flight::route('GET ' . $WS_PREFIX . '/user', function() {
$searchText = Flight::request()->query['search']; $searchText = Flight::request()->query['search'];
$users = $dao->searchUser($searchText); $users = $dao->searchUser($searchText);
echo json_encode($users); Flight::json($users);
}); });
/** /**
...@@ -98,7 +97,7 @@ Flight::route('POST ' . $WS_PREFIX . '/user', function() { ...@@ -98,7 +97,7 @@ Flight::route('POST ' . $WS_PREFIX . '/user', function() {
$userHandler->saveUser($user); $userHandler->saveUser($user);
echo json_encode($user); Flight::json($user);
}); });
/** /**
......
(function () {
var AUTH_METHODS = ['eduGAIN', 'Google', 'Facebook', 'LinkedIn', 'X.509', 'LocalIdP'];
var vm = new Vue({
el: '#admin-vue',
data: {
oauth2Clients: [],
oauth2ClientToDelete: null
},
methods: {
addNewOAuth2Client: function () {
this.$data.oauth2Clients.push(getNewClient());
},
editOAuth2Client: function (client) {
client.edit = true;
vm.$forceUpdate();
},
getAuthMethodsString: function (authMethods) {
var selectedValues = [];
for (var i = 0; i < AUTH_METHODS.length; i++) {
var method = AUTH_METHODS[i];
if (authMethods[method] === true) {
selectedValues.push(method);
}
}
return selectedValues.join(', ');
},
saveOAuth2Client: function (client, index) {
if (client.id === null) {
createOAuth2Client(client, index);
} else {
updateOAuth2Client(client, index);
}
},
askConfirmDeleteOAuth2Client: function (client, index) {
vm.$data.oauth2ClientToDelete = index;
if (client.id === null) {
deleteOAuth2Client();
} else {
$('#client-to-delete').text(client.name);
$('#confirm-delete-client-modal').modal('show');
}
}
}
});
function getNewClient() {
var client = {
id: null,
name: null,
icon: null,
client: null,
secret: null,
redirectUrl: null,
scope: null,
authMethods: {},
edit: true
};
for (var i = 0; i < AUTH_METHODS.length; i++) {
client.authMethods[AUTH_METHODS[i]] = true;
}
return client;
}
/* Converts the model received from the server into a model
* useful for the Vue manipulation */
function getJsModel(client) {
var jsAuthMethods = {};
for (var i = 0; i < AUTH_METHODS.length; i++) {
var method = AUTH_METHODS[i];
jsAuthMethods[method] = client.authMethods.includes(method);
}
client.authMethods = jsAuthMethods;
client.edit = false;
return client;
}
/* Converts the model manipulated by Vue in the model expected by the back-end */
function getBackendModel(client) {
var client = Vue.util.extend({}, client);
var authMethods = [];
for (var i = 0; i < AUTH_METHODS.length; i++) {
var method = AUTH_METHODS[i];
if (client.authMethods[method] === true) {
authMethods.push(method);
}
}
client.authMethods = authMethods;
delete client.edit;
return client;
}
function createOAuth2Client(client, index) {
showWaiting();
$.ajax({
url: 'admin/oauth2_clients',
method: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(getBackendModel(client))
}).then(function (data) {
vm.$data.oauth2Clients[index] = getJsModel(data);
vm.$forceUpdate();
hideWaiting();
});
}
function updateOAuth2Client(client, index) {
showWaiting();
$.ajax({
url: 'admin/oauth2_clients',
method: 'PUT',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(getBackendModel(client))
}).then(function (data) {
vm.$data.oauth2Clients[index] = getJsModel(data);
vm.$forceUpdate();
hideWaiting();
});
}
function deleteOAuth2Client() {
var client = vm.$data.oauth2Clients[vm.$data.oauth2ClientToDelete];
if (client.id === null) {
vm.$data.oauth2Clients.splice(vm.$data.oauth2ClientToDelete, 1);
vm.$forceUpdate();
} else {
showWaiting();
$.ajax({
url: 'admin/oauth2_clients/' + client.id,
method: 'DELETE'
}).then(function () {
vm.$data.oauth2Clients.splice(vm.$data.oauth2ClientToDelete, 1);
$('#confirm-delete-client-modal').modal('hide');
vm.$forceUpdate();
hideWaiting();
});
}
}
$(document).on('click', '#confirm-delete-client', deleteOAuth2Client);
showWaiting();
$.ajax({
url: 'admin/oauth2_clients',
method: 'GET',
dataType: 'json'
}).then(function (data) {
var clients = [];
for (var i = 0; i < data.length; i++) {
clients.push(getJsModel(data[i]));
}
vm.$data.oauth2Clients = clients;
hideWaiting();
});
})();
\ No newline at end of file
CREATE TABLE `oauth2_client` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`icon` varchar(255),
`client` varchar(255) NOT NULL,
`secret` varchar(255) NOT NULL,
`redirect_url` text NOT NULL,
`scope` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth2_client_auth_methods` (
`client_id` int NOT NULL,
`auth_method` varchar(255) NOT NULL,
PRIMARY KEY (`client_id`, `auth_method`),
FOREIGN KEY (`client_id`) REFERENCES `oauth2_client`(`id`)
);
CREATE TABLE `user` ( CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, `id` bigint(20) NOT NULL AUTO_INCREMENT,
`primary_identity` bigint(20) DEFAULT NULL, `primary_identity` bigint(20) DEFAULT NULL,
......
<?php
include 'include/header.php';
?>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<h1 class="text-center">Admin panel</h1>
<div id="admin-vue">
<button class="btn btn-success pull-right" v-on:click="addNewOAuth2Client">
<span class="glyphicon glyphicon-plus-sign"></span> Add client
</button>
<h2>OAuth2 clients</h2>
<br/>
<div class="panel panel-default" v-for="(client, index) in oauth2Clients">
<div class="panel-heading">
<button class="btn btn-danger pull-right" v-on:click="askConfirmDeleteOAuth2Client(client, index)">
<span class="glyphicon glyphicon-trash"></span> Delete
</button>
<button class="btn btn-primary pull-right" v-on:click="editOAuth2Client(client)" v-if="!client.edit">
<span class="glyphicon glyphicon-pencil"></span> Edit
</button>
<button class="btn btn-primary pull-right" v-on:click="saveOAuth2Client(client, index)" v-if="client.edit">
<span class="glyphicon glyphicon-floppy-disk"></span> Save
</button>
<strong>{{client.name}}</strong>
</div>
<div class="panel-body">
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label" for="name">Name</label>
<div class="col-sm-9">
<p class="form-control-static" v-if="!client.edit">{{client.name}}</p>
<input type="text" class="form-control" id="name" v-model="client.name" v-if="client.edit" />
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Icon</label>
<div class="col-sm-9">
<p class="form-control-static" v-if="!client.edit">{{client.icon}}</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" for="client_id">Client id</label>
<div class="col-sm-9">
<p class="form-control-static" v-if="!client.edit">{{client.client}}</p>
<input type="text" class="form-control" id="client_id" v-model="client.client" v-if="client.edit" />
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" for="client_secret">Client Secret</label>
<div class="col-sm-9">
<p class="form-control-static" v-if="!client.edit">{{client.secret}}</p>
<input type="text" class="form-control" id="client_secret" v-model="client.secret" v-if="client.edit" />
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" for="redirect_url">Redirect URL</label>
<div class="col-sm-9">
<p class="form-control-static" v-if="!client.edit">{{client.redirectUrl}}</p>
<input type="text" class="form-control" id="redirect_url" v-model="client.redirectUrl" v-if="client.edit" />
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" for="scope">Scope</label>
<div class="col-sm-9">
<p class="form-control-static" v-if="!client.edit">{{client.scope}}</p>
<input type="text" class="form-control" id="scope" v-model="client.scope" v-if="client.edit" />
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" for="authenticationMethods">Authentication methods</label>
<div class="col-sm-9">
<p class="form-control-static" v-if="!client.edit">{{getAuthMethodsString(client.authMethods)}}</p>
<div v-if="client.edit">
<div class="checkbox">
<label>
<input type="checkbox" v-model="client.authMethods['eduGAIN']"> eduGAIN
</label>
<label>
<input type="checkbox" v-model="client.authMethods['Google']"> Google
</label>
<label>
<input type="checkbox" v-model="client.authMethods['Facebook']"> Facebook
</label>
<label>
<input type="checkbox" v-model="client.authMethods['LinkedIn']"> LinkedIn
</label>
<label>
<input type="checkbox" v-model="client.authMethods['X.509']"> X.509
</label>
<label>
<input type="checkbox" v-model="client.authMethods['LocalIdP']"> LocalIdP
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="js/admin.js"></script>
<div class="modal fade" tabindex="-1" role="dialog" id="confirm-delete-client-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Confirm action</h4>
</div>
<div class="modal-body">
<p>Are you sure that you want to delete the client <span id="client-to-delete"></span>?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="confirm-delete-client">Delete</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<?php
include 'include/footer.php';
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment