From 10cb0602667116c02564157f6661cc862eb234cf Mon Sep 17 00:00:00 2001
From: Sonia Zorba <zorba@oats.inaf.it>
Date: Mon, 17 Jul 2017 12:57:04 +0200
Subject: [PATCH] Added services list when no callback is defined and other
 improvements

---
 auth/oauth2/linkedin_token.php    |  13 +-
 classes/CallbackHandler.php       |  46 +++++--
 classes/GrouperClient.php         | 171 -------------------------
 classes/MailSender.php            |   7 +-
 classes/MySQLDAO.php              |   4 +-
 classes/SessionData.php           |   8 +-
 classes/UserHandler.php           |  53 +++++---
 config.php                        |  17 ++-
 css/animation.css                 | 206 ++++++++++++++++++++----------
 css/style.css                     |  15 ++-
 include/footer.php                |  15 +++
 include/front-controller.php      |   7 +-
 include/header.php                |   4 +-
 service-logos/account-manager.png | Bin 0 -> 4686 bytes
 service-logos/asiago.gif          | Bin 0 -> 2672 bytes
 service-logos/grouper.png         | Bin 0 -> 4787 bytes
 service-logos/tng.png             | Bin 0 -> 3996 bytes
 views/index.php                   |  11 +-
 views/services-list.php           |  37 ++++++
 19 files changed, 327 insertions(+), 287 deletions(-)
 delete mode 100644 classes/GrouperClient.php
 create mode 100644 service-logos/account-manager.png
 create mode 100644 service-logos/asiago.gif
 create mode 100644 service-logos/grouper.png
 create mode 100644 service-logos/tng.png
 create mode 100644 views/services-list.php

diff --git a/auth/oauth2/linkedin_token.php b/auth/oauth2/linkedin_token.php
index d4e88e4..e2ec275 100644
--- a/auth/oauth2/linkedin_token.php
+++ b/auth/oauth2/linkedin_token.php
@@ -61,17 +61,19 @@ curl_setopt($conn1, CURLOPT_POSTFIELDS, $post_string);
 
 //perform our request
 $result1 = curl_exec($conn1);
+$info1 = curl_getinfo($conn1);
 
