diff --git a/README.md b/README.md
index 99cf896e6bcc09502d53100f30415da85bded2fc..4e425b9dfc2834a7e48968661e929c654a55ff26 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,16 @@
-# 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:
 
@@ -8,7 +18,82 @@ For installing PHP dependencies run:
 
 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
-    mysql -u root -p rap < sql/create-tables.sql
+See the wiki: https://www.ict.inaf.it/gitlab/zorba/rap-ia2/wikis/home
diff --git a/auth/oauth2/facebook_login.php b/auth/oauth2/facebook_login.php
index e6d59870a30602330fb5c82222bf88f889163354..844cf9f6a6dc8c677f1f43c1229a89e949d3bda3 100755
--- a/auth/oauth2/facebook_login.php
+++ b/auth/oauth2/facebook_login.php
@@ -22,9 +22,12 @@
  * 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';
 startSession();
 
+// Retrieve Facebook configuration
 $Facebook = $AUTHENTICATION_METHODS['Facebook'];
 
 $fb = new Facebook\Facebook([
@@ -35,7 +38,8 @@ $fb = new Facebook\Facebook([
 
 $helper = $fb->getRedirectLoginHelper();
 
-$permissions = ['email']; // Optional permissions
+$permissions = ['email']; // Optional permissions: we need user email
+
 $loginUrl = $helper->getLoginUrl($Facebook['callback'], $permissions);
 
 header("Location: $loginUrl");
diff --git a/auth/oauth2/facebook_token.php b/auth/oauth2/facebook_token.php
index bd431656877232218597121b121086f3c0d85f09..d4383144134ff3d465558bc1a6d6bbd62827a719 100755
--- a/auth/oauth2/facebook_token.php
+++ b/auth/oauth2/facebook_token.php
@@ -22,9 +22,12 @@
  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+/* Facebook callback page */
+
 include '../../include/init.php';
 startSession();
 
+// Retrieve Facebook configuration
 $Facebook = $AUTHENTICATION_METHODS['Facebook'];
 
 $fb = new Facebook\Facebook([
@@ -80,9 +83,11 @@ $fbUser = $response->getGraphUser();
 
 $typedId = $fbUser["id"];
 
+// Search if the user is already registered into RAP using the Facebook ID.
 $user = $userHandler->findUserByIdentity(RAP\Identity::FACEBOOK, $typedId);
 
 if ($user === null) {
+    // Create new user
     $user = new RAP\User();
 
     $identity = new RAP\Identity(RAP\Identity::FACEBOOK);
diff --git a/auth/oauth2/google_token.php b/auth/oauth2/google_token.php
index 8f056d5c5d17e0d88946f3dc0e6e426bf6c5274d..c5a5bd43c175baf3abfa8b5e03cd7c193be1e7d0 100644
--- a/auth/oauth2/google_token.php
+++ b/auth/oauth2/google_token.php
@@ -22,9 +22,12 @@
  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+/* Google redirect and callback page */
+
 include '../../include/init.php';
 startSession();
 
+// Retrieve Google configuration
 $Google = $AUTHENTICATION_METHODS['Google'];
 
 $client = new Google_Client(array(
@@ -53,7 +56,7 @@ if (isset($_GET['code'])) {
 
 if ($client->getAccessToken()) {
 
-    // Query web service
+    // Query web service for retrieving user information
     $service = new Google_Service_People($client);
 
     try {
@@ -74,9 +77,11 @@ if ($client->getAccessToken()) {
 
     $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);
 
     if ($user === null) {
+        // Create new user
         $user = new RAP\User();
 
         $identity = new RAP\Identity(RAP\Identity::GOOGLE);
diff --git a/auth/oauth2/linkedin_login.php b/auth/oauth2/linkedin_login.php
index df940e81b57d84b22a99132e701924b203e30d74..2c64969c6e03d78549090327a7af3cc67fa5b61a 100644
--- a/auth/oauth2/linkedin_login.php
+++ b/auth/oauth2/linkedin_login.php
@@ -22,9 +22,12 @@
  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+/* This page redirects to LinkedIn login page */
+
 include '../../include/init.php';
 startSession();
 
+// Retrieve LinkedIn configuration
 $LinkedIn = $AUTHENTICATION_METHODS['LinkedIn'];
 
 $url = "https://www.linkedin.com/oauth/v2/authorization?response_type=code";
diff --git a/auth/oauth2/linkedin_token.php b/auth/oauth2/linkedin_token.php
index e38700ab76a7e4f19e32988de46ffe62936d3374..fde3cb8c203e2364e7241ec2d677b0255e5986bf 100644
--- a/auth/oauth2/linkedin_token.php
+++ b/auth/oauth2/linkedin_token.php
@@ -22,9 +22,12 @@
  * 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';
 startSession();
 
+// Retrieve LinkedIn configuration
 $LinkedIn = $AUTHENTICATION_METHODS['LinkedIn'];
 
 if (!isset($_REQUEST['code'])) {
@@ -100,9 +103,11 @@ if ($info2['http_code'] === 200) {
 
     $typedId = $data['id'];
 
+    // Search if the user is already registered into RAP using the LinkedIn ID.
     $user = $userHandler->findUserByIdentity(RAP\Identity::LINKEDIN, $typedId);
 
     if ($user === null) {
+        // Create new user
         $user = new RAP\User();
 
         $identity = new RAP\Identity(RAP\Identity::LINKEDIN);
diff --git a/auth/saml2/aai.php b/auth/saml2/aai.php
index 59723e06f1b1a441606f7be8c9e11e05a8d1f173..cd89610c0b7fe03824153addadeb37c215e77b99 100644
--- a/auth/saml2/aai.php
+++ b/auth/saml2/aai.php
@@ -22,16 +22,29 @@
  * 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';
 startSession();
 
 if (isset($_SERVER['Shib-Session-ID'])) {
 
+    // Retrieving eduPersonPrincipalName (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);
 
     if ($user === null) {
+        // Creating a new user
         $user = new RAP\User();
 
         $identity = new RAP\Identity(RAP\Identity::EDU_GAIN);
diff --git a/auth/x509/certlogin.php b/auth/x509/certlogin.php
index ba9c20e807b0c6c549994b2cd2000f9524e96b2d..61085fe22ce60690a405dbf0db00c3c87900dd73 100644
--- a/auth/x509/certlogin.php
+++ b/auth/x509/certlogin.php
@@ -22,6 +22,12 @@
  * 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';
 startSession();
@@ -44,10 +50,20 @@ function saveUserFromX509Data($x509Data) {
     $userHandler->saveUser($user);
 
     $session->x509DataToRegister = null;
+    $session->save();
 
     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) {
 
     $user = saveUserFromX509Data($session->x509DataToRegister);
diff --git a/classes/CallbackHandler.php b/classes/CallbackHandler.php
index 44840615d1e9525303afce2ef40d5cc9f4ce149b..dfd6c5d935ea772fb6bbe867ec4ad96615fbf7a4 100644
--- a/classes/CallbackHandler.php
+++ b/classes/CallbackHandler.php
@@ -24,6 +24,9 @@
 
 namespace RAP;
 
+/**
+ * Manage callback URL validation and redirection
+ */
 class CallbackHandler {
 
     private $dao;
@@ -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) {
 
@@ -62,6 +69,13 @@ class CallbackHandler {
         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) {
 
         foreach ($this->callbacks as $callback) {
diff --git a/classes/DAO.php b/classes/DAO.php
index 9a28d92f582d5dd429beb67264522c8941364b2b..c5d5039b41e0661053affd93f8f5f9e64ea9edd3 100644
--- a/classes/DAO.php
+++ b/classes/DAO.php
@@ -24,46 +24,104 @@
 
 namespace RAP;
 
+/**
+ * Data Access Object interface for accessing the RAP database.
+ * Current implementations: RAP\MySQLDAO
+ */
 interface DAO {
 
+    /**
+     * @return type PDO object for accessing the database
+     */
     function getDBHandler();
 
+    /**
+     * Store a new login token into the database.
+     * @param type $token login token
+     * @param type $userId
+     */
     function createLoginToken($token, $userId);
 
+    /**
+     * Retrieve the user ID from the login token.
+     * @param type $token
+     * @return type user ID
+     */
     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);
 
     /**
-     * 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);
 
     /**
-     * Return the new user ID.
+     * Create a new user.
+     * @return the new user ID.
      */
     function createUser();
 
+    /**
+     * @return RAP\User an user object, null if nothing was found.
+     */
     function findUserById($userId);
 
     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 $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);
 
+    /**
+     * 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);
 
+    /**
+     * 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 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);
 
+    /**
+     * 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);
 }
diff --git a/classes/Identity.php b/classes/Identity.php
index 500a1aafe2be3183dc3f1c0bd013e607433d34bd..7710595fe534008d4fc00b0cdfb05c53971fb6cc 100644
--- a/classes/Identity.php
+++ b/classes/Identity.php
@@ -24,6 +24,9 @@
 
 namespace RAP;
 
+/**
+ * Data model for identities.
+ */
 class Identity {
 
     const EDU_GAIN = "eduGAIN";
@@ -31,9 +34,8 @@ class Identity {
     const GOOGLE = "Google";
     const FACEBOOK = "Facebook";
     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.
@@ -46,23 +48,27 @@ class Identity {
     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;
 
     /**
      * 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;
 
     /**
-     * First name
+     * First name.
+     * This should be mandatory, however for old IA2 users we have only email address.
      */
     public $name;
 
     /**
      * Last name / Family name
+     * This should be mandatory, however for old IA2 users we have only email address.
      */
     public $surname;
 
@@ -73,6 +79,9 @@ class Identity {
 
     /**
      * 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;
 
diff --git a/classes/MailSender.php b/classes/MailSender.php
index 67742f43c7330f6abadf9ed19a149c8ced4c08b9..deca8f7bf27fe4efef888a98cf1fe278a0fdc6ed 100644
--- a/classes/MailSender.php
+++ b/classes/MailSender.php
@@ -24,6 +24,10 @@
 
 namespace RAP;
 
+/**
+ * Manage mail sending.
+ * Currently used only for join email messages.
+ */
 class MailSender {
 
     private $serverName;
@@ -34,6 +38,13 @@ class MailSender {
         $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) {
 
         $subject = "IA2 RAP: Join request";
diff --git a/classes/MySQLDAO.php b/classes/MySQLDAO.php
index 03b492c7fe713d4daa83b04f0e915ddd78ced404..c139a0587e0bdd1c6160b0959f00eff4f66df859 100644
--- a/classes/MySQLDAO.php
+++ b/classes/MySQLDAO.php
@@ -26,6 +26,9 @@ namespace RAP;
 
 use PDO;
 
+/**
+ * MySQL implementation of the DAO interface. See comments on the DAO interface.
+ */
 class MySQLDAO implements DAO {
 
     private $config;
@@ -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) {
         $dbh = $this->getDBHandler();
 
diff --git a/classes/SessionData.php b/classes/SessionData.php
index 4b365a84254964525e4ba50a57a80cae9bbf4517..294e934230c1aebe482a566c4c7e474205c716fa 100644
--- a/classes/SessionData.php
+++ b/classes/SessionData.php
@@ -24,6 +24,10 @@
 
 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 {
 
     private $dao;
@@ -34,14 +38,26 @@ class SessionData {
     public $userSearchResults;
     public $x509DataToRegister;
 
+    /**
+     * @todo: move DAO away from here
+     */
     public function __construct(DAO $dao) {
         $this->dao = $dao;
     }
 
+    /**
+     * Store the data into the $_SESSION PHP variable
+     */
     public function save() {
         $_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) {
 
         if (!isset($_SESSION['SessionData'])) {
@@ -70,6 +86,12 @@ class SessionData {
         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) {
         $users = $this->dao->searchUser($searchText);
 
@@ -85,6 +107,12 @@ class SessionData {
         $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) {
         foreach ($this->user->identities as $identity) {
             $identity->primary = ($identity->id === $identityId);
diff --git a/classes/User.php b/classes/User.php
index e82db49d2f42c4a707db4eee35aa32fbbf02f82f..21967ff717e1ab17a1dccb49f73912a5c3072d5b 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -24,9 +24,14 @@
 
 namespace RAP;
 
+/**
+ * Data model for the user. An user is a set of identities.
+ */
 class User {
 
+    // User ID
     public $id;
+    // List of identities
     public $identities;
 
     public function __construct() {
@@ -43,6 +48,7 @@ class User {
                 return $identity->email;
             }
         }
+        // A primary identity MUST be defined
         throw new \Exception("No primary identity defined for user " . $this->id);
     }
 
diff --git a/classes/UserHandler.php b/classes/UserHandler.php
index 5f1d52d9ce225ef9ac9b7d05e82417698c6c95e2..4614a8f17b5004b7552c16e2a60eda7d56f8a76b 100644
--- a/classes/UserHandler.php
+++ b/classes/UserHandler.php
@@ -24,6 +24,9 @@
 
 namespace RAP;
 
+/**
+ * Perform operations on users.
+ */
 class UserHandler {
 
     private $dao;
@@ -34,6 +37,11 @@ class UserHandler {
         $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) {
 
         $primarySpecified = true;
@@ -60,6 +68,11 @@ class UserHandler {
         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() {
         $joinURL = $this->grouperConfig['wsURL'];
 
@@ -73,6 +86,7 @@ class UserHandler {
 
     public function joinUsers($userId1, $userId2) {
 
+        // Call Grouper for moving groups and privileges from one user to the other
         if ($this->grouperConfig !== null) {
 
             //create cURL connection
@@ -103,6 +117,7 @@ class UserHandler {
             }
         }
 
+        // Call DAO for performing join operation into the RAP database.
         $this->dao->joinUsers($userId1, $userId2);
     }
 
diff --git a/classes/UserSearchResult.php b/classes/UserSearchResult.php
index 685abc61b1932480412416e60b98f36b03cec843..c1d49d90c8284a12229685fddb6ba6e704b10f86 100644
--- a/classes/UserSearchResult.php
+++ b/classes/UserSearchResult.php
@@ -24,9 +24,19 @@
 
 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 {
 
+    // The user object is wrapped by this class and hidden to AJAX.
     private $user;
+    // Only this text is returned to the AJAX call.
+    // See gui-backend.php (/user?search endpoint)
     public $userDisplayText;
 
     public static function buildFromUser(User $user) {
diff --git a/classes/Util.php b/classes/Util.php
index 9a786b1a9f4aba5a7a057cc9250043daee978504..4879b9b08d3005625644a1736b2202b030093860 100644
--- a/classes/Util.php
+++ b/classes/Util.php
@@ -29,6 +29,9 @@ namespace RAP;
  */
 class Util {
 
+    /**
+     * @return string random string
+     */
     public static function createNewToken() {
         // Credits: http://stackoverflow.com/a/18890309/771431
         return bin2hex(openssl_random_pseudo_bytes(16));
diff --git a/classes/X509Data.php b/classes/X509Data.php
index 3c774111bd090605597f182aa384b3653eaf489e..bede4cdc4a9fa7e844fd53d51e49e5dbb65b73e5 100644
--- a/classes/X509Data.php
+++ b/classes/X509Data.php
@@ -24,18 +24,27 @@
 
 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 {
 
     public $email;
-    public $fullName;
     public $institution;
     public $serialNumber;
+    // name(s) and surname, space separated
+    public $fullName;
     public $name;
     public $surname;
+    // list of possible names: this is populated when fullName has more than two words
     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) {
         $cnSplit = explode(" ", $cn);
@@ -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) {
 
         $parsedX509 = openssl_x509_parse($sslClientCert);
@@ -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
      */
     private static function bchexdec($hex) {
@@ -96,6 +111,9 @@ class X509Data {
         return $dec;
     }
 
+    /**
+     * Populate name and surname or candidateNames variables
+     */
     private function fillNameAndSurnameOrCandidates() {
         $nameSplit = explode(' ', $this->fullName);
 
@@ -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) {
         $candidateName = $this->candidateNames[$candidateNameIndex];
         $this->name = $candidateName;
         $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) {
 
         $parsedData = new X509Data();
diff --git a/config-example.php b/config-example.php
index 8a66713140f79f84e89dce469264044cdf1a61b1..d9bf364413b12f053284be5c7ea233c8111eff5a 100644
--- a/config-example.php
+++ b/config-example.php
@@ -23,7 +23,7 @@
  */
 
 $CONTEXT_ROOT = "/rap-ia2";
-$VERSION = "1.0.1";
+$VERSION = "1.0.2";
 
 $PROTOCOL = stripos($_SERVER['SERVER_PROTOCOL'], 'https') ? 'https://' : 'http://';
 $BASE_PATH = $PROTOCOL . $_SERVER['HTTP_HOST'] . $CONTEXT_ROOT;
diff --git a/css/style.css b/css/style.css
index bdc99e9b4961043543d61226d7dd80977a5640db..021218b894fe81a217d6e3e322c0b01bc07b31b1 100644
--- a/css/style.css
+++ b/css/style.css
@@ -3,6 +3,7 @@ body {
     padding-bottom: 150px;
 }
 
+/* Waiting div overlay */
 .waiting {
     position: fixed;
     top: 0;
@@ -27,6 +28,7 @@ body {
     vertical-align: middle;
 }
 
+/* CSS animation for authentication method buttons in the RAP main page. */
 @keyframes home_pulse {
     from {
         transform: scale(1, 1);
@@ -37,6 +39,7 @@ body {
     }
 }
 
+/* Box containing one or more authentication methods in the RAP main page. */
 .home-box {
     display: inline-block;
     width: 240px;
diff --git a/include/front-controller.php b/include/front-controller.php
index 202806fc2992507d891b7834e9a2db5513805265..2d1c4056a88654aefad96b8d6e67b6ebac80a566 100644
--- a/include/front-controller.php
+++ b/include/front-controller.php
@@ -17,18 +17,24 @@ function setCallback($callback) {
     return $session->getCallbackURL();
 }
 
+/**
+ * Display the main page (authentication method selection) or the available
+ * services list if a valid callback is not found
+ */
 Flight::route('/', function() {
     startSession();
     $callback = setCallback(Flight::request()->data['callback']);
-    global $session, $callbackHandler, $BASE_PATH, $AUTHENTICATION_METHODS;
+    global $session, $callbackHandler, $BASE_PATH, $AUTHENTICATION_METHODS, $VERSION;
     if ($callback === null && $session->user === null) {
         Flight::render('services-list.php', array('title' => 'RAP',
+            'version' => $VERSION,
             'action' => $BASE_PATH . '/'));
     } else if ($callback !== null && $session->user !== null) {
         $redirectURL = $callbackHandler->getLoginWithTokenURL($session->user->id, $callback);
         Flight::redirect($redirectURL);
     } else {
         Flight::render('index.php', array('title' => 'RAP',
+            'version' => $VERSION,
             'session' => $session, 'auth' => $AUTHENTICATION_METHODS));
     }
 });
@@ -72,6 +78,9 @@ Flight::route('/direct', function() {
     sendAuthRedirect($AUTHENTICATION_METHODS['DirectIdP']['url']);
 });
 
+/**
+ * Render the join confirmation page (confirmation link received by email).
+ */
 Flight::route('GET /confirm-join', function() {
     $token = Flight::request()->query['token'];
 
@@ -80,7 +89,7 @@ Flight::route('GET /confirm-join', function() {
         die("Token not found");
     }
 
-    global $dao;
+    global $dao, $VERSION;
 
     $userIds = $dao->findJoinRequest($token);
     if ($userIds === null) {
@@ -92,11 +101,15 @@ Flight::route('GET /confirm-join', function() {
     $targetUser = $dao->findUserById($userIds[1]);
 
     Flight::render('confirm-join.php', array('title' => 'RAP',
+        'version' => $VERSION,
         'token' => $token,
         'applicantUser' => $applicantUser,
         'targetUser' => $targetUser));
 });
 
+/**
+ * Confirm a join and show the page containing the operation status.
+ */
 Flight::route('POST /confirm-join', function() {
 
     global $dao, $userHandler, $auditLog;
@@ -123,18 +136,24 @@ Flight::route('POST /confirm-join', function() {
     session_start();
     session_destroy();
 
-    global $BASE_PATH;
+    global $BASE_PATH, $VERSION;
     Flight::render('join-success.php', array('title' => 'Success - RAP Join Request',
+        'version' => $VERSION,
         'basePath' => $BASE_PATH));
 });
 
+/**
+ * Render the page for selecting th correct name and username from candidates
+ * list during a X.509 registration.
+ */
 Flight::route('GET /x509-name-surname', function() {
 
     startSession();
-    global $session, $BASE_PATH;
+    global $session, $BASE_PATH, $VERSION;
 
     if ($session->x509DataToRegister !== null && $session->x509DataToRegister->name === null) {
         Flight::render('x509-name-surname.php', array('title' => 'Select name and surname',
+            'version' => $VERSION,
             'fullName' => $session->x509DataToRegister->fullName,
             'candidateNames' => $session->x509DataToRegister->candidateNames));
     } else {
@@ -143,6 +162,10 @@ Flight::route('GET /x509-name-surname', function() {
     }
 });
 
+/**
+ * Complete the X.509 registration selecting the correct name and surname specified
+ * by the user.
+ */
 Flight::route('POST /submit-x509-name', function() {
 
     $selectedNameIndex = Flight::request()->data['selected-name'];
diff --git a/include/gui-backend.php b/include/gui-backend.php
index d29b905fe532b7216657c4a4c9e8b60dbffc7eae..44e3eda9384dd4bb5aa03b63734d7cdda528a41d 100644
--- a/include/gui-backend.php
+++ b/include/gui-backend.php
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * REST backend for JavaScript code.
+ * REST backend for JavaScript code (AJAX calls).
  */
 //
 
@@ -16,6 +16,9 @@ function checkSession() {
     }
 }
 
+/**
+ * This is called when an user search for other users into the join modal dialog.
+ */
 Flight::route('GET /user', function() {
 
     checkSession();
@@ -48,17 +51,23 @@ Flight::route('POST /join', function() {
     echo $selectedSearchResult->userDisplayText;
 });
 
-
+/**
+ * Set the primary identity based on the index of the identity clicked by the
+ * user on the Account Management page. We MUST use the index and not directly
+ * the identity id for security reason (one can use the browser developer tools
+ * for calling the AJAX call with an arbitrary number).
+ */
 Flight::route('POST /primary-identity', function() {
 
     checkSession();
     global $session, $dao;
 
-    $identityId = Flight::request()->data['id'];
+    $identityIndex = intval(Flight::request()->data['index']);
+    $identityId = $session->user->identities[$identityIndex]->id;
 
     $dao->setPrimaryIdentity($session->user->id, $identityId);
     $session->updatePrimaryIdentity($identityId);
-    
+
     // Following variable is used to render user-data
     $user = $session->user;
     include 'user-data.php';
diff --git a/include/header.php b/include/header.php
index 64d7268d0d0939ebddd610122c06aef2eb30d0e3..267bb9c942bcf496ee4e529208a2aae0d9e65ed8 100644
--- a/include/header.php
+++ b/include/header.php
@@ -9,7 +9,7 @@
         <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
         <link rel="stylesheet" href="css/style.css?v=2" />
         <link rel="stylesheet" href="css/animation.css?v=2" />
-        <script src="js/script.js"></script>
+        <script src="js/script.js?v=<?php echo $version; ?>"></script>
     </head>
     <body>
         <header id="main-header">
diff --git a/include/init.php b/include/init.php
index 568565e699046faa0de1d013862ef8d1cbf22945..67fa2ba5683762c9f9005fa1797331d2c873501d 100644
--- a/include/init.php
+++ b/include/init.php
@@ -22,6 +22,10 @@
  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+/**
+ * Initialization file called by all the other pages.
+ * Probably some global variables should be removed from here.
+ */
 define('ROOT', dirname(dirname(__FILE__)));
 
 // Defining autoload for RAP classes
diff --git a/include/rest-web-service.php b/include/rest-web-service.php
index a3d507d3526a328f04dc44f3578cbbb8969a613a..b69161a3d869f6f90d0c2906453f3f3cef9719dd 100644
--- a/include/rest-web-service.php
+++ b/include/rest-web-service.php
@@ -2,10 +2,23 @@
 
 /**
  * REST Web Service using http://flightphp.com/
+ * This Web Service should be called only by authorized applications external to
+ * RAP, so this directory should be password protected.
+ * 
+ * Apache configuration:
+ * <Location "/rap-ia2/ws">
+ *     AuthType basic
+ *     AuthName RAP
+ *     AuthUserFile rap-service-passwd
+ *     Require valid-user
+ * </Location>
  */
 //
 $WS_PREFIX = '/ws';
 
+/**
+ * Retrieve user information from login token.
+ */
 Flight::route('GET ' . $WS_PREFIX . '/user-info', function() {
 
     global $dao;
@@ -24,6 +37,9 @@ Flight::route('GET ' . $WS_PREFIX . '/user-info', function() {
     echo $userData;
 });
 
+/**
+ * Retrieve user information from user ID.
+ */
 Flight::route('GET ' . $WS_PREFIX . '/user/@userId', function($userId) {
 
     global $dao;
@@ -38,6 +54,9 @@ Flight::route('GET ' . $WS_PREFIX . '/user/@userId', function($userId) {
     }
 });
 
+/**
+ * Search users from search text (name, surname, email).
+ */
 Flight::route('GET ' . $WS_PREFIX . '/user', function() {
 
     global $dao;
@@ -48,7 +67,7 @@ Flight::route('GET ' . $WS_PREFIX . '/user', function() {
 });
 
 /**
- * Create new user from identity data. Return the new user.
+ * Create new user from identity data. Return the new user encoded in JSON.
  */
 Flight::route('POST ' . $WS_PREFIX . '/user', function() {
 
@@ -82,6 +101,9 @@ Flight::route('POST ' . $WS_PREFIX . '/user', function() {
     echo json_encode($user);
 });
 
+/**
+ * Perform a join.
+ */
 Flight::route('POST ' . $WS_PREFIX . '/join', function() {
 
     global $userHandler;
diff --git a/include/user-data.php b/include/user-data.php
index 37c05e194da56aa68d9096dd50caa3795b30a87f..87df4a1dff27a7b5b4bc8cad4507e2f33b80700f 100644
--- a/include/user-data.php
+++ b/include/user-data.php
@@ -1,39 +1,49 @@
-<?php foreach ($user->identities as $identity) { ?>
+<?php
+/**
+ * This fragment represent a panel containing information about an user and its
+ * set of identities.
+ */
+$i = 0; // identity index
+foreach ($user->identities as $identity) {
+    ?>
     <dl class="dl-horizontal">
         <dt>
             <?php if (!isset($readOnly)) { ?>
-                <?php if ($identity->primary) { ?>
+        <?php if ($identity->primary) { ?>
                     <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="This is your primary identity. You will receive email messages on the address related to this identity.">
                         <span class="glyphicon glyphicon-star"></span>
                     </span>
-                <?php } else { ?>
+        <?php } else { ?>
                     <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="Click on this icon to set this as the primary identity">
-                        <a href="#" onclick="setPrimaryIdentity(<?php echo $identity->id; ?>);">
+                        <a href="#" onclick="setPrimaryIdentity(<?php echo $i; ?>);">
                             <span class="glyphicon glyphicon-star-empty"></span>
                         </a>
                     </span>
                 <?php } ?>
-            <?php } ?>
+    <?php } ?>
             Type
         </dt>
         <dd><?php echo $identity->getUIType(); ?></dd>
         <dt>E-mail</dt>
         <dd><?php echo $identity->email; ?></dd>
-        <?php if ($identity->eppn !== null) { ?>
+    <?php if ($identity->eppn !== null) { ?>
             <dt><abbr title="EduPerson Principal Name, an unique identifier used into federations.">EPPN</abbr></dt>
             <dd><?php echo $identity->eppn; ?></dd>
         <?php } ?>
-        <?php if ($identity->name !== null) { ?>
+    <?php if ($identity->name !== null) { ?>
             <dt>Name</dt>
             <dd><?php echo $identity->name; ?></dd>
         <?php } ?>
-        <?php if ($identity->surname !== null) { ?>
+    <?php if ($identity->surname !== null) { ?>
             <dt>Surname</dt>
             <dd><?php echo $identity->surname; ?></dd>
         <?php } ?>
-        <?php if ($identity->institution !== null) { ?>
+    <?php if ($identity->institution !== null) { ?>
             <dt>Institution</dt>
             <dd><?php echo $identity->institution; ?></dd>
-        <?php } ?>
+    <?php } ?>
     </dl>
-<?php } ?>
+    <?php
+    $i++;
+}
+?>
diff --git a/js/index.js b/js/index.js
index a55d573df947a3c6262da841bc5a46520f3e69fb..800f74f843ba519dceb921bd400100dcf046c1d9 100644
--- a/js/index.js
+++ b/js/index.js
@@ -1,6 +1,14 @@
+/**
+ * JS code included only in index.php page.
+ */
+// IIFE for keeping private functions and variables inside.
 (function ($) {
 
-    // function factory used to generate function to be executed at timeout (see below)
+    /**
+     * Function factory used to generate function to be executed at timeout (see below)
+     * @param {string} searchText
+     * @returns {Function}
+     */
     function searchUserFactory(searchText) {
         return function () {
             $.get('user?search=' + searchText, function (response) {
@@ -19,6 +27,10 @@
         };
     }
 
+    /**
+     * Function associated to the "Send join request" button on the join modal
+     * dialog.
+     */
     function sendJoinRequest() {
 
         $userSelector = $('#user-selector-group select');
@@ -34,16 +46,27 @@
         }
     }
 
-    window.setPrimaryIdentity = function (identityId) {
+    /**
+     * Select the primary identity from the available identities of the user.
+     * @param {int} index
+     */
+    window.setPrimaryIdentity = function (index) {
         $.post('primary-identity', {
-            id: identityId
+            index: index
         }, function (response) {
             $('#panel-identities .panel-body').html(response);
-            // restore tooltips
+            // restore tooltips (JS event handlers are deleted when DOM element
+            // is replaced).
             loadTooltips();
         });
     };
 
+    /**
+     * Initialize Bootstrap tooltips.
+     * As specified into Bootstrap documentation "For performance reasons, the
+     * Tooltip and Popover data-apis are opt-in, meaning you must initialize
+     * them yourself."
+     */
     function loadTooltips() {
         $('.primary-identity-icon').tooltip();
     }
diff --git a/js/script.js b/js/script.js
index 744088f5a496bb7552150503c5872c39dcbc71e8..3435cc6c02c50786ca792c2c91ea37d618f6fff5 100644
--- a/js/script.js
+++ b/js/script.js
@@ -1,3 +1,6 @@
+/**
+ * Common scripts.
+ */
 (function ($) {
 
     // Loading/waiting animation
@@ -8,6 +11,7 @@
         $('.waiting').addClass('hide');
     };
 
+    // Showing loading animation during AJAX calls.
     $(document).ajaxStart(showWaiting);
     $(document).ajaxStop(hideWaiting);
 
diff --git a/js/x509-name-surname.js b/js/x509-name-surname.js
index fb1488295003e7ec0d0a05642fcb9d0e9fbcbff7..771b62e55206abee49fe7920e0e451c47462b2b3 100644
--- a/js/x509-name-surname.js
+++ b/js/x509-name-surname.js
@@ -1,10 +1,20 @@
+/**
+ * JavaScript necessary for the page views/x509-name-surname.php
+ */
 (function ($) {
 
     var _fullName;
+    /**
+     * @param {string} fullName: name+surname
+     */
     window.initPage = function (fullName) {
         _fullName = fullName;
     };
 
+    /**
+     * Calculate the surname removing the selected candidate name from the full
+     * name.
+     */
     function fillSurnameFromSelectedCandidateName() {
         var selectedName = $('#name-selector option:selected').text().trim();
         var surname = _fullName.substring(selectedName.length + 1);
@@ -13,6 +23,7 @@
 
     // When the document is loaded
     $(document).ready(function () {
+        // Add event handler on the name-selector dropdown menu
         $(document).on('change', '#name-selector', fillSurnameFromSelectedCandidateName);
         fillSurnameFromSelectedCandidateName();
     });
diff --git a/views/index.php b/views/index.php
index c667d11d193c9710181fc9e1ef24bd1c623daccd..ebcc6d96c47ac5b80f4f3c2e137ca99b7a99a938 100644
--- a/views/index.php
+++ b/views/index.php
@@ -1,7 +1,7 @@
 <?php
 include 'include/header.php';
 ?>
-<script src="js/index.js"></script>
+<script src="js/index.js?v=<?php echo $version; ?>"></script>
 
 <?php if ($session->user === null) { ?>
     <div class="row">
diff --git a/views/services-list.php b/views/services-list.php
index 800bff9654125da92247db2bed6a87747f2668d3..f74215ce00e839b8c09ca32526d3d97efacf8e05 100644
--- a/views/services-list.php
+++ b/views/services-list.php
@@ -1,4 +1,7 @@
 <?php
+/**
+ * This page is specific for IA2.
+ */
 include 'include/header.php';
 ?>
 <div class="col-sm-offset-2 col-sm-10 services-list-wrapper">
diff --git a/views/x509-name-surname.php b/views/x509-name-surname.php
index aa5f159e70efbb4fc81d87cd5cc7ae1b8847bfe6..2b7080849005c10e8f6fc7eb33a08efc9d0a8285 100644
--- a/views/x509-name-surname.php
+++ b/views/x509-name-surname.php
@@ -1,7 +1,7 @@
 <?php
 include 'include/header.php';
 ?>
-<script src="js/x509-name-surname.js"></script>
+<script src="js/x509-name-surname.js?v=<?php echo $version; ?>"></script>
 <script>initPage('<?php echo $fullName; ?>');</script>
 
 <h1 class="text-center page-title">Name and surname selection</h1>