Files
friendica-addons/saml/saml.php
T
2022-11-19 21:41:07 -05:00

475 lines
13 KiB
PHP
Executable File

<?php
/*
* Name: SAML SSO and SLO
* Description: replace login and registration with a SAML identity provider.
* Version: 1.0
* Author: Ryan <https://friendica.verya.pe/profile/ryan>
*/
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\User;
use Friendica\Util\Strings;
use OneLogin\Saml2\Utils;
require_once(__DIR__ . '/vendor/autoload.php');
define('PW_LEN', 32); // number of characters to use for random passwords
function saml_module($a)
{
}
function saml_init(App $a)
{
if (DI::args()->getArgc() < 2) {
return;
}
if (!saml_is_configured()) {
echo 'Please configure the SAML add-on via the admin interface.';
return;
}
switch (DI::args()->get(1)) {
case 'metadata.xml':
saml_metadata();
break;
case 'sso':
saml_sso_reply($a);
break;
case 'slo':
saml_slo_reply();
break;
}
exit();
}
function saml_metadata()
{
try {
$settings = new \OneLogin\Saml2\Settings(saml_settings());
$metadata = $settings->getSPMetadata();
$errors = $settings->validateMetadata($metadata);
if (empty($errors)) {
header('Content-Type: text/xml');
echo $metadata;
} else {
throw new \OneLogin\Saml2\Error(
'Invalid SP metadata: '.implode(', ', $errors),
\OneLogin\Saml2\Error::METADATA_SP_INVALID
);
}
} catch (Exception $e) {
Logger::error($e->getMessage());
}
}
function saml_install()
{
Hook::register('login_hook', __FILE__, 'saml_sso_initiate');
Hook::register('logging_out', __FILE__, 'saml_slo_initiate');
Hook::register('head', __FILE__, 'saml_head');
Hook::register('footer', __FILE__, 'saml_footer');
}
function saml_head(App $a, string &$body)
{
DI::page()->registerStylesheet(__DIR__ . '/saml.css');
}
function saml_footer(App $a, string &$body)
{
$fragment = addslashes(BBCode::convert(DI::config()->get('saml', 'settings_statement')));
$body .= <<<EOL
<script>
var target=$("#settings-nickname-desc");
if (target.length) { target.append("<p>$fragment</p>"); }
</script>
EOL;
}
function saml_is_configured()
{
return
DI::config()->get('saml', 'idp_id') &&
DI::config()->get('saml', 'client_id') &&
DI::config()->get('saml', 'sso_url') &&
DI::config()->get('saml', 'slo_request_url') &&
DI::config()->get('saml', 'slo_response_url') &&
DI::config()->get('saml', 'sp_key') &&
DI::config()->get('saml', 'sp_cert') &&
DI::config()->get('saml', 'idp_cert');
}
function saml_sso_initiate(App $a, string &$body)
{
if (!saml_is_configured()) {
Logger::warning('SAML SSO tried to trigger, but the SAML addon is not configured yet!');
return;
}
$auth = new \OneLogin\Saml2\Auth(saml_settings());
$ssoBuiltUrl = $auth->login(null, [], false, false, true);
DI::session()->set('AuthNRequestID', $auth->getLastRequestID());
header('Pragma: no-cache');
header('Cache-Control: no-cache, must-revalidate');
header('Location: ' . $ssoBuiltUrl);
exit();
}
function saml_sso_reply(App $a)
{
$auth = new \OneLogin\Saml2\Auth(saml_settings());
$requestID = null;
if (DI::session()->exists('AuthNRequestID')) {
$requestID = DI::session()->get('AuthNRequestID');
}
$auth->processResponse($requestID);
DI::session()->remove('AuthNRequestID');
$errors = $auth->getErrors();
if (!empty($errors)) {
echo 'Errors encountered.';
Logger::error(implode(', ', $errors));
exit();
}
if (!$auth->isAuthenticated()) {
echo 'Not authenticated';
exit();
}
$username = $auth->getNameId();
$email = $auth->getAttributeWithFriendlyName('email')[0];
$name = $auth->getAttributeWithFriendlyName('givenName')[0];
$last_name = $auth->getAttributeWithFriendlyName('surname')[0];
if (strlen($last_name)) {
$name .= " $last_name";
}
if (!DBA::exists('user', ['nickname' => $username])) {
$user = saml_create_user($username, $email, $name);
} else {
$user = User::getByNickname($username);
}
if (!empty($user['uid'])) {
DI::auth()->setForUser($a, $user);
}
if (isset($_POST['RelayState']) && Utils::getSelfURL() != $_POST['RelayState']) {
$auth->redirectTo($_POST['RelayState']);
}
}
function saml_slo_initiate(App $a)
{
if (!saml_is_configured()) {
Logger::warning('SAML SLO tried to trigger, but the SAML addon is not configured yet!');
return;
}
$auth = new \OneLogin\Saml2\Auth(saml_settings());
$sloBuiltUrl = $auth->logout();
DI::session()->set('LogoutRequestID', $auth->getLastRequestID());
header('Pragma: no-cache');
header('Cache-Control: no-cache, must-revalidate');
header('Location: ' . $sloBuiltUrl);
exit();
}
function saml_slo_reply()
{
$auth = new \OneLogin\Saml2\Auth(saml_settings());
if (DI::session()->exists('LogoutRequestID')) {
$requestID = DI::session()->get('LogoutRequestID');
} else {
$requestID = null;
}
$auth->processSLO(false, $requestID);
$errors = $auth->getErrors();
if (empty($errors)) {
$auth->redirectTo(DI::baseUrl());
} else {
Logger::error(implode(', ', $errors));
}
}
function saml_input($key, $label, $description)
{
return [
'$' . $key => [
$key,
$label,
DI::config()->get('saml', $key),
$description,
true, // all the fields are required
]
];
}
function saml_addon_admin(App $a, string &$o)
{
$form =
saml_input(
'settings_statement',
DI::l10n()->t('Settings statement'), <