mirror of
https://github.com/netzbegruenung/user_saml.git
synced 2024-05-11 05:06:06 +02:00
Merge pull request #558 from nextcloud/enh/noid/settings-table
Move SAML configurations to a table of their own
This commit is contained in:
commit
d47892d0b3
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,6 +1,16 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## 5.0.0
|
||||||
|
### Changed
|
||||||
|
- store configurations in a separate database table, not appconfig
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- occ commands for modifying SAML configurations
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Ability to change SAML configuration with occ app-config, use the new occ commands instead
|
||||||
|
|
||||||
## 4.1.0
|
## 4.1.0
|
||||||
### Added
|
### Added
|
||||||
- Nextcloud 22 support
|
- Nextcloud 22 support
|
||||||
|
|
|
@ -38,12 +38,8 @@ try {
|
||||||
\OC::$server->getLogger()->logException($e);
|
\OC::$server->getLogger()->logException($e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$samlSettings = new \OCA\User_SAML\SAMLSettings(
|
|
||||||
$urlGenerator,
|
$samlSettings = \OC::$server->query(\OCA\User_SAML\SAMLSettings::class);
|
||||||
$config,
|
|
||||||
$request,
|
|
||||||
$session
|
|
||||||
);
|
|
||||||
|
|
||||||
$userData = new \OCA\User_SAML\UserData(
|
$userData = new \OCA\User_SAML\UserData(
|
||||||
new \OCA\User_SAML\UserResolver(\OC::$server->getUserManager()),
|
new \OCA\User_SAML\UserResolver(\OC::$server->getUserManager()),
|
||||||
|
@ -72,11 +68,6 @@ $returnScript = false;
|
||||||
$type = '';
|
$type = '';
|
||||||
switch ($config->getAppValue('user_saml', 'type')) {
|
switch ($config->getAppValue('user_saml', 'type')) {
|
||||||
case 'saml':
|
case 'saml':
|
||||||
try {
|
|
||||||
$oneLoginSettings = new \OneLogin\Saml2\Settings($samlSettings->getOneLoginSettingsArray(1));
|
|
||||||
} catch (\OneLogin\SAML2\Error $e) {
|
|
||||||
$returnScript = true;
|
|
||||||
}
|
|
||||||
$type = 'saml';
|
$type = 'saml';
|
||||||
break;
|
break;
|
||||||
case 'environment-variable':
|
case 'environment-variable':
|
||||||
|
|
|
@ -16,7 +16,7 @@ The following providers are supported and tested at the moment:
|
||||||
* Any other provider that authenticates using the environment variable
|
* Any other provider that authenticates using the environment variable
|
||||||
|
|
||||||
While theoretically any other authentication provider implementing either one of those standards is compatible, we like to note that they are not part of any internal test matrix.]]></description>
|
While theoretically any other authentication provider implementing either one of those standards is compatible, we like to note that they are not part of any internal test matrix.]]></description>
|
||||||
<version>4.3.0</version>
|
<version>5.0.0</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author>Lukas Reschke</author>
|
<author>Lukas Reschke</author>
|
||||||
<namespace>User_SAML</namespace>
|
<namespace>User_SAML</namespace>
|
||||||
|
@ -36,6 +36,10 @@ While theoretically any other authentication provider implementing either one of
|
||||||
<nextcloud min-version="21" max-version="24" />
|
<nextcloud min-version="21" max-version="24" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<commands>
|
<commands>
|
||||||
|
<command>OCA\User_SAML\Command\ConfigCreate</command>
|
||||||
|
<command>OCA\User_SAML\Command\ConfigDelete</command>
|
||||||
|
<command>OCA\User_SAML\Command\ConfigGet</command>
|
||||||
|
<command>OCA\User_SAML\Command\ConfigSet</command>
|
||||||
<command>OCA\User_SAML\Command\GetMetadata</command>
|
<command>OCA\User_SAML\Command\GetMetadata</command>
|
||||||
</commands>
|
</commands>
|
||||||
<settings>
|
<settings>
|
||||||
|
|
|
@ -69,20 +69,47 @@ return [
|
||||||
'url' => '/saml/selectUserBackEnd',
|
'url' => '/saml/selectUserBackEnd',
|
||||||
'verb' => 'GET',
|
'verb' => 'GET',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Settings#getSamlProviderIds',
|
||||||
|
'url' => '/settings/providers',
|
||||||
|
'verb' => 'GET',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Settings#getSamlProviderSettings',
|
'name' => 'Settings#getSamlProviderSettings',
|
||||||
'url' => '/settings/providerSettings/{providerId}',
|
'url' => '/settings/providerSettings/{providerId}',
|
||||||
'verb' => 'GET',
|
'verb' => 'GET',
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'providerId' => '1'
|
'providerId' => 1
|
||||||
|
],
|
||||||
|
'requirements' => [
|
||||||
|
'providerId' => '\d+'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Settings#setProviderSetting',
|
||||||
|
'url' => '/settings/providerSettings/{providerId}',
|
||||||
|
'verb' => 'PUT',
|
||||||
|
'defaults' => [
|
||||||
|
'providerId' => 1
|
||||||
|
],
|
||||||
|
'requirements' => [
|
||||||
|
'providerId' => '\d+'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Settings#newSamlProviderSettingsId',
|
||||||
|
'url' => '/settings/providerSettings',
|
||||||
|
'verb' => 'POST',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Settings#deleteSamlProviderSettings',
|
'name' => 'Settings#deleteSamlProviderSettings',
|
||||||
'url' => '/settings/providerSettings/{providerId}',
|
'url' => '/settings/providerSettings/{providerId}',
|
||||||
'verb' => 'DELETE',
|
'verb' => 'DELETE',
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'providerId' => '1'
|
'providerId' => 1
|
||||||
|
],
|
||||||
|
'requirements' => [
|
||||||
|
'providerId' => '\d+'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
178
js/admin.js
178
js/admin.js
|
@ -8,20 +8,30 @@
|
||||||
currentConfig: '1',
|
currentConfig: '1',
|
||||||
providerIds: '1',
|
providerIds: '1',
|
||||||
|
|
||||||
_getAppConfig: function (key) {
|
|
||||||
return $.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: OC.linkToOCS('apps/provisioning_api/api/v1', 2) + 'config/apps' + '/user_saml/' + key + '?format=json'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
init: function(callback) {
|
init: function(callback) {
|
||||||
this._getAppConfig('providerIds').done(function (data){
|
var xhr = new XMLHttpRequest();
|
||||||
if (data.ocs.data.data !== '') {
|
xhr.open('GET', OC.generateUrl('/apps/user_saml/settings/providers'));
|
||||||
OCA.User_SAML.Admin.providerIds = data.ocs.data.data;
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
OCA.User_SAML.Admin.currentConfig = OCA.User_SAML.Admin.providerIds.split(',').sort()[0];
|
xhr.setRequestHeader('requesttoken', OC.requestToken);
|
||||||
|
|
||||||
|
xhr.onload = function () {
|
||||||
|
var response = JSON.parse(xhr.response);
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
if (response.providerIds !== "") {
|
||||||
|
OCA.User_SAML.Admin.providerIds += ',' + response.providerIds;
|
||||||
|
OCA.User_SAML.Admin.currentConfig = OCA.User_SAML.Admin.providerIds.split(',')[0];
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
console.error("Could not fetch new provider ID");
|
||||||
}
|
}
|
||||||
callback();
|
};
|
||||||
});
|
xhr.onerror = function () {
|
||||||
|
console.error("Could not fetch new provider ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
},
|
},
|
||||||
chooseEnv: function() {
|
chooseEnv: function() {
|
||||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||||
|
@ -60,23 +70,49 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new provider
|
* Add a new provider
|
||||||
* @returns {number} id of the provider
|
|
||||||
*/
|
*/
|
||||||
addProvider: function(callback) {
|
addProvider: function (callback) {
|
||||||
var providerIds = OCA.User_SAML.Admin.providerIds.split(',');
|
var xhr = new XMLHttpRequest();
|
||||||
var nextId = 1;
|
xhr.open('POST', OC.generateUrl('/apps/user_saml/settings/providerSettings'));
|
||||||
if (providerIds.indexOf('1') >= 0) {
|
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||||
nextId = 2;
|
xhr.setRequestHeader('requesttoken', OC.requestToken)
|
||||||
while ($.inArray('' + nextId, providerIds) >= 0) {
|
|
||||||
nextId++;
|
xhr.onload = function () {
|
||||||
|
var response = JSON.parse(xhr.response)
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
OCA.User_SAML.Admin.providerIds += ',' + response.id;
|
||||||
|
callback(response.id)
|
||||||
|
} else {
|
||||||
|
console.error("Could not fetch new provider ID")
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
OCP.AppConfig.setValue('user_saml', 'providerIds', OCA.User_SAML.Admin.providerIds + ',' + nextId, {
|
xhr.onerror = function () {
|
||||||
success: function () {
|
console.error("Could not fetch new provider ID");
|
||||||
OCA.User_SAML.Admin.providerIds += ',' + nextId;
|
};
|
||||||
callback(nextId)
|
|
||||||
|
xhr.send();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateProvider: function (configKey, configValue, successCb, errorCb) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('PUT', OC.generateUrl('/apps/user_saml/settings/providerSettings/' + this.currentConfig));
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
xhr.setRequestHeader('requesttoken', OC.requestToken);
|
||||||
|
|
||||||
|
xhr.onload = function () {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
successCb();
|
||||||
|
} else {
|
||||||
|
console.error("Could not update config");
|
||||||
|
errorCb();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.error("Could not update config");
|
||||||
|
errorCb();
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(JSON.stringify({configKey: configKey, configValue: configValue.trim()}));
|
||||||
},
|
},
|
||||||
|
|
||||||
removeProvider: function(callback) {
|
removeProvider: function(callback) {
|
||||||
|
@ -86,17 +122,8 @@
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
providerIds.splice(index, 1);
|
providerIds.splice(index, 1);
|
||||||
}
|
}
|
||||||
var config = this.currentConfig;
|
|
||||||
$.ajax({ url: OC.generateUrl('/apps/user_saml/settings/providerSettings/' + this.currentConfig), type: 'DELETE'})
|
$.ajax({ url: OC.generateUrl('/apps/user_saml/settings/providerSettings/' + this.currentConfig), type: 'DELETE'})
|
||||||
.done(function(data) {
|
.done(callback(this.currentConfig));
|
||||||
OCP.AppConfig.setValue('user_saml', 'providerIds', providerIds.join(','), {
|
|
||||||
success: function () {
|
|
||||||
OCA.User_SAML.Admin.providerIds = providerIds.join(',');
|
|
||||||
callback(config);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -105,14 +132,22 @@
|
||||||
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.setSamlConfigValue, this, category, setting, value));
|
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.setSamlConfigValue, this, category, setting, value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// store global config flags without idp prefix
|
|
||||||
var configIdentifier = this.getConfigIdentifier();
|
|
||||||
if (global === true) {
|
|
||||||
configIdentifier = '';
|
|
||||||
}
|
|
||||||
OC.msg.startSaving('#user-saml-save-indicator');
|
OC.msg.startSaving('#user-saml-save-indicator');
|
||||||
OCP.AppConfig.setValue('user_saml', configIdentifier + category + '-' + setting, value.trim());
|
|
||||||
OC.msg.finishedSaving('#user-saml-save-indicator', {status: 'success', data: {message: t('user_saml', 'Saved')}});
|
var callbacks = {
|
||||||
|
success: function () {
|
||||||
|
OC.msg.finishedSaving('#user-saml-save-indicator', {status: 'success', data: {message: t('user_saml', 'Saved')}});
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
OC.msg.finishedSaving('#user-saml-save-indicator', {status: 'error', data: {message: t('user_saml', 'Could not save')}});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (global) {
|
||||||
|
OCP.AppConfig.setValue('user_saml', category + '-' + setting, value, callbacks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateProvider(category + '-' + setting, value, callbacks.success, callbacks.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})(OCA);
|
})(OCA);
|
||||||
|
@ -175,35 +210,48 @@ $(function() {
|
||||||
$('.account-list li[data-id="' + providerId + '"]').addClass('active');
|
$('.account-list li[data-id="' + providerId + '"]').addClass('active');
|
||||||
OCA.User_SAML.Admin.currentConfig = '' + providerId;
|
OCA.User_SAML.Admin.currentConfig = '' + providerId;
|
||||||
$.get(OC.generateUrl('/apps/user_saml/settings/providerSettings/' + providerId)).done(function(data) {
|
$.get(OC.generateUrl('/apps/user_saml/settings/providerSettings/' + providerId)).done(function(data) {
|
||||||
Object.keys(data).forEach(function(category, index){
|
document.querySelectorAll('#user-saml-settings input[type="text"], #user-saml-settings textarea').forEach(function (inputNode) {
|
||||||
|
inputNode.value = '';
|
||||||
|
});
|
||||||
|
document.querySelectorAll('#user-saml-settings input[type="checkbox"]').forEach(function (inputNode) {
|
||||||
|
inputNode.checked = false;
|
||||||
|
inputNode.setAttribute('value', '0');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('#user-saml-settings select option').forEach(function (inputNode) {
|
||||||
|
inputNode.selected = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(data).forEach(function(category){
|
||||||
var entries = data[category];
|
var entries = data[category];
|
||||||
Object.keys(entries).forEach(function (configKey) {
|
Object.keys(entries).forEach(function (configKey) {
|
||||||
var element = $('#user-saml-settings *[data-key="' + configKey + '"]');
|
var htmlElement = document.querySelector('#user-saml-settings *[data-key="' + configKey + '"]')
|
||||||
if ($('#user-saml-settings #user-saml-' + category + ' #user-saml-' + configKey).length) {
|
|| document.querySelector('#user-saml-' + category + ' #user-saml-' + configKey)
|
||||||
element = $('#user-saml-' + category + ' #user-saml-' + configKey);
|
|| document.querySelector('#user-saml-' + category + ' [name="' + configKey + '"]');
|
||||||
|
|
||||||
|
if (!htmlElement) {
|
||||||
|
console.log("could not find element for " + configKey);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if ($('#user-saml-settings #user-saml-' + category + ' [name="' + configKey + '"]').length) {
|
|
||||||
element = $('#user-saml-' + category + ' [name="' + configKey + '"]');
|
if ((htmlElement.tagName === 'INPUT' && htmlElement.getAttribute('type') === 'text')
|
||||||
}
|
|| htmlElement.tagName === 'TEXTAREA'
|
||||||
if(element.is('input') && element.prop('type') === 'text') {
|
) {
|
||||||
element.val(entries[configKey])
|
htmlElement.nodeValue = entries[configKey];
|
||||||
}
|
htmlElement.value = entries[configKey];
|
||||||
else if(element.is('textarea')) {
|
} else if (htmlElement.tagName === 'INPUT' && htmlElement.getAttribute('type') === 'checkbox') {
|
||||||
element.val(entries[configKey]);
|
htmlElement.checked = entries[configKey] === '1';
|
||||||
}
|
htmlElement.value = entries[configKey] === '1' ? '1' : '0';
|
||||||
else if(element.prop('type') === 'checkbox') {
|
} else if (htmlElement.tagName === 'SELECT') {
|
||||||
var value = entries[configKey] === '1' ? '1' : '0';
|
htmlElement.querySelector('[value="' + entries[configKey] + '"]').selected = true;
|
||||||
element.val(value);
|
|
||||||
} else {
|
} else {
|
||||||
console.log('unable to find element for ' + configKey);
|
console.error("Could not handle " + configKey + " Tag is " + htmlElement.tagName + " and type is " + htmlElement.getAttribute("type"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$('input:checkbox[value="1"]').attr('checked', true);
|
|
||||||
$('input:checkbox[value="0"]').attr('checked', false);
|
var xmlDownloadButton = document.getElementById('get-metadata');
|
||||||
var xmlDownloadButton = $('#get-metadata');
|
var url = xmlDownloadButton.dataset.base + '?idp=' + providerId;
|
||||||
var url = xmlDownloadButton.data('base') + '?idp=' + providerId;
|
xmlDownloadButton.setAttribute('href', url);
|
||||||
xmlDownloadButton.attr('href', url);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace OCA\User_SAML\AppInfo;
|
||||||
|
|
||||||
use OCA\User_SAML\DavPlugin;
|
use OCA\User_SAML\DavPlugin;
|
||||||
use OCA\User_SAML\Middleware\OnlyLoggedInMiddleware;
|
use OCA\User_SAML\Middleware\OnlyLoggedInMiddleware;
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
use OCP\AppFramework\App;
|
use OCP\AppFramework\App;
|
||||||
use OCP\AppFramework\IAppContainer;
|
use OCP\AppFramework\IAppContainer;
|
||||||
use OCP\SabrePluginEvent;
|
use OCP\SabrePluginEvent;
|
||||||
|
@ -48,7 +49,8 @@ class Application extends App {
|
||||||
return new DavPlugin(
|
return new DavPlugin(
|
||||||
$server->getSession(),
|
$server->getSession(),
|
||||||
$server->getConfig(),
|
$server->getConfig(),
|
||||||
$_SERVER
|
$_SERVER,
|
||||||
|
$server->get(SAMLSettings::class)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
53
lib/Command/ConfigCreate.php
Normal file
53
lib/Command/ConfigCreate.php
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\User_SAML\Command;
|
||||||
|
|
||||||
|
use OC\Core\Command\Base;
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class ConfigCreate extends Base {
|
||||||
|
|
||||||
|
/** @var SAMLSettings */
|
||||||
|
private $samlSettings;
|
||||||
|
|
||||||
|
public function __construct(SAMLSettings $samlSettings) {
|
||||||
|
parent::__construct();
|
||||||
|
$this->samlSettings = $samlSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this->setName('saml:config:create');
|
||||||
|
$this->setDescription('Creates a new config and prints the new provider ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$output->writeln($this->samlSettings->getNewProviderId());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
72
lib/Command/ConfigDelete.php
Normal file
72
lib/Command/ConfigDelete.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\User_SAML\Command;
|
||||||
|
|
||||||
|
use OC\Core\Command\Base;
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
|
use OCP\DB\Exception;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class ConfigDelete extends Base {
|
||||||
|
/** @var SAMLSettings */
|
||||||
|
private $samlSettings;
|
||||||
|
|
||||||
|
public function __construct(SAMLSettings $samlSettings) {
|
||||||
|
parent::__construct();
|
||||||
|
$this->samlSettings = $samlSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this->setName('saml:config:delete');
|
||||||
|
|
||||||
|
$this->addArgument(
|
||||||
|
'providerId',
|
||||||
|
InputArgument::REQUIRED,
|
||||||
|
'ProviderID of the SAML config to edit'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$pId = (int)$input->getArgument('providerId');
|
||||||
|
|
||||||
|
if ((string)$pId !== $input->getArgument('providerId')) {
|
||||||
|
// Make sure we don't delete provider with id 0 by error
|
||||||
|
$output->writeln('<error>providerId argument needs to be an number. Got: ' . $pId . '</error>');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$this->samlSettings->delete($pId);
|
||||||
|
$output->writeln('Provider deleted.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$output->writeln('<error>Provider with id: ' . $providerId . ' does not exist.</error>');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
74
lib/Command/ConfigGet.php
Normal file
74
lib/Command/ConfigGet.php
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\User_SAML\Command;
|
||||||
|
|
||||||
|
use OC\Core\Command\Base;
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class ConfigGet extends Base {
|
||||||
|
|
||||||
|
/** @var SAMLSettings */
|
||||||
|
private $samlSettings;
|
||||||
|
|
||||||
|
public function __construct(SAMLSettings $samlSettings) {
|
||||||
|
parent::__construct();
|
||||||
|
$this->samlSettings = $samlSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this->setName('saml:config:get');
|
||||||
|
|
||||||
|
$this->addOption(
|
||||||
|
'providerId',
|
||||||
|
'p',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'ProviderID of a SAML config to print'
|
||||||
|
);
|
||||||
|
parent::configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$providerId = (int)$input->getOption('providerId');
|
||||||
|
if (!empty($providerId)) {
|
||||||
|
$providerIds = [$providerId];
|
||||||
|
} else {
|
||||||
|
$providerIds = array_keys($this->samlSettings->getListOfIdps());
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = [];
|
||||||
|
foreach ($providerIds as $pid) {
|
||||||
|
$settings[$pid] = $this->samlSettings->get($pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeArrayInOutputFormat($input, $output, $settings);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
97
lib/Command/ConfigSet.php
Normal file
97
lib/Command/ConfigSet.php
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\User_SAML\Command;
|
||||||
|
|
||||||
|
use OC\Core\Command\Base;
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
|
use OCP\DB\Exception;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class ConfigSet extends Base {
|
||||||
|
|
||||||
|
/** @var SAMLSettings */
|
||||||
|
private $samlSettings;
|
||||||
|
|
||||||
|
public function __construct(SAMLSettings $samlSettings) {
|
||||||
|
parent::__construct();
|
||||||
|
$this->samlSettings = $samlSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this->setName('saml:config:set');
|
||||||
|
|
||||||
|
$this->addArgument(
|
||||||
|
'providerId',
|
||||||
|
InputArgument::REQUIRED,
|
||||||
|
'ProviderID of the SAML config to edit'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (SAMLSettings::IDP_CONFIG_KEYS as $key) {
|
||||||
|
$this->addOption(
|
||||||
|
$key,
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$pId = (int)$input->getArgument('providerId');
|
||||||
|
|
||||||
|
if ((string)$pId !== $input->getArgument('providerId')) {
|
||||||
|
// Make sure we don't delete provider with id 0 by error
|
||||||
|
$output->writeln('<error>providerId argument needs to be an number. Got: ' . $pId . '</error>');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$settings = $this->samlSettings->get($pId);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$output->writeln('<error>Provider with id: ' . $providerId . ' does not exist.</error>');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($input->getOptions() as $key => $value) {
|
||||||
|
if (!in_array($key, SAMLSettings::IDP_CONFIG_KEYS) || $value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($value === '') {
|
||||||
|
unset($settings[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$settings[$key] = $value;
|
||||||
|
}
|
||||||
|
$this->samlSettings->set($pId, $settings);
|
||||||
|
$output->writeln('The provider\'s config was updated.');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use OCA\User_SAML\SAMLSettings;
|
use OCA\User_SAML\SAMLSettings;
|
||||||
|
use OneLogin\Saml2\Error;
|
||||||
use OneLogin\Saml2\Settings;
|
use OneLogin\Saml2\Settings;
|
||||||
|
|
||||||
class GetMetadata extends Command {
|
class GetMetadata extends Command {
|
||||||
|
@ -67,7 +68,7 @@ EOT
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
$idp = $input->getArgument('idp');
|
$idp = (int)$input->getArgument('idp');
|
||||||
$settings = new Settings($this->SAMLSettings->getOneLoginSettingsArray($idp));
|
$settings = new Settings($this->SAMLSettings->getOneLoginSettingsArray($idp));
|
||||||
$metadata = $settings->getSPMetadata();
|
$metadata = $settings->getSPMetadata();
|
||||||
$errors = $settings->validateMetadata($metadata);
|
$errors = $settings->validateMetadata($metadata);
|
||||||
|
|
|
@ -51,7 +51,7 @@ class SAMLController extends Controller {
|
||||||
/** @var IUserSession */
|
/** @var IUserSession */
|
||||||
private $userSession;
|
private $userSession;
|
||||||
/** @var SAMLSettings */
|
/** @var SAMLSettings */
|
||||||
private $SAMLSettings;
|
private $samlSettings;
|
||||||
/** @var UserBackend */
|
/** @var UserBackend */
|
||||||
private $userBackend;
|
private $userBackend;
|
||||||
/** @var IConfig */
|
/** @var IConfig */
|
||||||
|
@ -76,7 +76,7 @@ class SAMLController extends Controller {
|
||||||
* @param IRequest $request
|
* @param IRequest $request
|
||||||
* @param ISession $session
|
* @param ISession $session
|
||||||
* @param IUserSession $userSession
|
* @param IUserSession $userSession
|
||||||
* @param SAMLSettings $SAMLSettings
|
* @param SAMLSettings $samlSettings
|
||||||
* @param UserBackend $userBackend
|
* @param UserBackend $userBackend
|
||||||
* @param IConfig $config
|
* @param IConfig $config
|
||||||
* @param IURLGenerator $urlGenerator
|
* @param IURLGenerator $urlGenerator
|
||||||
|
@ -84,24 +84,24 @@ class SAMLController extends Controller {
|
||||||
* @param IL10N $l
|
* @param IL10N $l
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
$appName,
|
$appName,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
ISession $session,
|
ISession $session,
|
||||||
IUserSession $userSession,
|
IUserSession $userSession,
|
||||||
SAMLSettings $SAMLSettings,
|
SAMLSettings $samlSettings,
|
||||||
UserBackend $userBackend,
|
UserBackend $userBackend,
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
IURLGenerator $urlGenerator,
|
IURLGenerator $urlGenerator,
|
||||||
ILogger $logger,
|
ILogger $logger,
|
||||||
IL10N $l,
|
IL10N $l,
|
||||||
UserResolver $userResolver,
|
UserResolver $userResolver,
|
||||||
UserData $userData,
|
UserData $userData,
|
||||||
ICrypto $crypto
|
ICrypto $crypto
|
||||||
) {
|
) {
|
||||||
parent::__construct($appName, $request);
|
parent::__construct($appName, $request);
|
||||||
$this->session = $session;
|
$this->session = $session;
|
||||||
$this->userSession = $userSession;
|
$this->userSession = $userSession;
|
||||||
$this->SAMLSettings = $SAMLSettings;
|
$this->samlSettings = $samlSettings;
|
||||||
$this->userBackend = $userBackend;
|
$this->userBackend = $userBackend;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
@ -171,7 +171,7 @@ class SAMLController extends Controller {
|
||||||
$type = $this->config->getAppValue($this->appName, 'type');
|
$type = $this->config->getAppValue($this->appName, 'type');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'saml':
|
case 'saml':
|
||||||
$auth = new Auth($this->SAMLSettings->getOneLoginSettingsArray($idp));
|
$auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp));
|
||||||
$ssoUrl = $auth->login(null, [], false, false, true);
|
$ssoUrl = $auth->login(null, [], false, false, true);
|
||||||
$response = new Http\RedirectResponse($ssoUrl);
|
$response = new Http\RedirectResponse($ssoUrl);
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ class SAMLController extends Controller {
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public function getMetadata($idp) {
|
public function getMetadata($idp) {
|
||||||
$settings = new Settings($this->SAMLSettings->getOneLoginSettingsArray($idp));
|
$settings = new Settings($this->samlSettings->getOneLoginSettingsArray($idp));
|
||||||
$metadata = $settings->getSPMetadata();
|
$metadata = $settings->getSPMetadata();
|
||||||
$errors = $settings->validateMetadata($metadata);
|
$errors = $settings->validateMetadata($metadata);
|
||||||
if (empty($errors)) {
|
if (empty($errors)) {
|
||||||
|
@ -304,7 +304,7 @@ class SAMLController extends Controller {
|
||||||
return new Http\RedirectResponse($this->urlGenerator->getAbsoluteURL('/'));
|
return new Http\RedirectResponse($this->urlGenerator->getAbsoluteURL('/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$auth = new Auth($this->SAMLSettings->getOneLoginSettingsArray($idp));
|
$auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp));
|
||||||
$auth->processResponse($AuthNRequestID);
|
$auth->processResponse($AuthNRequestID);
|
||||||
|
|
||||||
$this->logger->debug('Attributes send by the IDP: ' . json_encode($auth->getAttributes()));
|
$this->logger->debug('Attributes send by the IDP: ' . json_encode($auth->getAttributes()));
|
||||||
|
@ -411,14 +411,14 @@ class SAMLController extends Controller {
|
||||||
|
|
||||||
if ($pass) {
|
if ($pass) {
|
||||||
$idp = $this->session->get('user_saml.Idp');
|
$idp = $this->session->get('user_saml.Idp');
|
||||||
$auth = new Auth($this->SAMLSettings->getOneLoginSettingsArray($idp));
|
$auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp));
|
||||||
$stay = true ; // $auth will return the redirect URL but won't perform the redirect himself
|
$stay = true ; // $auth will return the redirect URL but won't perform the redirect himself
|
||||||
if ($isFromIDP) {
|
if ($isFromIDP) {
|
||||||
$keepLocalSession = true ; // do not let processSLO to delete the entire session. Let userSession->logout do the job
|
$keepLocalSession = true ; // do not let processSLO to delete the entire session. Let userSession->logout do the job
|
||||||
$targetUrl = $auth->processSLO(
|
$targetUrl = $auth->processSLO(
|
||||||
$keepLocalSession,
|
$keepLocalSession,
|
||||||
null,
|
null,
|
||||||
$this->SAMLSettings->usesSloWebServerDecode(),
|
$this->samlSettings->usesSloWebServerDecode($idp),
|
||||||
null,
|
null,
|
||||||
$stay
|
$stay
|
||||||
);
|
);
|
||||||
|
@ -432,7 +432,6 @@ class SAMLController extends Controller {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If request is not from IDP, we send the logout request to the IDP
|
// If request is not from IDP, we send the logout request to the IDP
|
||||||
$parameters = [];
|
|
||||||
$nameId = $this->session->get('user_saml.samlNameId');
|
$nameId = $this->session->get('user_saml.samlNameId');
|
||||||
$nameIdFormat = $this->session->get('user_saml.samlNameIdFormat');
|
$nameIdFormat = $this->session->get('user_saml.samlNameIdFormat');
|
||||||
$nameIdNameQualifier = $this->session->get('user_saml.samlNameIdNameQualifier');
|
$nameIdNameQualifier = $this->session->get('user_saml.samlNameIdNameQualifier');
|
||||||
|
@ -490,7 +489,7 @@ class SAMLController extends Controller {
|
||||||
public function selectUserBackEnd($redirectUrl) {
|
public function selectUserBackEnd($redirectUrl) {
|
||||||
$attributes = ['loginUrls' => []];
|
$attributes = ['loginUrls' => []];
|
||||||
|
|
||||||
if ($this->SAMLSettings->allowMultipleUserBackEnds()) {
|
if ($this->samlSettings->allowMultipleUserBackEnds()) {
|
||||||
$displayName = $this->l->t('Direct log in');
|
$displayName = $this->l->t('Direct log in');
|
||||||
|
|
||||||
$customDisplayName = $this->config->getAppValue('user_saml', 'directLoginName', '');
|
$customDisplayName = $this->config->getAppValue('user_saml', 'directLoginName', '');
|
||||||
|
@ -520,7 +519,7 @@ class SAMLController extends Controller {
|
||||||
*/
|
*/
|
||||||
private function getIdps($redirectUrl) {
|
private function getIdps($redirectUrl) {
|
||||||
$result = [];
|
$result = [];
|
||||||
$idps = $this->SAMLSettings->getListOfIdps();
|
$idps = $this->samlSettings->getListOfIdps();
|
||||||
foreach ($idps as $idpId => $displayName) {
|
foreach ($idps as $idpId => $displayName) {
|
||||||
$result[] = [
|
$result[] = [
|
||||||
'url' => $this->getSSOUrl($redirectUrl, $idpId),
|
'url' => $this->getSSOUrl($redirectUrl, $idpId),
|
||||||
|
|
|
@ -23,11 +23,14 @@
|
||||||
|
|
||||||
namespace OCA\User_SAML\Controller;
|
namespace OCA\User_SAML\Controller;
|
||||||
|
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
use OCA\User_SAML\Settings\Admin;
|
use OCA\User_SAML\Settings\Admin;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
use OCP\AppFramework\Http\Response;
|
use OCP\AppFramework\Http\Response;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
use OneLogin\Saml2\Constants;
|
||||||
|
|
||||||
class SettingsController extends Controller {
|
class SettingsController extends Controller {
|
||||||
|
|
||||||
|
@ -35,21 +38,32 @@ class SettingsController extends Controller {
|
||||||
private $config;
|
private $config;
|
||||||
/** @var Admin */
|
/** @var Admin */
|
||||||
private $admin;
|
private $admin;
|
||||||
|
/** @var SAMLSettings */
|
||||||
|
private $samlSettings;
|
||||||
|
|
||||||
public function __construct($appName,
|
public function __construct(
|
||||||
IRequest $request,
|
$appName,
|
||||||
IConfig $config,
|
IRequest $request,
|
||||||
Admin $admin) {
|
IConfig $config,
|
||||||
|
Admin $admin,
|
||||||
|
SAMLSettings $samlSettings
|
||||||
|
) {
|
||||||
parent::__construct($appName, $request);
|
parent::__construct($appName, $request);
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->admin = $admin;
|
$this->admin = $admin;
|
||||||
|
$this->samlSettings = $samlSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSamlProviderIds(): DataResponse {
|
||||||
|
$keys = array_keys($this->samlSettings->getListOfIdps());
|
||||||
|
return new DataResponse([ 'providerIds' => implode(',', $keys)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $providerId
|
* @param $providerId
|
||||||
* @return array of categories containing entries for each config parameter with their value
|
* @return array of categories containing entries for each config parameter with their value
|
||||||
*/
|
*/
|
||||||
public function getSamlProviderSettings($providerId) {
|
public function getSamlProviderSettings(int $providerId) {
|
||||||
/**
|
/**
|
||||||
* This uses the list of available config parameters from the admin section
|
* This uses the list of available config parameters from the admin section
|
||||||
* and extends it with fields that are not coming from \OCA\User_SAML\Settings\Admin
|
* and extends it with fields that are not coming from \OCA\User_SAML\Settings\Admin
|
||||||
|
@ -63,13 +77,15 @@ class SettingsController extends Controller {
|
||||||
'x509cert' => ['required' => false],
|
'x509cert' => ['required' => false],
|
||||||
];
|
];
|
||||||
/* Fetch all config values for the given providerId */
|
/* Fetch all config values for the given providerId */
|
||||||
$settings = [];
|
|
||||||
|
// initialize settings with default value for option box (others are left empty)
|
||||||
|
$settings['sp']['name-id-format'] = Constants::NAMEID_UNSPECIFIED;
|
||||||
|
$storedSettings = $this->samlSettings->get($providerId);
|
||||||
foreach ($params as $category => $content) {
|
foreach ($params as $category => $content) {
|
||||||
if (empty($content) || $category === 'providers' || $category === 'type') {
|
if (empty($content) || $category === 'providers' || $category === 'type') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($content as $setting => $details) {
|
foreach ($content as $setting => $details) {
|
||||||
$prefix = $providerId === '1' ? '' : $providerId . '-';
|
|
||||||
/* use security as category instead of security-* */
|
/* use security as category instead of security-* */
|
||||||
if (strpos($category, 'security-') === 0) {
|
if (strpos($category, 'security-') === 0) {
|
||||||
$category = 'security';
|
$category = 'security';
|
||||||
|
@ -78,42 +94,41 @@ class SettingsController extends Controller {
|
||||||
// as this is the only category that has the saml- prefix on config keys
|
// as this is the only category that has the saml- prefix on config keys
|
||||||
if (strpos($category, 'attribute-mapping') === 0) {
|
if (strpos($category, 'attribute-mapping') === 0) {
|
||||||
$category = 'attribute-mapping';
|
$category = 'attribute-mapping';
|
||||||
$key = $prefix . 'saml-attribute-mapping' . '-' . $setting;
|
$key = 'saml-attribute-mapping' . '-' . $setting;
|
||||||
|
} elseif ($category === 'name-id-formats') {
|
||||||
|
if ($setting === $storedSettings['sp-name-id-format']) {
|
||||||
|
$settings['sp']['name-id-format'] = $storedSettings['sp-name-id-format'];
|
||||||
|
//continue 2;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$key = $prefix . $category . '-' . $setting;
|
$key = $category . '-' . $setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($details['global']) && $details['global']) {
|
||||||
|
// Read legacy data from oc_appconfig
|
||||||
|
$settings[$category][$setting] = $this->config->getAppValue('user_saml', $key, '');
|
||||||
|
} else {
|
||||||
|
$settings[$category][$setting] = $storedSettings[$key] ?? '';
|
||||||
}
|
}
|
||||||
$settings[$category][$setting] = $this->config->getAppValue('user_saml', $key, '');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $settings;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteSamlProviderSettings($providerId) {
|
public function deleteSamlProviderSettings($providerId) {
|
||||||
$params = $this->admin->getForm()->getParams();
|
$this->samlSettings->delete($providerId);
|
||||||
$params['idp'] = [
|
|
||||||
'singleLogoutService.url' => null,
|
|
||||||
'singleLogoutService.responseUrl' => null,
|
|
||||||
'singleSignOnService.url' => null,
|
|
||||||
'idp-entityId' => null,
|
|
||||||
];
|
|
||||||
/* Fetch all config values for the given providerId */
|
|
||||||
foreach ($params as $category => $content) {
|
|
||||||
if (!is_array($content) || $category === 'providers') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foreach ($content as $setting => $details) {
|
|
||||||
if (isset($details['global']) && $details['global'] === true) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$prefix = $providerId === '1' ? '' : $providerId . '-';
|
|
||||||
$key = $prefix . $category . '-' . $setting;
|
|
||||||
/* use security as category instead of security-* */
|
|
||||||
if (strpos($category, 'security-') === 0) {
|
|
||||||
$category = 'security';
|
|
||||||
}
|
|
||||||
$this->config->deleteAppValue('user_saml', $key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Response();
|
return new Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setProviderSetting(int $providerId, string $configKey, string $configValue) {
|
||||||
|
$configuration = $this->samlSettings->get($providerId);
|
||||||
|
$configuration[$configKey] = $configValue;
|
||||||
|
$this->samlSettings->set($providerId, $configuration);
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newSamlProviderSettingsId() {
|
||||||
|
return new DataResponse(['id' => $this->samlSettings->getNewProviderId()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,14 @@ class DavPlugin extends ServerPlugin {
|
||||||
private $auth;
|
private $auth;
|
||||||
/** @var Server */
|
/** @var Server */
|
||||||
private $server;
|
private $server;
|
||||||
|
/** @var SAMLSettings */
|
||||||
|
private $samlSettings;
|
||||||
|
|
||||||
public function __construct(ISession $session, IConfig $config, array $auth) {
|
public function __construct(ISession $session, IConfig $config, array $auth, SAMLSettings $samlSettings) {
|
||||||
$this->session = $session;
|
$this->session = $session;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->auth = $auth;
|
$this->auth = $auth;
|
||||||
|
$this->samlSettings = $samlSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +57,7 @@ class DavPlugin extends ServerPlugin {
|
||||||
$this->config->getAppValue('user_saml', 'type') === 'environment-variable' &&
|
$this->config->getAppValue('user_saml', 'type') === 'environment-variable' &&
|
||||||
!$this->session->exists('user_saml.samlUserData')
|
!$this->session->exists('user_saml.samlUserData')
|
||||||
) {
|
) {
|
||||||
$uidMapping = $this->config->getAppValue('user_saml', 'general-uid_mapping');
|
$uidMapping = $this->samlSettings->get(1)['general-uid_mapping'];
|
||||||
if (isset($this->auth[$uidMapping])) {
|
if (isset($this->auth[$uidMapping])) {
|
||||||
$this->session->set(Auth::DAV_AUTHENTICATED, $this->auth[$uidMapping]);
|
$this->session->set(Auth::DAV_AUTHENTICATED, $this->auth[$uidMapping]);
|
||||||
$this->session->set('user_saml.samlUserData', $this->auth);
|
$this->session->set('user_saml.samlUserData', $this->auth);
|
||||||
|
|
71
lib/Db/ConfigurationsEntity.php
Normal file
71
lib/Db/ConfigurationsEntity.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\User_SAML\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
use function json_decode;
|
||||||
|
use function json_encode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method string getName()
|
||||||
|
* @method void setName(string $value)
|
||||||
|
* @method void setConfiguration(string $value)
|
||||||
|
* @method string getConfiguration()
|
||||||
|
*/
|
||||||
|
class ConfigurationsEntity extends Entity {
|
||||||
|
/** @var string */
|
||||||
|
public $name;
|
||||||
|
/** @var string */
|
||||||
|
public $configuration;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->addType('name', 'string');
|
||||||
|
$this->addType('configuration', 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets also the name, because it is a shorthand to 'general-idp0_display_name'
|
||||||
|
*
|
||||||
|
* @throws \JsonException
|
||||||
|
*/
|
||||||
|
public function importConfiguration(array $configuration): void {
|
||||||
|
$this->setConfiguration(json_encode($configuration, JSON_THROW_ON_ERROR));
|
||||||
|
$this->setName($configuration['general-idp0_display_name'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfigurationArray(): array {
|
||||||
|
return json_decode($this->configuration, true) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asArray(): array {
|
||||||
|
return [
|
||||||
|
'id' => $this->getId(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'configuration' => $this->getConfigurationArray()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
101
lib/Db/ConfigurationsMapper.php
Normal file
101
lib/Db/ConfigurationsMapper.php
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||||
|
*
|
||||||
|
* @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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\User_SAML\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
class ConfigurationsMapper extends QBMapper {
|
||||||
|
public function __construct(IDBConnection $db) {
|
||||||
|
parent::__construct($db, 'user_saml_configurations', ConfigurationsEntity::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(int $id, array $configuration): void {
|
||||||
|
$entity = new ConfigurationsEntity();
|
||||||
|
$entity->setId($id);
|
||||||
|
$entity->importConfiguration($configuration);
|
||||||
|
$this->insertOrUpdate($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteById(int $id): void {
|
||||||
|
$entity = new ConfigurationsEntity();
|
||||||
|
$entity->setId($id);
|
||||||
|
$this->delete($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll(): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'configuration')
|
||||||
|
->from('user_saml_configurations')
|
||||||
|
->orderBy('id', 'ASC');
|
||||||
|
|
||||||
|
/** @var ConfigurationsEntity $entity */
|
||||||
|
$entities = $this->findEntities($qb);
|
||||||
|
$result = [];
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$result[$entity->getId()] = $entity->getConfigurationArray();
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(int $idp): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'configuration')
|
||||||
|
->from('user_saml_configurations')
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($idp, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
|
/** @var ConfigurationsEntity $entity */
|
||||||
|
try {
|
||||||
|
$entity = $this->findEntity($qb);
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $entity->getConfigurationArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reserveId(): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id')
|
||||||
|
->from('user_saml_configurations')
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
|
->setMaxResults(1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$entity = $this->findEntity($qb);
|
||||||
|
$newId = $entity->getId() + 1; // autoincrement manually
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
$newId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newEntity = new ConfigurationsEntity();
|
||||||
|
$newEntity->setId($newId);
|
||||||
|
$newEntity->importConfiguration([]);
|
||||||
|
return $this->insert($newEntity)->getId();
|
||||||
|
}
|
||||||
|
}
|
231
lib/Migration/Version5000Date20211025124248.php
Normal file
231
lib/Migration/Version5000Date20211025124248.php
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\User_SAML\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\DB\Types;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated migration step: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
class Version5000Date20211025124248 extends SimpleMigrationStep {
|
||||||
|
private const IDP_CONFIG_KEYS = [
|
||||||
|
'general-idp0_display_name',
|
||||||
|
'general-uid_mapping',
|
||||||
|
'idp-entityId',
|
||||||
|
'idp-singleLogoutService.responseUrl',
|
||||||
|
'idp-singleLogoutService.url',
|
||||||
|
'idp-singleSignOnService.url',
|
||||||
|
'idp-x509cert',
|
||||||
|
'security-authnRequestsSigned',
|
||||||
|
'security-general',
|
||||||
|
'security-logoutRequestSigned',
|
||||||
|
'security-logoutResponseSigned',
|
||||||
|
'security-lowercaseUrlencoding',
|
||||||
|
'security-nameIdEncrypted',
|
||||||
|
'security-offer',
|
||||||
|
'security-required',
|
||||||
|
'security-signatureAlgorithm',
|
||||||
|
'security-signMetadata',
|
||||||
|
'security-sloWebServerDecode',
|
||||||
|
'security-wantAssertionsEncrypted',
|
||||||
|
'security-wantAssertionsSigned',
|
||||||
|
'security-wantMessagesSigned',
|
||||||
|
'security-wantNameId',
|
||||||
|
'security-wantNameIdEncrypted',
|
||||||
|
'security-wantXMLValidation',
|
||||||
|
'saml-attribute-mapping-displayName_mapping',
|
||||||
|
'saml-attribute-mapping-email_mapping',
|
||||||
|
'saml-attribute-mapping-group_mapping',
|
||||||
|
'saml-attribute-mapping-home_mapping',
|
||||||
|
'saml-attribute-mapping-quota_mapping',
|
||||||
|
'sp-x509cert',
|
||||||
|
'sp-name-id-format',
|
||||||
|
'sp-privateKey',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var IDBConnection */
|
||||||
|
private $dbc;
|
||||||
|
|
||||||
|
/** @var ?IQueryBuilder */
|
||||||
|
private $insertQuery;
|
||||||
|
|
||||||
|
/** @var ?IQueryBuilder */
|
||||||
|
private $deleteQuery;
|
||||||
|
|
||||||
|
/** @var ?IQueryBuilder */
|
||||||
|
private $readQuery;
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $dbc) {
|
||||||
|
$this->dbc = $dbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure():ISchemaWrapper $schemaClosure
|
||||||
|
* @param array $options
|
||||||
|
* @return null|ISchemaWrapper
|
||||||
|
*/
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||||
|
/** @var ISchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
if (!$schema->hasTable('user_saml_configurations')) {
|
||||||
|
$table = $schema->createTable('user_saml_configurations');
|
||||||
|
$table->addColumn('id', Types::INTEGER, [
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
$table->addColumn('name', Types::STRING, [
|
||||||
|
'length' => 256,
|
||||||
|
'notnull' => false,
|
||||||
|
'default' => '',
|
||||||
|
]);
|
||||||
|
$table->addColumn('configuration', Types::TEXT, [
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
$table->setPrimaryKey(['id'], 'idx_user_saml_config');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure():IschemaWrapper $schemaClosure
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||||
|
$prefixes = $this->fetchPrefixes();
|
||||||
|
foreach ($prefixes as $prefix) {
|
||||||
|
$keyStart = $prefix === 1 ? '' : $prefix . '-';
|
||||||
|
$configKeys = array_reduce(
|
||||||
|
self::IDP_CONFIG_KEYS,
|
||||||
|
function (array $carry, string $rawConfigKey) use ($keyStart): array {
|
||||||
|
$carry[] = $keyStart . $rawConfigKey;
|
||||||
|
return $carry;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
$configData = $keysToDelete = [];
|
||||||
|
$gRows = $this->readConfiguration($configKeys);
|
||||||
|
while ($row = $gRows->current()) {
|
||||||
|
$configData[$this->normalizeKey($row['configkey'])] = $row['configvalue'];
|
||||||
|
$keysToDelete[] = $row['configkey'];
|
||||||
|
$gRows->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($configData)) {
|
||||||
|
continue; // No config found
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->insertConfiguration($prefix, $configData) && !empty($keysToDelete)) {
|
||||||
|
$this->deleteOldConfiguration($keysToDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->deletePrefixes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-param list<string> $keys the list of keys to delete
|
||||||
|
*/
|
||||||
|
protected function deleteOldConfiguration(array $keys): bool {
|
||||||
|
if (!$this->deleteQuery) {
|
||||||
|
$this->deleteQuery = $this->dbc->getQueryBuilder();
|
||||||
|
|
||||||
|
$this->deleteQuery->delete('appconfig')
|
||||||
|
->where($this->deleteQuery->expr()->eq('appid', $this->deleteQuery->createNamedParameter('user_saml')))
|
||||||
|
->andWhere($this->deleteQuery->expr()->in('configkey', $this->deleteQuery->createParameter('cfgKeys')));
|
||||||
|
}
|
||||||
|
|
||||||
|
$deletedRows = $this->deleteQuery
|
||||||
|
->setParameter('cfgKeys', $keys, IQueryBuilder::PARAM_STR_ARRAY)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
return $deletedRows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $configData The key-value map of config to save
|
||||||
|
*/
|
||||||
|
protected function insertConfiguration(int $id, array $configData): bool {
|
||||||
|
if (!$this->insertQuery) {
|
||||||
|
$this->insertQuery = $this->dbc->getQueryBuilder();
|
||||||
|
$this->insertQuery->insert('user_saml_configurations')
|
||||||
|
->values([
|
||||||
|
'id' => $this->insertQuery->createParameter('configId'),
|
||||||
|
'name' => $this->insertQuery->createParameter('displayName'),
|
||||||
|
'configuration' => $this->insertQuery->createParameter('configuration'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertedRows = $this->insertQuery
|
||||||
|
->setParameter('configId', $id)
|
||||||
|
->setParameter('displayName', $configData['general-idp0_display_name'] ?? '')
|
||||||
|
->setParameter('configuration', \json_encode($configData, JSON_THROW_ON_ERROR))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
return $insertedRows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @psalm-param list<string> $configKeys */
|
||||||
|
protected function readConfiguration(array $configKeys): \Generator {
|
||||||
|
if (!$this->readQuery) {
|
||||||
|
$this->readQuery = $this->dbc->getQueryBuilder();
|
||||||
|
$this->readQuery->select('configkey', 'configvalue')
|
||||||
|
->from('appconfig')
|
||||||
|
->where($this->readQuery->expr()->eq('appid', $this->readQuery->createNamedParameter('user_saml')))
|
||||||
|
->andWhere($this->readQuery->expr()->in('configkey', $this->readQuery->createParameter('cfgKeys')));
|
||||||
|
}
|
||||||
|
|
||||||
|
$r = $this->readQuery->setParameter('cfgKeys', $configKeys, IQueryBuilder::PARAM_STR_ARRAY)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
while ($row = $r->fetch()) {
|
||||||
|
yield $row;
|
||||||
|
}
|
||||||
|
$r->closeCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function normalizeKey(string $prefixedKey): string {
|
||||||
|
$isPrefixed = \preg_match('/^[0-9]*-/', $prefixedKey, $matches);
|
||||||
|
if ($isPrefixed === 0) {
|
||||||
|
return $prefixedKey;
|
||||||
|
} elseif ($isPrefixed === 1) {
|
||||||
|
return \substr($prefixedKey, strlen($matches[0]));
|
||||||
|
}
|
||||||
|
throw new \RuntimeException('Invalid regex pattern');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @psalm-return list<int> */
|
||||||
|
protected function fetchPrefixes(): array {
|
||||||
|
$q = $this->dbc->getQueryBuilder();
|
||||||
|
$q->select('configvalue')
|
||||||
|
->from('appconfig')
|
||||||
|
->where($q->expr()->eq('appid', $q->createNamedParameter('user_saml')))
|
||||||
|
->andWhere($q->expr()->eq('configkey', $q->createNamedParameter('providerIds')));
|
||||||
|
|
||||||
|
$r = $q->execute();
|
||||||
|
$prefixes = $r->fetchOne();
|
||||||
|
if ($prefixes === false) {
|
||||||
|
return [1]; // 1 is the default value for providerIds
|
||||||
|
}
|
||||||
|
return array_map('intval', explode(',', $prefixes));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deletePrefixes(): void {
|
||||||
|
$q = $this->dbc->getQueryBuilder();
|
||||||
|
$q->delete('appconfig')
|
||||||
|
->where($q->expr()->eq('appid', $q->createNamedParameter('user_saml')))
|
||||||
|
->andWhere($q->expr()->eq('configkey', $q->createNamedParameter('providerIds')))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,9 @@
|
||||||
|
|
||||||
namespace OCA\User_SAML;
|
namespace OCA\User_SAML;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use OCA\User_SAML\Db\ConfigurationsMapper;
|
||||||
|
use OCP\DB\Exception;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\ISession;
|
use OCP\ISession;
|
||||||
|
@ -28,16 +31,59 @@ use OCP\IURLGenerator;
|
||||||
use OneLogin\Saml2\Constants;
|
use OneLogin\Saml2\Constants;
|
||||||
|
|
||||||
class SAMLSettings {
|
class SAMLSettings {
|
||||||
|
private const LOADED_NONE = 0;
|
||||||
|
private const LOADED_CHOSEN = 1;
|
||||||
|
private const LOADED_ALL = 2;
|
||||||
|
|
||||||
|
public const IDP_CONFIG_KEYS = [
|
||||||
|
'general-idp0_display_name',
|
||||||
|
'general-uid_mapping',
|
||||||
|
'idp-entityId',
|
||||||
|
'idp-singleLogoutService.responseUrl',
|
||||||
|
'idp-singleLogoutService.url',
|
||||||
|
'idp-singleSignOnService.url',
|
||||||
|
'idp-x509cert',
|
||||||
|
'security-authnRequestsSigned',
|
||||||
|
'security-general',
|
||||||
|
'security-logoutRequestSigned',
|
||||||
|
'security-logoutResponseSigned',
|
||||||
|
'security-lowercaseUrlencoding',
|
||||||
|
'security-nameIdEncrypted',
|
||||||
|
'security-offer',
|
||||||
|
'security-required',
|
||||||
|
'security-signatureAlgorithm',
|
||||||
|
'security-signMetadata',
|
||||||
|
'security-sloWebServerDecode',
|
||||||
|
'security-wantAssertionsEncrypted',
|
||||||
|
'security-wantAssertionsSigned',
|
||||||
|
'security-wantMessagesSigned',
|
||||||
|
'security-wantNameId',
|
||||||
|
'security-wantNameIdEncrypted',
|
||||||
|
'security-wantXMLValidation',
|
||||||
|
'saml-attribute-mapping-displayName_mapping',
|
||||||
|
'saml-attribute-mapping-email_mapping',
|
||||||
|
'saml-attribute-mapping-group_mapping',
|
||||||
|
'saml-attribute-mapping-home_mapping',
|
||||||
|
'saml-attribute-mapping-quota_mapping',
|
||||||
|
'sp-x509cert',
|
||||||
|
'sp-name-id-format',
|
||||||
|
'sp-privateKey',
|
||||||
|
];
|
||||||
|
|
||||||
/** @var IURLGenerator */
|
/** @var IURLGenerator */
|
||||||
private $urlGenerator;
|
private $urlGenerator;
|
||||||
/** @var IConfig */
|
/** @var IConfig */
|
||||||
private $config;
|
private $config;
|
||||||
/** @var IRequest */
|
|
||||||
private $request;
|
|
||||||
/** @var ISession */
|
/** @var ISession */
|
||||||
private $session;
|
private $session;
|
||||||
/** @var array list of global settings which are valid for every idp */
|
/** @var array list of global settings which are valid for every idp */
|
||||||
private $globalSettings = ['general-require_provisioned_account', 'general-allow_multiple_user_back_ends', 'general-use_saml_auth_for_desktop'];
|
private $globalSettings = ['general-require_provisioned_account', 'general-allow_multiple_user_back_ends', 'general-use_saml_auth_for_desktop'];
|
||||||
|
/** @var array<int, array<string, string>> */
|
||||||
|
private $configurations;
|
||||||
|
/** @var int */
|
||||||
|
private $configurationsLoadedState = self::LOADED_NONE;
|
||||||
|
/** @var ConfigurationsMapper */
|
||||||
|
private $mapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param IURLGenerator $urlGenerator
|
* @param IURLGenerator $urlGenerator
|
||||||
|
@ -45,148 +91,167 @@ class SAMLSettings {
|
||||||
* @param IRequest $request
|
* @param IRequest $request
|
||||||
* @param ISession $session
|
* @param ISession $session
|
||||||
*/
|
*/
|
||||||
public function __construct(IURLGenerator $urlGenerator,
|
public function __construct(
|
||||||
IConfig $config,
|
IURLGenerator $urlGenerator,
|
||||||
IRequest $request,
|
IConfig $config,
|
||||||
ISession $session) {
|
ISession $session,
|
||||||
|
ConfigurationsMapper $mapper
|
||||||
|
) {
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->request = $request;
|
|
||||||
$this->session = $session;
|
$this->session = $session;
|
||||||
|
$this->mapper = $mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get list of the configured IDPs
|
* Get list of the configured IDPs
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array<int, string>
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function getListOfIdps() {
|
public function getListOfIdps(): array {
|
||||||
|
$this->ensureConfigurationsLoaded();
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
foreach ($this->configurations as $configID => $config) {
|
||||||
$providerIds = explode(',', $this->config->getAppValue('user_saml', 'providerIds', '1'));
|
// no fancy array_* method, because there might be thousands
|
||||||
natsort($providerIds);
|
$result[$configID] = $config['general-idp0_display_name'] ?? '';
|
||||||
|
|
||||||
foreach ($providerIds as $id) {
|
|
||||||
$prefix = $id === '1' ? '' : $id .'-';
|
|
||||||
$result[$id] = $this->config->getAppValue('user_saml', $prefix . 'general-idp0_display_name', '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
asort($result);
|
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if multiple user back ends are allowed
|
* Check if multiple user back ends are allowed
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function allowMultipleUserBackEnds() {
|
public function allowMultipleUserBackEnds(): bool {
|
||||||
$type = $this->config->getAppValue('user_saml', 'type');
|
$type = $this->config->getAppValue('user_saml', 'type');
|
||||||
$setting = $this->config->getAppValue('user_saml', 'general-allow_multiple_user_back_ends', '0');
|
$setting = $this->config->getAppValue('user_saml', 'general-allow_multiple_user_back_ends', '0');
|
||||||
return ($setting === '1' && $type === 'saml');
|
return ($setting === '1' && $type === 'saml');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function usesSloWebServerDecode() : bool {
|
public function usesSloWebServerDecode(int $idp): bool {
|
||||||
return $this->config->getAppValue('user_saml', 'security-sloWebServerDecode', '0') === '1';
|
$config = $this->get($idp);
|
||||||
|
return ($config['security-sloWebServerDecode'] ?? false) === '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get config for given IDP
|
* Get config for given IDP
|
||||||
*
|
*
|
||||||
* @param int $idp
|
* @throws Exception
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getOneLoginSettingsArray($idp) {
|
public function getOneLoginSettingsArray(int $idp): array {
|
||||||
$prefix = '';
|
$this->ensureConfigurationsLoaded($idp);
|
||||||
if ($idp > 1) {
|
|
||||||
$prefix = $idp . '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
$settings = [
|
$settings = [
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
'debug' => $this->config->getSystemValue('debug', false),
|
'debug' => $this->config->getSystemValue('debug', false),
|
||||||
'baseurl' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.base'),
|
'baseurl' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.base'),
|
||||||
'security' => [
|
'security' => [
|
||||||
'nameIdEncrypted' => ($this->config->getAppValue('user_saml', $prefix . 'security-nameIdEncrypted', '0') === '1') ? true : false,
|
'nameIdEncrypted' => ($this->configurations[$idp]['security-nameIdEncrypted'] ?? '0') === '1',
|
||||||
'authnRequestsSigned' => ($this->config->getAppValue('user_saml', $prefix . 'security-authnRequestsSigned', '0') === '1') ? true : false,
|
'authnRequestsSigned' => ($this->configurations[$idp]['security-authnRequestsSigned'] ?? '0') === '1',
|
||||||
'logoutRequestSigned' => ($this->config->getAppValue('user_saml', $prefix . 'security-logoutRequestSigned', '0') === '1') ? true : false,
|
'logoutRequestSigned' => ($this->configurations[$idp]['security-logoutRequestSigned'] ?? '0') === '1',
|
||||||
'logoutResponseSigned' => ($this->config->getAppValue('user_saml', $prefix . 'security-logoutResponseSigned', '0') === '1') ? true : false,
|
'logoutResponseSigned' => ($this->configurations[$idp]['security-logoutResponseSigned'] ?? '0') === '1',
|
||||||
'signMetadata' => ($this->config->getAppValue('user_saml', $prefix . 'security-signMetadata', '0') === '1') ? true : false,
|
'signMetadata' => ($this->configurations[$idp]['security-signMetadata'] ?? '0') === '1',
|
||||||
'wantMessagesSigned' => ($this->config->getAppValue('user_saml', $prefix . 'security-wantMessagesSigned', '0') === '1') ? true : false,
|
'wantMessagesSigned' => ($this->configurations[$idp]['security-wantMessagesSigned'] ?? '0') === '1',
|
||||||
'wantAssertionsSigned' => ($this->config->getAppValue('user_saml', $prefix . 'security-wantAssertionsSigned', '0') === '1') ? true : false,
|
'wantAssertionsSigned' => ($this->configurations[$idp]['security-wantAssertionsSigned'] ?? '0') === '1',
|
||||||
'wantAssertionsEncrypted' => ($this->config->getAppValue('user_saml', $prefix . 'security-wantAssertionsEncrypted', '0') === '1') ? true : false,
|
'wantAssertionsEncrypted' => ($this->configurations[$idp]['security-wantAssertionsEncrypted'] ?? '0') === '1',
|
||||||
'wantNameId' => ($this->config->getAppValue('user_saml', $prefix . 'security-wantNameId', '0') === '1') ? true : false,
|
'wantNameId' => ($this->configurations[$idp]['security-wantNameId'] ?? '0') === '1',
|
||||||
'wantNameIdEncrypted' => ($this->config->getAppValue('user_saml', $prefix . 'security-wantNameIdEncrypted', '0') === '1') ? true : false,
|
'wantNameIdEncrypted' => ($this->configurations[$idp]['security-wantNameIdEncrypted'] ?? '0') === '1',
|
||||||
'wantXMLValidation' => ($this->config->getAppValue('user_saml', $prefix . 'security-wantXMLValidation', '0') === '1') ? true : false,
|
'wantXMLValidation' => ($this->configurations[$idp]['security-wantXMLValidation'] ?? '0') === '1',
|
||||||
'requestedAuthnContext' => false,
|
'requestedAuthnContext' => false,
|
||||||
'lowercaseUrlencoding' => ($this->config->getAppValue('user_saml', $prefix . 'security-lowercaseUrlencoding', '0') === '1') ? true : false,
|
'lowercaseUrlencoding' => ($this->configurations[$idp]['security-lowercaseUrlencoding'] ?? '0') === '1',
|
||||||
'signatureAlgorithm' => $this->config->getAppValue('user_saml', $prefix . 'security-signatureAlgorithm', null)
|
'signatureAlgorithm' => $this->configurations[$idp]['security-signatureAlgorithm'] ?? null,
|
||||||
|
// "sloWebServerDecode" is not expected to be passed to the OneLogin class
|
||||||
],
|
],
|
||||||
'sp' => [
|
'sp' => [
|
||||||
'entityId' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.getMetadata'),
|
'entityId' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.getMetadata'),
|
||||||
'assertionConsumerService' => [
|
'assertionConsumerService' => [
|
||||||
'url' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.assertionConsumerService'),
|
'url' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.assertionConsumerService'),
|
||||||
],
|
],
|
||||||
'NameIDFormat' => $this->config->getAppValue('user_saml', $prefix . 'sp-name-id-format', Constants::NAMEID_UNSPECIFIED)
|
'NameIDFormat' => $this->configurations[$idp]['sp-name-id-format'] ?? Constants::NAMEID_UNSPECIFIED,
|
||||||
|
'x509cert' => $this->configurations[$idp]['sp-x509cert'] ?? '',
|
||||||
|
'privateKey' => $this->configurations[$idp]['sp-privateKey'] ?? '',
|
||||||
],
|
],
|
||||||
'idp' => [
|
'idp' => [
|
||||||
'entityId' => $this->config->getAppValue('user_saml', $prefix . 'idp-entityId', ''),
|
'entityId' => $this->configurations[$idp]['idp-entityId'] ?? '',
|
||||||
'singleSignOnService' => [
|
'singleSignOnService' => [
|
||||||
'url' => $this->config->getAppValue('user_saml', $prefix . 'idp-singleSignOnService.url', ''),
|
'url' => $this->configurations[$idp]['idp-singleSignOnService.url'] ?? '',
|
||||||
],
|
],
|
||||||
|
'x509cert' => $this->configurations[$idp]['idp-x509cert'] ?? '',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$spx509cert = $this->config->getAppValue('user_saml', $prefix . 'sp-x509cert', '');
|
// must be added only if configured
|
||||||
$spxprivateKey = $this->config->getAppValue('user_saml', $prefix . 'sp-privateKey', '');
|
if (($this->configurations[$idp]['idp-singleLogoutService.url'] ?? '') !== '') {
|
||||||
if ($spx509cert !== '') {
|
$settings['idp']['singleLogoutService'] = ['url' => $this->configurations[$idp]['idp-singleLogoutService.url']];
|
||||||
$settings['sp']['x509cert'] = $spx509cert;
|
$settings['sp']['singleLogoutService'] = ['url' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.singleLogoutService')];
|
||||||
}
|
if (($this->configurations[$idp]['idp-singleLogoutService.responseUrl'] ?? '') !== '') {
|
||||||
if ($spxprivateKey !== '') {
|
$settings['idp']['singleLogoutService']['responseUrl'] = $this->configurations[$idp]['idp-singleLogoutService.responseUrl'];
|
||||||
$settings['sp']['privateKey'] = $spxprivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
$idpx509cert = $this->config->getAppValue('user_saml', $prefix . 'idp-x509cert', '');
|
|
||||||
if ($idpx509cert !== '') {
|
|
||||||
$settings['idp']['x509cert'] = $idpx509cert;
|
|
||||||
}
|
|
||||||
|
|
||||||
$slo = $this->config->getAppValue('user_saml', $prefix . 'idp-singleLogoutService.url', '');
|
|
||||||
if ($slo !== '') {
|
|
||||||
$settings['idp']['singleLogoutService'] = [
|
|
||||||
'url' => $this->config->getAppValue('user_saml', $prefix . 'idp-singleLogoutService.url', ''),
|
|
||||||
];
|
|
||||||
$settings['sp']['singleLogoutService'] = [
|
|
||||||
'url' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.singleLogoutService'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$sloResponseUrl = $this->config->getAppValue('user_saml', $prefix . 'idp-singleLogoutService.responseUrl', '');
|
|
||||||
if ($sloResponseUrl !== '') {
|
|
||||||
$settings['idp']['singleLogoutService']['responseUrl'] = $sloResponseUrl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $settings;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getProviderId(): int {
|
||||||
|
// defaults to 1, needed for env-mode
|
||||||
|
return (int)($this->session->get('user_saml.Idp') ?? 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNewProviderId(): int {
|
||||||
|
return $this->mapper->reserveId();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* calculate prefix for config values
|
* @throws Exception
|
||||||
*
|
|
||||||
* @param string name of the setting
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function getPrefix($setting = '') {
|
public function get(int $id): array {
|
||||||
$prefix = '';
|
$this->ensureConfigurationsLoaded($id);
|
||||||
if (!empty($setting) && in_array($setting, $this->globalSettings)) {
|
return $this->configurations[$id] ?? [];
|
||||||
return $prefix;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function set(int $id, array $settings): void {
|
||||||
|
foreach (array_keys($settings) as $configKey) {
|
||||||
|
if (!in_array($configKey, self::IDP_CONFIG_KEYS)) {
|
||||||
|
throw new InvalidArgumentException('Invalid config key');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$idp = $this->session->get('user_saml.Idp');
|
$this->mapper->set($id, $settings);
|
||||||
if ((int)$idp > 1) {
|
}
|
||||||
$prefix = $idp . '-';
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function delete(int $id): void {
|
||||||
|
$this->mapper->deleteById($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function ensureConfigurationsLoaded(int $idp = -1): void {
|
||||||
|
if (self::LOADED_ALL === $this->configurationsLoadedState
|
||||||
|
|| (self::LOADED_CHOSEN === $this->configurationsLoadedState
|
||||||
|
&& isset($this->configurations[$idp])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $prefix;
|
if ($idp !== -1) {
|
||||||
|
$this->configurations[$idp] = $this->mapper->get($idp);
|
||||||
|
} else {
|
||||||
|
$configs = $this->mapper->getAll();
|
||||||
|
foreach ($configs as $id => $config) {
|
||||||
|
$this->configurations[$id] = $config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->configurationsLoadedState = $idp === -1 ? self::LOADED_ALL : self::LOADED_CHOSEN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
namespace OCA\User_SAML\Settings;
|
namespace OCA\User_SAML\Settings;
|
||||||
|
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\Defaults;
|
use OCP\Defaults;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
|
@ -37,30 +38,33 @@ class Admin implements ISettings {
|
||||||
private $defaults;
|
private $defaults;
|
||||||
/** @var IConfig */
|
/** @var IConfig */
|
||||||
private $config;
|
private $config;
|
||||||
|
/** @var SAMLSettings */
|
||||||
|
private $samlSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param IL10N $l10n
|
* @param IL10N $l10n
|
||||||
* @param Defaults $defaults
|
* @param Defaults $defaults
|
||||||
* @param IConfig $config
|
* @param IConfig $config
|
||||||
*/
|
*/
|
||||||
public function __construct(IL10N $l10n,
|
public function __construct(
|
||||||
Defaults $defaults,
|
IL10N $l10n,
|
||||||
IConfig $config) {
|
Defaults $defaults,
|
||||||
|
IConfig $config,
|
||||||
|
SAMLSettings $samlSettings
|
||||||
|
) {
|
||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
$this->defaults = $defaults;
|
$this->defaults = $defaults;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
$this->samlSettings = $samlSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return TemplateResponse
|
* @return TemplateResponse
|
||||||
*/
|
*/
|
||||||
public function getForm() {
|
public function getForm() {
|
||||||
$providerIds = explode(',', $this->config->getAppValue('user_saml', 'providerIds', '1'));
|
$providerIds = $this->samlSettings->getListOfIdps();
|
||||||
natsort($providerIds);
|
|
||||||
$providers = [];
|
$providers = [];
|
||||||
foreach ($providerIds as $id) {
|
foreach ($providerIds as $id => $name) {
|
||||||
$prefix = $id === '1' ? '' : $id .'-';
|
|
||||||
$name = $this->config->getAppValue('user_saml', $prefix . 'general-idp0_display_name', '');
|
|
||||||
$providers[] = [
|
$providers[] = [
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'name' => $name === '' ? $this->l10n->t('Provider ') . $id : $name
|
'name' => $name === '' ? $this->l10n->t('Provider ') . $id : $name
|
||||||
|
@ -134,45 +138,51 @@ class Admin implements ISettings {
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$selectedNameIdFormat = $this->config->getAppValue('user_saml', 'sp-name-id-format', Constants::NAMEID_UNSPECIFIED);
|
$firstIdPConfig = isset($providers[0]) ? $this->samlSettings->get($providers[0]['id']) : null;
|
||||||
$nameIdFormats = [
|
$nameIdFormats = [
|
||||||
Constants::NAMEID_EMAIL_ADDRESS => [
|
Constants::NAMEID_EMAIL_ADDRESS => [
|
||||||
'label' => $this->l10n->t('Email address'),
|
'label' => $this->l10n->t('Email address'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_EMAIL_ADDRESS,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_ENCRYPTED => [
|
Constants::NAMEID_ENCRYPTED => [
|
||||||
'label' => $this->l10n->t('Encrypted'),
|
'label' => $this->l10n->t('Encrypted'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_ENCRYPTED,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_ENTITY => [
|
Constants::NAMEID_ENTITY => [
|
||||||
'label' => $this->l10n->t('Entity'),
|
'label' => $this->l10n->t('Entity'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_ENTITY,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_KERBEROS => [
|
Constants::NAMEID_KERBEROS => [
|
||||||
'label' => $this->l10n->t('Kerberos'),
|
'label' => $this->l10n->t('Kerberos'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_KERBEROS,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_PERSISTENT => [
|
Constants::NAMEID_PERSISTENT => [
|
||||||
'label' => $this->l10n->t('Persistent'),
|
'label' => $this->l10n->t('Persistent'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_PERSISTENT,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_TRANSIENT => [
|
Constants::NAMEID_TRANSIENT => [
|
||||||
'label' => $this->l10n->t('Transient'),
|
'label' => $this->l10n->t('Transient'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_TRANSIENT,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_UNSPECIFIED => [
|
Constants::NAMEID_UNSPECIFIED => [
|
||||||
'label' => $this->l10n->t('Unspecified'),
|
'label' => $this->l10n->t('Unspecified'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_UNSPECIFIED,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME => [
|
Constants::NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME => [
|
||||||
'label' => $this->l10n->t('Windows domain qualified name'),
|
'label' => $this->l10n->t('Windows domain qualified name'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
Constants::NAMEID_X509_SUBJECT_NAME => [
|
Constants::NAMEID_X509_SUBJECT_NAME => [
|
||||||
'label' => $this->l10n->t('X509 subject name'),
|
'label' => $this->l10n->t('X509 subject name'),
|
||||||
'selected' => $selectedNameIdFormat === Constants::NAMEID_X509_SUBJECT_NAME,
|
'selected' => false,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
$chosenFormat = $firstIdPConfig['sp-name-id-format'] ?? '';
|
||||||
|
if ($firstIdPConfig !== null && isset($nameIdFormats[$chosenFormat])) {
|
||||||
|
$nameIdFormats[$chosenFormat]['selected'] = true;
|
||||||
|
} else {
|
||||||
|
$nameIdFormats[Constants::NAMEID_UNSPECIFIED]['selected'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
$type = $this->config->getAppValue('user_saml', 'type');
|
$type = $this->config->getAppValue('user_saml', 'type');
|
||||||
if ($type === 'saml') {
|
if ($type === 'saml') {
|
||||||
|
@ -203,7 +213,8 @@ class Admin implements ISettings {
|
||||||
'attribute-mapping' => $attributeMappingSettings,
|
'attribute-mapping' => $attributeMappingSettings,
|
||||||
'name-id-formats' => $nameIdFormats,
|
'name-id-formats' => $nameIdFormats,
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'providers' => $providers
|
'providers' => $providers,
|
||||||
|
'config' => $firstIdPConfig,
|
||||||
];
|
];
|
||||||
|
|
||||||
return new TemplateResponse('user_saml', 'admin', $params);
|
return new TemplateResponse('user_saml', 'admin', $params);
|
||||||
|
|
|
@ -394,8 +394,10 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getLogoutUrl() {
|
public function getLogoutUrl() {
|
||||||
$prefix = $this->settings->getPrefix();
|
$id = $this->settings->getProviderId();
|
||||||
$slo = $this->config->getAppValue('user_saml', $prefix . 'idp-singleLogoutService.url', '');
|
$settings = $this->settings->get($id);
|
||||||
|
$slo = $settings['idp-singleLogoutService.url'] ?? '';
|
||||||
|
|
||||||
if ($slo === '') {
|
if ($slo === '') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -543,9 +545,12 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
|
||||||
self::$backends = $backends;
|
self::$backends = $backends;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \OCP\DB\Exception
|
||||||
|
*/
|
||||||
private function getAttributeKeys($name) {
|
private function getAttributeKeys($name) {
|
||||||
$prefix = $this->settings->getPrefix($name);
|
$settings = $this->settings->get($this->settings->getProviderId());
|
||||||
$keys = explode(' ', $this->config->getAppValue('user_saml', $prefix . $name, ''));
|
$keys = explode(' ', $settings[$name] ?? $this->config->getAppValue('user_saml', $name, ''));
|
||||||
|
|
||||||
if (count($keys) === 1 && $keys[0] === '') {
|
if (count($keys) === 1 && $keys[0] === '') {
|
||||||
throw new \InvalidArgumentException('Attribute is not configured');
|
throw new \InvalidArgumentException('Attribute is not configured');
|
||||||
|
|
|
@ -57,9 +57,8 @@ class UserData {
|
||||||
|
|
||||||
public function hasUidMappingAttribute(): bool {
|
public function hasUidMappingAttribute(): bool {
|
||||||
$this->assertIsInitialized();
|
$this->assertIsInitialized();
|
||||||
$prefix = $this->samlSettings->getPrefix();
|
$attribute = $this->getUidMappingAttribute();
|
||||||
$uidMapping = $this->config->getAppValue('user_saml', $prefix . 'general-uid_mapping');
|
return $attribute !== null && isset($this->attributes[$attribute]);
|
||||||
return isset($this->attributes[$uidMapping]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOriginalUid(): string {
|
public function getOriginalUid(): string {
|
||||||
|
@ -84,9 +83,8 @@ class UserData {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function extractSamlUserId(): string {
|
protected function extractSamlUserId(): string {
|
||||||
$prefix = $this->samlSettings->getPrefix();
|
$uidMapping = $this->getUidMappingAttribute();
|
||||||
$uidMapping = $this->config->getAppValue('user_saml', $prefix . 'general-uid_mapping');
|
if ($uidMapping !== null && isset($this->attributes[$uidMapping])) {
|
||||||
if (isset($this->attributes[$uidMapping])) {
|
|
||||||
if (is_array($this->attributes[$uidMapping])) {
|
if (is_array($this->attributes[$uidMapping])) {
|
||||||
return trim($this->attributes[$uidMapping][0]);
|
return trim($this->attributes[$uidMapping][0]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -148,4 +146,12 @@ class UserData {
|
||||||
throw new \LogicException('UserData have to be initialized with setAttributes first');
|
throw new \LogicException('UserData have to be initialized with setAttributes first');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getProviderSettings(): array {
|
||||||
|
return $this->samlSettings->get($this->samlSettings->getProviderId());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getUidMappingAttribute(): ?string {
|
||||||
|
return $this->getProviderSettings()['general-uid_mapping'] ?? null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,12 +55,12 @@ style('user_saml', 'admin');
|
||||||
<?php foreach ($_['general'] as $key => $attribute): ?>
|
<?php foreach ($_['general'] as $key => $attribute): ?>
|
||||||
<?php if ($attribute['type'] === 'checkbox' && $attribute['global']): ?>
|
<?php if ($attribute['type'] === 'checkbox' && $attribute['global']): ?>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" data-key="<?php p($key)?>" id="user-saml-general-<?php p($key)?>" name="<?php p($key)?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'general-'.$key, '0')) ?>">
|
<input type="checkbox" data-key="<?php p($key)?>" id="user-saml-general-<?php p($key)?>" name="<?php p($key)?>" value="<?php p($_['config']['general-'.$key] ?? '0') ?>">
|
||||||
<label for="user-saml-general-<?php p($key)?>"><?php p($attribute['text']) ?></label><br/>
|
<label for="user-saml-general-<?php p($key)?>"><?php p($attribute['text']) ?></label><br/>
|
||||||
</p>
|
</p>
|
||||||
<?php elseif ($attribute['type'] === 'line' && isset($attribute['global'])): ?>
|
<?php elseif ($attribute['type'] === 'line' && isset($attribute['global'])): ?>
|
||||||
<p>
|
<p>
|
||||||
<input data-key="<?php p($key)?>" name="<?php p($key) ?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'general-'.$key, '')) ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="<?php p($attribute['text']) ?>"/>
|
<input data-key="<?php p($key)?>" name="<?php p($key) ?>" value="<?php p($_['config']['general-'.$key] ?? '') ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="<?php p($attribute['text']) ?>"/>
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
@ -85,12 +85,12 @@ style('user_saml', 'admin');
|
||||||
<?php foreach ($_['general'] as $key => $attribute): ?>
|
<?php foreach ($_['general'] as $key => $attribute): ?>
|
||||||
<?php if ($attribute['type'] === 'checkbox' && !$attribute['global']): ?>
|
<?php if ($attribute['type'] === 'checkbox' && !$attribute['global']): ?>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" data-key="<?php p($key)?>" id="user-saml-general-<?php p($key)?>" name="<?php p($key)?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'general-'.$key, '0')) ?>">
|
<input type="checkbox" data-key="<?php p($key)?>" id="user-saml-general-<?php p($key)?>" name="<?php p($key)?>" value="<?php p($_['config']['general-'.$key] ?? '0') ?>">
|
||||||
<label for="user-saml-general-<?php p($key)?>"><?php p($attribute['text']) ?></label><br/>
|
<label for="user-saml-general-<?php p($key)?>"><?php p($attribute['text']) ?></label><br/>
|
||||||
</p>
|
</p>
|
||||||
<?php elseif ($attribute['type'] === 'line' && !isset($attribute['global'])): ?>
|
<?php elseif ($attribute['type'] === 'line' && !isset($attribute['global'])): ?>
|
||||||
<p>
|
<p>
|
||||||
<input data-key="<?php p($key)?>" name="<?php p($key) ?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'general-'.$key, '')) ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="<?php p($attribute['text']) ?>"/>
|
<input data-key="<?php p($key)?>" name="<?php p($key) ?>" value="<?php p($_['config']['general-'.$key] ?? '') ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="<?php p($attribute['text']) ?>"/>
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
@ -118,7 +118,7 @@ style('user_saml', 'admin');
|
||||||
</select>
|
</select>
|
||||||
<?php foreach ($_['sp'] as $key => $text): ?>
|
<?php foreach ($_['sp'] as $key => $text): ?>
|
||||||
<p>
|
<p>
|
||||||
<textarea name="<?php p($key) ?>" placeholder="<?php p($text) ?>"><?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'sp-'.$key, '')) ?></textarea>
|
<textarea name="<?php p($key) ?>" placeholder="<?php p($text) ?>"><?php p($_['config']['sp-'.$key] ?? '') ?></textarea>
|
||||||
</p>
|
</p>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,13 +129,13 @@ style('user_saml', 'admin');
|
||||||
<?php print_unescaped($l->t('Configure your IdP settings here.')) ?>
|
<?php print_unescaped($l->t('Configure your IdP settings here.')) ?>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p><input data-key="idp-entityId" name="entityId" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'idp-entityId', '')) ?>" type="text" class="required" placeholder="<?php p($l->t('Identifier of the IdP entity (must be a URI)')) ?>"/></p>
|
<p><input data-key="idp-entityId" name="entityId" value="<?php p($_['config']['idp-entityId'] ?? '') ?>" type="text" class="required" placeholder="<?php p($l->t('Identifier of the IdP entity (must be a URI)')) ?>"/></p>
|
||||||
<p><input name="singleSignOnService.url" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'idp-singleSignOnService.url', '')) ?>" type="text" class="required" placeholder="<?php p($l->t('URL Target of the IdP where the SP will send the Authentication Request Message')) ?>"/></p>
|
<p><input name="singleSignOnService.url" value="<?php p($_['config']['idp-singleSignOnService.url'] ?? '') ?>" type="text" class="required" placeholder="<?php p($l->t('URL Target of the IdP where the SP will send the Authentication Request Message')) ?>"/></p>
|
||||||
<p><span class="toggle"><?php p($l->t('Show optional Identity Provider settings…')) ?></span></p>
|
<p><span class="toggle"><?php p($l->t('Show optional Identity Provider settings…')) ?></span></p>
|
||||||
<div class="hidden">
|
<div class="hidden">
|
||||||
<p><input name="singleLogoutService.url" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'idp-singleLogoutService.url', '')) ?>" type="text" placeholder="<?php p($l->t('URL Location of the IdP where the SP will send the SLO Request')) ?>"/></p>
|
<p><input name="singleLogoutService.url" value="<?php p($_['config']['idp-singleLogoutService.url'] ?? '') ?>" type="text" placeholder="<?php p($l->t('URL Location of the IdP where the SP will send the SLO Request')) ?>"/></p>
|
||||||
<p><input name="singleLogoutService.responseUrl" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'idp-singleLogoutService.responseUrl', '')) ?>" type="text" placeholder="<?php p($l->t('URL Location of the IDP\'s SLO Response')) ?>"/></p>
|
<p><input name="singleLogoutService.responseUrl" value="<?php p($_['config']['idp-singleLogoutService.responseUrl'] ?? '') ?>" type="text" placeholder="<?php p($l->t('URL Location of the IDP\'s SLO Response')) ?>"/></p>
|
||||||
<p><textarea name="x509cert" placeholder="<?php p($l->t('Public X.509 certificate of the IdP')) ?>"><?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'idp-x509cert', '')) ?></textarea></p>
|
<p><textarea name="x509cert" placeholder="<?php p($l->t('Public X.509 certificate of the IdP')) ?>"><?php p($_['config']['idp-x509cert'] ?? '') ?></textarea></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ style('user_saml', 'admin');
|
||||||
<?php
|
<?php
|
||||||
if ($attribute['type'] === 'line'): ?>
|
if ($attribute['type'] === 'line'): ?>
|
||||||
<p>
|
<p>
|
||||||
<input name="<?php p($key) ?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'saml-attribute-mapping-'.$key, '')) ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="<?php p($attribute['text']) ?>"/>
|
<input name="<?php p($key) ?>" value="<?php p($_['config']['saml-attribute-mapping-'.$key] ?? '') ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="<?php p($attribute['text']) ?>"/>
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
@ -168,14 +168,14 @@ style('user_saml', 'admin');
|
||||||
<h4><?php p($l->t('Signatures and encryption offered')) ?></h4>
|
<h4><?php p($l->t('Signatures and encryption offered')) ?></h4>
|
||||||
<?php foreach ($_['security-offer'] as $key => $text): ?>
|
<?php foreach ($_['security-offer'] as $key => $text): ?>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" id="user-saml-<?php p($key)?>" name="<?php p($key)?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'security-'.$key, '0')) ?>" class="checkbox">
|
<input type="checkbox" id="user-saml-<?php p($key)?>" name="<?php p($key)?>" value="<?php p($_['config']['security-'.$key] ?? '0') ?>" class="checkbox">
|
||||||
<label for="user-saml-<?php p($key)?>"><?php p($text) ?></label><br/>
|
<label for="user-saml-<?php p($key)?>"><?php p($text) ?></label><br/>
|
||||||
</p>
|
</p>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<h4><?php p($l->t('Signatures and encryption required')) ?></h4>
|
<h4><?php p($l->t('Signatures and encryption required')) ?></h4>
|
||||||
<?php foreach ($_['security-required'] as $key => $text): ?>
|
<?php foreach ($_['security-required'] as $key => $text): ?>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" id="user-saml-<?php p($key)?>" name="<?php p($key)?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'security-'.$key, '0')) ?>" class="checkbox">
|
<input type="checkbox" id="user-saml-<?php p($key)?>" name="<?php p($key)?>" value="<?php p($_['config']['security-'.$key] ?? '0') ?>" class="checkbox">
|
||||||
<label for="user-saml-<?php p($key)?>"><?php p($text) ?></label>
|
<label for="user-saml-<?php p($key)?>"><?php p($text) ?></label>
|
||||||
</p>
|
</p>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
@ -185,12 +185,12 @@ style('user_saml', 'admin');
|
||||||
<?php $text = $attribute['text'] ?>
|
<?php $text = $attribute['text'] ?>
|
||||||
<p>
|
<p>
|
||||||
<label><?php p($attribute['text']) ?></label><br />
|
<label><?php p($attribute['text']) ?></label><br />
|
||||||
<input data-key="<?php p($key)?>" name="<?php p($key) ?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'security-'.$key, '')) ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
|
<input data-key="<?php p($key)?>" name="<?php p($key) ?>" value="<?php p($_['config']['security-'.$key] ?? '') ?>" type="text" <?php if (isset($attribute['required']) && $attribute['required'] === true): ?>class="required"<?php endif;?> placeholder="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
|
||||||
</p>
|
</p>
|
||||||
<?php } else { ?>
|
<?php } else { ?>
|
||||||
<?php $text = $attribute ?>
|
<?php $text = $attribute ?>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" id="user-saml-<?php p($key)?>" name="<?php p($key)?>" value="<?php p(\OC::$server->getConfig()->getAppValue('user_saml', 'security-'.$key, '0')) ?>" class="checkbox">
|
<input type="checkbox" id="user-saml-<?php p($key)?>" name="<?php p($key)?>" value="<?php p($_['config']['security-'.$key] ?? '0') ?>" class="checkbox">
|
||||||
<label for="user-saml-<?php p($key)?>"><?php p($text) ?></label><br/>
|
<label for="user-saml-<?php p($key)?>"><?php p($text) ?></label><br/>
|
||||||
</p>
|
</p>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
|
@ -74,6 +74,15 @@ class FeatureContext implements Context {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shell_exec(
|
||||||
|
sprintf(
|
||||||
|
'sudo -u apache %s %s saml:config:delete 1',
|
||||||
|
PHP_BINARY,
|
||||||
|
__DIR__ . '/../../../../../../occ',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$this->changedSettings = [];
|
$this->changedSettings = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,14 +94,33 @@ class FeatureContext implements Context {
|
||||||
*/
|
*/
|
||||||
public function theSettingIsSetTo($settingName,
|
public function theSettingIsSetTo($settingName,
|
||||||
$value) {
|
$value) {
|
||||||
$this->changedSettings[] = $settingName;
|
if (in_array($settingName, [
|
||||||
|
'type',
|
||||||
|
'general-require_provisioned_account',
|
||||||
|
'general-allow_multiple_user_back_ends',
|
||||||
|
'general-use_saml_auth_for_desktop'
|
||||||
|
])) {
|
||||||
|
$this->changedSettings[] = $settingName;
|
||||||
|
shell_exec(
|
||||||
|
sprintf(
|
||||||
|
'sudo -u apache %s %s config:app:set --value="%s" user_saml %s',
|
||||||
|
PHP_BINARY,
|
||||||
|
__DIR__ . '/../../../../../../occ',
|
||||||
|
$value,
|
||||||
|
$settingName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
shell_exec(
|
shell_exec(
|
||||||
sprintf(
|
sprintf(
|
||||||
'sudo -u apache %s %s config:app:set --value="%s" user_saml %s',
|
'sudo -u apache %s %s saml:config:set --"%s"="%s" %d',
|
||||||
PHP_BINARY,
|
PHP_BINARY,
|
||||||
__DIR__ . '/../../../../../../occ',
|
__DIR__ . '/../../../../../../occ',
|
||||||
|
$settingName,
|
||||||
$value,
|
$value,
|
||||||
$settingName
|
1
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
|
|
||||||
*
|
|
||||||
* @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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\User_SAML\Tests\AppInfo;
|
|
||||||
|
|
||||||
use Test\TestCase;
|
|
||||||
|
|
||||||
class Test extends TestCase {
|
|
||||||
public function testFile() {
|
|
||||||
$dir = __DIR__;
|
|
||||||
$routes = require_once __DIR__ . '/../../../appinfo/routes.php';
|
|
||||||
|
|
||||||
$expected = [
|
|
||||||
'routes' => [
|
|
||||||
[
|
|
||||||
'name' => 'SAML#login',
|
|
||||||
'url' => '/saml/login',
|
|
||||||
'verb' => 'GET',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#base',
|
|
||||||
'url' => '/saml',
|
|
||||||
'verb' => 'GET',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#getMetadata',
|
|
||||||
'url' => '/saml/metadata',
|
|
||||||
'verb' => 'GET',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#assertionConsumerService',
|
|
||||||
'url' => '/saml/acs',
|
|
||||||
'verb' => 'POST',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#singleLogoutService',
|
|
||||||
'url' => '/saml/sls',
|
|
||||||
'verb' => 'GET',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#singleLogoutService',
|
|
||||||
'url' => '/saml/sls',
|
|
||||||
'verb' => 'POST',
|
|
||||||
'postfix' => 'slspost',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#notProvisioned',
|
|
||||||
'url' => '/saml/notProvisioned',
|
|
||||||
'verb' => 'GET',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#genericError',
|
|
||||||
'url' => '/saml/error',
|
|
||||||
'verb' => 'GET',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'SAML#selectUserBackEnd',
|
|
||||||
'url' => '/saml/selectUserBackEnd',
|
|
||||||
'verb' => 'GET',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'Settings#getSamlProviderSettings',
|
|
||||||
'url' => '/settings/providerSettings/{providerId}',
|
|
||||||
'verb' => 'GET',
|
|
||||||
'defaults' => [
|
|
||||||
'providerId' => '1'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'Settings#deleteSamlProviderSettings',
|
|
||||||
'url' => '/settings/providerSettings/{providerId}',
|
|
||||||
'verb' => 'DELETE',
|
|
||||||
'defaults' => [
|
|
||||||
'providerId' => '1'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => 'Timezone#setTimezone',
|
|
||||||
'url' => '/config/timezone',
|
|
||||||
'verb' => 'POST',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
$this->assertSame($expected, $routes);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,78 +23,46 @@ namespace OCA\User_SAML\Tests\Command;
|
||||||
|
|
||||||
use OCA\User_SAML\Command\GetMetadata;
|
use OCA\User_SAML\Command\GetMetadata;
|
||||||
use OCA\User_SAML\SAMLSettings;
|
use OCA\User_SAML\SAMLSettings;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use OCP\IConfig;
|
|
||||||
use OCP\IRequest;
|
|
||||||
use OCP\ISession;
|
|
||||||
use OCP\IURLGenerator;
|
|
||||||
|
|
||||||
class GetMetadataTest extends \Test\TestCase {
|
class GetMetadataTest extends \Test\TestCase {
|
||||||
|
|
||||||
/** @var GetMetadata|\PHPUnit_Framework_MockObject_MockObject*/
|
/** @var GetMetadata|MockObject*/
|
||||||
protected $GetMetadata;
|
protected $GetMetadata;
|
||||||
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
|
/** @var SAMLSettings|MockObject*/
|
||||||
private $request;
|
|
||||||
/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
|
|
||||||
private $session;
|
|
||||||
/** @var SAMLSettings|\PHPUnit_Framework_MockObject_MockObject*/
|
|
||||||
private $samlSettings;
|
private $samlSettings;
|
||||||
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
|
|
||||||
private $config;
|
|
||||||
/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
|
|
||||||
private $urlGenerator;
|
|
||||||
|
|
||||||
|
|
||||||
protected function setUp(): void {
|
protected function setUp(): void {
|
||||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
$this->samlSettings = $this->createMock(SAMLSettings::class);
|
||||||
$this->config = $this->createMock(IConfig::class);
|
|
||||||
$this->request = $this->createMock(IRequest::class);
|
|
||||||
$this->session = $this->createMock(ISession::class);
|
|
||||||
|
|
||||||
$this->samlSettings = new SAMLSettings($this->urlGenerator,
|
|
||||||
$this->config,
|
|
||||||
$this->request,
|
|
||||||
$this->session);
|
|
||||||
$this->GetMetadata = new GetMetadata($this->samlSettings);
|
$this->GetMetadata = new GetMetadata($this->samlSettings);
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetMetadata() {
|
public function testGetMetadata() {
|
||||||
$inputInterface = $this->createMock(InputInterface::class);
|
$inputInterface = $this->createMock(InputInterface::class);
|
||||||
$outputInterface = $this->createMock(OutputInterface::class);
|
$outputInterface = $this->createMock(OutputInterface::class);
|
||||||
$this->urlGenerator
|
|
||||||
->expects($this->at(0))
|
$this->samlSettings->expects($this->any())
|
||||||
->method('linkToRouteAbsolute')
|
->method('getOneLoginSettingsArray')
|
||||||
->with('user_saml.SAML.base')
|
->willReturn([
|
||||||
->willReturn('https://nextcloud.com/base/');
|
'baseurl' => 'https://nextcloud.com/base/',
|
||||||
$this->urlGenerator
|
'idp' => [
|
||||||
->expects($this->at(1))
|
'entityId' => 'dummy',
|
||||||
->method('linkToRouteAbsolute')
|
'singleSignOnService' => ['url' => 'https://example.com/sso'],
|
||||||
->with('user_saml.SAML.getMetadata')
|
'x509cert' => 'DUMMY CERTIFICATE',
|
||||||
->willReturn('https://nextcloud.com/metadata/');
|
],
|
||||||
$this->urlGenerator
|
'sp' => [
|
||||||
->expects($this->at(2))
|
'entityId' => 'https://nextcloud.com/metadata/',
|
||||||
->method('linkToRouteAbsolute')
|
'assertionConsumerService' => [
|
||||||
->with('user_saml.SAML.assertionConsumerService')
|
'url' => 'https://nextcloud.com/acs/',
|
||||||
->willReturn('https://nextcloud.com/acs/');
|
],
|
||||||
$this->config->expects($this->any())->method('getAppValue')
|
]
|
||||||
->willReturnCallback(function ($app, $key, $default) {
|
]);
|
||||||
if ($key == 'idp-entityId') {
|
|
||||||
return "dummy";
|
|
||||||
}
|
|
||||||
if ($key == 'idp-singleSignOnService.url') {
|
|
||||||
return "https://example.com/sso";
|
|
||||||
}
|
|
||||||
if ($key == 'idp-x509cert') {
|
|
||||||
return "DUMMY CERTIFICATE";
|
|
||||||
}
|
|
||||||
return $default;
|
|
||||||
});
|
|
||||||
|
|
||||||
$outputInterface->expects($this->once())->method('writeln')
|
$outputInterface->expects($this->once())->method('writeln')
|
||||||
->with($this->stringContains('md:EntityDescriptor'));
|
->with($this->stringContains('md:EntityDescriptor'));
|
||||||
|
|
||||||
$this->invokePrivate($this->GetMetadata, 'execute', [$inputInterface, $outputInterface]);
|
$this->invokePrivate($this->GetMetadata, 'execute', [$inputInterface, $outputInterface]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,19 @@
|
||||||
|
|
||||||
namespace OCA\User_SAML\Tests\Settings;
|
namespace OCA\User_SAML\Tests\Settings;
|
||||||
|
|
||||||
|
use OCA\User_SAML\SAMLSettings;
|
||||||
|
use OCA\User_SAML\Settings\Admin;
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\Defaults;
|
use OCP\Defaults;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OneLogin\Saml2\Constants;
|
use OneLogin\Saml2\Constants;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
|
||||||
class AdminTest extends \Test\TestCase {
|
class AdminTest extends \Test\TestCase {
|
||||||
/** @var \OCA\User_SAML\Settings\Admin */
|
/** @var SAMLSettings|MockObject */
|
||||||
|
private $settings;
|
||||||
|
/** @var Admin */
|
||||||
private $admin;
|
private $admin;
|
||||||
/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
|
/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
private $l10n;
|
private $l10n;
|
||||||
|
@ -41,11 +46,13 @@ class AdminTest extends \Test\TestCase {
|
||||||
$this->l10n = $this->createMock(IL10N::class);
|
$this->l10n = $this->createMock(IL10N::class);
|
||||||
$this->defaults = $this->createMock(Defaults::class);
|
$this->defaults = $this->createMock(Defaults::class);
|
||||||
$this->config = $this->createMock(IConfig::class);
|
$this->config = $this->createMock(IConfig::class);
|
||||||
|
$this->settings = $this->createMock(SAMLSettings::class);
|
||||||
|
|
||||||
$this->admin = new \OCA\User_SAML\Settings\Admin(
|
$this->admin = new Admin(
|
||||||
$this->l10n,
|
$this->l10n,
|
||||||
$this->defaults,
|
$this->defaults,
|
||||||
$this->config
|
$this->config,
|
||||||
|
$this->settings
|
||||||
);
|
);
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
@ -193,32 +200,21 @@ class AdminTest extends \Test\TestCase {
|
||||||
['id' => 2, 'name' => 'Provider 2']
|
['id' => 2, 'name' => 'Provider 2']
|
||||||
],
|
],
|
||||||
'name-id-formats' => $nameIdFormats,
|
'name-id-formats' => $nameIdFormats,
|
||||||
|
'config' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetFormWithoutType() {
|
public function testGetFormWithoutType() {
|
||||||
|
$this->settings->expects($this->once())
|
||||||
|
->method('getListOfIdps')
|
||||||
|
->willReturn([
|
||||||
|
1 => 'Provider 1',
|
||||||
|
2 => 'Provider 2',
|
||||||
|
]);
|
||||||
$this->config
|
$this->config
|
||||||
->expects($this->at(0))
|
->expects($this->once())
|
||||||
->method('getAppValue')
|
|
||||||
->with('user_saml', 'providerIds')
|
|
||||||
->willReturn('1,2');
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(1))
|
|
||||||
->method('getAppValue')
|
|
||||||
->willReturn('Provider 1');
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(2))
|
|
||||||
->method('getAppValue')
|
|
||||||
->willReturn('Provider 2');
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(3))
|
|
||||||
->method('getAppValue')
|
|
||||||
->with('user_saml', 'sp-name-id-format')
|
|
||||||
->will($this->returnArgument(2));
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(4))
|
|
||||||
->method('getAppValue')
|
->method('getAppValue')
|
||||||
->with('user_saml', 'type')
|
->with('user_saml', 'type')
|
||||||
->willReturn('');
|
->willReturn('');
|
||||||
|
@ -234,26 +230,14 @@ class AdminTest extends \Test\TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetFormWithSaml() {
|
public function testGetFormWithSaml() {
|
||||||
|
$this->settings->expects($this->once())
|
||||||
|
->method('getListOfIdps')
|
||||||
|
->willReturn([
|
||||||
|
1 => 'Provider 1',
|
||||||
|
2 => 'Provider 2',
|
||||||
|
]);
|
||||||
$this->config
|
$this->config
|
||||||
->expects($this->at(0))
|
->expects($this->once())
|
||||||
->method('getAppValue')
|
|
||||||
->with('user_saml', 'providerIds')
|
|
||||||
->willReturn('1,2');
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(1))
|
|
||||||
->method('getAppValue')
|
|
||||||
->willReturn('Provider 1');
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(2))
|
|
||||||
->method('getAppValue')
|
|
||||||
->willReturn('Provider 2');
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(3))
|
|
||||||
->method('getAppValue')
|
|
||||||
->with('user_saml', 'sp-name-id-format')
|
|
||||||
->will($this->returnArgument(2));
|
|
||||||
$this->config
|
|
||||||
->expects($this->at(4))
|
|
||||||
->method('getAppValue')
|
->method('getAppValue')
|
||||||
->with('user_saml', 'type')
|
->with('user_saml', 'type')
|
||||||
->willReturn('saml');
|
->willReturn('saml');
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<phpunit bootstrap="bootstrap.php"
|
<phpunit bootstrap="bootstrap.php"
|
||||||
strict="true"
|
|
||||||
verbose="true"
|
verbose="true"
|
||||||
timeoutForSmallTests="900"
|
timeoutForSmallTests="900"
|
||||||
timeoutForMediumTests="900"
|
timeoutForMediumTests="900"
|
||||||
|
@ -16,8 +15,4 @@
|
||||||
<directory suffix=".php">../../../user_saml/lib</directory>
|
<directory suffix=".php">../../../user_saml/lib</directory>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
<logging>
|
|
||||||
<!-- and this is where your report will be written -->
|
|
||||||
<log type="coverage-clover" target="./clover.xml"/>
|
|
||||||
</logging>
|
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
Loading…
Reference in a new issue