-if ($result1) {
+if ($info1['http_code'] === 200) {
     $my_token = json_decode($result1, TRUE);
     $access_token = $my_token['access_token'];
     $expires_in = $my_token['expires_in'];
     curl_close($conn1);
 } else {
     //show information regarding the error
-    $errorMessage = curl_errno($conn1) . "-";
-    $errorMessage = $errorMessage . curl_error($conn1);
+    $errorMessage = "Error: LinkedIn server response code: " . $info1['http_code'] . " - ";
+    $errorMessage .= curl_error($conn1);
     curl_close($conn1);
+    http_response_code(500);
     die($errorMessage);
 }
 
@@ -84,8 +86,9 @@ curl_setopt($conn2, CURLOPT_HTTPHEADER, array(
 
 curl_setopt($conn2, CURLOPT_RETURNTRANSFER, true);
 $result = curl_exec($conn2);
+$info2 = curl_getinfo($conn2);
 
-if ($result) {
+if ($info2['http_code'] === 200) {
     $data = json_decode($result, TRUE);
 
     curl_close($conn2);
@@ -116,7 +119,7 @@ if ($result) {
     $callbackHandler->manageLoginRedirect($user, $session);
 } else {
     //show information regarding the error
-    $errorMessage = curl_errno($conn2) . "-";
+    $errorMessage = "Error: LinkedIn server response code: " . $info2['http_code'] . " - ";
     $errorMessage = $errorMessage . curl_error($conn2);
     curl_close($conn2);
     die($errorMessage);
diff --git a/classes/CallbackHandler.php b/classes/CallbackHandler.php
index bd48a0d..4484061 100644
--- a/classes/CallbackHandler.php
+++ b/classes/CallbackHandler.php
@@ -36,37 +36,65 @@ class CallbackHandler {
         $this->callbacks = $callbacks;
     }
 
+    /**
+     * If a callback URL is not in the configured list we should return null.
+     */
+    public function filterCallbackURL($callbackURL) {
+        foreach ($this->callbacks as $callback) {
+            if ($callback['url'] === $callbackURL) {
+                return $callbackURL;
+            }
+        }
+        return null;
+    }
+
     /**
      * returns null if the callback URL is not listed in configuration file.
      */
     public function getCallbackTitle($callbackURL) {
 
-        if ($callbackURL === null) {
-            return "Account Management";
+        foreach ($this->callbacks as $callback) {
+            if ($callback['url'] === $callbackURL) {
+                return $callback['title'];
+            }
         }
 
+        return null;
+    }
+
+    public function getCallbackLogo($callbackURL) {
+
         foreach ($this->callbacks as $callback) {
             if ($callback['url'] === $callbackURL) {
-                return $callback['title'];
+                if (array_key_exists('logo', $callback)) {
+                    return $callback['logo'];
+                } else {
+                    return null;
+                }
             }
         }
 
-        throw new \Exception("Unauthorized callback URL");
+        return null;
     }
 
     public function manageLoginRedirect($user, SessionData $session) {
 
-        if ($session->getCallbackURL() !== null) {
-            // External login using token
-            header('Location: ' . $this->getLoginWithTokenURL($user->id, $session->getCallbackURL()));
-            die();
-        } else {
+        if ($session->getCallbackURL() === null) {
+            http_response_code(401);
+            die("Unauthorized callback URL");
+        }
+
+        if ($session->getCallbackURL() === $this->basePath . '/') {
             // Login in session
             $session->user = $user;
             $session->save();
             // Return to index
             header('Location: ' . $this->basePath);
             die();
+        } else {
+            // External login using token
+            header('Location: ' . $this->getLoginWithTokenURL($user->id, $session->getCallbackURL()));
+            die();
         }
     }
 
diff --git a/classes/GrouperClient.php b/classes/GrouperClient.php
deleted file mode 100644
index 73029c3..0000000
--- a/classes/GrouperClient.php
+++ /dev/null
@@ -1,171 +0,0 @@
-<?php
-
-/* ----------------------------------------------------------------------------
- *               INAF - National Institute for Astrophysics
- *               IRA  - Radioastronomical Institute - Bologna
- *               OATS - Astronomical Observatory - Trieste
- * ----------------------------------------------------------------------------
- *
- * Copyright (C) 2016 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;
-
-class GrouperClient {
-
-    private $client;
-
-    function __construct($config) {
-
-        $this->client = new \SoapClient($config['wsdlURL'], array(
-            'login' => $config['user'],
-            'password' => $config['password'],
-            'trace' => 1,
-            // See: https://bugs.php.net/bug.php?id=36226
-            'features' => SOAP_SINGLE_ELEMENT_ARRAYS
-                )
-        );
-    }
-
-    private function getBaseRequestParams() {
-        return array(
-            'clientVersion' => 'v2_3_000'
-        );
-    }
-
-    private function startsWith($haystack, $needle) {
-        return strpos($haystack, "$needle", 0) === 0;
-    }
-
-    private function isSuccess($response) {
-        $success = isset($response->return->resultMetadata) && $response->return->resultMetadata->resultCode === 'SUCCESS';
-        if (!$success) {
-            throw new \Exception("Web Service Failure. Response=" . json_encode($response));
-        }
-        return $success;
-    }
-
-    public function getSubjectGroups($subjectId) {
-
-        $params = $this->getBaseRequestParams();
-        $params['subjectLookups'] = array(
-            'subjectId' => $subjectId,
-            'subjectSourceId' => 'RAP'
-        );
-
-        $response = $this->client->getGroups($params);
-
-        if ($this->isSuccess($response)) {
-            if (count($response->return->results) === 1) {
-                $groups = [];
-                if ($response->return->results[0]->wsGroups !== null) {
-                    foreach ($response->return->results[0]->wsGroups as $group) {
-                        if (!$this->startsWith($group->name, 'etc:')) {
-                            array_push($groups, $group->name);
-                        }
-                    }
-                }
-                return $groups;
-            } else {
-                throw new \Exception("Wrong results number. Response=" . json_encode($response));
-            }
-        }
-    }
-
-    public function getSubjectPrivileges($subjectId) {
-
-        $params = $this->getBaseRequestParams();
-        $params['subjectId'] = $subjectId;
-        $params['subjectSourceId'] = 'RAP';
-
-        $response = $this->client->getGrouperPrivilegesLite($params);
-
-        $privilegesMap = [];
-        if ($this->isSuccess($response)) {
-            if ($response->return->privilegeResults !== null) {
-                foreach ($response->return->privilegeResults as $item) {
-                    $groupName = $item->wsGroup->name;
-                    $privilege = $item->privilegeName;
-
-                    if (!array_key_exists($groupName, $privilegesMap)) {
-                        $groupPrivileges = [];
-                    } else {
-                        $groupPrivileges = $privilegesMap[$groupName];
-                    }
-                    $groupPrivileges[] = $privilege;
-                    $privilegesMap[$groupName] = $groupPrivileges;
-                }
-            }
-        }
-
-        return $privilegesMap;
-    }
-
-    private function getBasePrivilegeRequestParams($subjectId, $groupName, $privilegeNames) {
-        $params = $this->getBaseRequestParams();
-        $params['wsSubjectLookups'] = array(
-            'subjectId' => $subjectId,
-            'subjectSourceId' => 'RAP'
-        );
-        $params['wsGroupLookup'] = array(
-            'groupName' => $groupName
-        );
-        $params['privilegeNames'] = $privilegeNames;
-
-        return $params;
-    }
-
-    public function assignPrivileges($subjectId, $groupName, $privilegeNames) {
-
-        $params = $this->getBasePrivilegeRequestParams($subjectId, $groupName, $privilegeNames);
-        $params['allowed'] = 'T'; // true
-
-        return $this->client->assignGrouperPrivileges($params);
-    }
-
-    public function removePrivileges($subjectId, $groupName, $privilegeNames) {
-
-        $params = $this->getBasePrivilegeRequestParams($subjectId, $groupName, $privilegeNames);
-        $params['allowed'] = 'F'; // false
-
-        return $this->client->assignGrouperPrivileges($params);
-    }
-
-    public function addMemberships($subjectId, $groups) {
-
-        foreach ($groups as $group) {
-            $params = $this->getBaseRequestParams();
-            $params['subjectId'] = $subjectId;
-            $params['subjectSourceId'] = 'RAP';
-            $params['groupName'] = $group;
-
-            $this->client->addMemberLite($params);
-        }
-    }
-
-    public function removeMemberships($subjectId, $groups) {
-
-        foreach ($groups as $group) {
-            $params = $this->getBaseRequestParams();
-            $params['subjectId'] = $subjectId;
-            $params['subjectSourceId'] = 'RAP';
-            $params['groupName'] = $group;
-
-            $this->client->deleteMemberLite($params);
-        }
-    }
-
-}
diff --git a/classes/MailSender.php b/classes/MailSender.php
index 606743d..67742f4 100644
--- a/classes/MailSender.php
+++ b/classes/MailSender.php
@@ -72,9 +72,12 @@ class MailSender {
             $body .= "<br/>";
         }
 
-        $body .= "<br/>If you and this user are the same person click on the following link for joining your accounts:<br/>";
+        $body .= "<br/>If you and this user are <b>the same person</b> click on the following link for joining your accounts:<br/>";
         $body .= "<a href=\"$confirmJoinURL\" target=\"blank_\">$confirmJoinURL</a>";
-        $body .= "<br/><br/>Otherwise you can ignore this email<br/><br/>";
+        $body .= "<br/><br/>Otherwise you can ignore this email.<br/>";
+
+        $body .= '<p><b>Please don\'t use this functionality for sharing resources between your coworkers</b>, use <a href="https://sso.ia2.inaf.it/grouper">Grouper</a> for that.</p>';
+        $body .= '<br/>';
 
         $body .= "<b>*** This is an automatically generated email, please do not reply to this message ***</b><br/>";
         $body .= "If you need information please contact <a href=\"mailto:ia2@oats.inaf.it\">IA2 Staff</a>";
diff --git a/classes/MySQLDAO.php b/classes/MySQLDAO.php
index 4dc5bcc..02dea0d 100644
--- a/classes/MySQLDAO.php
+++ b/classes/MySQLDAO.php
@@ -208,7 +208,8 @@ class MySQLDAO implements DAO {
                 . " i.`id`, `type`, `typed_id`, `email`, `name`, `surname`, `institution`, `eppn`"
                 . " FROM identity i"
                 . " JOIN `user` u on u.id = i.user_id"
-                . " WHERE `email` LIKE :email OR `name` LIKE :name OR `surname` LIKE :surname";
+                . " WHERE `email` LIKE :email OR `name` LIKE :name OR `surname` LIKE :surname"
+                . " OR CONCAT(`name`,' ',`surname`) LIKE :namesurname";
 
         $stmt = $dbh->prepare($query);
 
@@ -216,6 +217,7 @@ class MySQLDAO implements DAO {
         $stmt->bindParam(':email', $searchParam);
         $stmt->bindParam(':name', $searchParam);
         $stmt->bindParam(':surname', $searchParam);
+        $stmt->bindParam(':namesurname', $searchParam);
 
         $stmt->execute();
 
diff --git a/classes/SessionData.php b/classes/SessionData.php
index 8dd9f52..4b365a8 100644
--- a/classes/SessionData.php
+++ b/classes/SessionData.php
@@ -29,6 +29,7 @@ class SessionData {
     private $dao;
     private $callbackURL;
     private $callbackTitle;
+    private $callbackLogo;
     public $user;
     public $userSearchResults;
     public $x509DataToRegister;
@@ -51,8 +52,9 @@ class SessionData {
     }
 
     public function setCallbackURL(CallbackHandler $callbackHandler, $callbackURL) {
-        $this->callbackURL = $callbackURL;
+        $this->callbackURL = $callbackHandler->filterCallbackURL($callbackURL);
         $this->callbackTitle = $callbackHandler->getCallbackTitle($callbackURL);
+        $this->callbackLogo = $callbackHandler->getCallbackLogo($callbackURL);
         $this->save();
     }
 
@@ -64,6 +66,10 @@ class SessionData {
         return $this->callbackTitle;
     }
 
+    public function getCallbackLogo() {
+        return $this->callbackLogo;
+    }
+
     public function searchUser($searchText) {
         $users = $this->dao->searchUser($searchText);
 
diff --git a/classes/UserHandler.php b/classes/UserHandler.php
index b0b7330..5f1d52d 100644
--- a/classes/UserHandler.php
+++ b/classes/UserHandler.php
@@ -60,30 +60,47 @@ class UserHandler {
         return $this->dao->findUserByIdentity($type, $identifier);
     }
 
-    public function joinUsers($userId1, $userId2) {
+    private function getJoinURL() {
+        $joinURL = $this->grouperConfig['wsURL'];
 
-        if ($this->grouperConfig !== null) {
-            $gc = new GrouperClient($this->grouperConfig);
+        if (substr($joinURL, -1) !== '/') {
+            $joinURL .= '/';
+        }
+        $joinURL .= 'ia2join';
 
-            $grouperUser1 = 'RAP:' . $userId1;
-            $grouperUser2 = 'RAP:' . $userId2;
+        return $joinURL;
+    }
 
-            $groupsToMove = $gc->getSubjectGroups($grouperUser2);
-            $privilegesMap = $gc->getSubjectPrivileges($grouperUser2);
+    public function joinUsers($userId1, $userId2) {
 
-            // Adding memberships
-            $gc->addMemberships($grouperUser1, $groupsToMove);
-            // Adding privileges
-            foreach ($privilegesMap as $groupName => $privileges) {
-                $gc->assignPrivileges($grouperUser1, $groupName, $privileges);
-            }
+        if ($this->grouperConfig !== null) {
 
-            // Removing privileges
-            foreach ($privilegesMap as $groupName => $privileges) {
-                $gc->removePrivileges($grouperUser2, $groupName, $privileges);
+            //create cURL connection
+            $conn = curl_init($this->getJoinURL());
+
+            //set options
+            curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30);
+            curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);
+            curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true);
+            curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1);
+            curl_setopt($conn, CURLOPT_USERPWD, $this->grouperConfig['user'] . ":" . $this->grouperConfig['password']);
+
+            //set data to be posted
+            curl_setopt($conn, CURLOPT_POST, 1);
+            curl_setopt($conn, CURLOPT_POSTFIELDS, "subject1Id=RAP:$userId1&subject2Id=RAP:$userId2");
+
+            //perform the request
+            $response = curl_exec($conn);
+            $info = curl_getinfo($conn);
+
+            if ($info['http_code'] === 200) {
+                curl_close($conn);
+            } else {
+                //show information regarding the error
+                curl_close($conn);
+                http_response_code(500);
+                die('Error: Grouper response code: ' . $info['http_code']);
             }
-            // Removing memberships
-            $gc->removeMemberships($grouperUser2, $groupsToMove);
         }
 
         $this->dao->joinUsers($userId1, $userId2);
diff --git a/config.php b/config.php
index d4cc396..d73e7e8 100644
--- a/config.php
+++ b/config.php
@@ -23,7 +23,7 @@
  */
 
 $CONTEXT_ROOT = "/rap-ia2";
-$VERSION = "1.0.0";
+$VERSION = "1.0.1";
 
 $PROTOCOL = stripos($_SERVER['SERVER_PROTOCOL'], 'https') ? 'https://' : 'http://';
 $BASE_PATH = $PROTOCOL . $_SERVER['HTTP_HOST'] . $CONTEXT_ROOT;
@@ -34,7 +34,13 @@ $LOG_LEVEL = Monolog\Logger::DEBUG;
 $CALLBACKS = [
     array(
         'url' => 'http://localhost:8087/grouper',
-        'title' => 'Login to Grouper'
+        'title' => 'Login to Grouper',
+        'logo' => 'grouper.png'
+    ),
+    array(
+        'url' => 'http://localhost/rap-ia2/',
+        'title' => 'Account Management',
+        'logo' => 'account-manager.png'
     )
 ];
 
@@ -73,7 +79,12 @@ $AUTHENTICATION_METHODS = array(
 );
 
 $GROUPER = array(
-    'wsdlURL' => 'http://localhost:8087/grouper-ws/services/GrouperService_v2_3?wsdl',
+    'wsURL' => 'http://localhost:8087/grouper-ws/',
     'user' => 'GrouperSystem',
     'password' => '***REMOVED***'
 );
+/*$GROUPER = array(
+    'wsURL' => 'https://sso.ia2.inaf.it/grouper-ws/',
+    'user' => 'GrouperSystem',
+    'password' => '***REMOVED***321'
+);*/
diff --git a/css/animation.css b/css/animation.css
index ac5a956..be95362 100644
--- a/css/animation.css
+++ b/css/animation.css
@@ -1,85 +1,151 @@
+@charset "UTF-8";
 /*
    Animation example, for spinners
 */
 .animate-spin {
-  -moz-animation: spin 2s infinite linear;
-  -o-animation: spin 2s infinite linear;
-  -webkit-animation: spin 2s infinite linear;
-  animation: spin 2s infinite linear;
-  display: inline-block;
+    -moz-animation: spin 2s infinite linear;
+    -o-animation: spin 2s infinite linear;
+    -webkit-animation: spin 2s infinite linear;
+    animation: spin 2s infinite linear;
+    display: inline-block;
 }
 @-moz-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
+    0% {
+        -moz-transform: rotate(0deg);
+        -o-transform: rotate(0deg);
+        -webkit-transform: rotate(0deg);
+        transform: rotate(0deg);
+    }
+
+    100% {
+        -moz-transform: rotate(359deg);
+        -o-transform: rotate(359deg);
+        -webkit-transform: rotate(359deg);
+        transform: rotate(359deg);
+    }
 }
 @-webkit-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
+    0% {
+        -moz-transform: rotate(0deg);
+        -o-transform: rotate(0deg);
+        -webkit-transform: rotate(0deg);
+        transform: rotate(0deg);
+    }
+
+    100% {
+        -moz-transform: rotate(359deg);
+        -o-transform: rotate(359deg);
+        -webkit-transform: rotate(359deg);
+        transform: rotate(359deg);
+    }
 }
 @-o-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
+    0% {
+        -moz-transform: rotate(0deg);
+        -o-transform: rotate(0deg);
+        -webkit-transform: rotate(0deg);
+        transform: rotate(0deg);
+    }
+
+    100% {
+        -moz-transform: rotate(359deg);
+        -o-transform: rotate(359deg);
+        -webkit-transform: rotate(359deg);
+        transform: rotate(359deg);
+    }
 }
 @-ms-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
+    0% {
+        -moz-transform: rotate(0deg);
+        -o-transform: rotate(0deg);
+        -webkit-transform: rotate(0deg);
+        transform: rotate(0deg);
+    }
+
+    100% {
+        -moz-transform: rotate(359deg);
+        -o-transform: rotate(359deg);
+        -webkit-transform: rotate(359deg);
+        transform: rotate(359deg);
+    }
 }
 @keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
+    0% {
+        -moz-transform: rotate(0deg);
+        -o-transform: rotate(0deg);
+        -webkit-transform: rotate(0deg);
+        transform: rotate(0deg);
+    }
+
+    100% {
+        -moz-transform: rotate(359deg);
+        -o-transform: rotate(359deg);
+        -webkit-transform: rotate(359deg);
+        transform: rotate(359deg);
+    }
+}
+
+/*!
+ * animate.css -http://daneden.me/animate
+ * Version - 3.5.2
+ * Licensed under the MIT license - http://opensource.org/licenses/MIT
+ *
+ * Copyright (c) 2017 Daniel Eden
+ */
+.animated {
+    animation-duration: 1s;
+    animation-fill-mode: both;
+}
+
+.animated.infinite {
+    animation-iteration-count: infinite;
 }
+
+.animated.hinge {
+    animation-duration: 2s;
+}
+
+.animated.flipOutX,
+.animated.flipOutY,
+.animated.bounceIn,
+.animated.bounceOut {
+    animation-duration: .75s;
+}
+
+
+@keyframes bounceIn {
+    from, 20%, 40%, 60%, 80%, to {
+        animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+    }
+
+    0% {
+        opacity: 0;
+        transform: scale3d(.3, .3, .3);
+    }
+
+    20% {
+        transform: scale3d(1.1, 1.1, 1.1);
+    }
+
+    40% {
+        transform: scale3d(.9, .9, .9);
+    }
+
+    60% {
+        opacity: 1;
+        transform: scale3d(1.03, 1.03, 1.03);
+    }
+
+    80% {
+        transform: scale3d(.97, .97, .97);
+    }
+
+    to {
+        opacity: 1;
+        transform: scale3d(1, 1, 1);
+    }
+}
+
+.bounceIn {
+    animation-name: bounceIn;
+}
\ No newline at end of file
diff --git a/css/style.css b/css/style.css
index cc7f30a..bdc99e9 100644
--- a/css/style.css
+++ b/css/style.css
@@ -27,7 +27,7 @@ body {
     vertical-align: middle;
 }
 
-@keyframes pulse {
+@keyframes home_pulse {
     from {
         transform: scale(1, 1);
     }
@@ -64,7 +64,7 @@ body {
 .home-box .img-wrapper a:hover {
     animation-duration: 0.2s;
     animation-fill-mode: both;
-    animation-name: pulse;
+    animation-name: home_pulse;
     animation-timing-function: ease-in;
 }
 
@@ -174,4 +174,15 @@ body {
 }
 .primary-identity-icon a:hover {
     color: #888;
+}
+
+.services-list-wrapper {
+    font-size: 18px;
+}
+.services-list-wrapper .btn-link {
+    font-size: 17px;
+}
+.service-logo {
+    padding-right: 10px;
+    max-height: 50px;
 }
\ No newline at end of file
diff --git a/include/footer.php b/include/footer.php
index 5d0b79f..39fdc44 100644
--- a/include/footer.php
+++ b/include/footer.php
@@ -1,3 +1,18 @@
+<br/>
+<div class="row">
+    <div class="col-sm-8 col-sm-offset-2">
+        <div class="alert alert-info text-center">
+            <div class="animated bounceIn hinge">
+                <span class="glyphicon glyphicon-info-sign"></span>
+                <strong>Need help?</strong> Please read our <a href="https://sso.ia2.inaf.it/home/index.php?lang=en"><u>User guide</u></a> and <a href="https://sso.ia2.inaf.it/home/faq.php?lang=en"><u>FAQ</u></a>.
+            </div>
+        </div>
+    </div>
+</div>
+<div class="text-center">
+    <a href="https://sso.ia2.inaf.it/home/privacy.php?lang=en" target="blank_">Privacy policy</a>
+</div>
+
 </div>
 <div class="waiting hide">
     <span class="icon-wrapper">
diff --git a/include/front-controller.php b/include/front-controller.php
index bab647c..5010fcd 100644
--- a/include/front-controller.php
+++ b/include/front-controller.php
@@ -20,8 +20,11 @@ function setCallback($callback) {
 Flight::route('/', function() {
     startSession();
     $callback = setCallback(Flight::request()->data['callback']);
-    global $session, $callbackHandler, $AUTHENTICATION_METHODS;
-    if ($callback !== null && $session->user !== null) {
+    global $session, $callbackHandler, $BASE_PATH, $AUTHENTICATION_METHODS;
+    if ($callback === null && $session->user === null) {
+        Flight::render('services-list.php', array('title' => 'RAP',
+            'action' => $BASE_PATH . '/'));
+    } else if ($callback !== null && $session->user !== null) {
         $redirectURL = $callbackHandler->getLoginWithTokenURL($session->user->id, $callback);
         Flight::redirect($redirectURL);
     } else {
diff --git a/include/header.php b/include/header.php
index 45b48d5..64d7268 100644
--- a/include/header.php
+++ b/include/header.php
@@ -7,8 +7,8 @@
         <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous" />
         <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
         <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" />
-        <link rel="stylesheet" href="css/animation.css" />
+        <link rel="stylesheet" href="css/style.css?v=2" />
+        <link rel="stylesheet" href="css/animation.css?v=2" />
         <script src="js/script.js"></script>
     </head>
     <body>
diff --git a/service-logos/account-manager.png b/service-logos/account-manager.png
new file mode 100644
index 0000000000000000000000000000000000000000..23c914f1b7886d3451ad13be8ac3e5ef3ccd8d41
GIT binary patch
literal 4686
zcmeAS@N?(olHy`uVBq!ia0y~yV6bOkU@+ofV_;x-eUO=vfq{W7$=lt9;Xep2*t>i(
z0|NtRfk$L90|Va?5N4dJ%_q&kz`$PO>Fdh=kX?|Iktf9TL<a){gJg+oM2T~LZf<H`
z34?P{YGO%hib8p2Nrr;Er*A-bBcC_}gWxt#7srr{duO9w=SQdi{cp3q_@nAZS!>V9
zTRbnP>3ZMF%i3<}#-3lsxN)O+0Gqx;1Ir$VJ<gAw{^Jyo(pOUxSiPf!qft%qRKv#7
zHKMuKO>!@*Wd^C23EeKtk^epCb4;Cn*_rdFi(Xr0T2GqVT|4h{jotnCpMT!-*>E<s
zhmk?R>DK<qFI2_E#dEKh-Yv13%P@gOfIY!|dmDod!`8ITk<l?RYrcQ~{`+G^43j~N
zz~#940!{s|m8=`{^Yxg{ue!Qw=@hRtqnSJnF^ez0*jxL%%xwPo<<q85PxjKB<hArq
zV`Jmji4!MI%`uB++3=(?Zs)~}E5$ow7#wm{9z0fPdXw_e@4-~BqrS^8XEOYrb@ti6
z`ah55fB*mczW)8*vbRzBTch5xJz!EeHse6)-LDrfUVQ3b|LZchzs<)ZCI0^Bc^Ycg
zhG`#9GVC|}wo!P)<H@%Uy#89HGtuJ-!<+i=cgy?d+SUI0_xbtx_xW3+Y8iGh?r0H^
zYYb<IIyOV=c}-<aP0g?D_}{m#B=_5vi5}2h8WNa&SHS6nU(<G`d!FZ(DcGqb)>w!4
z|64Zi`OQw3MeGM|?Em+5J$wDf?)W(~=FXK(?)hqTY*LFrw}2Dlg>ShGlcHYdtu1|a
zX{q<h*49=ZyZQcetV+EMj`1X~QCRw=$FNIa)5@o}>)$^Sl|Fe>SF&ce`POSkKHJFk
zAK%T$GvB)W-Hr2g-!=<#b91}voPL$sb9Axk2{k!a&82gKvKTjbEuHlFbo{>{0jD$J
z`+tSXH=LK4d`v>V;U15VddFLat@m#i_>`+8Mt=zMlIlMm%dq$K>C^3TRWFw+m%qES
z^Zd5AbzYe=47XJeavm?tSQ}=%+<$%^FE8&}`(G~>XSWFWYl=!Gmn?5Bw`h_UoY|{z
z`}Lb8iY3;i%cmGI#4>#Q_bPn9)a-fl<hJUp?hlf>t@`1=U_wvl&24kF#kv>Q|2QoF
z?s61Avxng^9fmE!U)-)=j|yHoh2eqAqFZ%08FZG47$2K(c2A<U(aSO(<6|rh?`E6l
z^Tq8bNObYKdEV0h%@f1um>vP4=X1Wt?XRnSa=!lGXM=_umJT<kpZk2JlQRTZ#1vH=
zHP13hFc~l<h#aV6_|5Po$ZPk~DP>E85~H#=MKk>fOZUk3<y7QrxN`shzwiHwcFqxY
zQt2^#bl6QSndkW&^=sFzRq4m>+VXs6`n;1a53;3C@*kd1d`|ZCrd^Cr7`|N1+WKay
zcDT?~uU7TRp0zJ7EKJMK&zC%4^nA|*uccZ~i+0}3+Uj(?@Qj~(Lw?U&S(X3>v4(Xw
zZ{FPUyfSuM?(Jg|W&8`L3OVU8d`#LnLu;y6=e&7xt1mP68fu7j@7ccH+>_%Z;|+6T
z<Khq7_y2wSZ{^CB|J)W&4DsUhT3IHu+|bBq)2}C!{m<F|`|;SE;Y_O0%%xsSH{HuH
zX{xZvE7Pr*y>uhPovOWZ!Cp681oqvPytVYJ=F$)oDc<bD!i%q7zD%5}6s9#bLT`Gv
zuoD}j%&)ue>&!pCTD@K_E+$5X@9>89pjSa&GYyYvEdA5k+WKm$cKD~~=jXrw`;fnW
z#wMN9Udu0k&d$!x4mvfVIB%QKrMq)tFMa&ALP$HSXW=o4U5T-?eAJSsT&iPmkE{83
zwEf&1%i@yS+S>iOw<V7k-toxbWZ+QTa&6c45UsC&U;5Xd66rpArN919vi0n<vqH3%
z9yVAbm3-)IQM>L{J1=Wq23Ca<8FiHwjpF{a_ceXFw>?yZ-YXydUMo2F<HYc|%BLr0
z8mG&xxSG`-bN%jNiPv3M=DM>OFf;{8mCLBJKRB?n_<7&IFU#$1*%Ejh!Z;GQ8|F>Z
zxF|b`gV&*~wA54ah&$K4LYYZkOYgDY+m_4jkXKgr?STCMAIJUIu3z78(RW#xQ~&O<
z$t?m(tO+mf>@05gn{QVu-%!U;xYqRbowDceOLp6CtAAExd1D*z+OM|aa)Bz2N)r`k
z`Yf9(B)n9l`_!X-;qkSl^&2DZWJiXz2_(JW#3r~cxBPn6*2e4c^|d>$X7Mt31bO{D
zX7G#ggLHC^h`uI6Vx*XQOO3+B9qgx#6<PXOrn*|PG7EgR(J%k_=;%KK9^)Rvbav+4
zFAUwq_hL0fex09Zds|#JBT4(yi!e^UrBmF@t&EM0`<_=e8M?i{`nlI|is3Qt)+S?X
z>(_1%r?{ryoI88=>vdPN?lL3=POUJX*iv|IZq?p<rJtXj&12YD_IBgU<Ck(zIWsw|
zS+~yWXU)A(uSh|~?^(5nET6YJ9h^C5&Knkg#t&Irvlutre)_T9BFx7tvvS_^k~MpO
zf7xcNGWB$-+R~I3flq-uZ53Ns91qx>&u-3J?;Uep_4@+tBNEOM$w#)ml|B49EbqRU
zjcEUIW+#u+62dME-^c0a_Harj>z>{e6}aDj`~2xAQ=HE2G&%Ny@8VtEmB%EKbxvQp
zd-v|D<hyqp`y<RR_MSa>@SvC3?AS1`savnfF;w;+sJ;LF`lXj&KHazx!ReqJ70f)L
zwzhU*YGmRS-d(@hGuFml53Q}O-Oaw=kTt|>Wkz!A^Lf>IWfc__dOU|k8P4{eZNK}D
z+re8i^xdmhyZ-(>U;ppfj~_pn8oqMuVRV>vR&D3;IJt{a!J4VoJ$ag!N>(3gGhEhd
zsLOD4vbulV|C9c8MwV7qR?=;WM<s;sE%lai>fmWwFwJkd_`d&t-}~48zI}fm*Mq)m
zyTlsR8D8AZ-+%V?*IgY>_wTw19kN_L)yvh#&(A8*?Z&m${9AULo&9i2bKNzo54H8Y
z2kvZ4KCU*u>eb3@r;Zwlw%*-a_L&_s*?R4?u&}WC{OWf*JsGyoI{S=k>6WXGDs$)0
zPj?j9uwvDo?9ahonF1^|rKMM2olrK5?am9lx;Ol5!Mer8`*;$5U0E6YUe2!O$F6I;
zB>u*~59)|=6j<}_=H}<urtkmrw4Bk0@kFrK>ti#vot9?Mn&`n282|n-#~L<)Ag^er
zj<n-a&+@D-85$yVmT6DzVqf5)IrrbS?fXjC?%!V@=M~v|r+&G&mu7a~<0td$e!Y}F
z-Y>tOF(J?^QrPLl_n`Lw;y2pm>vl}?TOQt|up;l;ojZIMibwdH5_HA7MUNjp&dqR5
ziOEFq3G-6jyT1G}9Ez*2X7M`wJJKm^KCkxM&FF2p>pe81t#i(0tYv0c`uKSN`MT%U
z_jj@-@Hs5Kn$_B~@&DT+-)lJ+ls!1m_~-L^`}*vHf`T~Bt=pn&6<b<@PR+`_{Zqg8
zrMLOMpJ(&$b=hqDI?vYrhVl~Yvo9GGX8Jg3ifSy?Vt7~ckUM^dt&Pndo&`IdJ&Y11
zeyt2y^=0b%npC+1xeVG@v$npp=zDd?M}TG1&75ltcPhW#OkY3Os`QoB^O``f$os9o
zvy*$4PtmgbaDdso?#p6%U#1xsqrz>@-@a7+B2QgmrjJ{D;tS>#%Ys&(I&s3IH%KaD
z?e@)GfnKc}Bi8(T(9A!LF=WY<I2*a?PJ8x?&hSy|egE68A(UbJyqZrZS8m+6G1_Zo
z=-RJka%l`KjshW<UVgb_eBP#*_kc-5(&n2vERGwV2DH4hst{sQY++2OxqsbJ;LR_)
z&v9vs88go9d^jin*Uk{FrL)d1V_$IN>ebT6KcCOvf9?K#`}I>+xgFZE^6FuOFD(Mz
z{<dGQ^d6V1eq+_JXUe8$yVlj@Z>gC6@~zwJ?NPcdi6<C$Y>m?0xM9PCthKC5r@WbP
ztoZ)-yR*&n)fQ=aw<LOOQ#!RtCvx@GEQWVBpn7Y5#iLG3uT0fTe49Pp)EU>v+g6o4
ztiJ#IUP;N8Q)|OczrQj?_Vv%VZZ`9t7alKs#89<t*|J|;txmTjlHEU6?B+VR>iX-I
z+wWDa*3WWOZ&ncaSyT79PwTmwSocqEZthTqR(H9|B{Te%ua0_Mu-R`?kk_ttw||^T
zx7&P3LYqm$eD>LCk1eWJv*c~h)p_!{a?0scxmwPJ{0IJBkE_nT@p+HV^E>xe8m06^
zo=r2ZeRpT)-t6q`-;8fMk3YV9_wL=Y)mLwQi90rfuStRBLG-kzx3}qXDpqMHE}Not
zd)r}+HLcrnSNr+i<dVLd=XX8B<j8>o4mNZBzPntW=>PZE_5E`DD?UD|W61P+*{7@K
z#PNJi`T4VF-~Rr2zP`@Bfs<kJ#|oSD+hvD7+gQJPvM=93t0mzrWBSq{!N6I&Lfz8O
zpFMkYadPVQ*QML$c5I)&wJ^hR%e5=tYtKn+PTm+H<FG6D<Mg<ym;a2@&&hnPtzR#o
zwDEMMY+}#RDM8ie=Gj{3^&I4{`_OE*uzTI>X^dB<1o^Hl_239(c(Jecw@$Lpn_lyK
z9i~#e8)tgB#T}}>?;o?TW~VvNVMaxlZL3$W-nMDeqT-!%?zpuaT+GHgF{tR=PFKy+
z*z3M-3#ywHBrK}Gz1g?_&r^M6^SnC}yiExn925Jx+U`ENcSJ&2B3V=O>A(H|zOFy_
z<x9!8;`zVt>{~v+u4>)-_4@n^PDp4ou5b%o%yd9qUH$CU)#1@C6QTrIUQbIcJT~L5
zrI>n)fmdctU0vOSr_<x*e!nZ<zne+nMB3(~U%!6cm3zA}HFDQq-3i~X>U(J(mw0S+
zY!_n+LyGPB*SqWf{yH!_f1hQ+!$YkHofd9belg>Vkkcinj*2UCw@PQc$!Yf1ob9!8
z)nkj*H*?A|YiepfJ~=u0=f{s9kItJnkEubF!BkUq<<+dc*Vn~be>q?O?{l!YUW~<#
zO`D836x3T@7#+JIJ5Sm}bGmTnk+bXT|Gu8S?_+QN9_|D-hlMwD!k#~SW^=vt@4kI@
zFL$lG7M0D=CXrmk@T>N|{LK0D<#$$md~_i?fA7}xw6taGFJ?Tcxqn{3X_6C%`K--%
zz2(oltW48bnv=QqwNnSkfzHm(t&flQi~G&7*l1y97UrQgdFhHPrHm=-<M-QrIHkS5
zXV1@Pvls7s|6bVXi_80uWw8Qni8o8{wzkVvy|^a6|HskU?)`FmWxJ0~W0=CQCRA%`
z)#8gE*x1-=yjH3ln{hZeIQaW~%i?E8o}Ql0Kh<lgqh_j`=G2s)qms#HiY<4QU##tz
z;I-1h>*kUv*DRlBf2^o^`r+Z>*2(Jr_bkiaL=?Wiw>LYm&X4iS9J|_I2lDGa&;EV=
z+BH57#VTc{2$xm*OV==Js7^k4XrXiar+&L%FCHB^a^%vB7cV9WEaF(ekf6%Z#8&;V
zRs7h$Z`=3F{`~vC{(r&Fn0X8vrW;;UZt)24n!BR=S0Hn~xrgM^kb-lvUYRkPSMO$R
zz4WujuKeGhpKpGCeqKFo+O$a@E9N{a+NsuX>c|ln-MN0NSBI~^)$68w%(7hRb+8jh
zf7M^k``?4Ty}fU8i|c*a|M{Ht-^-UT?|uFH^<5{9Yj^M7eKcqL4Q~BC1@}P#@&Eh&
z|F&ES^92^YRxWtITP{Y($zti1q@JBE6Iz0_wr<-tZRVUgXZCzPXYG0Z^NESd?ysg?
zI(4v_-PVa?chOTXBaS8)m8Q3hYD=ednQ=zFE?FD)dgjcTC;vUS|L=L`+__oG?tLM<
zckNmw!mxZw(XkmFYtDT57^`xt|Forl;koYtUi@NQt;+iQ|9ooJ-}mEDgvq5S+tOE8
z)^>Jw-u7Bqc6Q%YE6;P4`b$gJzyG~>d;Wd7bs<`|3^BJpKJ>qJltYm(dClo<Z~qza
z@bCL}E4w|u{_j`YiE$$Kzi*y@7Q8aVlIQR>Gt>Ipt7DEuOfsCN(6WQU;;8!nB!;AE
zsb8mfEnT?%eqFV}rhVT}DEF^f611{wN|Cv&ycfs9xsT4?)>TRF$=R>uZ!lkh$>+(j
zlRtm{JTuq2{E@S>bN1iAf3ss_V?~pF?n+GVlbRMSyZnO0;ziH8^OkRKytYeksgAei
zbWX+I4$04)pBn<Q?`ll-%F4{_<l*IQWw=&VRn;}+l9K?7lF&Y1-t?Y{L8q>+Fulg2
zVA=3B#i%m9prAmD=Wv#Ym0hRQf+bU4-^}@Dwsgx}uG0@6K8!wm_^^}LPMO-}`K!0h
z3Gxaxo_R+1_O@_FlTO356)}4JYu2pckxbsjsrX2^>RYejvZI#z-@ks<RhxX0i(%uQ
zJ$K?)hWzR^jJ;TS@6@MT5|@RX*6{H0o!h-GS|w3J&Ek0>L+7`a$CJ72v?`()q~<-}
ynsa+u&&CLq2C;4b9)-;2kreeaJ^y*nf99YK<^{olA^{8x3=E#GelF{r5}E)>#UZc&

literal 0
HcmV?d00001

diff --git a/service-logos/asiago.gif b/service-logos/asiago.gif
new file mode 100644
index 0000000000000000000000000000000000000000..18dbb154e2cc4f13bcab57ad332d932995f6a17f
GIT binary patch
literal 2672
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
U>R*=^@NHe;?)z#I3k!oa0EGcab^rhX

literal 0
HcmV?d00001

diff --git a/service-logos/grouper.png b/service-logos/grouper.png
new file mode 100644
index 0000000000000000000000000000000000000000..b3ee45d31f45ba97614cdd6cbbc67243fe51f709
GIT binary patch
literal 4787
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
D1=Bej

literal 0
HcmV?d00001

diff --git a/service-logos/tng.png b/service-logos/tng.png
new file mode 100644
index 0000000000000000000000000000000000000000..719dd541c971506e67d5ca26444ce98ad36c1f7d
GIT binary patch
literal 3996
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

literal 0
HcmV?d00001

diff --git a/views/index.php b/views/index.php
index bd9a127..c667d11 100644
--- a/views/index.php
+++ b/views/index.php
@@ -6,7 +6,16 @@ include 'include/header.php';
 <?php if ($session->user === null) { ?>
     <div class="row">
         <div class="col-xs-12">
-            <h1 class="text-center page-title"><?php echo $session->getCallbackTitle(); ?></h1>
+            <h1 class="text-center page-title">
+                <?php
+                if ($session->getCallbackLogo() != null) {
+                    ?>
+                    <img class="service-logo" src="service-logos/<?php echo $session->getCallbackLogo(); ?>" alt="" />
+                    <?php
+                }
+                echo $session->getCallbackTitle();
+                ?>
+            </h1>
         </div>
     </div>
     <div class="row" id="auth-panel">
diff --git a/views/services-list.php b/views/services-list.php
new file mode 100644
index 0000000..800bff9
--- /dev/null
+++ b/views/services-list.php
@@ -0,0 +1,37 @@
+<?php
+include 'include/header.php';
+?>
+<div class="col-sm-offset-2 col-sm-10 services-list-wrapper">
+    <p>Please choose the service where you want to login:</p>
+    <ul>
+        <li>
+            <form action="<?php echo $action; ?>" method="POST">
+                <input name="callback" type="hidden" value="http://archives.ia2.inaf.it/tng/rest/login/rapinput" />
+                <input type="submit" class="btn btn-link" value="Telescopio Nazionale Galileo (TNG) portal" />
+            </form>
+        </li>
+        <li>
+            <form action="<?php echo $action; ?>" method="POST">
+                <input name="callback" type="hidden" value="http://archives.ia2.inaf.it/aao/rest/login/rapinput" />
+                <input type="submit" class="btn btn-link" value="Asiago Astrophysical Observatory portal" />
+            </form>
+        </li>
+        <li>
+            <form action="<?php echo $action; ?>" method="POST">
+                <input name="callback" type="hidden" value="<?php echo $action; ?>" />
+                <input type="submit" class="btn btn-link" value="RAP Account Management" />
+            </form>
+        </li>
+        <li>
+            <form action="<?php echo $action; ?>" method="POST">
+                <input name="callback" type="hidden" value="https://sso.ia2.inaf.it/grouper" />
+                <input type="submit" class="btn btn-link" value="Grouper (groups management)" />
+            </form>
+        </li>
+    </ul>
+    <br/>
+</div>
+
+<?php
+include 'include/footer.php';
+
-- 
GitLab