summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files_external/l10n/pt_BR.js2
-rw-r--r--apps/files_external/l10n/pt_BR.json2
-rw-r--r--apps/systemtags/admin.php23
-rw-r--r--apps/systemtags/appinfo/app.php3
-rw-r--r--apps/systemtags/appinfo/info.xml2
-rw-r--r--apps/systemtags/js/admin.js164
-rw-r--r--apps/systemtags/lib/AppInfo/Application.php37
-rw-r--r--apps/systemtags/templates/admin.php59
-rw-r--r--apps/theming/js/settings-admin.js27
-rw-r--r--apps/theming/lib/controller/themingcontroller.php43
-rw-r--r--apps/theming/lib/util.php25
-rw-r--r--apps/theming/tests/lib/UtilTest.php21
-rw-r--r--apps/theming/tests/lib/controller/ThemingControllerTest.php129
-rw-r--r--apps/user_ldap/ajax/wizard.php2
-rw-r--r--apps/user_ldap/appinfo/app.php2
-rw-r--r--apps/user_ldap/appinfo/install.php4
-rw-r--r--apps/user_ldap/appinfo/update.php24
-rw-r--r--apps/user_ldap/lib/Access.php73
-rw-r--r--apps/user_ldap/lib/Connection.php6
-rw-r--r--apps/user_ldap/lib/Helper.php65
-rw-r--r--apps/user_ldap/lib/IUserLDAP.php49
-rw-r--r--apps/user_ldap/lib/Jobs/UpdateGroups.php2
-rw-r--r--apps/user_ldap/lib/LDAPProvider.php188
-rw-r--r--apps/user_ldap/lib/LDAPProviderFactory.php59
-rw-r--r--apps/user_ldap/lib/Proxy.php2
-rw-r--r--apps/user_ldap/lib/User_LDAP.php35
-rw-r--r--apps/user_ldap/lib/User_Proxy.php32
-rw-r--r--apps/user_ldap/tests/AccessTest.php51
-rw-r--r--apps/user_ldap/tests/Group_LDAPTest.php3
-rw-r--r--apps/user_ldap/tests/Integration/AbstractIntegrationTest.php14
-rw-r--r--apps/user_ldap/tests/LDAPProviderTest.php338
-rw-r--r--apps/user_ldap/tests/User/UserTest.php3
-rw-r--r--apps/user_ldap/tests/User_LDAPTest.php4
-rw-r--r--apps/user_ldap/tests/WizardTest.php3
-rw-r--r--core/css/inputs.css4
-rw-r--r--core/img/actions/checkmark-white.svg5
-rw-r--r--core/img/actions/radio-checked-white.svg1
-rw-r--r--core/l10n/pt_BR.js8
-rw-r--r--core/l10n/pt_BR.json8
-rw-r--r--lib/private/Server.php19
-rw-r--r--lib/public/LDAP/IDeletionFlagSupport.php45
-rw-r--r--lib/public/LDAP/ILDAPProvider.php105
-rw-r--r--lib/public/LDAP/ILDAPProviderFactory.php53
-rw-r--r--settings/l10n/es.js2
-rw-r--r--settings/l10n/es.json2
-rw-r--r--settings/l10n/pt_BR.js4
-rw-r--r--settings/l10n/pt_BR.json4
47 files changed, 1581 insertions, 175 deletions
diff --git a/apps/files_external/l10n/pt_BR.js b/apps/files_external/l10n/pt_BR.js
index 39a6e766677..99aa844fb5b 100644
--- a/apps/files_external/l10n/pt_BR.js
+++ b/apps/files_external/l10n/pt_BR.js
@@ -31,7 +31,7 @@ OC.L10N.register(
"External mount error" : "Erro de montagem externa",
"external-storage" : "armazenamento-externo",
"Couldn't get the list of Windows network drive mount points: empty response from the server" : "Não foi possível obter a lista unidades de pontos de montagem da rede do Windows: resposta vazia a partir do servidor",
- "Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Alguns dos pontos de montagem externos configurados não estão conectados. Por favor clique na linha vermelha(s) para mais informações",
+ "Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Alguns dos pontos de montagem externos configurados não estão conectados. Clique na(s) linha(s) vermelha(s) para mais informações",
"Please enter the credentials for the {mount} mount" : "Por favor, insira as credenciais para montar {mount}",
"Username" : "Nome de Usuário",
"Password" : "Senha",
diff --git a/apps/files_external/l10n/pt_BR.json b/apps/files_external/l10n/pt_BR.json
index 7d6a3b5b10d..aee79057b4d 100644
--- a/apps/files_external/l10n/pt_BR.json
+++ b/apps/files_external/l10n/pt_BR.json
@@ -29,7 +29,7 @@
"External mount error" : "Erro de montagem externa",
"external-storage" : "armazenamento-externo",
"Couldn't get the list of Windows network drive mount points: empty response from the server" : "Não foi possível obter a lista unidades de pontos de montagem da rede do Windows: resposta vazia a partir do servidor",
- "Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Alguns dos pontos de montagem externos configurados não estão conectados. Por favor clique na linha vermelha(s) para mais informações",
+ "Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Alguns dos pontos de montagem externos configurados não estão conectados. Clique na(s) linha(s) vermelha(s) para mais informações",
"Please enter the credentials for the {mount} mount" : "Por favor, insira as credenciais para montar {mount}",
"Username" : "Nome de Usuário",
"Password" : "Senha",
diff --git a/apps/systemtags/admin.php b/apps/systemtags/admin.php
new file mode 100644
index 00000000000..45ea577e8ab
--- /dev/null
+++ b/apps/systemtags/admin.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @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/>.
+ *
+ */
+
+$template = new \OCP\Template('systemtags', 'admin');
+return $template->fetchPage();
diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php
index af91e5fdbcd..5a365c4ef15 100644
--- a/apps/systemtags/appinfo/app.php
+++ b/apps/systemtags/appinfo/app.php
@@ -78,6 +78,9 @@ $mapperListener = function(MapperEvent $event) use ($activityManager) {
$eventDispatcher->addListener(MapperEvent::EVENT_ASSIGN, $mapperListener);
$eventDispatcher->addListener(MapperEvent::EVENT_UNASSIGN, $mapperListener);
+$app = new \OCA\SystemTags\AppInfo\Application();
+$app->registerAdminPage();
+
$l = \OC::$server->getL10N('systemtags');
\OCA\Files\App::getNavigationManager()->add(
diff --git a/apps/systemtags/appinfo/info.xml b/apps/systemtags/appinfo/info.xml
index 1d75610f545..3521658ac20 100644
--- a/apps/systemtags/appinfo/info.xml
+++ b/apps/systemtags/appinfo/info.xml
@@ -5,7 +5,7 @@
<description>Collaborative tagging functionality which shares tags among users. Great for teams.
(If you are a provider with a multi-tenancy installation, it is advised to deactivate this app as tags are shared.)</description>
<licence>AGPL</licence>
- <author>Vincent Petry</author>
+ <author>Vincent Petry, Joas Schilling</author>
<default_enable/>
<version>1.0.0</version>
<dependencies>
diff --git a/apps/systemtags/js/admin.js b/apps/systemtags/js/admin.js
new file mode 100644
index 00000000000..ed21f82f3ba
--- /dev/null
+++ b/apps/systemtags/js/admin.js
@@ -0,0 +1,164 @@
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @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/>.
+ *
+ */
+
+(function() {
+ if (!OCA.SystemTags) {
+ /**
+ * @namespace
+ */
+ OCA.SystemTags = {};
+ }
+
+ OCA.SystemTags.Admin = {
+
+ collection: null,
+
+ init: function() {
+ var self = this;
+
+ this.collection = OC.SystemTags.collection;
+ this.collection.fetch({
+ success: function() {
+ $('#systemtag').select2(_.extend(self.select2));
+ }
+ });
+
+ $('#systemtag_submit').on('click', _.bind(this._onClickSubmit, this));
+ $('#systemtag_delete').on('click', _.bind(this._onClickDelete, this));
+ $('#systemtag_reset').on('click', _.bind(this._onClickReset, this));
+ },
+
+ /**
+ * Selecting a systemtag in select2
+ *
+ * @param {OC.SystemTags.SystemTagModel} tag
+ */
+ onSelectTag: function (tag) {
+ var level = 0;
+ if (tag.get('userVisible')) {
+ level += 2;
+ if (tag.get('userAssignable')) {
+ level += 1;
+ }
+ }
+
+ $('#systemtag_name').val(tag.get('name'));
+ $('#systemtag_level').val(level);
+
+ this._prepareForm(tag.get('id'));
+ },
+
+ /**
+ * Clicking the "Create"/"Update" button
+ */
+ _onClickSubmit: function () {
+ var level = parseInt($('#systemtag_level').val(), 10),
+ tagId = $('#systemtags').attr('data-systemtag-id');
+ var data = {
+ name: $('#systemtag_name').val(),
+ userVisible: level === 2 || level === 3,
+ userAssignable: level === 3
+ };
+
+ if (tagId) {
+ var model = this.collection.get(tagId);
+ model.save(data);
+ } else {
+ this.collection.create(data);
+ }
+
+ this._onClickReset();
+ },
+
+ /**
+ * Clicking the "Delete" button
+ */
+ _onClickDelete: function () {
+ var tagId = $('#systemtags').attr('data-systemtag-id');
+ var model = this.collection.get(tagId);
+ model.destroy();
+
+ this._onClickReset();
+ },
+
+ /**
+ * Clicking the "Reset" button
+ */
+ _onClickReset: function () {
+ $('#systemtag_name').val('');
+ $('#systemtag_level').val(3);
+ this._prepareForm(0);
+ },
+
+ /**
+ * Prepare the form for create/update
+ *
+ * @param {int} tagId
+ */
+ _prepareForm: function (tagId) {
+ if (tagId > 0) {
+ $('#systemtags').attr('data-systemtag-id', tagId);
+ $('#systemtag_delete').removeClass('hidden');
+ $('#systemtag_submit').val(t('systemtags_manager', 'Update'));
+ } else {
+ $('#systemtag').select2('val', '');
+ $('#systemtags').attr('data-systemtag-id', '');
+ $('#systemtag_delete').addClass('hidden');
+ $('#systemtag_submit').val(t('systemtags_manager', 'Create'));
+ }
+ },
+
+ /**
+ * Select2 options for the SystemTag dropdown
+ */
+ select2: {
+ allowClear: false,
+ multiple: false,
+ placeholder: t('systemtags_manager', 'Select tag…'),
+ query: _.debounce(function(query) {
+ query.callback({
+ results: OCA.SystemTags.Admin.collection.filterByName(query.term)
+ });
+ }, 100, true),
+ id: function(element) {
+ return element;
+ },
+ initSelection: function(element, callback) {
+ var selection = ($(element).val() || []).split('|').sort();
+ callback(selection);
+ },
+ formatResult: function (tag) {
+ return OC.SystemTags.getDescriptiveTag(tag);
+ },
+ formatSelection: function (tag) {
+ OCA.SystemTags.Admin.onSelectTag(tag);
+ return OC.SystemTags.getDescriptiveTag(tag);
+ },
+ escapeMarkup: function(m) {
+ return m;
+ }
+ }
+ };
+})();
+
+$(document).ready(function() {
+ OCA.SystemTags.Admin.init();
+});
+
diff --git a/apps/systemtags/lib/AppInfo/Application.php b/apps/systemtags/lib/AppInfo/Application.php
new file mode 100644
index 00000000000..7cd49d6424b
--- /dev/null
+++ b/apps/systemtags/lib/AppInfo/Application.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @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\SystemTags\AppInfo;
+
+use OCP\AppFramework\App;
+
+class Application extends App {
+ public function __construct() {
+ parent::__construct('systemtags');
+ }
+
+ /**
+ * Register admin settings
+ */
+ public function registerAdminPage() {
+ \OCP\App::registerAdmin($this->getContainer()->getAppName(), 'admin');
+ }
+}
diff --git a/apps/systemtags/templates/admin.php b/apps/systemtags/templates/admin.php
new file mode 100644
index 00000000000..883e998ed61
--- /dev/null
+++ b/apps/systemtags/templates/admin.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @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/>.
+ *
+ */
+
+vendor_script('core', 'select2/select2');
+vendor_style('core', 'select2/select2');
+script('core', [
+ 'oc-backbone-webdav',
+ 'systemtags/systemtags',
+ 'systemtags/systemtagmodel',
+ 'systemtags/systemtagscollection',
+]);
+
+script('systemtags', 'admin');
+
+/** @var \OCP\IL10N $l */
+?>
+
+<form id="systemtags" class="section" data-systemtag-id="">
+ <h2><?php p($l->t('Collaborative tags')); ?></h2>
+
+ <input type="hidden" name="systemtag" id="systemtag" placeholder="<?php p($l->t('Select tag…')); ?>" style="width: 400px;" />
+
+ <br><br>
+
+ <input type="text" id="systemtag_name" name="systemtag_name" placeholder="<?php p($l->t('Name')); ?>" style="width: 200px;">
+
+ <span id="systemtag_delete" class="hidden">
+ <img src="<?php p(\OCP\Template::image_path('core', 'actions/delete.svg')); ?>" alt="<?php p($l->t('Delete')); ?>">
+ </span>
+
+ <br>
+
+ <select id="systemtag_level">
+ <option value="3"><?php p($l->t('Public')); ?></option>
+ <option value="2"><?php p($l->t('Restricted')); ?></option>
+ <option value="0"><?php p($l->t('Invisible')); ?></option>
+ </select>
+
+ <input type="button" id="systemtag_submit" value="<?php p($l->t('Create')); ?>">
+ <input type="button" id="systemtag_reset" value="<?php p($l->t('Reset')); ?>">
+</form>
diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js
index 941ec5c711b..01ff9123842 100644
--- a/apps/theming/js/settings-admin.js
+++ b/apps/theming/js/settings-admin.js
@@ -46,29 +46,46 @@ function calculateLuminance(rgb) {
return (0.299*r + 0.587*g + 0.114*b)/255;
}
+function generateRadioButton(color) {
+ var radioButton = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' +
+ '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="' + color + '"/></svg>';
+ return btoa(radioButton);
+}
+
function preview(setting, value) {
if (setting === 'color') {
var headerClass = document.getElementById('header');
var expandDisplayNameClass = document.getElementById('expandDisplayName');
var headerAppName = headerClass.getElementsByClassName('header-appname')[0];
var textColor, icon;
+ var luminance = calculateLuminance(value);
+ var elementColor = value;
- if (calculateLuminance(value) > 0.5) {
+ if (luminance > 0.5) {
textColor = "#000000";
icon = 'caret-dark';
} else {
textColor = "#ffffff";
icon = 'caret';
}
+ if (luminance>0.8) {
+ elementColor = '#555555';
+ }
headerClass.style.background = value;
headerClass.style.backgroundImage = '../img/logo-icon.svg';
expandDisplayNameClass.style.color = textColor;
headerAppName.style.color = textColor;
- $(headerClass).find('.icon-caret').each(function() {
- $(this).css('background-image', "url('" + OC.getRootPath() + '/core/img/actions/' + icon + ".svg')");
- });
+ $('#previewStyles').html(
+ '#header .icon-caret { background-image: url(\'' + OC.getRootPath() + '/core/img/actions/' + icon + '.svg\') }' +
+ 'input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' +
+ 'background-image:url(\'' + OC.getRootPath() + '/core/img/actions/checkmark-white.svg\');' +
+ 'background-color: ' + elementColor + '; background-position: center center; background-size:contain;' +
+ 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;}' +
+ 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' +
+ 'background-image: url(\'data:image/svg+xml;base64,' + generateRadioButton(elementColor) + '\'); }'
+ );
}
if (setting === 'logoMime') {
console.log(setting);
@@ -87,6 +104,8 @@ function preview(setting, value) {
$(document).ready(function () {
$('#theming [data-toggle="tooltip"]').tooltip();
+ $('html > head').append($('<style type="text/css" id="previewStyles"></style>'));
+
var uploadParamsLogo = {
pasteZone: null,
dropZone: null,
diff --git a/apps/theming/lib/controller/themingcontroller.php b/apps/theming/lib/controller/themingcontroller.php
index 3e5d6f3e0d1..6a61293828f 100644
--- a/apps/theming/lib/controller/themingcontroller.php
+++ b/apps/theming/lib/controller/themingcontroller.php
@@ -214,35 +214,46 @@ class ThemingController extends Controller {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
$responseCss = '';
$color = $this->config->getAppValue($this->appName, 'color');
+ $elementColor = Util::elementColor($color);
if($color !== '') {
$responseCss .= sprintf(
- '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: %s}',
+ '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: %s}' . "\n",
$color
);
+ $responseCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' .
+ 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' .
+ 'background-color: %s; background-position: center center; background-size:contain;' .
+ 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' .
+ "}\n",
+ \OC::$WEBROOT,
+ $elementColor
+ );
+ $responseCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' .
+ 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' .
+ "}\n";
}
$logo = $this->config->getAppValue($this->appName, 'logoMime');
if($logo !== '') {
- $responseCss .= sprintf('#header .logo {
- background-image: url(\'./logo?v='.$cacheBusterValue.'\');
- background-size: contain;
- }
- #header .logo-icon {
- background-image: url(\'./logo?v='.$cacheBusterValue.'\');
- background-size: contain;
- }'
+ $responseCss .= sprintf(
+ '#header .logo {' .
+ 'background-image: url(\'./logo?v='.$cacheBusterValue.'\')' .
+ 'background-size: contain;' .
+ '}' . "\n" .
+ '#header .logo-icon {' .
+ 'background-image: url(\'./logo?v='.$cacheBusterValue.'\');' .
+ 'background-size: contain;' .
+ '}' . "\n"
);
}
$backgroundLogo = $this->config->getAppValue($this->appName, 'backgroundMime');
if($backgroundLogo !== '') {
- $responseCss .= '#body-login {
- background-image: url(\'./loginbackground?v='.$cacheBusterValue.'\');
- }';
+ $responseCss .= '#body-login {background-image: url(\'./loginbackground?v='.$cacheBusterValue.'\');}' . "\n";
}
if(Util::invertTextColor($color)) {
- $responseCss .= '#header .header-appname, #expandDisplayName { color: #000000; } ';
- $responseCss .= '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } ';
- $responseCss .= '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }';
- $responseCss .= '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }';
+ $responseCss .= '#header .header-appname, #expandDisplayName { color: #000000; }' . "\n";
+ $responseCss .= '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); }' . "\n";
+ $responseCss .= '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }' . "\n";
+ $responseCss .= '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }' . "\n";
}
\OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT');
diff --git a/apps/theming/lib/util.php b/apps/theming/lib/util.php
index 2088650b19d..f0ce30ac5ba 100644
--- a/apps/theming/lib/util.php
+++ b/apps/theming/lib/util.php
@@ -39,6 +39,21 @@ class Util {
}
/**
+ * get color for on-page elements:
+ * theme color by default, grey if theme color is to bright
+ * @param $color
+ * @return string
+ */
+ public static function elementColor($color) {
+ $l = self::calculateLuminance($color);
+ if($l>0.8) {
+ return '#555555';
+ } else {
+ return $color;
+ }
+ }
+
+ /**
* @param string $color rgb color value
* @return float
*/
@@ -56,4 +71,14 @@ class Util {
return (0.299 * $r + 0.587 * $g + 0.114 * $b)/255;
}
+ /**
+ * @param $color
+ * @return string base64 encoded radio button svg
+ */
+ public static function generateRadioButton($color) {
+ $radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' .
+ '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="'.$color.'"/></svg>';
+ return base64_encode($radioButtonIcon);
+ }
+
}
diff --git a/apps/theming/tests/lib/UtilTest.php b/apps/theming/tests/lib/UtilTest.php
index 9ebb11d6288..cf64b389d11 100644
--- a/apps/theming/tests/lib/UtilTest.php
+++ b/apps/theming/tests/lib/UtilTest.php
@@ -65,4 +65,25 @@ class UtilTest extends TestCase {
$invert = Util::invertTextColor('');
$this->assertEquals(false, $invert);
}
+
+ public function testElementColorDefault() {
+ $elementColor = Util::elementColor("#000000");
+ $this->assertEquals('#000000', $elementColor);
+ }
+
+ public function testElementColorOnBrightBackground() {
+ $elementColor = Util::elementColor('#ffffff');
+ $this->assertEquals('#555555', $elementColor);
+ }
+
+ public function testGenerateRadioButtonWhite() {
+ $button = Util::generateRadioButton('#ffffff');
+ $expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiNmZmZmZmYiLz48L3N2Zz4=';
+ $this->assertEquals($expected, $button);
+ }
+ public function testGenerateRadioButtonBlack() {
+ $button = Util::generateRadioButton('#000000');
+ $expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiMwMDAwMDAiLz48L3N2Zz4=';
+ $this->assertEquals($expected, $button);
+ }
}
diff --git a/apps/theming/tests/lib/controller/ThemingControllerTest.php b/apps/theming/tests/lib/controller/ThemingControllerTest.php
index 24eb0510f99..59e33c755db 100644
--- a/apps/theming/tests/lib/controller/ThemingControllerTest.php
+++ b/apps/theming/tests/lib/controller/ThemingControllerTest.php
@@ -26,6 +26,7 @@ namespace OCA\Theming\Tests\Controller;
use OCA\Theming\Controller\ThemingController;
use OCA\Theming\Template;
+use OCA\Theming\Util;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\Files\IRootFolder;
@@ -327,7 +328,20 @@ class ThemingControllerTest extends TestCase {
->with('theming', 'backgroundMime', '')
->willReturn('');
- $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}', 'style', 'text/css');
+ $elementColor = '#000';
+ $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}' . "\n";
+ $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' .
+ 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' .
+ 'background-color: %s; background-position: center center; background-size:contain;' .
+ 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' .
+ "}\n",
+ \OC::$WEBROOT,
+ $elementColor
+ );
+ $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' .
+ 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' .
+ "}\n";
+ $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css');
$expected->cacheFor(3600);
@$this->assertEquals($expected, $this->themingController->getStylesheet());
}
@@ -353,8 +367,24 @@ class ThemingControllerTest extends TestCase {
->method('getAppValue')
->with('theming', 'backgroundMime', '')
->willReturn('');
-
- $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } .searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css');
+ $elementColor = '#555555';
+ $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}' . "\n";
+ $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' .
+ 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' .
+ 'background-color: %s; background-position: center center; background-size:contain;' .
+ 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' .
+ "}\n",
+ \OC::$WEBROOT,
+ $elementColor
+ );
+ $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' .
+ 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' .
+ "}\n";
+ $expectedCss .= '#header .header-appname, #expandDisplayName { color: #000000; }' . "\n" .
+ '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); }' . "\n" .
+ '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }' . "\n" .
+ '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }' . "\n";
+ $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css');
$expected->cacheFor(3600);
@$this->assertEquals($expected, $this->themingController->getStylesheet());
}
@@ -381,14 +411,15 @@ class ThemingControllerTest extends TestCase {
->with('theming', 'backgroundMime', '')
->willReturn('');
- $expected = new Http\DataDownloadResponse('#header .logo {
- background-image: url(\'./logo?v=0\');
- background-size: contain;
- }
- #header .logo-icon {
- background-image: url(\'./logo?v=0\');
- background-size: contain;
- }', 'style', 'text/css');
+ $expectedCss = '#header .logo {' .
+ 'background-image: url(\'./logo?v=0\')' .
+ 'background-size: contain;' .
+ '}' . "\n" .
+ '#header .logo-icon {' .
+ 'background-image: url(\'./logo?v=0\');' .
+ 'background-size: contain;' .
+ '}' . "\n";
+ $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css');
$expected->cacheFor(3600);
@$this->assertEquals($expected, $this->themingController->getStylesheet());
}
@@ -415,9 +446,8 @@ class ThemingControllerTest extends TestCase {
->with('theming', 'backgroundMime', '')
->willReturn('text/svg');
- $expected = new Http\DataDownloadResponse('#body-login {
- background-image: url(\'./loginbackground?v=0\');
- }', 'style', 'text/css');
+ $expectedCss = '#body-login {background-image: url(\'./loginbackground?v=0\');}' . "\n";
+ $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css');
$expected->cacheFor(3600);
@$this->assertEquals($expected, $this->themingController->getStylesheet());
}
@@ -444,16 +474,30 @@ class ThemingControllerTest extends TestCase {
->with('theming', 'backgroundMime', '')
->willReturn('image/png');
- $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}#header .logo {
- background-image: url(\'./logo?v=0\');
- background-size: contain;
- }
- #header .logo-icon {
- background-image: url(\'./logo?v=0\');
- background-size: contain;
- }#body-login {
- background-image: url(\'./loginbackground?v=0\');
- }', 'style', 'text/css');
+ $elementColor = '#000';
+ $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}' . "\n";
+ $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' .
+ 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' .
+ 'background-color: %s; background-position: center center; background-size:contain;' .
+ 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' .
+ "}\n",
+ \OC::$WEBROOT,
+ $elementColor
+ );
+ $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' .
+ 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' .
+ "}\n";
+ $expectedCss .= '#header .logo {' .
+ 'background-image: url(\'./logo?v=0\')' .
+ 'background-size: contain;' .
+ '}' . "\n" .
+ '#header .logo-icon {' .
+ 'background-image: url(\'./logo?v=0\');' .
+ 'background-size: contain;' .
+ '}' . "\n";
+ $expectedCss .= '#body-login {background-image: url(\'./loginbackground?v=0\');}' . PHP_EOL;
+
+ $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css');
$expected->cacheFor(3600);
@$this->assertEquals($expected, $this->themingController->getStylesheet());
}
@@ -479,16 +523,33 @@ class ThemingControllerTest extends TestCase {
->with('theming', 'backgroundMime', '')
->willReturn('image/png');
- $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}#header .logo {
- background-image: url(\'./logo?v=0\');
- background-size: contain;
- }
- #header .logo-icon {
- background-image: url(\'./logo?v=0\');
- background-size: contain;
- }#body-login {
- background-image: url(\'./loginbackground?v=0\');
- }#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } .searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css');
+ $elementColor = '#555555';
+ $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}' . "\n";
+ $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' .
+ 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' .
+ 'background-color: %s; background-position: center center; background-size:contain;' .
+ 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' .
+ "}\n",
+ \OC::$WEBROOT,
+ $elementColor
+ );
+ $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' .
+ 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' .
+ "}\n";
+ $expectedCss .= '#header .logo {' .
+ 'background-image: url(\'./logo?v=0\')' .
+ 'background-size: contain;' .
+ '}' . PHP_EOL .
+ '#header .logo-icon {' .
+ 'background-image: url(\'./logo?v=0\');' .
+ 'background-size: contain;' .
+ '}' . PHP_EOL;
+ $expectedCss .= '#body-login {background-image: url(\'./loginbackground?v=0\');}' . PHP_EOL;
+ $expectedCss .= '#header .header-appname, #expandDisplayName { color: #000000; }' . PHP_EOL .
+ '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); }' . PHP_EOL .
+ '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }' . PHP_EOL .
+ '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }' . PHP_EOL;
+ $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css');
$expected->cacheFor(3600);
@$this->assertEquals($expected, $this->themingController->getStylesheet());
}
diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php
index b5eab90af48..654fb70ced5 100644
--- a/apps/user_ldap/ajax/wizard.php
+++ b/apps/user_ldap/ajax/wizard.php
@@ -60,7 +60,7 @@ $userManager = new \OCA\User_LDAP\User\Manager(
\OC::$server->getDatabaseConnection(),
\OC::$server->getUserManager());
-$access = new \OCA\User_LDAP\Access($con, $ldapWrapper, $userManager);
+$access = new \OCA\User_LDAP\Access($con, $ldapWrapper, $userManager, new \OCA\User_LDAP\Helper());
$wizard = new \OCA\User_LDAP\Wizard($configuration, $ldapWrapper, $access);
diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php
index 18987614bdb..10cc003a3f5 100644
--- a/apps/user_ldap/appinfo/app.php
+++ b/apps/user_ldap/appinfo/app.php
@@ -44,7 +44,7 @@ if(count($configPrefixes) === 1) {
\OC::$server->getUserManager()
);
$connector = new OCA\User_LDAP\Connection($ldapWrapper, $configPrefixes[0]);
- $ldapAccess = new OCA\User_LDAP\Access($connector, $ldapWrapper, $userManager);
+ $ldapAccess = new OCA\User_LDAP\Access($connector, $ldapWrapper, $userManager, $helper);
$ldapAccess->setUserMapper(new OCA\User_LDAP\Mapping\UserMapping($dbc));
$ldapAccess->setGroupMapper(new OCA\User_LDAP\Mapping\GroupMapping($dbc));
diff --git a/apps/user_ldap/appinfo/install.php b/apps/user_ldap/appinfo/install.php
index b3c92b0024a..c16a1f4a039 100644
--- a/apps/user_ldap/appinfo/install.php
+++ b/apps/user_ldap/appinfo/install.php
@@ -4,6 +4,7 @@
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christopher Schäpers <kondou@ts.unde.re>
+ * @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@@ -24,3 +25,6 @@ $state = OCP\Config::getSystemValue('ldapIgnoreNamingRules', 'doSet');
if($state === 'doSet') {
OCP\Config::setSystemValue('ldapIgnoreNamingRules', false);
}
+
+$helper = new \OCA\User_LDAP\Helper();
+$helper->setLDAPProvider();
diff --git a/apps/user_ldap/appinfo/update.php b/apps/user_ldap/appinfo/update.php
new file mode 100644
index 00000000000..3c9745338e6
--- /dev/null
+++ b/apps/user_ldap/appinfo/update.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+$helper = new \OCA\User_LDAP\Helper();
+$helper->setLDAPProvider();
diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php
index dabf243eda1..299ad581644 100644
--- a/apps/user_ldap/lib/Access.php
+++ b/apps/user_ldap/lib/Access.php
@@ -20,6 +20,7 @@
* @author Ralph Krimmel <rkrimme1@gwdg.de>
* @author Renaud Fortier <Renaud.Fortier@fsaa.ulaval.ca>
* @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@@ -77,13 +78,19 @@ class Access extends LDAPUtility implements IUserTools {
* @var AbstractMapping $userMapper
*/
protected $groupMapper;
+
+ /**
+ * @var \OCA\User_LDAP\Helper
+ */
+ private $helper;
public function __construct(Connection $connection, ILDAPWrapper $ldap,
- Manager $userManager) {
+ Manager $userManager, Helper $helper) {
parent::__construct($ldap);
$this->connection = $connection;
$this->userManager = $userManager;
$this->userManager->setLdapAccess($this);
+ $this->helper = $helper;
}
/**
@@ -173,7 +180,7 @@ class Access extends LDAPUtility implements IUserTools {
// (cf. #12306), 500 is default for paging and should work everywhere.
$maxResults = $pagingSize > 20 ? $pagingSize : 500;
$this->initPagedSearch($filter, array($dn), array($attr), $maxResults, 0);
- $dn = $this->DNasBaseParameter($dn);
+ $dn = $this->helper->DNasBaseParameter($dn);
$rr = @$this->ldap->read($cr, $dn, $filter, array($attr));
if(!$this->ldap->isResource($rr)) {
if(!empty($attr)) {
@@ -201,7 +208,7 @@ class Access extends LDAPUtility implements IUserTools {
$values = array();
for($i=0;$i<$result[$attr]['count'];$i++) {
if($this->resemblesDN($attr)) {
- $values[] = $this->sanitizeDN($result[$attr][$i]);
+ $values[] = $this->helper->sanitizeDN($result[$attr][$i]);
} elseif(strtolower($attr) === 'objectguid' || strtolower($attr) === 'guid') {
$values[] = $this->convertObjectGUID2Str($result[$attr][$i]);
} else {
@@ -243,49 +250,6 @@ class Access extends LDAPUtility implements IUserTools {
}
/**
- * sanitizes a DN received from the LDAP server
- * @param array $dn the DN in question
- * @return array the sanitized DN
- */
- private function sanitizeDN($dn) {
- //treating multiple base DNs
- if(is_array($dn)) {
- $result = array();
- foreach($dn as $singleDN) {
- $result[] = $this->sanitizeDN($singleDN);
- }
- return $result;
- }
-
- //OID sometimes gives back DNs with whitespace after the comma
- // a la "uid=foo, cn=bar, dn=..." We need to tackle this!
- $dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
-
- //make comparisons and everything work
- $dn = mb_strtolower($dn, 'UTF-8');
-
- //escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
- //to use the DN in search filters, \ needs to be escaped to \5c additionally
- //to use them in bases, we convert them back to simple backslashes in readAttribute()
- $replacements = array(
- '\,' => '\5c2C',
- '\=' => '\5c3D',
- '\+' => '\5c2B',
- '\<' => '\5c3C',
- '\>' => '\5c3E',
- '\;' => '\5c3B',
- '\"' => '\5c22',
- '\#' => '\5c23',
- '(' => '\28',
- ')' => '\29',
- '*' => '\2A',
- );
- $dn = str_replace(array_keys($replacements), array_values($replacements), $dn);
-
- return $dn;
- }
-
- /**
* returns a DN-string that is cleaned from not domain parts, e.g.
* cn=foo,cn=bar,dc=foobar,dc=server,dc=org
* becomes dc=foobar,dc=server,dc=org
@@ -1071,10 +1035,10 @@ class Access extends LDAPUtility implements IUserTools {
}
if($key !== 'dn') {
$selection[$i][$key] = $this->resemblesDN($key) ?
- $this->sanitizeDN($item[$key])
+ $this->helper->sanitizeDN($item[$key])
: $item[$key];
} else {
- $selection[$i][$key] = [$this->sanitizeDN($item[$key])];
+ $selection[$i][$key] = [$this->helper->sanitizeDN($item[$key])];
}
}
@@ -1298,7 +1262,7 @@ class Access extends LDAPUtility implements IUserTools {
* @return bool
*/
public function areCredentialsValid($name, $password) {
- $name = $this->DNasBaseParameter($name);
+ $name = $this->helper->DNasBaseParameter($name);
$testConnection = clone $this->connection;
$credentials = array(
'ldapAgentName' => $name,
@@ -1570,15 +1534,6 @@ class Access extends LDAPUtility implements IUserTools {
}
/**
- * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
- * @param string $dn the DN
- * @return string
- */
- private function DNasBaseParameter($dn) {
- return str_ireplace('\\5c', '\\', $dn);
- }
-
- /**
* checks if the given DN is part of the given base DN(s)
* @param string $dn the DN
* @param string[] $bases array containing the allowed base DN or DNs
@@ -1586,7 +1541,7 @@ class Access extends LDAPUtility implements IUserTools {
*/
public function isDNPartOfBase($dn, $bases) {
$belongsToBase = false;
- $bases = $this->sanitizeDN($bases);
+ $bases = $this->helper->sanitizeDN($bases);
foreach($bases as $base) {
$belongsToBase = true;
diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php
index 7bd5e97e4f4..7fb26526195 100644
--- a/apps/user_ldap/lib/Connection.php
+++ b/apps/user_ldap/lib/Connection.php
@@ -11,6 +11,7 @@
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@@ -52,6 +53,8 @@ class Connection extends LDAPUtility {
private $configID;
private $configured = false;
private $hasPagedResultSupport = true;
+ //whether connection should be kept on __destruct
+ private $dontDestruct = false;
/**
* @var bool runtime flag that indicates whether supported primary groups are available
@@ -93,7 +96,7 @@ class Connection extends LDAPUtility {
}
public function __destruct() {
- if($this->ldap->isResource($this->ldapConnectionRes)) {
+ if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
@$this->ldap->unbind($this->ldapConnectionRes);
};
}
@@ -105,6 +108,7 @@ class Connection extends LDAPUtility {
$this->configuration = new Configuration($this->configPrefix,
!is_null($this->configID));
$this->ldapConnectionRes = null;
+ $this->dontDestruct = true;
}
/**
diff --git a/apps/user_ldap/lib/Helper.php b/apps/user_ldap/lib/Helper.php
index ccc1d2c0b44..90807a3c526 100644
--- a/apps/user_ldap/lib/Helper.php
+++ b/apps/user_ldap/lib/Helper.php
@@ -10,6 +10,7 @@
* @author Morris Jobke <hey@morrisjobke.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <pvince81@owncloud.com>
+ * @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@@ -183,6 +184,70 @@ class Helper {
return $domain;
}
+
+ /**
+ *
+ * Set the LDAPProvider in the config
+ *
+ */
+ public function setLDAPProvider() {
+ $current = \OC::$server->getConfig()->getSystemValue('ldapProviderFactory', null);
+ if(is_null($current)) {
+ \OC::$server->getConfig()->setSystemValue('ldapProviderFactory', '\\OCA\\User_LDAP\\LDAPProviderFactory');
+ }
+ }
+
+ /**
+ * sanitizes a DN received from the LDAP server
+ * @param array $dn the DN in question
+ * @return array the sanitized DN
+ */
+ public function sanitizeDN($dn) {
+ //treating multiple base DNs
+ if(is_array($dn)) {
+ $result = array();
+ foreach($dn as $singleDN) {
+ $result[] = $this->sanitizeDN($singleDN);
+ }
+ return $result;
+ }
+
+ //OID sometimes gives back DNs with whitespace after the comma
+ // a la "uid=foo, cn=bar, dn=..." We need to tackle this!
+ $dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
+
+ //make comparisons and everything work
+ $dn = mb_strtolower($dn, 'UTF-8');
+
+ //escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
+ //to use the DN in search filters, \ needs to be escaped to \5c additionally
+ //to use them in bases, we convert them back to simple backslashes in readAttribute()
+ $replacements = array(
+ '\,' => '\5c2C',
+ '\=' => '\5c3D',
+ '\+' => '\5c2B',
+ '\<' => '\5c3C',
+ '\>' => '\5c3E',
+ '\;' => '\5c3B',
+ '\"' => '\5c22',
+ '\#' => '\5c23',
+ '(' => '\28',
+ ')' => '\29',
+ '*' => '\2A',
+ );
+ $dn = str_replace(array_keys($replacements), array_values($replacements), $dn);
+
+ return $dn;
+ }
+
+ /**
+ * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
+ * @param string $dn the DN
+ * @return string
+ */
+ public function DNasBaseParameter($dn) {
+ return str_ireplace('\\5c', '\\', $dn);
+ }
/**
* listens to a hook thrown by server2server sharing and replaces the given
diff --git a/apps/user_ldap/lib/IUserLDAP.php b/apps/user_ldap/lib/IUserLDAP.php
new file mode 100644
index 00000000000..cb7d0138892
--- /dev/null
+++ b/apps/user_ldap/lib/IUserLDAP.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP;
+
+interface IUserLDAP {
+
+ //Functions used by LDAPProvider
+
+ /**
+ * Return access for LDAP interaction.
+ * @param string $uid
+ * @return Access instance of Access for LDAP interaction
+ */
+ public function getLDAPAccess($uid);
+
+ /**
+ * Return a new LDAP connection for the specified user.
+ * @param string $uid
+ * @return resource of the LDAP connection
+ */
+ public function getNewLDAPConnection($uid);
+
+ /**
+ * Return the username for the given LDAP DN, if available.
+ * @param string $dn
+ * @return string|false with the username
+ */
+ public function dn2UserName($dn);
+}
diff --git a/apps/user_ldap/lib/Jobs/UpdateGroups.php b/apps/user_ldap/lib/Jobs/UpdateGroups.php
index 91d40d58742..047b95a6d9b 100644
--- a/apps/user_ldap/lib/Jobs/UpdateGroups.php
+++ b/apps/user_ldap/lib/Jobs/UpdateGroups.php
@@ -188,7 +188,7 @@ class UpdateGroups extends \OC\BackgroundJob\TimedJob {
$dbc,
\OC::$server->getUserManager());
$connector = new Connection($ldapWrapper, $configPrefixes[0]);
- $ldapAccess = new Access($connector, $ldapWrapper, $userManager);
+ $ldapAccess = new Access($connector, $ldapWrapper, $userManager, $helper);
$groupMapper = new GroupMapping($dbc);
$userMapper = new UserMapping($dbc);
$ldapAccess->setGroupMapper($groupMapper);
diff --git a/apps/user_ldap/lib/LDAPProvider.php b/apps/user_ldap/lib/LDAPProvider.php
new file mode 100644
index 00000000000..5bd6a0fd08c
--- /dev/null
+++ b/apps/user_ldap/lib/LDAPProvider.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP;
+
+use OCP\IUserBackend;
+use OCP\LDAP\ILDAPProvider;
+use OCP\LDAP\IDeletionFlagSupport;
+use OCP\IServerContainer;
+use OCA\User_LDAP\User\DeletedUsersIndex;
+use OCA\User_LDAP\Mapping\UserMapping;
+
+/**
+ * LDAP provider for pulic access to the LDAP backend.
+ */
+class LDAPProvider implements ILDAPProvider, IDeletionFlagSupport {
+
+ private $backend;
+ private $logger;
+ private $helper;
+ private $deletedUsersIndex;
+
+ /**
+ * Create new LDAPProvider
+ * @param \OCP\IServerContainer $serverContainer
+ * @throws \Exception if user_ldap app was not enabled
+ */
+ public function __construct(IServerContainer $serverContainer, Helper $helper, DeletedUsersIndex $deletedUsersIndex) {
+ $this->logger = $serverContainer->getLogger();
+ $this->helper = $helper;
+ $this->deletedUsersIndex = $deletedUsersIndex;
+ foreach ($serverContainer->getUserManager()->getBackends() as $backend){
+ $this->logger->debug('instance '.get_class($backend).' backend.', ['app' => 'user_ldap']);
+ if ($backend instanceof IUserLDAP) {
+ $this->backend = $backend;
+ return;
+ }
+ }
+ throw new \Exception('To use the LDAPProvider, user_ldap app must be enabled');
+ }
+
+ /**
+ * Translate an user id to LDAP DN
+ * @param string $uid user id
+ * @return string with the LDAP DN
+ * @throws \Exception if translation was unsuccessful
+ */
+ public function getUserDN($uid) {
+ if(!$this->backend->userExists($uid)){
+ throw new \Exception('User id not found in LDAP');
+ }
+ $result = $this->backend->getLDAPAccess($uid)->username2dn($uid);
+ if(!$result){
+ throw new \Exception('Translation to LDAP DN unsuccessful');
+ }
+ return $result;
+ }
+
+ /**
+ * Translate a LDAP DN to an internal user name. If there is no mapping between
+ * the DN and the user name, a new one will be created.
+ * @param string $dn LDAP DN
+ * @return string with the internal user name
+ * @throws \Exception if translation was unsuccessful
+ */
+ public function getUserName($dn) {
+ $result = $this->backend->dn2UserName($dn);
+ if(!$result){
+ throw new \Exception('Translation to internal user name unsuccessful');
+ }
+ return $result;
+ }
+
+ /**
+ * Convert a stored DN so it can be used as base parameter for LDAP queries.
+ * @param string $dn the DN in question
+ * @return string
+ */
+ public function DNasBaseParameter($dn) {
+ return $this->helper->DNasBaseParameter($dn);
+ }
+
+ /**
+ * Sanitize a DN received from the LDAP server.
+ * @param array $dn the DN in question
+ * @return array the sanitized DN
+ */
+ public function sanitizeDN($dn) {
+ return $this->helper->sanitizeDN($dn);
+ }
+
+ /**
+ * Return a new LDAP connection resource for the specified user.
+ * The connection must be closed manually.
+ * @param string $uid user id
+ * @return resource of the LDAP connection
+ * @throws \Exception if user id was not found in LDAP
+ */
+ public function getLDAPConnection($uid) {
+ if(!$this->backend->userExists($uid)){
+ throw new \Exception('User id not found in LDAP');
+ }
+ return $this->backend->getNewLDAPConnection($uid);
+ }
+
+ /**
+ * Get the LDAP base for users.
+ * @param string $uid user id
+ * @return string the base for users
+ * @throws \Exception if user id was not found in LDAP
+ */
+ public function getLDAPBaseUsers($uid) {
+ if(!$this->backend->userExists($uid)){
+ throw new \Exception('User id not found in LDAP');
+ }
+ return $this->backend->getLDAPAccess($uid)->getConnection()->getConfiguration()['ldap_base_users'];
+ }
+
+ /**
+ * Get the LDAP base for groups.
+ * @param string $uid user id
+ * @return string the base for groups
+ * @throws \Exception if user id was not found in LDAP
+ */
+ public function getLDAPBaseGroups($uid) {
+ if(!$this->backend->userExists($uid)){
+ throw new \Exception('User id not found in LDAP');
+ }
+ return $this->backend->getLDAPAccess($uid)->getConnection()->getConfiguration()['ldap_base_groups'];
+ }
+
+ /**
+ * Clear the cache if a cache is used, otherwise do nothing.
+ * @param string $uid user id
+ * @throws \Exception if user id was not found in LDAP
+ */
+ public function clearCache($uid) {
+ if(!$this->backend->userExists($uid)){
+ throw new \Exception('User id not found in LDAP');
+ }
+ $this->backend->getLDAPAccess($uid)->getConnection()->clearCache();
+ }
+
+ /**
+ * Check whether a LDAP DN exists
+ * @param string $dn LDAP DN
+ * @return bool whether the DN exists
+ */
+ public function dnExists($dn) {
+ $result = $this->backend->dn2UserName($dn);
+ return !$result ? false : true;
+ }
+
+ /**
+ * Flag record for deletion.
+ * @param string $uid user id
+ */
+ public function flagRecord($uid) {
+ $this->deletedUsersIndex->markUser($uid);
+ }
+
+ /**
+ * Unflag record for deletion.
+ * @param string $uid user id
+ */
+ public function unflagRecord($uid) {
+ //do nothing
+ }
+}
diff --git a/apps/user_ldap/lib/LDAPProviderFactory.php b/apps/user_ldap/lib/LDAPProviderFactory.php
new file mode 100644
index 00000000000..528af001037
--- /dev/null
+++ b/apps/user_ldap/lib/LDAPProviderFactory.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP;
+
+use OCP\LDAP\ILDAPProviderFactory;
+use OCP\IServerContainer;
+use OCA\User_LDAP\User\DeletedUsersIndex;
+use OCA\User_LDAP\Mapping\UserMapping;
+
+class LDAPProviderFactory implements ILDAPProviderFactory {
+ /**
+ * Server container
+ *
+ * @var IServerContainer
+ */
+ private $serverContainer;
+
+ /**
+ * Constructor for the LDAP provider factory
+ *
+ * @param IServerContainer $serverContainer server container
+ */
+ public function __construct(IServerContainer $serverContainer) {
+ $this->serverContainer = $serverContainer;
+ }
+
+ /**
+ * creates and returns an instance of the ILDAPProvider
+ *
+ * @return OCP\LDAP\ILDAPProvider
+ */
+ public function getLDAPProvider() {
+ $dbConnection = $this->serverContainer->getDatabaseConnection();
+ $userMapping = new UserMapping($dbConnection);
+ return new LDAPProvider($this->serverContainer, new Helper(),
+ new DeletedUsersIndex($this->serverContainer->getConfig(),
+ $dbConnection, $userMapping));
+ }
+}
diff --git a/apps/user_ldap/lib/Proxy.php b/apps/user_ldap/lib/Proxy.php
index 07cc1ea0e8c..db1c761656f 100644
--- a/apps/user_ldap/lib/Proxy.php
+++ b/apps/user_ldap/lib/Proxy.php
@@ -77,7 +77,7 @@ abstract class Proxy {
$userManager =
new Manager($ocConfig, $fs, $log, $avatarM, new \OCP\Image(), $db, $coreUserManager);
$connector = new Connection($this->ldap, $configPrefix);
- $access = new Access($connector, $this->ldap, $userManager);
+ $access = new Access($connector, $this->ldap, $userManager, new Helper());
$access->setUserMapper($userMap);
$access->setGroupMapper($groupMap);
self::$accesses[$configPrefix] = $access;
diff --git a/apps/user_ldap/lib/User_LDAP.php b/apps/user_ldap/lib/User_LDAP.php
index a2a65bb8406..7e5968e9003 100644
--- a/apps/user_ldap/lib/User_LDAP.php
+++ b/apps/user_ldap/lib/User_LDAP.php
@@ -15,6 +15,7 @@
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Tom Needham <tom@owncloud.com>
+ * @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@@ -39,7 +40,7 @@ use OCA\User_LDAP\User\OfflineUser;
use OCA\User_LDAP\User\User;
use OCP\IConfig;
-class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface {
+class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
/** @var string[] $homesToKill */
protected $homesToKill = array();
@@ -90,6 +91,16 @@ class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
return false;
}
}
+
+ /**
+ * returns the username for the given LDAP DN, if available
+ *
+ * @param string $dn
+ * @return string|false with the username
+ */
+ public function dn2UserName($dn) {
+ return $this->access->dn2username($dn);
+ }
/**
* returns an LDAP record based on a given login name
@@ -462,5 +473,25 @@ class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
public function getBackendName(){
return 'LDAP';
}
-
+
+ /**
+ * Return access for LDAP interaction.
+ * @param string $uid
+ * @return Access instance of Access for LDAP interaction
+ */
+ public function getLDAPAccess($uid) {
+ return $this->access;
+ }
+
+ /**
+ * Return LDAP connection resource from a cloned connection.
+ * The cloned connection needs to be closed manually.
+ * of the current access.
+ * @param string $uid
+ * @return resource of the LDAP connection
+ */
+ public function getNewLDAPConnection($uid) {
+ $connection = clone $this->access->getConnection();
+ return $connection->getConnectionResource();
+ }
}
diff --git a/apps/user_ldap/lib/User_Proxy.php b/apps/user_ldap/lib/User_Proxy.php
index c86d4f29ec4..cced469a7ae 100644
--- a/apps/user_ldap/lib/User_Proxy.php
+++ b/apps/user_ldap/lib/User_Proxy.php
@@ -9,6 +9,7 @@
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@@ -31,7 +32,7 @@ namespace OCA\User_LDAP;
use OCA\User_LDAP\User\User;
use OCP\IConfig;
-class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface {
+class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
private $backends = array();
private $refBackend = null;
@@ -193,6 +194,17 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface
$id = 'LOGINNAME,' . $loginName;
return $this->handleRequest($id, 'loginName2UserName', array($loginName));
}
+
+ /**
+ * returns the username for the given LDAP DN, if available
+ *
+ * @param string $dn
+ * @return string|false with the username
+ */
+ public function dn2UserName($dn) {
+ $id = 'DN,' . $dn;
+ return $this->handleRequest($id, 'dn2UserName', array($dn));
+ }
/**
* get the user's home directory
@@ -273,4 +285,22 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface
return $users;
}
+ /**
+ * Return access for LDAP interaction.
+ * @param string $uid
+ * @return Access instance of Access for LDAP interaction
+ */
+ public function getLDAPAccess($uid) {
+ return $this->handleRequest($uid, 'getLDAPAccess', array($uid));
+ }
+
+ /**
+ * Return a new LDAP connection for the specified user.
+ * The connection needs to be closed manually.
+ * @param string $uid
+ * @return resource of the LDAP connection
+ */
+ public function getNewLDAPConnection($uid) {
+ return $this->handleRequest($uid, 'getNewLDAPConnection', array($uid));
+ }
}
diff --git a/apps/user_ldap/tests/AccessTest.php b/apps/user_ldap/tests/AccessTest.php
index f96813ba711..2fddafa214b 100644
--- a/apps/user_ldap/tests/AccessTest.php
+++ b/apps/user_ldap/tests/AccessTest.php
@@ -60,21 +60,22 @@ class AccessTest extends \Test\TestCase {
$this->getMock('\OCP\Image'),
$this->getMock('\OCP\IDBConnection'),
$this->getMock('\OCP\IUserManager')));
+ $helper = new \OCA\User_LDAP\Helper();
- return array($lw, $connector, $um);
+ return array($lw, $connector, $um, $helper);
}
public function testEscapeFilterPartValidChars() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$input = 'okay';
$this->assertTrue($input === $access->escapeFilterPart($input));
}
public function testEscapeFilterPartEscapeWildcard() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$input = '*';
$expected = '\\\\*';
@@ -82,8 +83,8 @@ class AccessTest extends \Test\TestCase {
}
public function testEscapeFilterPartEscapeWildcard2() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$input = 'foo*bar';
$expected = 'foo\\\\*bar';
@@ -92,8 +93,8 @@ class AccessTest extends \Test\TestCase {
/** @dataProvider convertSID2StrSuccessData */
public function testConvertSID2StrSuccess(array $sidArray, $sidExpected) {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$sidBinary = implode('', $sidArray);
$this->assertSame($sidExpected, $access->convertSID2Str($sidBinary));
@@ -127,8 +128,8 @@ class AccessTest extends \Test\TestCase {
}
public function testConvertSID2StrInputError() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$sidIllegal = 'foobar';
$sidExpected = '';
@@ -137,8 +138,8 @@ class AccessTest extends \Test\TestCase {
}
public function testGetDomainDNFromDNSuccess() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$inputDN = 'uid=zaphod,cn=foobar,dc=my,dc=server,dc=com';
$domainDN = 'dc=my,dc=server,dc=com';
@@ -152,8 +153,8 @@ class AccessTest extends \Test\TestCase {
}
public function testGetDomainDNFromDNError() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$inputDN = 'foobar';
$expected = '';
@@ -187,8 +188,8 @@ class AccessTest extends \Test\TestCase {
}
public function testStringResemblesDN() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$cases = $this->getResemblesDNInputData();
@@ -208,9 +209,9 @@ class AccessTest extends \Test\TestCase {
}
public function testStringResemblesDNLDAPmod() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
$lw = new \OCA\User_LDAP\LDAP();
- $access = new Access($con, $lw, $um);
+ $access = new Access($con, $lw, $um, $helper);
if(!function_exists('ldap_explode_dn')) {
$this->markTestSkipped('LDAP Module not available');
@@ -224,8 +225,8 @@ class AccessTest extends \Test\TestCase {
}
public function testCacheUserHome() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$con->expects($this->once())
->method('writeToCache');
@@ -234,8 +235,8 @@ class AccessTest extends \Test\TestCase {
}
public function testBatchApplyUserAttributes() {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
- $access = new Access($con, $lw, $um);
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
+ $access = new Access($con, $lw, $um, $helper);
$mapperMock = $this->getMockBuilder('\OCA\User_LDAP\Mapping\UserMapping')
->disableOriginalConstructor()
->getMock();
@@ -294,7 +295,7 @@ class AccessTest extends \Test\TestCase {
* @dataProvider dNAttributeProvider
*/
public function testSanitizeDN($attribute) {
- list($lw, $con, $um) = $this->getConnectorAndLdapMock();
+ list($lw, $con, $um, $helper) = $this->getConnectorAndLdapMock();
$dnFromServer = 'cn=Mixed Cases,ou=Are Sufficient To,ou=Test,dc=example,dc=org';
@@ -309,7 +310,7 @@ class AccessTest extends \Test\TestCase {
$attribute => array('count' => 1, $dnFromServer)
)));
- $access = new Access($con, $lw, $um);
+ $access = new Access($con, $lw, $um, $helper);
$values = $access->readAttribute('uid=whoever,dc=example,dc=org', $attribute);
$this->assertSame($values[0], strtolower($dnFromServer));
}
diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php
index 71120bdb838..83ec2dedf22 100644
--- a/apps/user_ldap/tests/Group_LDAPTest.php
+++ b/apps/user_ldap/tests/Group_LDAPTest.php
@@ -55,9 +55,10 @@ class Group_LDAPTest extends \Test\TestCase {
$um = $this->getMockBuilder('\OCA\User_LDAP\User\Manager')
->disableOriginalConstructor()
->getMock();
+ $helper = new \OCA\User_LDAP\Helper();
$access = $this->getMock('\OCA\User_LDAP\Access',
$accMethods,
- array($connector, $lw, $um));
+ array($connector, $lw, $um, $helper));
$access->expects($this->any())
->method('getConnection')
diff --git a/apps/user_ldap/tests/Integration/AbstractIntegrationTest.php b/apps/user_ldap/tests/Integration/AbstractIntegrationTest.php
index 4ec36617c14..bd56494eac0 100644
--- a/apps/user_ldap/tests/Integration/AbstractIntegrationTest.php
+++ b/apps/user_ldap/tests/Integration/AbstractIntegrationTest.php
@@ -26,6 +26,7 @@ namespace OCA\User_LDAP\Tests\Integration;
use OCA\User_LDAP\Access;
use OCA\User_LDAP\Connection;
use OCA\User_LDAP\LDAP;
+use OCA\User_LDAP\Helper;
use OCA\User_LDAP\User\Manager;
abstract class AbstractIntegrationTest {
@@ -40,6 +41,9 @@ abstract class AbstractIntegrationTest {
/** @var Manager */
protected $userManager;
+
+ /** @var Helper */
+ protected $helper;
/** @var string */
protected $base;
@@ -65,6 +69,7 @@ abstract class AbstractIntegrationTest {
$this->initLDAPWrapper();
$this->initConnection();
$this->initUserManager();
+ $this->initHelper();
$this->initAccess();
}
@@ -103,12 +108,19 @@ abstract class AbstractIntegrationTest {
protected function initUserManager() {
$this->userManager = new FakeManager();
}
+
+ /**
+ * initializes the test Helper
+ */
+ protected function initHelper() {
+ $this->helper = new Helper();
+ }
/**
* initializes the Access test instance
*/
protected function initAccess() {
- $this->access = new Access($this->connection, $this->ldap, $this->userManager);
+ $this->access = new Access($this->connection, $this->ldap, $this->userManager, $this->helper);
}
/**
diff --git a/apps/user_ldap/tests/LDAPProviderTest.php b/apps/user_ldap/tests/LDAPProviderTest.php
new file mode 100644
index 00000000000..22dfb61e5fb
--- /dev/null
+++ b/apps/user_ldap/tests/LDAPProviderTest.php
@@ -0,0 +1,338 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\Tests;
+
+use OCP\IServerContainer;
+use OCA\User_LDAP\IUserLDAP;
+
+/**
+ * Class LDAPProviderTest
+ *
+ * @group DB
+ *
+ * @package OCA\User_LDAP\Tests
+ */
+class LDAPProviderTest extends \Test\TestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ }
+
+ private function getServerMock(IUserLDAP $backend) {
+ $server = $this->getMockBuilder('OC\Server')
+ ->setMethods(['getUserManager', 'getBackends'])
+ ->setConstructorArgs(['', new \OC\Config(\OC::$configDir)])
+ ->getMock();
+ $server->expects($this->at(1))
+ ->method('getBackends')
+ ->willReturn([$backend]);
+ $server->expects($this->any())
+ ->method($this->anything())
+ ->willReturnSelf();
+
+ return $server;
+ }
+
+ private function getLDAPProvider(IServerContainer $serverContainer) {
+ $factory = new \OCA\User_LDAP\LDAPProviderFactory($serverContainer);
+ return $factory->getLDAPProvider();
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage User id not found in LDAP
+ */
+ public function testGetUserDNUserIDNotFound() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())->method('userExists')->willReturn(false);
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->getUserDN('nonexisting_user');
+ }
+
+ public function testGetUserDN() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists', 'getLDAPAccess', 'username2dn'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->at(0))
+ ->method('userExists')
+ ->willReturn(true);
+ $backend->expects($this->at(2))
+ ->method('username2dn')
+ ->willReturn('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org');
+ $backend->expects($this->any())
+ ->method($this->anything())
+ ->willReturnSelf();
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertEquals('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org',
+ $ldapProvider->getUserDN('existing_user'));
+ }
+
+ public function testGetUserName() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['dn2UserName'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())
+ ->method('dn2UserName')
+ ->willReturn('existing_user');
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertEquals('existing_user',
+ $ldapProvider->getUserName('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org'));
+ }
+
+ public function testDNasBaseParameter() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods([])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $server = $this->getServerMock($backend);
+
+ $helper = new \OCA\User_LDAP\Helper();
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertEquals(
+ $helper->DNasBaseParameter('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org'),
+ $ldapProvider->DNasBaseParameter('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org'));
+ }
+
+ public function testSanitizeDN() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods([])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $server = $this->getServerMock($backend);
+
+ $helper = new \OCA\User_LDAP\Helper();
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertEquals(
+ $helper->sanitizeDN('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org'),
+ $ldapProvider->sanitizeDN('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org'));
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage User id not found in LDAP
+ */
+ public function testGetLDAPConnectionUserIDNotFound() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())->method('userExists')->willReturn(false);
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->getLDAPConnection('nonexisting_user');
+ }
+
+ public function testGetLDAPConnection() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists', 'getNewLDAPConnection'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())
+ ->method('userExists')
+ ->willReturn(true);
+ $backend->expects($this->any())
+ ->method('getNewLDAPConnection')
+ ->willReturn(true);
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertTrue($ldapProvider->getLDAPConnection('existing_user'));
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage User id not found in LDAP
+ */
+ public function testGetLDAPBaseUsersUserIDNotFound() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())->method('userExists')->willReturn(false);
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->getLDAPBaseUsers('nonexisting_user');
+ }
+
+ public function testGetLDAPBaseUsers() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists', 'getLDAPAccess', 'getConnection', 'getConfiguration'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->at(0))
+ ->method('userExists')
+ ->willReturn(true);
+ $backend->expects($this->at(3))
+ ->method('getConfiguration')
+ ->willReturn(array('ldap_base_users'=>'ou=users,dc=example,dc=org'));
+ $backend->expects($this->any())
+ ->method($this->anything())
+ ->willReturnSelf();
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertEquals('ou=users,dc=example,dc=org', $ldapProvider->getLDAPBaseUsers('existing_user'));
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage User id not found in LDAP
+ */
+ public function testGetLDAPBaseGroupsUserIDNotFound() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())->method('userExists')->willReturn(false);
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->getLDAPBaseGroups('nonexisting_user');
+ }
+
+ public function testGetLDAPBaseGroups() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists', 'getLDAPAccess', 'getConnection', 'getConfiguration'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->at(0))
+ ->method('userExists')
+ ->willReturn(true);
+ $backend->expects($this->at(3))
+ ->method('getConfiguration')
+ ->willReturn(array('ldap_base_groups'=>'ou=groups,dc=example,dc=org'));
+ $backend->expects($this->any())
+ ->method($this->anything())
+ ->willReturnSelf();
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertEquals('ou=groups,dc=example,dc=org', $ldapProvider->getLDAPBaseGroups('existing_user'));
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage User id not found in LDAP
+ */
+ public function testClearCacheUserIDNotFound() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())->method('userExists')->willReturn(false);
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->clearCache('nonexisting_user');
+ }
+
+ public function testClearCache() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['userExists', 'getLDAPAccess', 'getConnection', 'clearCache'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->at(0))
+ ->method('userExists')
+ ->willReturn(true);
+ $backend->expects($this->at(3))
+ ->method('clearCache')
+ ->willReturn(true);
+ $backend->expects($this->any())
+ ->method($this->anything())
+ ->willReturnSelf();
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->clearCache('existing_user');
+ $this->assertTrue(TRUE);
+ }
+
+ public function testDnExists() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods(['dn2UserName'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backend->expects($this->any())
+ ->method('dn2UserName')
+ ->willReturn('existing_user');
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $this->assertTrue($ldapProvider->dnExists('cn=existing_user,ou=Are Sufficient To,ou=Test,dc=example,dc=org'));
+ }
+
+ public function testFlagRecord() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods([])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->flagRecord('existing_user');
+ $this->assertTrue(TRUE);
+ }
+
+ public function testUnflagRecord() {
+ $backend = $this->getMockBuilder('OCA\User_LDAP\User_LDAP')
+ ->setMethods([])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $server = $this->getServerMock($backend);
+
+ $ldapProvider = $this->getLDAPProvider($server);
+ $ldapProvider->unflagRecord('existing_user');
+ $this->assertTrue(TRUE);
+ }
+}
diff --git a/apps/user_ldap/tests/User/UserTest.php b/apps/user_ldap/tests/User/UserTest.php
index cf1514009d8..d9e43dee047 100644
--- a/apps/user_ldap/tests/User/UserTest.php
+++ b/apps/user_ldap/tests/User/UserTest.php
@@ -71,8 +71,9 @@ class UserTest extends \Test\TestCase {
$umMethods, array($cfMock, $fsMock, $logMock, $avaMgr, $im, $dbc, $userMgr));
$connector = $this->getMock('\OCA\User_LDAP\Connection',
$conMethods, array($lw, null, null));
+ $helper = new \OCA\User_LDAP\Helper();
$access = $this->getMock('\OCA\User_LDAP\Access',
- $accMethods, array($connector, $lw, $um));
+ $accMethods, array($connector, $lw, $um, $helper));
return array($access, $connector);
}
diff --git a/apps/user_ldap/tests/User_LDAPTest.php b/apps/user_ldap/tests/User_LDAPTest.php
index ae86e3cf3a2..47e789142e5 100644
--- a/apps/user_ldap/tests/User_LDAPTest.php
+++ b/apps/user_ldap/tests/User_LDAPTest.php
@@ -93,9 +93,11 @@ class User_LDAPTest extends \Test\TestCase {
->method('getDeletedUser')
->will($this->returnValue($offlineUser));
+ $helper = new \OCA\User_LDAP\Helper();
+
$access = $this->getMock('\OCA\User_LDAP\Access',
$accMethods,
- array($connector, $lw, $um));
+ array($connector, $lw, $um, $helper));
$um->setLdapAccess($access);
diff --git a/apps/user_ldap/tests/WizardTest.php b/apps/user_ldap/tests/WizardTest.php
index cc110c6ee0d..e82cbcfc82a 100644
--- a/apps/user_ldap/tests/WizardTest.php
+++ b/apps/user_ldap/tests/WizardTest.php
@@ -69,8 +69,9 @@ class WizardTest extends \Test\TestCase {
$um = $this->getMockBuilder('\OCA\User_LDAP\User\Manager')
->disableOriginalConstructor()
->getMock();
+ $helper = new \OCA\User_LDAP\Helper();
$access = $this->getMock('\OCA\User_LDAP\Access',
- $accMethods, array($connector, $lw, $um));
+ $accMethods, array($connector, $lw, $um, $helper));
return array(new Wizard($conf, $lw, $access), $conf, $lw, $access);
}
diff --git a/core/css/inputs.css b/core/css/inputs.css
index cad627ac311..b58310a5c58 100644
--- a/core/css/inputs.css
+++ b/core/css/inputs.css
@@ -93,7 +93,6 @@ input[type="checkbox"].checkbox + label:before {
vertical-align: middle;
background: url('../img/actions/checkbox.svg') left top no-repeat;
- opacity: 0.7;
}
input[type="checkbox"].checkbox:disabled +label:before { opacity: .6; }
@@ -167,7 +166,6 @@ input[type="radio"].radio + label:before {
vertical-align: middle;
background: url('../img/actions/radio.svg') left top no-repeat;
- opacity: 0.7;
}
input[type="radio"].radio:checked + label:before {
@@ -187,7 +185,7 @@ input[type="radio"].radio--white + label:before {
}
input[type="radio"].radio--white:checked + label:before {
- background-image: url('../img/actions/radio-checked.svg');
+ background-image: url('../img/actions/radio-checked-white.svg');
}
input[type="radio"].radio--white:disabled + label:before {
diff --git a/core/img/actions/checkmark-white.svg b/core/img/actions/checkmark-white.svg
index 964624a9ce4..b294cb02941 100644
--- a/core/img/actions/checkmark-white.svg
+++ b/core/img/actions/checkmark-white.svg
@@ -1,4 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="16px" viewBox="-0.5 -0.5 16 16" width="16px" version="1.1" y="0px" x="0px" xmlns:cc="http://creativecommons.org/ns#" enable-background="new -0.5 -0.5 16 16" overflow="visible" xmlns:dc="http://purl.org/dc/elements/1.1/">
-<path fill="#fff" transform="translate(-.5 -.5)" d="m12.438 3.6875c-0.363 0-0.726 0.1314-1 0.4063l-4.5005 4.5-1.9687-2c-0.5498-0.5484-1.4489-0.5498-2 0l-0.5 0.5c-0.5512 0.5496-0.5512 1.4502 0 2l2.9687 2.9682c0.0063 0.007-0.0065 0.025 0 0.032l0.5 0.5c0.5497 0.55 1.4503 0.55 2 0l0.5-0.5 0.1875-0.219 5.313-5.2812c0.549-0.5498 0.549-1.4503 0-2l-0.5-0.5c-0.275-0.2749-0.638-0.4063-1-0.4063z"/>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="-0.5 -0.5 16 16" width="16" overflow="visible"><path d="M6.089 12.5l-4.95-4.95 1.414-1.414L6.09 9.671l6.345-6.383 1.433 1.434z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/core/img/actions/radio-checked-white.svg b/core/img/actions/radio-checked-white.svg
new file mode 100644
index 00000000000..d024c91ff19
--- /dev/null
+++ b/core/img/actions/radio-checked-white.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/core/l10n/pt_BR.js b/core/l10n/pt_BR.js
index dcb2dc4bad9..4df85eeb398 100644
--- a/core/l10n/pt_BR.js
+++ b/core/l10n/pt_BR.js
@@ -102,8 +102,8 @@ OC.L10N.register(
"Saving..." : "Salvando...",
"Dismiss" : "Dispensar",
"seconds ago" : "segundos atrás",
- "The link to reset your password has been sent to your email. If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator." : "O link para redefinir sua senha foi enviada para o seu e-mail. Se você não recebê-lo dentro de um período razoável de tempo, verifique suas pastas de spam/lixo. <br> Se ele não estiver lá, pergunte ao administrador do local.",
- "Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset.<br />If you are not sure what to do, please contact your administrator before you continue. <br />Do you really want to continue?" : "Seus arquivos são criptografados. Se você não ativou a chave de recuperação, não haverá maneira de obter seus dados de volta após a sua senha ser redefinida. <br/> Se você não tem certeza do que fazer, por favor, contate o administrador antes de continuar. <br/> Você realmente deseja continuar?",
+ "The link to reset your password has been sent to your email. If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator." : "O link para redefinir sua senha foi enviado para seu e-mail. Se você não recebê-lo dentro de um período razoável de tempo, verifique suas pastas de spam/lixo.<br> Se ele não estiver lá, pergunte ao administrador do local.",
+ "Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset.<br />If you are not sure what to do, please contact your administrator before you continue. <br />Do you really want to continue?" : "Seus arquivos são criptografados. Se você não ativou a chave de recuperação, não haverá maneira de obter seus dados de volta após a sua senha ser redefinida.<br/>Se não tiver certeza do que deve fazer, contate o administrador antes de continuar. <br/>Deseja realmente continuar?",
"I know what I'm doing" : "Eu sei o que estou fazendo",
"Password can not be changed. Please contact your administrator." : "A senha não pode ser alterada. Por favor, contate o administrador.",
"No" : "Não",
@@ -223,7 +223,7 @@ OC.L10N.register(
"Updating to {version}" : "Atualizando para {version}",
"An error occurred." : "Ocorreu um erro.",
"Please reload the page." : "Por favor recarregue a página",
- "The update was unsuccessful. For more information <a href=\"{url}\">check our forum post</a> covering this issue." : "A atualização não foi bem sucedida. Para mais informações <a href=\"{url}\">verificar o nosso post no fórum</a> que abrange esta questão.",
+ "The update was unsuccessful. For more information <a href=\"{url}\">check our forum post</a> covering this issue." : "A atualização não foi realizada com sucesso. Para mais informações <a href=\"{url}\">verifique o nosso post no fórum</a> que abrange esta questão.",
"The update was unsuccessful. Please report this issue to the <a href=\"https://github.com/nextcloud/server/issues\" target=\"_blank\">Nextcloud community</a>." : "Atualizado com sucesso. Por favor, informe este problema para a <a href=\"https://github.com/nextcloud/server/issues\" target=\"_blank\">comunidade Nextcloud</a>.",
"The update was successful. There were warnings." : "A atualização foi bem sucedida. Havia advertências.",
"The update was successful. Redirecting you to Nextcloud now." : "Atualizado com sucesso. Redirecionando para Nextcloud.",
@@ -295,7 +295,7 @@ OC.L10N.register(
"Alternative Logins" : "Logins Alternativos",
"Use the following link to reset your password: {link}" : "Use o seguinte link para redefinir sua senha: {link}",
"New password" : "Nova senha",
- "New Password" : "Nova Senha",
+ "New Password" : "Nova senha",
"Reset password" : "Redefinir senha",
"This Nextcloud instance is currently in single user mode." : "Nesta instância Nextcloud está em modo de usuário único.",
"This means only administrators can use the instance." : "Isso significa que apenas os administradores podem usar esta instância.",
diff --git a/core/l10n/pt_BR.json b/core/l10n/pt_BR.json
index 5b0c0f9e14d..3e0e63db6a3 100644
--- a/core/l10n/pt_BR.json
+++ b/core/l10n/pt_BR.json
@@ -100,8 +100,8 @@
"Saving..." : "Salvando...",
"Dismiss" : "Dispensar",
"seconds ago" : "segundos atrás",
- "The link to reset your password has been sent to your email. If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator." : "O link para redefinir sua senha foi enviada para o seu e-mail. Se você não recebê-lo dentro de um período razoável de tempo, verifique suas pastas de spam/lixo. <br> Se ele não estiver lá, pergunte ao administrador do local.",
- "Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset.<br />If you are not sure what to do, please contact your administrator before you continue. <br />Do you really want to continue?" : "Seus arquivos são criptografados. Se você não ativou a chave de recuperação, não haverá maneira de obter seus dados de volta após a sua senha ser redefinida. <br/> Se você não tem certeza do que fazer, por favor, contate o administrador antes de continuar. <br/> Você realmente deseja continuar?",
+ "The link to reset your password has been sent to your email. If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator." : "O link para redefinir sua senha foi enviado para seu e-mail. Se você não recebê-lo dentro de um período razoável de tempo, verifique suas pastas de spam/lixo.<br> Se ele não estiver lá, pergunte ao administrador do local.",
+ "Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset.<br />If you are not sure what to do, please contact your administrator before you continue. <br />Do you really want to continue?" : "Seus arquivos são criptografados. Se você não ativou a chave de recuperação, não haverá maneira de obter seus dados de volta após a sua senha ser redefinida.<br/>Se não tiver certeza do que deve fazer, contate o administrador antes de continuar. <br/>Deseja realmente continuar?",
"I know what I'm doing" : "Eu sei o que estou fazendo",
"Password can not be changed. Please contact your administrator." : "A senha não pode ser alterada. Por favor, contate o administrador.",
"No" : "Não",
@@ -221,7 +221,7 @@
"Updating to {version}" : "Atualizando para {version}",
"An error occurred." : "Ocorreu um erro.",
"Please reload the page." : "Por favor recarregue a página",
- "The update was unsuccessful. For more information <a href=\"{url}\">check our forum post</a> covering this issue." : "A atualização não foi bem sucedida. Para mais informações <a href=\"{url}\">verificar o nosso post no fórum</a> que abrange esta questão.",
+ "The update was unsuccessful. For more information <a href=\"{url}\">check our forum post</a> covering this issue." : "A atualização não foi realizada com sucesso. Para mais informações <a href=\"{url}\">verifique o nosso post no fórum</a> que abrange esta questão.",
"The update was unsuccessful. Please report this issue to the <a href=\"https://github.com/nextcloud/server/issues\" target=\"_blank\">Nextcloud community</a>." : "Atualizado com sucesso. Por favor, informe este problema para a <a href=\"https://github.com/nextcloud/server/issues\" target=\"_blank\">comunidade Nextcloud</a>.",
"The update was successful. There were warnings." : "A atualização foi bem sucedida. Havia advertências.",
"The update was successful. Redirecting you to Nextcloud now." : "Atualizado com sucesso. Redirecionando para Nextcloud.",
@@ -293,7 +293,7 @@
"Alternative Logins" : "Logins Alternativos",
"Use the following link to reset your password: {link}" : "Use o seguinte link para redefinir sua senha: {link}",
"New password" : "Nova senha",
- "New Password" : "Nova Senha",
+ "New Password" : "Nova senha",
"Reset password" : "Redefinir senha",
"This Nextcloud instance is currently in single user mode." : "Nesta instância Nextcloud está em modo de usuário único.",
"This means only administrators can use the instance." : "Isso significa que apenas os administradores podem usar esta instância.",
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 41092ceae6e..fd6ecdc297e 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -21,6 +21,7 @@
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Thomas Tanghus <thomas@tanghus.net>
* @author Vincent Petry <pvince81@owncloud.com>
+ * @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@@ -584,6 +585,16 @@ class Server extends ServerContainer implements IServerContainer {
$this->getLogger()
);
});
+ $this->registerService('LDAPProvider', function(Server $c) {
+ $config = $c->getConfig();
+ $factoryClass = $config->getSystemValue('ldapProviderFactory', null);
+ if(is_null($factoryClass)) {
+ throw new \Exception('ldapProviderFactory not set');
+ }
+ /** @var \OCP\LDAP\ILDAPProviderFactory $factory */
+ $factory = new $factoryClass($this);
+ return $factory->getLDAPProvider();
+ });
$this->registerService('LockingProvider', function (Server $c) {
$ini = $c->getIniWrapper();
$config = $c->getConfig();
@@ -1406,4 +1417,12 @@ class Server extends ServerContainer implements IServerContainer {
return $this->query('ShareManager');
}
+ /**
+ * Returns the LDAP Provider
+ *
+ * @return \OCP\LDAP\ILDAPProvider
+ */
+ public function getLDAPProvider() {
+ return $this->query('LDAPProvider');
+ }
}
diff --git a/lib/public/LDAP/IDeletionFlagSupport.php b/lib/public/LDAP/IDeletionFlagSupport.php
new file mode 100644
index 00000000000..5f7d3909195
--- /dev/null
+++ b/lib/public/LDAP/IDeletionFlagSupport.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\LDAP;
+
+/**
+ * Interface IDeletionFlagSupport
+ *
+ * @package OCP\LDAP
+ * @since 9.2.0
+ */
+interface IDeletionFlagSupport {
+ /**
+ * Flag record for deletion.
+ * @param string $uid user id
+ * @since 9.2.0
+ */
+ public function flagRecord($uid);
+
+ /**
+ * Unflag record for deletion.
+ * @param string $uid user id
+ * @since 9.2.0
+ */
+ public function unflagRecord($uid);
+}
diff --git a/lib/public/LDAP/ILDAPProvider.php b/lib/public/LDAP/ILDAPProvider.php
new file mode 100644
index 00000000000..473afb13885
--- /dev/null
+++ b/lib/public/LDAP/ILDAPProvider.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\LDAP;
+
+/**
+ * Interface ILDAPProvider
+ *
+ * @package OCP\LDAP
+ * @since 9.2.0
+ */
+interface ILDAPProvider {
+ /**
+ * Translate a user id to LDAP DN.
+ * @param string $uid user id
+ * @return string
+ * @since 9.2.0
+ */
+ public function getUserDN($uid);
+
+ /**
+ * Translate a LDAP DN to an internal user name.
+ * @param string $dn LDAP DN
+ * @return string with the internal user name
+ * @throws \Exception if translation was unsuccessful
+ * @since 9.2.0
+ */
+ public function getUserName($dn);
+
+ /**
+ * Convert a stored DN so it can be used as base parameter for LDAP queries.
+ * @param string $dn the DN
+ * @return string
+ * @since 9.2.0
+ */
+ public function DNasBaseParameter($dn);
+
+ /**
+ * Sanitize a DN received from the LDAP server.
+ * @param array $dn the DN in question
+ * @return array the sanitized DN
+ * @since 9.2.0
+ */
+ public function sanitizeDN($dn);
+
+ /**
+ * Return a new LDAP connection resource for the specified user.
+ * @param string $uid user id
+ * @return resource of the LDAP connection
+ * @since 9.2.0
+ */
+ public function getLDAPConnection($uid);
+
+ /**
+ * Get the LDAP base for users.
+ * @param string $uid user id
+ * @return string the base for users
+ * @throws \Exception if user id was not found in LDAP
+ * @since 9.2.0
+ */
+ public function getLDAPBaseUsers($uid);
+
+ /**
+ * Get the LDAP base for groups.
+ * @param string $uid user id
+ * @return string the base for groups
+ * @throws \Exception if user id was not found in LDAP
+ * @since 9.2.0
+ */
+ public function getLDAPBaseGroups($uid);
+
+ /**
+ * Check whether a LDAP DN exists
+ * @param string $dn LDAP DN
+ * @return bool whether the DN exists
+ * @since 9.2.0
+ */
+ public function dnExists($dn);
+
+ /**
+ * Clear the cache if a cache is used, otherwise do nothing.
+ * @param string $uid user id
+ * @since 9.2.0
+ */
+ public function clearCache($uid);
+}
diff --git a/lib/public/LDAP/ILDAPProviderFactory.php b/lib/public/LDAP/ILDAPProviderFactory.php
new file mode 100644
index 00000000000..99e7b8d27ea
--- /dev/null
+++ b/lib/public/LDAP/ILDAPProviderFactory.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2016, Roger Szabo (roger.szabo@web.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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\LDAP;
+
+use OCP\IServerContainer;
+
+/**
+ * Interface ILDAPProviderFactory
+ *
+ * This class is responsible for instantiating and returning an ILDAPProvider
+ * instance.
+ *
+ * @package OCP\LDAP
+ * @since 9.2.0
+ */
+interface ILDAPProviderFactory {
+
+ /**
+ * Constructor for the LDAP provider factory
+ *
+ * @param IServerContainer $serverContainer server container
+ * @since 9.2.0
+ */
+ public function __construct(IServerContainer $serverContainer);
+
+ /**
+ * creates and returns an instance of the ILDAPProvider
+ *
+ * @return ILDAPProvider
+ * @since 9.2.0
+ */
+ public function getLDAPProvider();
+}
diff --git a/settings/l10n/es.js b/settings/l10n/es.js
index d626c2b0be1..290224e24af 100644
--- a/settings/l10n/es.js
+++ b/settings/l10n/es.js
@@ -132,7 +132,7 @@ OC.L10N.register(
"Personal info" : "Información personal",
"Sessions" : "Sesiones",
"App passwords" : "Contraseñas de la app",
- "Sync clients" : "Sincronizar clientes",
+ "Sync clients" : "Clientes de sincronización",
"Everything (fatal issues, errors, warnings, info, debug)" : "Todo (Información, Avisos, Errores, debug y problemas fatales)",
"Info, warnings, errors and fatal issues" : "Información, Avisos, Errores y problemas fatales",
"Warnings, errors and fatal issues" : "Advertencias, errores y problemas fatales",
diff --git a/settings/l10n/es.json b/settings/l10n/es.json
index 9c23f11ba85..f510da17cd2 100644
--- a/settings/l10n/es.json
+++ b/settings/l10n/es.json
@@ -130,7 +130,7 @@
"Personal info" : "Información personal",
"Sessions" : "Sesiones",
"App passwords" : "Contraseñas de la app",
- "Sync clients" : "Sincronizar clientes",
+ "Sync clients" : "Clientes de sincronización",
"Everything (fatal issues, errors, warnings, info, debug)" : "Todo (Información, Avisos, Errores, debug y problemas fatales)",
"Info, warnings, errors and fatal issues" : "Información, Avisos, Errores y problemas fatales",
"Warnings, errors and fatal issues" : "Advertencias, errores y problemas fatales",
diff --git a/settings/l10n/pt_BR.js b/settings/l10n/pt_BR.js
index 53cad4b64ff..06662dabdbb 100644
--- a/settings/l10n/pt_BR.js
+++ b/settings/l10n/pt_BR.js
@@ -186,7 +186,7 @@ OC.L10N.register(
"Enable server-side encryption" : "Habilitar a Criptografia do Lado do Servidor",
"Please read carefully before activating server-side encryption: " : "Por favor, leia com atenção antes de ativar a criptografia do lado do servidor:",
"Once encryption is enabled, all files uploaded to the server from that point forward will be encrypted at rest on the server. It will only be possible to disable encryption at a later date if the active encryption module supports that function, and all pre-conditions (e.g. setting a recover key) are met." : "Uma vez que a criptografia é ativada, todos os arquivos carregados para o servidor a partir desse ponto em diante serão criptografados e estarão disponíveis no servidor. Só será possível desativar a criptografia em uma data posterior, se o módulo de criptografia ativo suporta essa função, e todas as pré-condições sejam cumpridas(por exemplo, definindo uma chave de recuperação).",
- "Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "Encriptação, por sí só, não garante segurança do sistema. Por favor, veja a documentação para mais informações sobe como o aplicativo de encriptação funciona e os casos de uso suportados.",
+ "Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "Encriptação, por si só, não garante segurança do sistema. Por favor, veja a documentação para mais informações sobe como o aplicativo de encriptação funciona e os casos de uso suportados.",
"Be aware that encryption always increases the file size." : "Esteja ciente de que a criptografia sempre aumenta o tamanho do arquivo.",
"It is always good to create regular backups of your data, in case of encryption make sure to backup the encryption keys along with your data." : "É sempre bom criar backups regulares dos seus dados, em caso de criptografia certifique-se de fazer backup das chaves de criptografia, juntamente com os seus dados.",
"This is the final warning: Do you really want to enable encryption?" : "Este é o aviso final: Você realmente quer ativar a criptografia?",
@@ -248,7 +248,7 @@ OC.L10N.register(
"SSL Root Certificates" : "Certificados Raiz SSL",
"Common Name" : "Nome",
"Valid until" : "Válido até",
- "Issued By" : "Emitido Por",
+ "Issued By" : "Emitido por",
"Valid until %s" : "Válido até %s",
"Import root certificate" : "Importar certificado raiz",
"Hey there,<br><br>just letting you know that you now have an %s account.<br><br>Your username: %s<br>Access it: <a href=\"%s\">%s</a><br><br>" : "Olá,<br><br>somente para lembrar que agora você tem uma conta %s.<br><br>Seu nome de usuário é: %s<br>Acesse em: <a href=\"%s\">%s</a><br><br>",
diff --git a/settings/l10n/pt_BR.json b/settings/l10n/pt_BR.json
index 09bceba7d05..d4e8b1353be 100644
--- a/settings/l10n/pt_BR.json
+++ b/settings/l10n/pt_BR.json
@@ -184,7 +184,7 @@
"Enable server-side encryption" : "Habilitar a Criptografia do Lado do Servidor",
"Please read carefully before activating server-side encryption: " : "Por favor, leia com atenção antes de ativar a criptografia do lado do servidor:",
"Once encryption is enabled, all files uploaded to the server from that point forward will be encrypted at rest on the server. It will only be possible to disable encryption at a later date if the active encryption module supports that function, and all pre-conditions (e.g. setting a recover key) are met." : "Uma vez que a criptografia é ativada, todos os arquivos carregados para o servidor a partir desse ponto em diante serão criptografados e estarão disponíveis no servidor. Só será possível desativar a criptografia em uma data posterior, se o módulo de criptografia ativo suporta essa função, e todas as pré-condições sejam cumpridas(por exemplo, definindo uma chave de recuperação).",
- "Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "Encriptação, por sí só, não garante segurança do sistema. Por favor, veja a documentação para mais informações sobe como o aplicativo de encriptação funciona e os casos de uso suportados.",
+ "Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "Encriptação, por si só, não garante segurança do sistema. Por favor, veja a documentação para mais informações sobe como o aplicativo de encriptação funciona e os casos de uso suportados.",
"Be aware that encryption always increases the file size." : "Esteja ciente de que a criptografia sempre aumenta o tamanho do arquivo.",
"It is always good to create regular backups of your data, in case of encryption make sure to backup the encryption keys along with your data." : "É sempre bom criar backups regulares dos seus dados, em caso de criptografia certifique-se de fazer backup das chaves de criptografia, juntamente com os seus dados.",
"This is the final warning: Do you really want to enable encryption?" : "Este é o aviso final: Você realmente quer ativar a criptografia?",
@@ -246,7 +246,7 @@
"SSL Root Certificates" : "Certificados Raiz SSL",
"Common Name" : "Nome",
"Valid until" : "Válido até",
- "Issued By" : "Emitido Por",
+ "Issued By" : "Emitido por",
"Valid until %s" : "Válido até %s",
"Import root certificate" : "Importar certificado raiz",
"Hey there,<br><br>just letting you know that you now have an %s account.<br><br>Your username: %s<br>Access it: <a href=\"%s\">%s</a><br><br>" : "Olá,<br><br>somente para lembrar que agora você tem uma conta %s.<br><br>Seu nome de usuário é: %s<br>Acesse em: <a href=\"%s\">%s</a><br><br>",