Skip to content
Commits on Source (12)
composer.lock
config.php
config.json
config.yaml
logs/
vendor/
......
composer_dockerize:
stage: .pre
tags:
- shell
script:
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
- docker build -t "${CI_REGISTRY_IMAGE}/composer" -f docker/composer-Dockerfile .
- docker push "${CI_REGISTRY_IMAGE}/composer"
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
when: never
composer_test_dockerize:
stage: .pre
tags:
- shell
script:
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
- docker build -t "${CI_REGISTRY_IMAGE}/composer/test" --build-arg "INCLUDE_TESTS=true" -f docker/composer-Dockerfile .
- docker push "${CI_REGISTRY_IMAGE}/composer/test"
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
when: never
db_dockerize:
stage: .pre
tags:
- shell
script:
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
- docker build -t "${CI_REGISTRY_IMAGE}/database" -f docker/db-Dockerfile .
- docker push "${CI_REGISTRY_IMAGE}/database"
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
when: never
- changes:
- docker/db-Dockerfile
- sql/*
base_dockerize:
stage: .pre
tags:
- shell
script:
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
- docker build -t "${CI_REGISTRY_IMAGE}/base" -f docker/base-Dockerfile .
- docker push "${CI_REGISTRY_IMAGE}/base"
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
when: never
test:
stage: test
tags:
- docker
image: "${CI_REGISTRY_IMAGE}/composer/test"
script:
- ./vendor/bin/phpunit --bootstrap vendor/autoload.php tests/
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
when: never
dockerize:
stage: build
tags:
- shell
script:
- docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
- docker build -t "${CI_REGISTRY_IMAGE}" -f docker/Dockerfile .
- docker push "${CI_REGISTRY_IMAGE}"
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
when: never
FROM ubuntu:18.10
# To fix "configuring tzdata" interactive input during apt install
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -yq --no-install-recommends \
apache2 \
libapache2-mod-php7.2 \
php7.2-xml \
php7.2-mbstring \
php-mysql \
php-curl \
libapache2-mod-shib2 \
make \
wget \
ca-certificates \
ssl-cert \
vim
# Copying Shibboleth SP configuration
COPY docker/shibboleth/shibboleth2.xml /etc/shibboleth/
COPY docker/shibboleth/sp-key.pem /etc/shibboleth/
COPY docker/shibboleth/sp-cert.pem /etc/shibboleth/
# Installing Embedded Discovery Service
WORKDIR /usr/local/src
RUN wget https://shibboleth.net/downloads/embedded-discovery-service/1.2.1/shibboleth-embedded-ds-1.2.1.tar.gz -O shibboleth-eds.tar.gz
RUN tar xzf shibboleth-eds.tar.gz
WORKDIR shibboleth-embedded-ds-1.2.1
RUN make install
RUN mv /etc/shibboleth-ds/shibboleth-ds.conf /etc/apache2/conf-available/shibboleth-ds.conf
RUN sed -i 's/Allow from All/Require all granted/g' /etc/apache2/conf-available/shibboleth-ds.conf
RUN a2enconf shibboleth-ds.conf
# Adding RAP Apache configuration
COPY docker/rap.conf /etc/apache2/conf-available/
RUN a2enconf rap.conf
# Enable mod_rewrite (for Flight framework)
RUN a2enmod rewrite
RUN a2enmod ssl
RUN a2ensite default-ssl
# Copying RAP php files
WORKDIR /var/www/html
COPY . rap-ia2
WORKDIR /var/www/html/rap-ia2
RUN mkdir -p logs
RUN chown -R www-data logs
# Starting shibd & Apache
CMD service shibd start && apachectl -D FOREGROUND
This diff is collapsed.
# RAP IA2
# Remote Authentication Portal
## Installation and configuration
An authentication portal supporting eduGAIN (using Shibboleth SP), social logins (Google, LinkedIn and Facebook) and X.509 certificates. Caller services always see an OIDC flow. Account linking and merging is supported. Currently used for authenticating on [IA2 services](https://sso.ia2.inaf.it).
Requirements:
## Docker demo
A working demo using Docker Compose is available on docker folder. It includes also [IA2 Group Membership Service](https://www.ict.inaf.it/gitlab/ia2/ia2-gms) (groups management). The demo is configured only with the fake login mode (for functional testing purposes), but you can configure any social login (see config-example.yaml). eduGAIN and X.509 can't work inside this setup since proper TLS certificates and Shibboleth SP need to be installed.
To start the demo:
docker-compose pull
docker-compose build
docker-compose up -d
RAP is available on http://localhost:8080/rap-ia2
After you performed a first login you can set your user as superadmin on GMS by running the following command:
docker exec -it <gms-db-container-id> bash -c "echo \"INSERT INTO gms_permission (group_id, user_id, permission, group_path) VALUES('ROOT', '1', 'ADMIN', '')\" | psql -U postgres"
* Apache httpd server (tested on Apache/2.4.6)
* PHP (5.4+), composer for dependecies
* MySQL/MariaDB (tested on MariaDB 5.5.52)
* PHP MySQL module
Then you can create groups and memberships using the GMS UI (http://localhost:8081/gms).
On Ubuntu:
Cleanup:
sudo apt install apache2 mariadb-server libapache2-mod-php mariadb-server php7.2-xml php7.2-mbstring php-mysql php-curl php-yaml
docker-compose down
docker volume prune
### PHP
## Manual Installation
Put RAP sources in `/var/www/html/rap-ia2`
Requirements:
* Apache httpd server
* PHP (and composer for retrieving dependecies)
* MySQL/MariaDB
For installing PHP dependencies run:
See the complete list of packages inside [base Dockerfile](docker/base-Dockerfile).
composer install
Put RAP sources in `/var/www/html/rap-ia2` and install PHP dependencies with `composer install`.
Install also the bcmath PHP package (used in X.509 parser).
......@@ -31,70 +47,13 @@ Create a dedicated database and user:
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, or /etc/mysql/mariadb.conf.d/*-server.cnf for MariaDB)
* under the section [mysqld] set `event_scheduler=1`
* restart MySQL
Then run the setup script:
mysql -u root -p < sql/setup-database.sql
### Apache (httpd)
* Enable .htaccess in rap folder:
In Apache configuration (e.g. /etc/apache2/apache2.conf) add:
<Directory /var/www/html/rap-ia2/>
AllowOverride All
</Directory>
* Enable Apache mod rewrite: `sudo a2enmod rewrite`
* 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.
See the [configuration file](docker/rap.conf) used in Docker image.
### Social networks
......@@ -106,12 +65,14 @@ Before using social API it is necessary to register an application on each socia
### Configuration file
Copy the `config-example.php` into `config.php` and edit it for matching your needs.
Copy the `config-example.yaml` into `config.yaml` and edit it for matching your needs.
### Generate keypair
php exec/generate-keypair.php
A cron job for key rotation has to be set up.
### Logs directory
Create the logs directory and assign ownership to the Apache user (usually www-data or apache)
......@@ -119,21 +80,17 @@ Create the logs directory and assign ownership to the Apache user (usually www-d
mkdir logs
sudo chown www-data logs
### Docker
Database image:
docker build -f docker/db-Dockerfile --tag rap-ia2/database .
### Run Unit Tests and build code coverage report
(XDebug or another code coverage driver needs to be installed; e.g. `sudo apt install php-xdebug`)
./vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-html build/coverage-report tests/
DAO tests are disabled by default, unless the environment variable `TEST_DAO` has been set to `true`. DAO tests require a test database up and running (it can be started using the related Docker image).
## Additional information and developer guide
See the wiki: https://www.ict.inaf.it/gitlab/zorba/rap-ia2/wikis/home
See the wiki: https://www.ict.inaf.it/gitlab/ia2/rap-ia2/wikis/home
## Troubleshooting
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
/**
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
trait ClientsLocator {
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
use phpseclib\Crypt\RSA;
......@@ -15,7 +21,7 @@ class JWKSHandler {
$this->locator = $locator;
}
public function generateKeyPair() {
public function generateKeyPair(): RSAKeyPair {
$rsa = new RSA();
......@@ -77,65 +83,4 @@ class JWKSHandler {
return $matches[1];
}
public function loadAllJWKS(): array {
foreach ($this->locator->getBrowserBasedOAuth2Clients() as $client) {
if ($client->jwks !== null) {
$this->loadJWKS($client->jwks);
}
}
$dao = $this->locator->getJWKSDAO();
return $dao->getAllPublicJWK();
}
private function loadJWKS($url) {
$dao = $this->locator->getJWKSDAO();
$conn = curl_init($url);
curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($conn);
$info = curl_getinfo($conn);
if ($info['http_code'] === 200) {
$jwks = json_decode($result, TRUE);
foreach ($jwks['keys'] as $key) {
$key['url'] = $url;
$jwk = $this->getPublicJWK($key);
$dao->updatePublicJWK($jwk);
}
} else {
error_log('Error while retrieving JWKS from ' . $url);
}
curl_close($conn);
}
private function getPublicJWK($data): PublicJWK {
// Convert Base64 uri-safe variant to default (needed for JWKS)
$n = strtr($data['n'], '-_', '+/');
$rsa = new RSA();
$key = "<RSAKeyPair>"
. "<Modulus>" . $n . "</Modulus>"
. "<Exponent>" . $data['e'] . "</Exponent>"
. "</RSAKeyPair>";
$rsa->loadKey($key, RSA::PUBLIC_FORMAT_XML);
$jwk = new PublicJWK();
$jwk->kid = $data['kid'];
$jwk->key = $rsa;
$jwk->url = $data['url'];
$jwk->updateTime = time();
return $jwk;
}
}
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
class OAuth2RequestHandler {
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
class OIDCDiscoveryGenerator {
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
use \Firebase\JWT\JWT;
......@@ -14,7 +20,7 @@ class TokenBuilder {
public function getIdToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null): string {
$keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
$keyPair = $this->getNewestKeyPair();
$payload = $this->createIdTokenPayloadArray($tokenData, $jwtCustomizer);
......@@ -30,10 +36,14 @@ class TokenBuilder {
'sub' => strval($user->id),
'iat' => intval($tokenData->creationTime),
'exp' => intval($tokenData->expirationTime),
'name' => $user->getCompleteName(),
'aud' => $tokenData->clientId
);
$name = $user->getCompleteName();
if ($name !== null) {
$payloadArr['name'] = $name;
}
if (in_array("email", $tokenData->scope)) {
$payloadArr['email'] = $user->getPrimaryEmail();
}
......@@ -55,7 +65,7 @@ class TokenBuilder {
public function getAccessToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null): string {
$keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
$keyPair = $this->getNewestKeyPair();
$user = $this->locator->getUserDAO()->findUserById($tokenData->userId);
if ($user === null) {
......@@ -137,7 +147,7 @@ class TokenBuilder {
$payload['exp'] = $iat + 3600;
}
$keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
$keyPair = $this->getNewestKeyPair();
return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId);
}
......@@ -146,7 +156,7 @@ class TokenBuilder {
* @param string $audience target service
*/
public function generateNewToken(string $subject, int $lifespan, string $audience) {
$keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
$keyPair = $this->getNewestKeyPair();
$iat = time();
$exp = $iat + $lifespan * 3600;
......@@ -179,4 +189,15 @@ class TokenBuilder {
throw new \Exception("Unable to find configuration for " . $audience);
}
private function getNewestKeyPair(): RSAKeyPair {
$keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
if ($keyPair === null) {
$keyPair = $this->locator->getJWKSHandler()->generateKeyPair();
}
return $keyPair;
}
}
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
use \Firebase\JWT\JWT;
......
<?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.
/*
* This file is part of rap
* Copyright (C) 2020 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
use \Firebase\JWT\JWT;
/**
* See https://tools.ietf.org/html/rfc8693
*/
......@@ -70,9 +52,9 @@ class TokenExchanger {
if ($params['expires_in'] !== null) {
$claims['exp'] = time() + intval($params['expires_in']);
}
$accessToken = $this->locator->getTokenBuilder()->generateToken($claims);
$data = [];
$data['access_token'] = $accessToken;
......@@ -92,56 +74,4 @@ class TokenExchanger {
return $audiences;
}
/**
* DEPRECATED (currently used by portals: to be removed)
*/
public function exchangeTokenOld(string $token) {
$key = $this->getExternalKeyForToken($token);
$decoded = JWT::decode($token, $key->key, ['RS256']);
$subject = $decoded->sub;
$lifespan = ($decoded->exp - time());
$data = [];
$data['access_token'] = $this->locator->getTokenBuilder()->generateNewToken($subject, $lifespan / 3600, "gms");
$data['issued_token_type'] = "urn:ietf:params:oauth:token-type:access_token";
$data['token_type'] = 'Bearer';
$data['expires_in'] = $lifespan;
return $data;
}
private function getExternalKeyForToken(string $token): PublicJWK {
$keys = $this->locator->getJWKSDAO()->getAllPublicJWK();
$parts = explode('.', $token);
$head = JWT::jsonDecode(JWT::urlsafeB64Decode($parts[0]));
$kid = $head->kid;
$key = $this->getKeyByKid($keys, $kid);
if ($key === null) {
$keys = $this->locator->getJWKSHandler()->loadAllJWKS();
}
$key = $this->getKeyByKid($keys, $kid);
if ($key !== null) {
return $key;
}
throw new \Exception("Invalid kid");
}
private function getKeyByKid(array $keys, string $kid): ?PublicJWK {
foreach ($keys as $key) {
if ($key->kid === $kid) {
return $key;
}
}
return null;
}
}
<?php
/* ----------------------------------------------------------------------------
* INAF - National Institute for Astrophysics
* IRA - Radioastronomical Institute - Bologna
* OATS - Astronomical Observatory - Trieste
* ----------------------------------------------------------------------------
*
/*
* This file is part of rap
* 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.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
......@@ -102,7 +86,7 @@ class UserHandler {
return $this->joinNewIdentity($user1, $user2);
}
// Call Grouper for moving groups and privileges from one user to the other
// Call GMS for moving groups and privileges from one user to the other
$remainingUserId = $this->locator->getGmsClient()->joinGroups($userId1, $userId2);
$remainingUser = $userId1 === $remainingUserId ? $user1 : $user2;
......
<?php
/* ----------------------------------------------------------------------------
* INAF - National Institute for Astrophysics
* IRA - Radioastronomical Institute - Bologna
* OATS - Astronomical Observatory - Trieste
* ----------------------------------------------------------------------------
*
/*
* This file is part of rap
* 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.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
interface AccessTokenDAO {
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
interface JWKSDAO {
......@@ -11,8 +17,4 @@ interface JWKSDAO {
public function insertRSAKeyPair(RSAKeyPair $keyPair): RSAKeyPair;
public function getNewestKeyPair(): ?RSAKeyPair;
public function getAllPublicJWK(): array;
public function updatePublicJWK(PublicJWK $jwk);
}
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
interface RefreshTokenDAO {
......
<?php
/* ----------------------------------------------------------------------------
* INAF - National Institute for Astrophysics
* IRA - Radioastronomical Institute - Bologna
* OATS - Astronomical Observatory - Trieste
* ----------------------------------------------------------------------------
*
/*
* This file is part of rap
* 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.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
......
<?php
/*
* This file is part of rap
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace RAP;
use PDO;
......