This commit is contained in:
commit
9b16074409
7 changed files with 480 additions and 0 deletions
16
.forgejo/workflows/tests.yaml
Normal file
16
.forgejo/workflows/tests.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
name: Run tests
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: docker.io/brenard/php-pre-commit:latest
|
||||||
|
options: "--workdir /src"
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install
|
||||||
|
- name: Run pre-commit
|
||||||
|
run: pre-commit run --all-files
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/vendor
|
||||||
|
/composer.lock
|
33
.pre-commit-config.yaml
Normal file
33
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Pre-commit hooks to run tests and ensure code is cleaned.
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
---
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/codespell-project/codespell
|
||||||
|
rev: v2.2.2
|
||||||
|
hooks:
|
||||||
|
- id: codespell
|
||||||
|
exclude: static/lib/|locales/.*\.js$|\.pot$
|
||||||
|
args: ["--write-changes"]
|
||||||
|
exclude_types: [csv, json]
|
||||||
|
- repo: https://github.com/adrienverge/yamllint
|
||||||
|
rev: v1.32.0
|
||||||
|
hooks:
|
||||||
|
- id: yamllint
|
||||||
|
ignore: .github/
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v2.7.1
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
- repo: https://github.com/digitalpulp/pre-commit-php.git
|
||||||
|
rev: 1.4.0
|
||||||
|
hooks:
|
||||||
|
- id: php-stan
|
||||||
|
files: ^(?!example/).*\.(php)$
|
||||||
|
args: ["--configuration=phpstan.neon"]
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
stages: [manual]
|
||||||
|
- id: check-json
|
||||||
|
exclude: (.vscode|.devcontainer)
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Test OIDC Client
|
||||||
|
|
||||||
|
This application acting as OIDC Client.
|
||||||
|
|
||||||
|
Features :
|
||||||
|
|
||||||
|
- Login/Logout on OIDC server
|
||||||
|
- Local logout
|
||||||
|
- Show authenticated user's information (token ID, claims and attributes)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- PHP 7.0 or greater
|
||||||
|
- `composer` to install dependencies (from `composer` Debian packages for instance,
|
||||||
|
see https://getcomposer.org/download/ otherwise)
|
||||||
|
- PHP `curl` and `json` extensions (from `php-curl` and `php-json` Debian packages for instance)
|
||||||
|
- Apache PHP support (using _mod_php_ or _PHP Fpm_, install _libapache2-mod-php_ Debian packages for instance)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
- Install the application and its requirements
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install -y libapache2-mod-php php-curl php-json git composer
|
||||||
|
mkdir -p /var/www/connection/
|
||||||
|
git clone https://gogs.zionetrix.net/bn8/test-oidc-client.git /var/www/connection/test-oidc-client
|
||||||
|
cd /var/www/connection/test-oidc-client
|
||||||
|
composer install
|
||||||
|
```
|
||||||
|
|
||||||
|
- Configure access to the application, for instance, in an existing Apache _VirtualHost_ definition by adding something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Alias /test-oidc /var/www/connection/test-oidc-client/public_html
|
||||||
|
ProxyPass /test-oidc !
|
||||||
|
<Directory /var/www/connection/test-oidc-client/public_html>
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
```
|
8
composer.json
Normal file
8
composer.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"jumbojett/openid-connect-php": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^1.11"
|
||||||
|
}
|
||||||
|
}
|
4
phpstan.neon
Normal file
4
phpstan.neon
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
paths:
|
||||||
|
- public_html
|
378
public_html/index.php
Normal file
378
public_html/index.php
Normal file
|
@ -0,0 +1,378 @@
|
||||||
|
<?php
|
||||||
|
# vim: shiftwidth=2 tabstop=2 expandtab
|
||||||
|
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Jumbojett\OpenIDConnectClient;
|
||||||
|
use Jumbojett\OpenIDConnectClientException;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
************************************
|
||||||
|
* Configuration *
|
||||||
|
************************************
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public app URL
|
||||||
|
$public_app_url = "https://connection.example.com/test-oidc";
|
||||||
|
|
||||||
|
// All valid OIDC servers
|
||||||
|
$oidc_servers = [
|
||||||
|
// OIDC server hostname
|
||||||
|
"connection.example.com" => [
|
||||||
|
// OIDC server root URL
|
||||||
|
"url" => "https://connection.example.com",
|
||||||
|
|
||||||
|
// Client ID (as provided by your IDP)
|
||||||
|
"client_id" => "27d65748-0aaa-42d9-90dc-353d94f2840a",
|
||||||
|
|
||||||
|
// Client secret (as provided by your IDP)
|
||||||
|
"client_secret" => "5d86b271-a3b8-4d75-b569-a86d48b75658",
|
||||||
|
|
||||||
|
// Requested OIDC scopes
|
||||||
|
"scopes" => ["profile", "email"],
|
||||||
|
|
||||||
|
// Expected attributes (optional, retrieve all proposed attributes otherwise)
|
||||||
|
/*
|
||||||
|
"expected_attributes" => [
|
||||||
|
"preferred_username",
|
||||||
|
"given_name",
|
||||||
|
"family_name",
|
||||||
|
"email",
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// FQDN of OIDC server
|
||||||
|
$default_oidc_server = key($oidc_servers);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
************************************
|
||||||
|
* Main *
|
||||||
|
************************************
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$_SESSION["warnings"] = isset($_SESSION["warnings"]) && is_array($_SESSION["warnings"])?$_SESSION["warnings"]:[];
|
||||||
|
|
||||||
|
if (isset($_REQUEST['server']) && !isset($oidc_servers[$_REQUEST['server']])) {
|
||||||
|
$_SESSION["warnings"][] = "Invalid OIDC server choiced";
|
||||||
|
unset($_REQUEST['server']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_REQUEST['server'])) {
|
||||||
|
$oidc_server = $_REQUEST['server'];
|
||||||
|
if ($_SESSION['oidc_server'] != $oidc_server) {
|
||||||
|
$_SESSION = []; // reset session on changing OIDC server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['oidc_server'])) {
|
||||||
|
$oidc_server = $_SESSION['oidc_server'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$oidc_server = $default_oidc_server;
|
||||||
|
$_SESSION = []; // reset session
|
||||||
|
}
|
||||||
|
$_SESSION['oidc_server'] = $oidc_server;
|
||||||
|
|
||||||
|
function reset_session() {
|
||||||
|
global $oidc_server;
|
||||||
|
$_SESSION = [];
|
||||||
|
if (isset($oidc_server) && $oidc_server)
|
||||||
|
$_SESSION['oidc_server'] = $oidc_server;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_show_oidc_client_config = false;
|
||||||
|
function show_oidc_client_config() {
|
||||||
|
global $_show_oidc_client_config, $oidc_client_config;
|
||||||
|
if ($_show_oidc_client_config) return true;
|
||||||
|
$_show_oidc_client_config = true;
|
||||||
|
echo "<h3>OIDC Client configuration</h3><ul>";
|
||||||
|
foreach($oidc_client_config as $cfg_name => $cfg_val) {
|
||||||
|
echo "<li><strong>$cfg_name :</strong> <em>$cfg_val</em></li>";
|
||||||
|
}
|
||||||
|
echo "</ul>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_warnings() {
|
||||||
|
if (!empty($_SESSION["warnings"])) {
|
||||||
|
echo "<h2 style='color: #f00'>Warnings message</h2><ul>";
|
||||||
|
foreach ($_SESSION["warnings"] as $msg) {
|
||||||
|
echo "<li>$msg</li>";
|
||||||
|
}
|
||||||
|
echo "</ul>";
|
||||||
|
}
|
||||||
|
$_SESSION["warnings"] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION["messages"] = isset($_SESSION["messages"]) && is_array($_SESSION["messages"])?$_SESSION["messages"]:[];
|
||||||
|
function show_messages() {
|
||||||
|
if (!empty($_SESSION["messages"])) {
|
||||||
|
echo "<div class='success'>";
|
||||||
|
if (count($_SESSION["messages"]) == 1) {
|
||||||
|
echo $_SESSION["messages"][0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echo "<ul>";
|
||||||
|
foreach($_SESSION["messages"] as $msg)
|
||||||
|
echo "<li>$msg</li>";
|
||||||
|
echo "</ul>";
|
||||||
|
}
|
||||||
|
echo "</div>";
|
||||||
|
}
|
||||||
|
$_SESSION["messages"] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function vardump($value) {
|
||||||
|
ob_start();
|
||||||
|
var_dump($value);
|
||||||
|
$value = ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirect($url=null) {
|
||||||
|
global $public_app_url;
|
||||||
|
$url = $url?$url:$public_app_url;
|
||||||
|
header("Location: $url");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_user_infos() {
|
||||||
|
echo "<h2>Token ID</h2>";
|
||||||
|
echo "<pre>".vardump($_SESSION['id_token'])."</pre>";
|
||||||
|
|
||||||
|
echo "<h2>Verified claims</h2>";
|
||||||
|
echo "<pre>".vardump($_SESSION['verified_claims'])."</pre>";
|
||||||
|
|
||||||
|
echo "<h2>Attributes</h2>";
|
||||||
|
echo "<ul>";
|
||||||
|
foreach($_SESSION['attributes'] as $attr => $value) {
|
||||||
|
$value = vardump($value);
|
||||||
|
echo "<li><b>{$attr} :</b><pre>{$value}</pre></li>\n";
|
||||||
|
}
|
||||||
|
echo "</ul>";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test OIDC</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
strong {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin-left: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
border-left: 1px solid;
|
||||||
|
background-color: #eee;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.success, div.error {
|
||||||
|
padding: 0.2em;
|
||||||
|
width: 50%;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.success {
|
||||||
|
color: #0E4700;
|
||||||
|
border: 1px solid #0E4700;
|
||||||
|
background-color: #99E774;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.error {
|
||||||
|
color: #f00;
|
||||||
|
border: 1px solid #f00;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #C56E6E;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<h1>Test OIDC Application</h1>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
show_warnings();
|
||||||
|
show_messages();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<h2>OIDC server selection</h2>
|
||||||
|
<form action='index.php' method='POST'>
|
||||||
|
<label for='server'>OIDC server</label> :
|
||||||
|
<select name='server' id='server' onchange="javascript:submit();">
|
||||||
|
<?php
|
||||||
|
foreach($oidc_servers as $server => $opts) {
|
||||||
|
echo "<option value='$server'".($oidc_server == $server?'selected':'').">$server</option>\n";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
<input type='submit' value='Change'/>
|
||||||
|
</form>
|
||||||
|
<h2>Menu</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href='?do=login'>Login</a></li>
|
||||||
|
<li><a href='?do=logout'>Logout on OIDC server</a></li>
|
||||||
|
<li><a href='?do=local_logout'>Logout on local application</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>OIDC Client Initialization ...</h2>
|
||||||
|
<?php
|
||||||
|
$oidc_client_config = [
|
||||||
|
"Public application URL" => $public_app_url,
|
||||||
|
'OIDC Hostname' => $oidc_server,
|
||||||
|
'IDP URL' => $oidc_servers[$oidc_server]['url'],
|
||||||
|
'Client ID' => (
|
||||||
|
substr($oidc_servers[$oidc_server]['client_id'], 0, 4)."...".
|
||||||
|
substr($oidc_servers[$oidc_server]['client_id'], -2)
|
||||||
|
),
|
||||||
|
'Client secret' => (
|
||||||
|
substr($oidc_servers[$oidc_server]['client_secret'], 0, 4)."...".
|
||||||
|
substr($oidc_servers[$oidc_server]['client_secret'], -2)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
$oidc = new OpenIDConnectClient(
|
||||||
|
$oidc_servers[$oidc_server]['url'],
|
||||||
|
$oidc_servers[$oidc_server]['client_id'],
|
||||||
|
$oidc_servers[$oidc_server]['client_secret']
|
||||||
|
);
|
||||||
|
|
||||||
|
$client_redirect_url = $public_app_url."/?do=login";
|
||||||
|
$oidc_client_config["Client redirect URL"] = $client_redirect_url;
|
||||||
|
$oidc->setRedirectURL($client_redirect_url);
|
||||||
|
|
||||||
|
$client_logout_redirect_url = "$public_app_url/?do=logout_callback";
|
||||||
|
$oidc_client_config["Client logout redirect URL"] = $client_logout_redirect_url;
|
||||||
|
|
||||||
|
$oidc_client_config["Scopes"] = implode(", ", $oidc_servers[$oidc_server]['scopes']);
|
||||||
|
$oidc->addScope($oidc_servers[$oidc_server]['scopes']);
|
||||||
|
|
||||||
|
$oidc_client_config['Expected attributes'] = (
|
||||||
|
isset($oidc_servers[$oidc_server]['expected_attributes']) // @phpstan-ignore-line
|
||||||
|
&& is_array($oidc_servers[$oidc_server]['expected_attributes'])
|
||||||
|
&& ! empty($oidc_servers[$oidc_server]['expected_attributes'])?
|
||||||
|
implode(", ", $oidc_servers[$oidc_server]['expected_attributes']):
|
||||||
|
"none (retrieve all provided attributes)"
|
||||||
|
);
|
||||||
|
|
||||||
|
echo "<p><strong>Client successfully initialized</strong></p>";
|
||||||
|
|
||||||
|
show_oidc_client_config();
|
||||||
|
show_warnings();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<h2>Action</h2>
|
||||||
|
<h3>State before running action</h3>
|
||||||
|
<?php
|
||||||
|
if (isset($_SESSION['idToken']) && $_SESSION['idToken']) {
|
||||||
|
echo "Authenticated";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echo "Not authenticated";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<h3>Running action...</h3>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (isset($_REQUEST['do'])) {
|
||||||
|
switch($_REQUEST['do']) {
|
||||||
|
case 'login':
|
||||||
|
try {
|
||||||
|
$oidc->authenticate();
|
||||||
|
}
|
||||||
|
catch(OpenIDConnectClientException $e) {
|
||||||
|
$_SESSION["warnings"][] = "Fail to authenticate: ".$e->getMessage();
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
$_SESSION["messages"][] = "Successfully authenticated";
|
||||||
|
$_SESSION['id_token'] = $oidc->getIdToken();
|
||||||
|
$_SESSION['verified_claims'] = $oidc->getVerifiedClaims();
|
||||||
|
|
||||||
|
$_SESSION['attributes'] = [];
|
||||||
|
if (
|
||||||
|
isset($oidc_servers[$oidc_server]['expected_attributes']) // @phpstan-ignore-line
|
||||||
|
&& is_array($oidc_servers[$oidc_server]['expected_attributes'])
|
||||||
|
&& !empty($oidc_servers[$oidc_server]['expected_attributes'])
|
||||||
|
) {
|
||||||
|
foreach($oidc_servers[$oidc_server]['expected_attributes'] as $attr) {
|
||||||
|
try {
|
||||||
|
$_SESSION['attributes'][$attr] = $oidc->requestUserInfo($attr);
|
||||||
|
}
|
||||||
|
catch(OpenIDConnectClientException $e) {
|
||||||
|
$_SESSION["warnings"][] = "Fail to retrieve attribute '$attr': ".$e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
$_SESSION['attributes'] = get_object_vars($oidc->requestUserInfo());
|
||||||
|
}
|
||||||
|
catch(OpenIDConnectClientException $e) {
|
||||||
|
$_SESSION["warnings"][] = "Fail to retrieve attributes: ".$e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
redirect();
|
||||||
|
break;
|
||||||
|
case 'logout':
|
||||||
|
if (isset($_SESSION['id_token'])) {
|
||||||
|
$_SESSION["logout_expected"] = true;
|
||||||
|
$oidc->signOut($_SESSION['id_token'], $client_logout_redirect_url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$_SESSION["warnings"] = "Not yet authenticated, can't logout!";
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "logout_callback":
|
||||||
|
if (isset($_SESSION["logout_expected"]) && $_SESSION["logout_expected"]) {
|
||||||
|
$_SESSION = ["oidc_server" => $oidc_server];
|
||||||
|
$_SESSION["messages"][] = "Successfully logout from OIDC server and client application.";
|
||||||
|
unset($_SESSION["logout_expected"]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$_SESSION["warnings"] = "Unexpected logout callback URL call";
|
||||||
|
}
|
||||||
|
redirect();
|
||||||
|
break;
|
||||||
|
case 'local_logout':
|
||||||
|
$_SESSION = ["oidc_server" => $oidc_server];
|
||||||
|
$_SESSION["messages"][] = "Successfully logout from client application only.";
|
||||||
|
redirect();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$_SESSION["warningsverified_claims"] = "Incorrect requested action";
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echo "Nothing to do";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_SESSION['id_token'])) {
|
||||||
|
echo "<h2>Authenticated user information</h2>";
|
||||||
|
show_user_infos();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue