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

Added comments and documentation + security fix

parent 4a9d24b4
No related branches found
No related tags found
No related merge requests found
Showing
with 347 additions and 31 deletions
# RAP 2 # RAP IA2
## Installation ## Installation and configuration
Requirements:
* Apache httpd server (tested on Apache/2.4.6)
* PHP (5.4+), composer for dependecies
* MySQL/MariaDB (tested on MariaDB 5.5.52)
### PHP
Put RAP sources in `/var/www/html/rap-ia2`
For installing PHP dependencies run: For installing PHP dependencies run:
...@@ -8,7 +18,82 @@ For installing PHP dependencies run: ...@@ -8,7 +18,82 @@ For installing PHP dependencies run:
Install also the bcmath PHP package (used in X.509 parser). Install also the bcmath PHP package (used in X.509 parser).
To setup the database edit scripts in the sql folder and run them: ### MySQL
Create a dedicated database and user:
CREATE DATABASE rap;
CREATE USER rap@localhost IDENTIFIED BY 'XXXXXX';
GRANT ALL PRIVILEGES ON rap.* TO rap@localhost;
Enable the event scheduler:
* open MySQL configuration file (e.g. /etc/my.cnf)
* set `event_scheduler=1`
* restart MySQL
Then run the setup script:
mysql -u root -p < sql/setup-database.sql
### Apache (httpd)
* Configure a valid HTTPS certificate on the server
* Configure X.509 client certificate authentication:
<Directory /var/www/html/rap-ia2/auth/x509/>
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
allow from all
SSLVerifyClient require
SSLVerifyDepth 10
SSLOptions +ExportCertData
</Directory>
* Shibboleth authentication:
<Directory /var/www/html/rap-ia2/auth/saml2/>
AuthType shibboleth
ShibRequestSetting requireSession 1
Require valid-user
</Directory>
* Protect log directory:
<Directory /var/www/html/rap-ia2/logs/>
Order deny,allow
Deny From All
</Directory>
* Protect RAP Web Service in Basic-Auth:
<Location "/rap-ia2/ws">
AuthType basic
AuthName RAP
AuthUserFile apachepasswd
Require valid-user
</Location>
* Then creates a password file for RAP Web Service Basic-Auth:
* `cd /etc/httpd/`
* `htpasswd -c apachepasswd rap`
* The last command creates an hashed password for an user "rap" and store it in a file named apachepasswd.
* Finally, restart the Apache server.
### Social networks
Before using social API it is necessary to register an application on each social network and obtain API keys and secrets:
* https://console.developers.google.com
* https://www.linkedin.com/developer/apps
* https://developers.facebook.com/apps
### Configuration file
Copy the `config-example.php` into `config.php` and edit it for matching your needs.
## Additional information and developer guide
mysql -u root -p < sql/create-db-and-user.sql See the wiki: https://www.ict.inaf.it/gitlab/zorba/rap-ia2/wikis/home
mysql -u root -p rap < sql/create-tables.sql
...@@ -22,9 +22,12 @@ ...@@ -22,9 +22,12 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
/* This page uses the Facebook API for generating the redirect URL to use for Facebook login */
include '../../include/init.php'; include '../../include/init.php';
startSession(); startSession();
// Retrieve Facebook configuration
$Facebook = $AUTHENTICATION_METHODS['Facebook']; $Facebook = $AUTHENTICATION_METHODS['Facebook'];
$fb = new Facebook\Facebook([ $fb = new Facebook\Facebook([
...@@ -35,7 +38,8 @@ $fb = new Facebook\Facebook([ ...@@ -35,7 +38,8 @@ $fb = new Facebook\Facebook([
$helper = $fb->getRedirectLoginHelper(); $helper = $fb->getRedirectLoginHelper();
$permissions = ['email']; // Optional permissions $permissions = ['email']; // Optional permissions: we need user email
$loginUrl = $helper->getLoginUrl($Facebook['callback'], $permissions); $loginUrl = $helper->getLoginUrl($Facebook['callback'], $permissions);
header("Location: $loginUrl"); header("Location: $loginUrl");
......
...@@ -22,9 +22,12 @@ ...@@ -22,9 +22,12 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
/* Facebook callback page */
include '../../include/init.php'; include '../../include/init.php';
startSession(); startSession();
// Retrieve Facebook configuration
$Facebook = $AUTHENTICATION_METHODS['Facebook']; $Facebook = $AUTHENTICATION_METHODS['Facebook'];
$fb = new Facebook\Facebook([ $fb = new Facebook\Facebook([
...@@ -80,9 +83,11 @@ $fbUser = $response->getGraphUser(); ...@@ -80,9 +83,11 @@ $fbUser = $response->getGraphUser();
$typedId = $fbUser["id"]; $typedId = $fbUser["id"];
// Search if the user is already registered into RAP using the Facebook ID.
$user = $userHandler->findUserByIdentity(RAP\Identity::FACEBOOK, $typedId); $user = $userHandler->findUserByIdentity(RAP\Identity::FACEBOOK, $typedId);
if ($user === null) { if ($user === null) {
// Create new user
$user = new RAP\User(); $user = new RAP\User();
$identity = new RAP\Identity(RAP\Identity::FACEBOOK); $identity = new RAP\Identity(RAP\Identity::FACEBOOK);
......
...@@ -22,9 +22,12 @@ ...@@ -22,9 +22,12 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
/* Google redirect and callback page */
include '../../include/init.php'; include '../../include/init.php';
startSession(); startSession();
// Retrieve Google configuration
$Google = $AUTHENTICATION_METHODS['Google']; $Google = $AUTHENTICATION_METHODS['Google'];
$client = new Google_Client(array( $client = new Google_Client(array(
...@@ -53,7 +56,7 @@ if (isset($_GET['code'])) { ...@@ -53,7 +56,7 @@ if (isset($_GET['code'])) {
if ($client->getAccessToken()) { if ($client->getAccessToken()) {
// Query web service // Query web service for retrieving user information
$service = new Google_Service_People($client); $service = new Google_Service_People($client);
try { try {
...@@ -74,9 +77,11 @@ if ($client->getAccessToken()) { ...@@ -74,9 +77,11 @@ if ($client->getAccessToken()) {
$typedId = explode('/', $res->getResourceName())[1]; $typedId = explode('/', $res->getResourceName())[1];
// Search if the user is already registered into RAP using the Google ID.
$user = $userHandler->findUserByIdentity(RAP\Identity::GOOGLE, $typedId); $user = $userHandler->findUserByIdentity(RAP\Identity::GOOGLE, $typedId);
if ($user === null) { if ($user === null) {
// Create new user
$user = new RAP\User(); $user = new RAP\User();
$identity = new RAP\Identity(RAP\Identity::GOOGLE); $identity = new RAP\Identity(RAP\Identity::GOOGLE);
......
...@@ -22,9 +22,12 @@ ...@@ -22,9 +22,12 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
/* This page redirects to LinkedIn login page */
include '../../include/init.php'; include '../../include/init.php';
startSession(); startSession();
// Retrieve LinkedIn configuration
$LinkedIn = $AUTHENTICATION_METHODS['LinkedIn']; $LinkedIn = $AUTHENTICATION_METHODS['LinkedIn'];
$url = "https://www.linkedin.com/oauth/v2/authorization?response_type=code"; $url = "https://www.linkedin.com/oauth/v2/authorization?response_type=code";
......
...@@ -22,9 +22,12 @@ ...@@ -22,9 +22,12 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
/* LinkedIn callback page. Curl is used, because LinkedIn doesn't provide official PHP API. */
include '../../include/init.php'; include '../../include/init.php';
startSession(); startSession();
// Retrieve LinkedIn configuration
$LinkedIn = $AUTHENTICATION_METHODS['LinkedIn']; $LinkedIn = $AUTHENTICATION_METHODS['LinkedIn'];
if (!isset($_REQUEST['code'])) { if (!isset($_REQUEST['code'])) {
...@@ -100,9 +103,11 @@ if ($info2['http_code'] === 200) { ...@@ -100,9 +103,11 @@ if ($info2['http_code'] === 200) {
$typedId = $data['id']; $typedId = $data['id'];
// Search if the user is already registered into RAP using the LinkedIn ID.
$user = $userHandler->findUserByIdentity(RAP\Identity::LINKEDIN, $typedId); $user = $userHandler->findUserByIdentity(RAP\Identity::LINKEDIN, $typedId);
if ($user === null) { if ($user === null) {
// Create new user
$user = new RAP\User(); $user = new RAP\User();
$identity = new RAP\Identity(RAP\Identity::LINKEDIN); $identity = new RAP\Identity(RAP\Identity::LINKEDIN);
......
...@@ -22,16 +22,29 @@ ...@@ -22,16 +22,29 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
/* This page MUST be protected by Shibboleth authentication
* On Apache httpd:
* AuthType shibboleth
* ShibRequestSetting requireSession 1
* Require valid-user
*/
include '../../include/init.php'; include '../../include/init.php';
startSession(); startSession();
if (isset($_SERVER['Shib-Session-ID'])) { if (isset($_SERVER['Shib-Session-ID'])) {
// Retrieving eduPersonPrincipalName (eppn)
$eppn = $_SERVER['eppn']; $eppn = $_SERVER['eppn'];
// Search if the user is already registered into RAP using the eppn.
// The persistent id should be a more appropriate identifier, however at IA2
// we need to import all INAF user into RAP, even if they will never register,
// and in that case we know only their eppn.
$user = $userHandler->findUserByIdentity(RAP\Identity::EDU_GAIN, $eppn); $user = $userHandler->findUserByIdentity(RAP\Identity::EDU_GAIN, $eppn);
if ($user === null) { if ($user === null) {
// Creating a new user
$user = new RAP\User(); $user = new RAP\User();
$identity = new RAP\Identity(RAP\Identity::EDU_GAIN); $identity = new RAP\Identity(RAP\Identity::EDU_GAIN);
......
...@@ -22,6 +22,12 @@ ...@@ -22,6 +22,12 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
/* This page must be protected by client certificate authentication
* On Apache httpd:
* SSLVerifyClient require
* SSLVerifyDepth 10
* SSLOptions +ExportCertData
*/
include '../../include/init.php'; include '../../include/init.php';
startSession(); startSession();
...@@ -44,10 +50,20 @@ function saveUserFromX509Data($x509Data) { ...@@ -44,10 +50,20 @@ function saveUserFromX509Data($x509Data) {
$userHandler->saveUser($user); $userHandler->saveUser($user);
$session->x509DataToRegister = null; $session->x509DataToRegister = null;
$session->save();
return $user; return $user;
} }
/**
* We want to extract name and surname from the X.509 certificate, however X.509
* puts name and surname together (inside the CN field).
* If name and surname are single words it is possible to retrieve them splitting
* on the space character, otherwise the user has to choose the correct combination.
* In that case partial X.509 data is temporarily stored into the user session and
* the page views/x509-name-surname.php is shown to the user before completing the
* registration, in order to allow him/her selecting the correct name and surname.
*/
if ($session->x509DataToRegister !== null && $session->x509DataToRegister->name !== null) { if ($session->x509DataToRegister !== null && $session->x509DataToRegister->name !== null) {
$user = saveUserFromX509Data($session->x509DataToRegister); $user = saveUserFromX509Data($session->x509DataToRegister);
......
...@@ -24,6 +24,9 @@ ...@@ -24,6 +24,9 @@
namespace RAP; namespace RAP;
/**
* Manage callback URL validation and redirection
*/
class CallbackHandler { class CallbackHandler {
private $dao; private $dao;
...@@ -49,7 +52,11 @@ class CallbackHandler { ...@@ -49,7 +52,11 @@ class CallbackHandler {
} }
/** /**
* returns null if the callback URL is not listed in configuration file. * Each callback has a title and a logo in order to avoid confusion in users
* and show in which application they are logging in using RAP.
* @param type $callbackURL
* @return type the callback title or null if the callback URL is not listed
* in configuration file or it doesn't have a title.
*/ */
public function getCallbackTitle($callbackURL) { public function getCallbackTitle($callbackURL) {
...@@ -62,6 +69,13 @@ class CallbackHandler { ...@@ -62,6 +69,13 @@ class CallbackHandler {
return null; return null;
} }
/**
* Each callback has a title and a logo in order to avoid confusion in users
* and show in which application they are logging in using RAP.
* @param type $callbackURL
* @return type the callback logo or null if the callback URL is not listed
* in configuration file or it doesn't have a logo.
*/
public function getCallbackLogo($callbackURL) { public function getCallbackLogo($callbackURL) {
foreach ($this->callbacks as $callback) { foreach ($this->callbacks as $callback) {
......
...@@ -24,46 +24,104 @@ ...@@ -24,46 +24,104 @@
namespace RAP; namespace RAP;
/**
* Data Access Object interface for accessing the RAP database.
* Current implementations: RAP\MySQLDAO
*/
interface DAO { interface DAO {
/**
* @return type PDO object for accessing the database
*/
function getDBHandler(); function getDBHandler();
/**
* Store a new login token into the database.
* @param type $token login token
* @param type $userId
*/
function createLoginToken($token, $userId); function createLoginToken($token, $userId);
/**
* Retrieve the user ID from the login token.
* @param type $token
* @return type user ID
*/
function findLoginToken($token); function findLoginToken($token);
/**
* Delete a login token from the database. This happens when the caller
* application has received the token and used it for retrieving user
* information from the token using the RAP REST web service.
* @param type $token login token
*/
function deleteLoginToken($token); function deleteLoginToken($token);
/** /**
* Return the new identity ID. * Create a new identity.
* @param type $userId the user ID associated to that identity
* @return type the new identity ID
*/ */
function insertIdentity(Identity $identity, $userId); function insertIdentity(Identity $identity, $userId);
/** /**
* Return the new user ID. * Create a new user.
* @return the new user ID.
*/ */
function createUser(); function createUser();
/**
* @return RAP\User an user object, null if nothing was found.
*/
function findUserById($userId); function findUserById($userId);
function setPrimaryIdentity($userId, $identityId); function setPrimaryIdentity($userId, $identityId);
/** /**
* Return a User object, null if nothing was found. * Retrieve the user associated to a given identity using the typedId.
* @param type $type Identity type (EDU_GAIN, X509, GOOGLE, ...) * @param type $type Identity type (EDU_GAIN, X509, GOOGLE, ...)
* @param type $identifier value used to search the identity in the database * @param type $typedId typed unique value used to search the identity in the database
* @return RAP\User an user object, null if nothing was found.
*/ */
function findUserByIdentity($type, $identifier); function findUserByIdentity($type, $typedId);
/**
* Retrieve a set of users matching a given search text.
* @param type $searchText name, surname or email
* @return list of RAP\User objects
*/
function searchUser($searchText); function searchUser($searchText);
/**
* Store into the database information about a new join request.
* @param type $token join token
* @param type $applicantUserId the user asking for the join
* @param type $targetUserId the user target of the join
*/
function createJoinRequest($token, $applicantUserId, $targetUserId); function createJoinRequest($token, $applicantUserId, $targetUserId);
/**
* Retrieve join request information.
* @param type $token join token
* @return an array of 2 elements having the applicant user id at the first
* position and the target user id at the second position; null if nothing
* was found.
* @throws Exception if multiple requests has been found for the same token.
*/
function findJoinRequest($token); function findJoinRequest($token);
function deleteUser($userId); /**
* Perform a join request.
* @param type $userId1 the user that will receive all identities
* @param type $userId2 the user that will lost the identities and will be
* deleted from the database
*/
function joinUsers($userId1, $userId2); function joinUsers($userId1, $userId2);
/**
* When a join action is performed the join request data (join token and user
* identifiers) needs to be removed from the database.
* @param type $token join token
*/
function deleteJoinRequest($token); function deleteJoinRequest($token);
} }
...@@ -24,6 +24,9 @@ ...@@ -24,6 +24,9 @@
namespace RAP; namespace RAP;
/**
* Data model for identities.
*/
class Identity { class Identity {
const EDU_GAIN = "eduGAIN"; const EDU_GAIN = "eduGAIN";
...@@ -31,9 +34,8 @@ class Identity { ...@@ -31,9 +34,8 @@ class Identity {
const GOOGLE = "Google"; const GOOGLE = "Google";
const FACEBOOK = "Facebook"; const FACEBOOK = "Facebook";
const LINKEDIN = "LinkedIn"; const LINKEDIN = "LinkedIn";
const LOCAL = "Local";
private static $ALLOWED_TYPES = [Identity::EDU_GAIN, Identity::X509, Identity::GOOGLE, Identity::FACEBOOK, Identity::LINKEDIN, Identity::LOCAL]; private static $ALLOWED_TYPES = [Identity::EDU_GAIN, Identity::X509, Identity::GOOGLE, Identity::FACEBOOK, Identity::LINKEDIN];
/** /**
* Identity id in the database. Mandatory field. * Identity id in the database. Mandatory field.
...@@ -46,23 +48,27 @@ class Identity { ...@@ -46,23 +48,27 @@ class Identity {
public $type; public $type;
/** /**
* Data related to specific account type (shibboleth persistent id, facebook id, etc, ...). Mandatory field. * Data related to specific account type (shibboleth persistent id,
* facebook id, certificate serial number, etc, ...). Mandatory field.
*/ */
public $typedId; public $typedId;
/** /**
* Primary email related to this identity. Mandatory field. * Primary email related to this identity. Mandatory field.
* User can have additional email addresses. These are stored into User class. * @todo Maybe an user can have additional email addresses (e.g.: Google
* API provides them). Should we store them somewhere?
*/ */
public $email; public $email;
/** /**
* First name * First name.
* This should be mandatory, however for old IA2 users we have only email address.
*/ */
public $name; public $name;
/** /**
* Last name / Family name * Last name / Family name
* This should be mandatory, however for old IA2 users we have only email address.
*/ */
public $surname; public $surname;
...@@ -73,6 +79,9 @@ class Identity { ...@@ -73,6 +79,9 @@ class Identity {
/** /**
* For eduGAIN identities. * For eduGAIN identities.
* This is currently the same as the typedId for eduGAIN identity types, because
* at IA2 we need this (see the wiki). Use the Shibboleth persistent id should
* be a more appropriate typedId for these cases.
*/ */
public $eppn; public $eppn;
......
...@@ -24,6 +24,10 @@ ...@@ -24,6 +24,10 @@
namespace RAP; namespace RAP;
/**
* Manage mail sending.
* Currently used only for join email messages.
*/
class MailSender { class MailSender {
private $serverName; private $serverName;
...@@ -34,6 +38,13 @@ class MailSender { ...@@ -34,6 +38,13 @@ class MailSender {
$this->basePath = $basePath; $this->basePath = $basePath;
} }
/**
* Send the email for confirming the join request.
* @param \RAP\User $recipientUser user target of the join requests: he/she
* will receive the email containing the confirmation link
* @param \RAP\User $applicantUser user that have requested the join
* @param string $token the join token
*/
public function sendJoinEmail(User $recipientUser, User $applicantUser, $token) { public function sendJoinEmail(User $recipientUser, User $applicantUser, $token) {
$subject = "IA2 RAP: Join request"; $subject = "IA2 RAP: Join request";
......
...@@ -26,6 +26,9 @@ namespace RAP; ...@@ -26,6 +26,9 @@ namespace RAP;
use PDO; use PDO;
/**
* MySQL implementation of the DAO interface. See comments on the DAO interface.
*/
class MySQLDAO implements DAO { class MySQLDAO implements DAO {
private $config; private $config;
...@@ -289,14 +292,6 @@ class MySQLDAO implements DAO { ...@@ -289,14 +292,6 @@ class MySQLDAO implements DAO {
} }
} }
public function deleteUser($userId) {
$dbh = $this->getDBHandler();
$stmt3 = $dbh->prepare("DELETE FROM TABLE `user` WHERE `id` = :id2");
$stmt3->bindParam(':id2', $userId);
$stmt3->execute();
}
public function joinUsers($userId1, $userId2) { public function joinUsers($userId1, $userId2) {
$dbh = $this->getDBHandler(); $dbh = $this->getDBHandler();
......
...@@ -24,6 +24,10 @@ ...@@ -24,6 +24,10 @@
namespace RAP; namespace RAP;
/**
* This class wraps various objects that need to be stored into the session in
* order to provide an object oriented transparent session management.
*/
class SessionData { class SessionData {
private $dao; private $dao;
...@@ -34,14 +38,26 @@ class SessionData { ...@@ -34,14 +38,26 @@ class SessionData {
public $userSearchResults; public $userSearchResults;
public $x509DataToRegister; public $x509DataToRegister;
/**
* @todo: move DAO away from here
*/
public function __construct(DAO $dao) { public function __construct(DAO $dao) {
$this->dao = $dao; $this->dao = $dao;
} }
/**
* Store the data into the $_SESSION PHP variable
*/
public function save() { public function save() {
$_SESSION['SessionData'] = $this; $_SESSION['SessionData'] = $this;
} }
/**
* Retrieve the SessionData object from the $_SESSION PHP variable. Create a
* new one if it is necessary.
* @param \RAP\DAO $dao
* @return \RAP\SessionData the SessionData object
*/
public static function get(DAO $dao) { public static function get(DAO $dao) {
if (!isset($_SESSION['SessionData'])) { if (!isset($_SESSION['SessionData'])) {
...@@ -70,6 +86,12 @@ class SessionData { ...@@ -70,6 +86,12 @@ class SessionData {
return $this->callbackLogo; return $this->callbackLogo;
} }
/**
* Perform a user search and store the results inside the session. This is
* used for achieving the user selection using the dropdown menu in the join
* request modal.
* @param string $searchText
*/
public function searchUser($searchText) { public function searchUser($searchText) {
$users = $this->dao->searchUser($searchText); $users = $this->dao->searchUser($searchText);
...@@ -85,6 +107,12 @@ class SessionData { ...@@ -85,6 +107,12 @@ class SessionData {
$this->save(); $this->save();
} }
/**
* Update the user data model stored into the session after the primary
* identity has changed, in order to avoid reading again the user data from
* the database.
* @param int $identityId
*/
public function updatePrimaryIdentity($identityId) { public function updatePrimaryIdentity($identityId) {
foreach ($this->user->identities as $identity) { foreach ($this->user->identities as $identity) {
$identity->primary = ($identity->id === $identityId); $identity->primary = ($identity->id === $identityId);
......
...@@ -24,9 +24,14 @@ ...@@ -24,9 +24,14 @@
namespace RAP; namespace RAP;
/**
* Data model for the user. An user is a set of identities.
*/
class User { class User {
// User ID
public $id; public $id;
// List of identities
public $identities; public $identities;
public function __construct() { public function __construct() {
...@@ -43,6 +48,7 @@ class User { ...@@ -43,6 +48,7 @@ class User {
return $identity->email; return $identity->email;
} }
} }
// A primary identity MUST be defined
throw new \Exception("No primary identity defined for user " . $this->id); throw new \Exception("No primary identity defined for user " . $this->id);
} }
......
...@@ -24,6 +24,9 @@ ...@@ -24,6 +24,9 @@
namespace RAP; namespace RAP;
/**
* Perform operations on users.
*/
class UserHandler { class UserHandler {
private $dao; private $dao;
...@@ -34,6 +37,11 @@ class UserHandler { ...@@ -34,6 +37,11 @@ class UserHandler {
$this->grouperConfig = $grouperConfig; $this->grouperConfig = $grouperConfig;
} }
/**
* Update user information into the database, creating a new user or adding
* new identities to it.
* @param \RAP\User $user
*/
public function saveUser(User $user) { public function saveUser(User $user) {
$primarySpecified = true; $primarySpecified = true;
...@@ -60,6 +68,11 @@ class UserHandler { ...@@ -60,6 +68,11 @@ class UserHandler {
return $this->dao->findUserByIdentity($type, $identifier); return $this->dao->findUserByIdentity($type, $identifier);
} }
/**
* Build an URL for the web service endpoint that needs to be called in order
* to move groups from one user to the other during a join operation.
* @return string grouper URL for the PrepareToJoinServlet
*/
private function getJoinURL() { private function getJoinURL() {
$joinURL = $this->grouperConfig['wsURL']; $joinURL = $this->grouperConfig['wsURL'];
...@@ -73,6 +86,7 @@ class UserHandler { ...@@ -73,6 +86,7 @@ class UserHandler {
public function joinUsers($userId1, $userId2) { public function joinUsers($userId1, $userId2) {
// Call Grouper for moving groups and privileges from one user to the other
if ($this->grouperConfig !== null) { if ($this->grouperConfig !== null) {
//create cURL connection //create cURL connection
...@@ -103,6 +117,7 @@ class UserHandler { ...@@ -103,6 +117,7 @@ class UserHandler {
} }
} }
// Call DAO for performing join operation into the RAP database.
$this->dao->joinUsers($userId1, $userId2); $this->dao->joinUsers($userId1, $userId2);
} }
......
...@@ -24,9 +24,19 @@ ...@@ -24,9 +24,19 @@
namespace RAP; namespace RAP;
/**
* Data model representing an item of the result of an user search. This is used
* in order to display the dropdown menu for user selection in the join modal
* dialog avoiding exposing the user identifiers in the AJAX calls.
* This data is stored into a list inside the SessionData object. The user will
* select one of the item by index and not by the user ID.
*/
class UserSearchResult { class UserSearchResult {
// The user object is wrapped by this class and hidden to AJAX.
private $user; private $user;
// Only this text is returned to the AJAX call.
// See gui-backend.php (/user?search endpoint)
public $userDisplayText; public $userDisplayText;
public static function buildFromUser(User $user) { public static function buildFromUser(User $user) {
......
...@@ -29,6 +29,9 @@ namespace RAP; ...@@ -29,6 +29,9 @@ namespace RAP;
*/ */
class Util { class Util {
/**
* @return string random string
*/
public static function createNewToken() { public static function createNewToken() {
// Credits: http://stackoverflow.com/a/18890309/771431 // Credits: http://stackoverflow.com/a/18890309/771431
return bin2hex(openssl_random_pseudo_bytes(16)); return bin2hex(openssl_random_pseudo_bytes(16));
......
...@@ -24,18 +24,27 @@ ...@@ -24,18 +24,27 @@
namespace RAP; namespace RAP;
/**
* Parse X.509 certificate extracting serial number, email, name, surname and institution.
* Because the certificate puts name and surname together, when name and surname are
* composed by more than two words this class returns a partial result containing
* possible alternatives.
*/
class X509Data { class X509Data {
public $email; public $email;
public $fullName;
public $institution; public $institution;
public $serialNumber; public $serialNumber;
// name(s) and surname, space separated
public $fullName;
public $name; public $name;
public $surname; public $surname;
// list of possible names: this is populated when fullName has more than two words
public $candidateNames; public $candidateNames;
/** /**
* Retrieve full name from CN, removing the e-mail if necessary * Retrieve full name of the person (name+surname) from CN,
* removing the e-mail if necessary.
*/ */
private function parseCN($cn) { private function parseCN($cn) {
$cnSplit = explode(" ", $cn); $cnSplit = explode(" ", $cn);
...@@ -64,6 +73,10 @@ class X509Data { ...@@ -64,6 +73,10 @@ class X509Data {
} }
} }
/**
* Extract data in a simpler way using openssl_x509_parse PHP function.
* @param type $sslClientCert $_SERVER['SSL_CLIENT_CERT']
*/
private function parseUsingOpenSSL($sslClientCert) { private function parseUsingOpenSSL($sslClientCert) {
$parsedX509 = openssl_x509_parse($sslClientCert); $parsedX509 = openssl_x509_parse($sslClientCert);
...@@ -85,6 +98,8 @@ class X509Data { ...@@ -85,6 +98,8 @@ class X509Data {
} }
/** /**
* The serial number is too big to be converted from hex to dec using the
* builtin hexdec function, so BC Math functions are used.
* Credits: https://stackoverflow.com/a/1273535/771431 * Credits: https://stackoverflow.com/a/1273535/771431
*/ */
private static function bchexdec($hex) { private static function bchexdec($hex) {
...@@ -96,6 +111,9 @@ class X509Data { ...@@ -96,6 +111,9 @@ class X509Data {
return $dec; return $dec;
} }
/**
* Populate name and surname or candidateNames variables
*/
private function fillNameAndSurnameOrCandidates() { private function fillNameAndSurnameOrCandidates() {
$nameSplit = explode(' ', $this->fullName); $nameSplit = explode(' ', $this->fullName);
...@@ -117,12 +135,25 @@ class X509Data { ...@@ -117,12 +135,25 @@ class X509Data {
} }
} }
/**
* This function is called when the user select the correct candidate name.
* Surname is calculated as the remaining string.
* @param type $candidateNameIndex the index of the selected element into
* the candidateNames list
*/
public function selectCandidateName($candidateNameIndex) { public function selectCandidateName($candidateNameIndex) {
$candidateName = $this->candidateNames[$candidateNameIndex]; $candidateName = $this->candidateNames[$candidateNameIndex];
$this->name = $candidateName; $this->name = $candidateName;
$this->surname = substr($this->fullName, strlen($candidateName) + 1); $this->surname = substr($this->fullName, strlen($candidateName) + 1);
} }
/**
* Extract client certificate data needed by RAP from PHP $_SERVER variable.
* @param type $server PHP $_SERVER variable. This is passed as parameter in order
* to make class testable (mocking the $_SERVER variable). Tests have not
* been written yet.
* @return \RAP\X509Data
*/
public static function parse($server) { public static function parse($server) {
$parsedData = new X509Data(); $parsedData = new X509Data();
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
*/ */
$CONTEXT_ROOT = "/rap-ia2"; $CONTEXT_ROOT = "/rap-ia2";
$VERSION = "1.0.1"; $VERSION = "1.0.2";
$PROTOCOL = stripos($_SERVER['SERVER_PROTOCOL'], 'https') ? 'https://' : 'http://'; $PROTOCOL = stripos($_SERVER['SERVER_PROTOCOL'], 'https') ? 'https://' : 'http://';
$BASE_PATH = $PROTOCOL . $_SERVER['HTTP_HOST'] . $CONTEXT_ROOT; $BASE_PATH = $PROTOCOL . $_SERVER['HTTP_HOST'] . $CONTEXT_ROOT;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment