From 314ae475f67051e84b99732d55026550de802552 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Mon, 14 Nov 2016 11:54:03 +0100 Subject: [PATCH] Add support for environment variable login --- appinfo/app.php | 35 +- appinfo/info.xml | 7 +- appinfo/update.php | 6 + js/admin.js | 53 +- lib/AppInfo/Application.php | 21 - .../SAMLController.php} | 96 ++- lib/Exceptions/NoUserFoundException.php | 30 + lib/Settings/Admin.php | 19 +- lib/Settings/Section.php | 2 +- lib/samlsettings.php | 3 +- lib/userbackend.php | 6 +- templates/admin.php | 24 +- tests/AppInfo/ApplicationTest.php | 1 - tests/Controller/SAMLControllerTest.php | 246 ++++++++ tests/Settings/AdminTest.php | 45 +- tests/Settings/SectionTest.php | 2 +- tests/clover.xml | 549 ++++++++++++++++++ 17 files changed, 1072 insertions(+), 73 deletions(-) rename lib/{controller/samlcontroller.php => Controller/SAMLController.php} (65%) create mode 100644 lib/Exceptions/NoUserFoundException.php create mode 100644 tests/Controller/SAMLControllerTest.php create mode 100644 tests/clover.xml diff --git a/appinfo/app.php b/appinfo/app.php index e59d594..88fd027 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -21,6 +21,13 @@ require_once __DIR__ . '/../3rdparty/vendor/autoload.php'; +// If we run in CLI mode do not setup the app as it can fail the OCC execution +// since the URLGenerator isn't accessible. +if(OC::$CLI) { + return; +} + + $urlGenerator = \OC::$server->getURLGenerator(); $config = \OC::$server->getConfig(); $request = \OC::$server->getRequest(); @@ -32,7 +39,7 @@ $samlSettings = new \OCA\User_SAML\SAMLSettings( $userBackend = new \OCA\User_SAML\UserBackend( $config, - \OC::$server->getURLGenerator(), + $urlGenerator, \OC::$server->getSession(), \OC::$server->getDb() ); @@ -41,17 +48,33 @@ OC_User::useBackend($userBackend); OC_User::handleApacheAuth(); // Setting up the one login config may fail, if so, do not catch the requests later. -try { - $oneLoginSettings = new \OneLogin_Saml2_Settings($samlSettings->getOneLoginSettingsArray()); -} catch(OneLogin_Saml2_Error $e) { +$returnScript = false; +$type = ''; +switch($config->getAppValue('user_saml', 'type')) { + case 'saml': + try { + $oneLoginSettings = new \OneLogin_Saml2_Settings($samlSettings->getOneLoginSettingsArray()); + } catch (OneLogin_Saml2_Error $e) { + $returnScript = true; + } + $type = 'saml'; + break; + case 'environment-variable': + \OC::$server->getSession()->set('user_saml.samlUserData', $_SERVER); + $type = 'environment-variable'; + break; +} + +if($returnScript === true) { return; } $redirectSituation = false; - // All requests that are not authenticated and match against the "/login" route are // redirected to the SAML login endpoint -if(!$userSession->isLoggedIn() && \OC::$server->getRequest()->getPathInfo() === '/login') { +if(!$userSession->isLoggedIn() && + \OC::$server->getRequest()->getPathInfo() === '/login' && + $type === 'saml') { $redirectSituation = true; } diff --git a/appinfo/info.xml b/appinfo/info.xml index 210d5cd..96a78ec 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -1,11 +1,12 @@ user_saml - SAML authentication - Authenticates user against a SAML backend, such as Shibboleth. + SSO & SAML authentication + Authenticates user against a SAML backend, such as Shibboleth or + other SSO solutions such as Kerberos. AGPL Lukas Reschke - 1.2.1 + 1.2.2 diff --git a/appinfo/update.php b/appinfo/update.php index 4900f20..ecf28c9 100644 --- a/appinfo/update.php +++ b/appinfo/update.php @@ -28,3 +28,9 @@ $installedVersion = $config->getAppValue('user_saml', 'installed_version'); if (version_compare($installedVersion, '1.2.1', '<')) { $config->setAppValue('user_saml', 'general-use_saml_auth_for_desktop', '1'); } + +// Versions below 1.2.2 don't have the choice between environment variable or +// native SAML integration as the default was SAML back then. +if (version_compare($installedVersion, '1.2.2', '<')) { + $config->setAppValue('user_saml', 'type', 'saml'); +} diff --git a/js/admin.js b/js/admin.js index bafe325..a3814c9 100644 --- a/js/admin.js +++ b/js/admin.js @@ -5,6 +5,31 @@ function setSAMLConfigValue(category, setting, value) { } $(function() { + // Hide depending on the setup state + var type = $('#user-saml').data('type'); + if(type !== '') { + $('#user-saml-choose-type').addClass('hidden'); + $('#user-saml-warning-admin-user').removeClass('hidden'); + } else { + $('#user-saml div:gt(2)').addClass('hidden'); + $('#user-saml-settings .button').addClass('hidden'); + } + if(type === 'environment-variable') { + $('#user-saml div:gt(4)').addClass('hidden'); + $('#user-saml-settings .button').addClass('hidden'); + } + + $('#user-saml-choose-saml').click(function(e) { + e.preventDefault(); + OC.AppConfig.setValue('user_saml', 'type', 'saml'); + location.reload(); + }); + $('#user-saml-choose-env').click(function(e) { + e.preventDefault(); + OC.AppConfig.setValue('user_saml', 'type', 'environment-variable'); + location.reload(); + }); + // Enable tabs $('input:checkbox[value="1"]').attr('checked', true); @@ -71,19 +96,21 @@ $(function() { }); $('#user-saml').change(function() { - // Checks on each request whether the settings make sense or not - $.ajax({ - url: OC.generateUrl('/apps/user_saml/saml/metadata'), - type: 'GET' - }).fail(function (e) { - if(e.status === 500) { - $('#user-saml-settings-complete').addClass('hidden'); - $('#user-saml-settings-incomplete').removeClass('hidden'); - } - }).success(function (e) { - $('#user-saml-settings-complete').removeClass('hidden'); - $('#user-saml-settings-incomplete').addClass('hidden'); - }) + if(type === 'saml') { + // Checks on each request whether the settings make sense or not + $.ajax({ + url: OC.generateUrl('/apps/user_saml/saml/metadata'), + type: 'GET' + }).fail(function (e) { + if (e.status === 500) { + $('#user-saml-settings-complete').addClass('hidden'); + $('#user-saml-settings-incomplete').removeClass('hidden'); + } + }).success(function (e) { + $('#user-saml-settings-complete').removeClass('hidden'); + $('#user-saml-settings-incomplete').addClass('hidden'); + }) + } }); $('#user-saml-settings .toggle').on('click', function() { diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 9f05299..cc29250 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -33,27 +33,6 @@ class Application extends App { parent::__construct('user_saml', $urlParams); $container = $this->getContainer(); - /** - * Controller - */ - $container->registerService('SAMLController', function(IAppContainer $c) { - /** @var \OC\Server $server */ - $server = $c->query('ServerContainer'); - return new SAMLController( - $c->getAppName(), - $server->getRequest(), - $server->getSession(), - $server->getUserSession(), - new SAMLSettings($server->getURLGenerator(), $server->getConfig()), - new UserBackend( - $server->getConfig(), - $server->getURLGenerator(), - $server->getSession(), - $server->getDb() - ) - ); - }); - /** * Middleware */ diff --git a/lib/controller/samlcontroller.php b/lib/Controller/SAMLController.php similarity index 65% rename from lib/controller/samlcontroller.php rename to lib/Controller/SAMLController.php index 7dc59f2..88bc02e 100644 --- a/lib/controller/samlcontroller.php +++ b/lib/Controller/SAMLController.php @@ -21,12 +21,16 @@ namespace OCA\User_SAML\Controller; +use OCA\User_SAML\Exceptions\NoUserFoundException; use OCA\User_SAML\SAMLSettings; use OCA\User_SAML\UserBackend; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCP\IConfig; use OCP\IRequest; use OCP\ISession; +use OCP\IURLGenerator; +use OCP\IUserManager; use OCP\IUserSession; class SAMLController extends Controller { @@ -38,6 +42,12 @@ class SAMLController extends Controller { private $SAMLSettings; /** @var UserBackend */ private $userBackend; + /** @var IConfig */ + private $config; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IUserManager */ + private $userManager; /** * @param string $appName @@ -46,29 +56,92 @@ class SAMLController extends Controller { * @param IUserSession $userSession * @param SAMLSettings $SAMLSettings * @param UserBackend $userBackend + * @param IConfig $config + * @param IURLGenerator $urlGenerator + * @param IUserManager $userManager */ public function __construct($appName, IRequest $request, ISession $session, IUserSession $userSession, SAMLSettings $SAMLSettings, - UserBackend $userBackend) { + UserBackend $userBackend, + IConfig $config, + IURLGenerator $urlGenerator, + IUserManager $userManager) { parent::__construct($appName, $request); $this->session = $session; $this->userSession = $userSession; $this->SAMLSettings = $SAMLSettings; $this->userBackend = $userBackend; + $this->config = $config; + $this->urlGenerator = $urlGenerator; + $this->userManager = $userManager; + } + + /** + * @param array $auth + * @throws NoUserFoundException + */ + private function autoprovisionIfPossible(array $auth) { + $uidMapping = $this->config->getAppValue('user_saml', 'general-uid_mapping'); + if(isset($auth[$uidMapping])) { + if(is_array($auth[$uidMapping])) { + $uid = $auth[$uidMapping][0]; + } else { + $uid = $auth[$uidMapping]; + } + + $userExists = $this->userManager->userExists($uid); + if($userExists === true) { + return; + } + + $autoProvisioningAllowed = $this->userBackend->autoprovisionAllowed(); + if(!$userExists && !$autoProvisioningAllowed) { + throw new NoUserFoundException(); + } elseif(!$userExists && $autoProvisioningAllowed) { + $this->userBackend->createUserIfNotExists($uid); + return; + } + } + + throw new NoUserFoundException(); } /** * @PublicPage * @UseSession * @OnlyUnauthenticatedUsers + * + * @return Http\RedirectResponse + * @throws \Exception */ public function login() { - $auth = new \OneLogin_Saml2_Auth($this->SAMLSettings->getOneLoginSettingsArray()); - $ssoUrl = $auth->login(null, array(), false, false, true); - $this->session->set('user_saml.AuthNRequestID', $auth->getLastRequestID()); + $type = $this->config->getAppValue($this->appName, 'type'); + switch($type) { + case 'saml': + $auth = new \OneLogin_Saml2_Auth($this->SAMLSettings->getOneLoginSettingsArray()); + $ssoUrl = $auth->login(null, [], false, false, true); + $this->session->set('user_saml.AuthNRequestID', $auth->getLastRequestID()); + break; + case 'environment-variable': + $ssoUrl = $this->urlGenerator->getAbsoluteURL('/'); + try { + $this->autoprovisionIfPossible($this->session->get('user_saml.samlUserData')); + } catch (NoUserFoundException $e) { + $ssoUrl = $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.notProvisioned'); + } + break; + default: + throw new \Exception( + sprintf( + 'Type of "%s" is not supported for user_saml', + $type + ) + ); + } + return new Http\RedirectResponse($ssoUrl); } @@ -111,6 +184,7 @@ class SAMLController extends Controller { if (!empty($errors)) { print_r('

'.implode(', ', $errors).'

'); } + if (!$auth->isAuthenticated()) { echo "

Not authenticated

"; exit(); @@ -118,15 +192,11 @@ class SAMLController extends Controller { // Check whether the user actually exists, if not redirect to an error page // explaining the issue. - $uidMapping = \OC::$server->getConfig()->getAppValue('user_saml', 'general-uid_mapping', ''); - if(isset($auth->getAttributes()[$uidMapping])) { - $uid = $auth->getAttributes()[$uidMapping][0]; - $userExists = \OC::$server->getUserManager()->userExists($uid); - if(!$userExists && !$this->userBackend->autoprovisionAllowed()) { - return new Http\RedirectResponse(\OC::$server->getURLGenerator()->linkToRouteAbsolute('user_saml.SAML.notProvisioned')); - } elseif(!$userExists && $this->userBackend->autoprovisionAllowed()) { - $this->userBackend->createUserIfNotExists($uid); - } + try { + $this->autoprovisionIfPossible($auth->getAttributes()); + } catch (NoUserFoundException $e) { + return new Http\RedirectResponse($this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.notProvisioned')); + } $this->session->set('user_saml.samlUserData', $auth->getAttributes()); diff --git a/lib/Exceptions/NoUserFoundException.php b/lib/Exceptions/NoUserFoundException.php new file mode 100644 index 0000000..19b4fc2 --- /dev/null +++ b/lib/Exceptions/NoUserFoundException.php @@ -0,0 +1,30 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\User_SAML\Exceptions; + +/** + * Class NoUserFoundException is thrown when no user has been found + * + * @package OCA\User_SAML\Exceptions + */ +class NoUserFoundException extends \Exception { +} diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 4824d95..279aee0 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -25,6 +25,7 @@ namespace OCA\User_SAML\Settings; use OCP\AppFramework\Http\TemplateResponse; use OCP\Defaults; +use OCP\IConfig; use OCP\IL10N; use OCP\Settings\ISettings; @@ -33,15 +34,20 @@ class Admin implements ISettings { private $l10n; /** @var Defaults */ private $defaults; + /** @var IConfig */ + private $config; /** * @param IL10N $l10n * @param Defaults $defaults + * @param IConfig $config */ public function __construct(IL10N $l10n, - Defaults $defaults) { + Defaults $defaults, + IConfig $config) { $this->l10n = $l10n; $this->defaults = $defaults; + $this->config = $config; } /** @@ -77,17 +83,22 @@ class Admin implements ISettings { 'text' => $this->l10n->t('Only allow authentication if an account is existent on some other backend. (e.g. LDAP)'), 'type' => 'checkbox', ], - 'use_saml_auth_for_desktop' => [ + ]; + + $type = $this->config->getAppValue('user_saml', 'type'); + if($type === 'saml') { + $generalSettings['use_saml_auth_for_desktop'] = [ 'text' => $this->l10n->t('Use SAML auth for the %s desktop clients (requires user re-authentication)', [$this->defaults->getName()]), 'type' => 'checkbox', - ], - ]; + ]; + } $params = [ 'sp' => $serviceProviderFields, 'security-offer' => $securityOfferFields, 'security-required' => $securityRequiredFields, 'general' => $generalSettings, + 'type' => $type, ]; return new TemplateResponse('user_saml', 'admin', $params); diff --git a/lib/Settings/Section.php b/lib/Settings/Section.php index 5fed8cd..c31568b 100644 --- a/lib/Settings/Section.php +++ b/lib/Settings/Section.php @@ -48,7 +48,7 @@ class Section implements ISection { * {@inheritdoc} */ public function getName() { - return $this->l->t('SAML authentication'); + return $this->l->t('SSO & SAML authentication'); } /** diff --git a/lib/samlsettings.php b/lib/samlsettings.php index 9e4a054..0b3d43d 100644 --- a/lib/samlsettings.php +++ b/lib/samlsettings.php @@ -43,7 +43,7 @@ class SAMLSettings { public function getOneLoginSettingsArray() { $settings = [ - // 'debug' => true, + 'debug' => true, // 'strict' => true, 'security' => [ 'nameIdEncrypted' => ($this->config->getAppValue('user_saml', 'security-nameIdEncrypted', '0') === '1') ? true : false, @@ -57,6 +57,7 @@ class SAMLSettings { 'wantNameId' => ($this->config->getAppValue('user_saml', 'security-wantNameId', '0') === '1') ? true : false, 'wantNameIdEncrypted' => ($this->config->getAppValue('user_saml', 'security-wantNameIdEncrypted', '0') === '1') ? true : false, 'wantXMLValidation' => ($this->config->getAppValue('user_saml', 'security-wantXMLValidation', '0') === '1') ? true : false, + 'requestedAuthnContext' => false, ], 'sp' => [ 'entityId' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.getMetadata'), diff --git a/lib/userbackend.php b/lib/userbackend.php index 2967078..de68a52 100644 --- a/lib/userbackend.php +++ b/lib/userbackend.php @@ -301,7 +301,11 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend { $uidMapping = $this->config->getAppValue('user_saml', 'general-uid_mapping', ''); if($uidMapping !== '' && isset($samlData[$uidMapping])) { - $uid = $samlData[$uidMapping][0]; + if(is_array($samlData[$uidMapping])) { + $uid = $samlData[$uidMapping][0]; + } else { + $uid = $samlData[$uidMapping]; + } if($this->userExists($uid)) { return $uid; } diff --git a/templates/admin.php b/templates/admin.php index 0ab7cea..5f4c478 100644 --- a/templates/admin.php +++ b/templates/admin.php @@ -4,11 +4,30 @@ style('user_saml', 'admin'); /** @var array $_ */ ?> -
-

t('SAML')); ?>

+ +

t('SSO & SAML authentication')); ?>

+
+ t('Please choose whether you want to authenticate using the SAML provider built-in in Nextcloud or whether you want to authenticate against an environment variable.')) ?> +
+ + +
+ + +

t('General')) ?>

$attribute): ?> @@ -26,6 +45,7 @@ style('user_saml', 'admin');
+

t('Service Provider Data')) ?>

diff --git a/tests/AppInfo/ApplicationTest.php b/tests/AppInfo/ApplicationTest.php index 32eac3a..69ca49b 100644 --- a/tests/AppInfo/ApplicationTest.php +++ b/tests/AppInfo/ApplicationTest.php @@ -47,7 +47,6 @@ class ApplicationTest extends \Test\TestCase { public function queryData() { return [ - ['SAMLController', SAMLController::class], ['OnlyLoggedInMiddleware', OnlyLoggedInMiddleware::class], ]; diff --git a/tests/Controller/SAMLControllerTest.php b/tests/Controller/SAMLControllerTest.php new file mode 100644 index 0000000..0714758 --- /dev/null +++ b/tests/Controller/SAMLControllerTest.php @@ -0,0 +1,246 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\User_SAML\Tests\Controller; + +use OCA\User_SAML\Controller\SAMLController; +use OCA\User_SAML\SAMLSettings; +use OCA\User_SAML\UserBackend; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\IConfig; +use OCP\IRequest; +use OCP\ISession; +use OCP\IURLGenerator; +use OCP\IUserBackend; +use OCP\IUserManager; +use OCP\IUserSession; +use Test\TestCase; + +class SAMLControllerTest extends TestCase { + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */ + private $session; + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + private $userSession; + /** @var SAMLSettings|\PHPUnit_Framework_MockObject_MockObject*/ + private $samlSettings; + /** @var UserBackend|\PHPUnit_Framework_MockObject_MockObject */ + private $userBackend; + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + private $config; + /** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */ + private $urlGenerator; + /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ + private $userManager; + /** @var SAMLController */ + private $samlController; + + public function setUp() { + parent::setUp(); + + $this->request = $this->createMock(IRequest::class); + $this->session = $this->createMock(ISession::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->samlSettings = $this->createMock(SAMLSettings::class); + $this->userBackend = $this->createMock(UserBackend::class); + $this->config = $this->createMock(IConfig::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->userManager = $this->createMock(IUserManager::class); + + $this->samlController = new SAMLController( + 'user_saml', + $this->request, + $this->session, + $this->userSession, + $this->samlSettings, + $this->userBackend, + $this->config, + $this->urlGenerator, + $this->userManager + ); + } + + /** + * @expectedExceptionMessage Type of "UnknownValue" is not supported for user_saml + * @expectedException \Exception + */ + public function testLoginWithInvalidAppValue() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('user_saml', 'type') + ->willReturn('UnknownValue'); + $this->samlController->login(); + } + + public function testLoginWithEnvVariableAndExistingUser() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('user_saml', 'type') + ->willReturn('environment-variable'); + $this->session + ->expects($this->once()) + ->method('get') + ->with('user_saml.samlUserData') + ->willReturn([ + 'foo' => 'bar', + 'uid' => 'MyUid', + 'bar' => 'foo', + ]); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('user_saml', 'general-uid_mapping') + ->willReturn('uid'); + $this->userManager + ->expects($this->once()) + ->method('userExists') + ->with('MyUid') + ->willReturn(true); + $this->urlGenerator + ->expects($this->once()) + ->method('getAbsoluteURL') + ->with('/') + ->willReturn('https://nextcloud.com/absolute/'); + + $expected = new RedirectResponse('https://nextcloud.com/absolute/'); + $this->assertEquals($expected, $this->samlController->login()); + } + + public function testLoginWithEnvVariableAndExistingUserAndArray() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('user_saml', 'type') + ->willReturn('environment-variable'); + $this->session + ->expects($this->once()) + ->method('get') + ->with('user_saml.samlUserData') + ->willReturn([ + 'foo' => 'bar', + 'uid' => ['MyUid'], + 'bar' => 'foo', + ]); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('user_saml', 'general-uid_mapping') + ->willReturn('uid'); + $this->userManager + ->expects($this->once()) + ->method('userExists') + ->with('MyUid') + ->willReturn(true); + $this->urlGenerator + ->expects($this->once()) + ->method('getAbsoluteURL') + ->with('/') + ->willReturn('https://nextcloud.com/absolute/'); + + $expected = new RedirectResponse('https://nextcloud.com/absolute/'); + $this->assertEquals($expected, $this->samlController->login()); + } + + public function testLoginWithEnvVariableAndNotExistingUserWithProvisioning() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('user_saml', 'type') + ->willReturn('environment-variable'); + $this->session + ->expects($this->once()) + ->method('get') + ->with('user_saml.samlUserData') + ->willReturn([ + 'foo' => 'bar', + 'uid' => 'MyUid', + 'bar' => 'foo', + ]); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('user_saml', 'general-uid_mapping') + ->willReturn('uid'); + $this->userManager + ->expects($this->once()) + ->method('userExists') + ->with('MyUid') + ->willReturn(false); + $this->urlGenerator + ->expects($this->once()) + ->method('getAbsoluteURL') + ->with('/') + ->willReturn('https://nextcloud.com/absolute/'); + $this->userBackend + ->expects($this->at(0)) + ->method('autoprovisionAllowed') + ->willReturn(true); + $this->userBackend + ->expects($this->at(1)) + ->method('createUserIfNotExists') + ->with('MyUid'); + + $expected = new RedirectResponse('https://nextcloud.com/absolute/'); + $this->assertEquals($expected, $this->samlController->login()); + } + + public function testLoginWithEnvVariableAndNotExistingUserWithoutProvisioning() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('user_saml', 'type') + ->willReturn('environment-variable'); + $this->session + ->expects($this->once()) + ->method('get') + ->with('user_saml.samlUserData') + ->willReturn([ + 'foo' => 'bar', + 'uid' => 'MyUid', + 'bar' => 'foo', + ]); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('user_saml', 'general-uid_mapping') + ->willReturn('uid'); + $this->userManager + ->expects($this->once()) + ->method('userExists') + ->with('MyUid') + ->willReturn(false); + $this->urlGenerator + ->expects($this->once()) + ->method('linkToRouteAbsolute') + ->with('user_saml.SAML.notProvisioned') + ->willReturn('https://nextcloud.com/notprovisioned/'); + $this->userBackend + ->expects($this->once()) + ->method('autoprovisionAllowed') + ->willReturn(false); + + $expected = new RedirectResponse('https://nextcloud.com/notprovisioned/'); + $this->assertEquals($expected, $this->samlController->login()); + } +} diff --git a/tests/Settings/AdminTest.php b/tests/Settings/AdminTest.php index e38a582..a2dcf1c 100644 --- a/tests/Settings/AdminTest.php +++ b/tests/Settings/AdminTest.php @@ -23,6 +23,7 @@ namespace OCA\User_SAML\Tests\Settings; use OCP\AppFramework\Http\TemplateResponse; use OCP\Defaults; +use OCP\IConfig; use OCP\IL10N; class AdminTest extends \Test\TestCase { @@ -32,30 +33,30 @@ class AdminTest extends \Test\TestCase { private $l10n; /** @var Defaults|\PHPUnit_Framework_MockObject_MockObject */ private $defaults; + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + private $config; public function setUp() { $this->l10n = $this->createMock(IL10N::class); $this->defaults = $this->createMock(Defaults::class); + $this->config = $this->createMock(IConfig::class); $this->admin = new \OCA\User_SAML\Settings\Admin( $this->l10n, - $this->defaults + $this->defaults, + $this->config ); return parent::setUp(); } - public function testGetForm() { + public function formDataProvider() { $this->l10n ->expects($this->any()) ->method('t') ->will($this->returnCallback(function($text, $parameters = array()) { return vsprintf($text, $parameters); })); - $this->defaults - ->expects($this->once()) - ->method('getName') - ->willReturn('Nextcloud'); $serviceProviderFields = [ 'x509cert' => 'X.509 certificate of the Service Provider', @@ -99,6 +100,38 @@ class AdminTest extends \Test\TestCase { 'general' => $generalSettings, ]; + return $params; + } + + public function testGetFormWithoutType() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('user_saml', 'type') + ->willReturn(''); + + $params = $this->formDataProvider(); + unset($params['general']['use_saml_auth_for_desktop']); + $params['type'] = ''; + + $expected = new TemplateResponse('user_saml', 'admin', $params); + $this->assertEquals($expected, $this->admin->getForm()); + } + + public function testGetFormWithSaml() { + $this->defaults + ->expects($this->once()) + ->method('getName') + ->willReturn('Nextcloud'); + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('user_saml', 'type') + ->willReturn('saml'); + + $params = $this->formDataProvider(); + $params['type'] = 'saml'; + $expected = new TemplateResponse('user_saml', 'admin', $params); $this->assertEquals($expected, $this->admin->getForm()); } diff --git a/tests/Settings/SectionTest.php b/tests/Settings/SectionTest.php index cb2a822..43946f7 100644 --- a/tests/Settings/SectionTest.php +++ b/tests/Settings/SectionTest.php @@ -44,7 +44,7 @@ class SectionTest extends \Test\TestCase { $this->l10n ->expects($this->once()) ->method('t') - ->with('SAML authentication') + ->with('SSO & SAML authentication') ->willReturn('SAML authentication'); $this->assertSame('SAML authentication', $this->section->getName()); diff --git a/tests/clover.xml b/tests/clover.xml new file mode 100644 index 0000000..5bc6439 --- /dev/null +++ b/tests/clover.xml @@ -0,0 +1,549 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +