Commit d03ed522 authored by Thomas Löffler's avatar Thomas Löffler

Merge branch 'develop' into 'master'

Release 06-08-19

See merge request !172
parents 8630693a eb752df6
Pipeline #7419 passed with stages
in 5 minutes and 26 seconds
......@@ -8,6 +8,6 @@
/import-db
/.bgsync*
/config.*.y*ml
/.webImageExtra
/.dbImageExtra
/.webimageExtra
/.dbimageExtra
/*-build/Dockerfile.example
APIVersion: v1.8.0
APIVersion: v1.9.1
name: my.typo3.org
type: typo3
docroot: public
php_version: "7.2"
webserver_type: nginx-fpm
dbimage: drud/ddev-dbserver:v1.8.0-10.1
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
......@@ -15,23 +14,22 @@ nfs_mount_enabled: false
provider: default
hooks:
post-start:
- exec: bash -c "sudo -E apt-get update"
- exec: bash -c "sudo -E apt-get install --yes --no-install-recommends apt-utils"
- exec: bash -c "sudo -E apt-get install --yes php7.2-ldap -o Dpkg::Options::=\"--force-confdef\"
-o Dpkg::Options::=\"--force-confold\" && killall -HUP php-fpm"
- exec: cp private/typo3conf/AdditionalConfiguration.ddev.php private/typo3conf/AdditionalConfiguration.php
- exec: composer install
- exec: npm --prefix private/typo3conf/ext/t3olayout/Build install
- exec: npm --prefix private/typo3conf/ext/t3olayout/Build run-script build
- exec: vendor/bin/typo3cms install:generatepackagestates
- exec: vendor/bin/typo3cms install:extensionsetupifpossible
webimage_extra_packages: [php7.2-ldap]
use_dns_when_possible: true
timezone: Europe/Berlin
# This config.yaml was created with ddev version v1.8.0
# webimage: drud/ddev-webserver:v1.8.0
# dbimage: drud/ddev-dbserver:v1.8.0-10.2
# dbaimage: drud/phpmyadmin:v1.8.0
# bgsyncimage: drud/ddev-bgsync:v1.8.0
# This config.yaml was created with ddev version v1.9.1
# webimage: drud/ddev-webserver:v1.9.0
# dbimage: drud/ddev-dbserver:v1.9.0-10.2
# dbaimage: drud/phpmyadmin:v1.9.0
# bgsyncimage: drud/ddev-bgsync:v1.9.0
# However we do not recommend explicitly wiring these images into the
# config.yaml as they may break future versions of ddev.
# You can update this config.yaml using 'ddev config'.
......@@ -39,7 +37,7 @@ hooks:
# Key features of ddev's config.yaml:
# name: <projectname> # Name of the project, automatically provides
# http://projectname.ddev.local and https://projectname.ddev.local
# http://projectname.ddev.site and https://projectname.ddev.site
# type: <projecttype> # drupal6/7/8, backdrop, typo3, wordpress, php
......@@ -66,8 +64,8 @@ hooks:
# additional_hostnames:
# - somename
# - someothername
# would provide http and https URLs for "somename.ddev.local"
# and "someothername.ddev.local".
# would provide http and https URLs for "somename.ddev.site"
# and "someothername.ddev.site".
# additional_fqdns:
# - example.com
......@@ -115,6 +113,12 @@ hooks:
# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic
# unless explicitly specified.
# phpmyadmin_port: "1000"
# The PHPMyAdmin port can be changed from the default 8036
# mailhog_port: "1001"
# The MailHog port can be changed from the default 8025
# webimage_extra_packages: [php-yaml, php7.3-ldap]
# Extra Debian packages that are needed in the webimage can be added here
# This is ignored if a free-form .ddev/web-build/Dockerfile is provided
......@@ -123,10 +127,26 @@ hooks:
# Extra Debian packages that are needed in the dbimage can be added here
# This is ignored if a free-form .ddev/db-build/Dockerfile is provided
# use_dns_when_possible: true
# If the host has internet access and the domain configured can
# successfully be looked up, DNS will be used for hostname resolution
# instead of editing /etc/hosts
# Defaults to true
# project_tld: ddev.site
# The top-level domain used for project URLs
# The default "ddev.site" allows DNS lookup via a wildcard
# For backward compatibility this can be changed to "ddev.local"
# ngrok_args: --subdomain mysite --auth "user:pass"
# Provide extra flags to the "ngrok http" command, see
# https://ngrok.com/docs#http or run "ngrok http -h"
# provider: default # Currently either "default" or "pantheon"
#
# Many ddev commands can be extended to run tasks after the ddev command is
# executed.
# Many ddev commands can be extended to run tasks before or after the
# ddev command is executed, for example "post-start", "post-import-db",
# "pre-composer", "post-composer"
# See https://ddev.readthedocs.io/en/stable/users/extending-commands/ for more
# information on the commands that can be extended and the tasks you can define
# for them. Example:
......
......@@ -4,4 +4,3 @@ services:
web:
environment:
- TYPO3_CONTEXT=Development
- DEBIAN_FRONTEND=noninteractive
This diff is collapsed.
......@@ -2,7 +2,7 @@ rootPageId: 1
base: 'https://my.typo3.org'
baseVariants:
-
base: 'http://my.typo3.org.ddev.local'
base: 'http://my.typo3.org.ddev.site'
condition: 'applicationContext == "Development"'
-
base: 'https://my-stage.typo3.org'
......
......@@ -159,34 +159,39 @@ class Ldap implements \Psr\Log\LoggerAwareInterface
* to syslog.
*
* @param string $username Username for bind
* @param array $values The password array
* @param array $values The password hashes as array
* @return bool
*/
public function setLdapPasswords($username, $values)
public function setLdapPasswords(string $username, array $values): bool
{
$ret = false;
// Create LDAP connection
if ($this->createLdapConnection() === true) {
// Try to bind as admin
if ($this->ldapBind($this->ldapConnection, $this->ldapBindDn, $this->ldapBindPassword) === true) {
$dn = $this->getDnForUserName($username);
// TODO Check if user exists and create if not exists?
// Finally try to update passwords
$result = $this->updateLdapAttribute($dn, 'userPassword', $values, true);
if ($result === false) {
$this->logger->error(ldap_error($this->ldapConnection));
try {
if ($this->createLdapConnection() === true) {
// Try to bind as admin
if ($this->ldapBind($this->ldapConnection, $this->ldapBindDn, $this->ldapBindPassword) === true) {
$dn = $this->getDnForUserName($username);
// Unset userPassword to remove deprecated password hashes
$unsetPasswordResult = ldap_mod_del($this->ldapConnection, trim($dn), ['userPassword' => []]);
if ($unsetPasswordResult === false) {
$this->logger->error(ldap_error($this->ldapConnection));
} else {
$updatePasswordResult = $this->updateLdapAttribute($dn, 'userPassword', $values, true);
if ($updatePasswordResult === false) {
$this->logger->error(ldap_error($this->ldapConnection));
} else {
return true;
}
}
} else {
$this->logger->error('Unable to bind to LDAP using: ' . ldap_error($this->ldapConnection));
}
} else {
$this->logger->error('Unable to bind to LDAP using: ' . ldap_error($this->ldapConnection));
$this->logger->error('No active LDAP connection available');
}
} else {
$this->logger->error('No active LDAP connection available');
} catch (\Exception $e) {
$this->logger->error('Could not create LDAP connection');
}
return $ret;
return false;
}
/**
......@@ -256,7 +261,7 @@ class Ldap implements \Psr\Log\LoggerAwareInterface
private function createLdapConnection()
{
$ret = false;
$port = intval($this->ldapServerPort);
$port = (int)$this->ldapServerPort;
try {
if (function_exists('ldap_connect')) {
$this->ldapConnection = @ldap_connect($this->ldapServer, ($port > 0 ? $port : null));
......@@ -274,12 +279,12 @@ class Ldap implements \Psr\Log\LoggerAwareInterface
}
}
} else {
throw new \RuntimeException(
throw new \Exception(
'Could not create LDAP connection: ' . ldap_error($this->ldapConnection),
1453993539
);
}
} catch (\RuntimeException $e) {
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
......@@ -506,20 +511,14 @@ class Ldap implements \Psr\Log\LoggerAwareInterface
if ($this->isSaltedPassword($user->getPassword()) === false) {
/** @var \T3o\T3oLdap\Utility\PasswordHashing $passwordHashing */
$passwordHashing = GeneralUtility::makeInstance(\T3o\T3oLdap\Utility\PasswordHashing::class);
$ldapUserArray['userPassword'][] = $passwordHashing->getPasswordHash($user->getPassword(), 'sha1');
$ldapUserArray['userPassword'][] = $passwordHashing->getPasswordHash($user->getPassword(), 'crypt');
$ldapUserArray['userPassword'][] = $passwordHashing->getPasswordHash($user->getPassword(), 'md5');
$ldapUserArray['userPassword'][] = $passwordHashing->getPasswordHash($user->getPassword(), 'crypt_sha512');
}
// if hash fields are filled, store them into ldap user and remove them afterwards
if ($this->isSaltedPassword($user->getPassword()) && ($myProfileUser->getHashMd5() || $myProfileUser->getHashSha1() || $myProfileUser->getHashCrypt())) {
$ldapUserArray['userPassword'][] = $myProfileUser->getHashSha1();
$ldapUserArray['userPassword'][] = $myProfileUser->getHashCrypt();
$ldapUserArray['userPassword'][] = $myProfileUser->getHashMd5();
$myProfileUser->setHashCrypt('');
$myProfileUser->setHashMd5('');
$myProfileUser->setHashSha1('');
if ($this->isSaltedPassword($user->getPassword()) && ($myProfileUser->getHashSha1() || $myProfileUser->getHashCryptSha512())) {
$ldapUserArray['userPassword'][] = $myProfileUser->getHashCryptSha512();
$myProfileUser->setHashCryptSha512('');
$myProfileRepository->update($myProfileUser);
GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class)->persistAll();
}
......@@ -543,19 +542,16 @@ class Ldap implements \Psr\Log\LoggerAwareInterface
* @param string $passwordString The password string
* @return bool
*/
private function isSaltedPassword($passwordString)
private function isSaltedPassword(string $passwordString): bool
{
$ret = false;
if ($passwordString !== '') {
$saltedHashingMethods = \TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory::getRegisteredSaltedHashingMethods();
foreach ($saltedHashingMethods as $saltedHashingMethod) {
/** @var \TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface $method */
$method = GeneralUtility::makeInstance($saltedHashingMethod);
if ($method->isAvailable() && $method->isValidSaltedPW($passwordString)) {
return true;
}
}
$isSalted = false;
try {
GeneralUtility::makeInstance(\TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory::class)->get($passwordString, 'FE');
$isSalted = true;
} catch (\TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException $e) {
}
return $isSalted;
}
/**
......
......@@ -25,24 +25,27 @@ class PasswordHashing
*
* @param string $clearText Cleartext representation of the password
* @param string $algorithm The hashing mechanism
* @param string $salt Optional salt
* @param int $rounds The number of rounds for Crypt Salt
* @return bool|string False on failure or the hashed password as string
*/
public function getPasswordHash($clearText, $algorithm = 'crypt', $salt = 'xy')
public function getPasswordHash($clearText, $algorithm = 'crypt_sha512', $rounds = 5000)
{
$ret = false;
if (trim($clearText) !== '') {
switch ($algorithm) {
case 'sha1':
$passwordHash = sha1($clearText, true);
$ret = '{SHA}' . base64_encode($passwordHash);
break;
case 'md5':
$passwordHash = md5($clearText, true);
$ret = '{MD5}' . base64_encode($passwordHash);
break;
case 'crypt':
$passwordHash = crypt($clearText, $salt);
case 'crypt_sha512':
$characters = array_merge(
range('0', '9'),
range('a', 'z'),
range('A', 'Z'),
['.', '/']
);
$salt = '';
$length = count($characters) - 1;
for ($i = 0; $i < 16; $i++) {
$salt .= $characters[rand(0, $length)];
}
$passwordHash = crypt($clearText, '$6$rounds=' . (int)$rounds . '$' . $salt . '$');
$ret = '{CRYPT}' . $passwordHash;
// no break
default:
......
......@@ -12,6 +12,8 @@ namespace T3o\T3oLdap\Utility;
* LICENSE.txt file that was distributed with this source code.
*/
use T3o\T3oLdap\Connectors\Ldap;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
......@@ -21,16 +23,18 @@ class PasswordUpdate implements \Psr\Log\LoggerAwareInterface
{
use \Psr\Log\LoggerAwareTrait;
const PASSWORD_METHODS = ['md5', 'sha1', 'crypt'];
const PASSWORD_METHODS = ['crypt_sha512'];
/**
* Update a password in various places (LDAP, TYPO3)
*
* @param string $username The username to update the password for
* @param string $clearTextPassword Cleartext password to hash and update
* @return bool
*/
public function updatePassword(string $username, string $clearTextPassword)
public function updatePassword(string $username, string $clearTextPassword): bool
{
$ret = false;
if (version_compare(TYPO3_version, '9.0', '<')) {
$extensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['t3o_ldap'] ?? '') ?? [];
} else {
......@@ -39,15 +43,24 @@ class PasswordUpdate implements \Psr\Log\LoggerAwareInterface
// Check if LDAP updates are enabled in extension configuration
if ((int)$extensionConfiguration['enableLdapPasswordUpdates'] === 1) {
/** @var \T3o\T3oLdap\Connectors\Ldap $ldap */
$ldap = GeneralUtility::makeInstance(\T3o\T3oLdap\Connectors\Ldap::class);
if ($ldap->setLdapPasswords($username, $this->getHashedPasswords($clearTextPassword))) {
$this->logger = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
/** @var Ldap $ldap */
$ldap = GeneralUtility::makeInstance(Ldap::class);
$passwordUpdateResult = $ldap->setLdapPasswords($username, $this->getHashedPasswords($clearTextPassword));
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
if ($passwordUpdateResult === true) {
$this->logger->info('Password successfully updated (Mechanisms: ' . strtoupper(implode(', ', self::PASSWORD_METHODS)) . ')');
$ret = true;
} else {
$this->logger->info('Password has not been updated (Mechanisms: ' . strtoupper(implode(', ', self::PASSWORD_METHODS)) . ')');
}
}
return $ret;
}
/**
* @param string $clearTextPassword
* @return array
*/
public function getHashedPasswords(string $clearTextPassword): array
{
$passwords = [];
......
......@@ -13,6 +13,7 @@ namespace T3o\T3oLdap\Utility;
* LICENSE.txt file that was distributed with this source code.
*/
use T3o\T3oLdap\Connectors\Ldap;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -38,9 +39,9 @@ class UserCreateUpdateDelete
{
$ret = false;
/** @var \T3o\T3oLdap\Connectors\Ldap $ldap */
/** @var Ldap $ldap */
try {
$ldap = new \T3o\T3oLdap\Connectors\Ldap();
$ldap = new Ldap();
} catch (\Exception $e) {
throw $e;
}
......
......@@ -28,11 +28,6 @@ class MyProfile extends \In2code\Femanager\Domain\Model\User
*/
protected $termsVersion = '';
/**
* @var string
*/
protected $hashMd5 = '';
/**
* @var string
*/
......@@ -41,7 +36,7 @@ class MyProfile extends \In2code\Femanager\Domain\Model\User
/**
* @var string
*/
protected $hashCrypt = '';
protected $hashCryptSha512 = '';
/**
* @return string
......@@ -107,16 +102,6 @@ class MyProfile extends \In2code\Femanager\Domain\Model\User
$this->termsVersion = $termsVersion;
}
public function getHashMd5(): string
{
return $this->hashMd5;
}
public function setHashMd5(string $hashMd5)
{
$this->hashMd5 = $hashMd5;
}
public function getHashSha1(): string
{
return $this->hashSha1;
......@@ -127,13 +112,13 @@ class MyProfile extends \In2code\Femanager\Domain\Model\User
$this->hashSha1 = $hashSha1;
}
public function getHashCrypt(): string
public function getHashCryptSha512(): string
{
return $this->hashCrypt;
return $this->hashCryptSha512;
}
public function setHashCrypt(string $hashCrypt)
public function setHashCryptSha512(string $hashCryptSha512): void
{
$this->hashCrypt = $hashCrypt;
$this->hashCryptSha512 = $hashCryptSha512;
}
}
......@@ -173,6 +173,7 @@ class FeManagerHooks
$plainTextPassword = '';
if ($user->_isDirty('password') && $user->getPassword() !== '') {
$plainTextPassword = $user->getPassword();
$user->setPassword($this->getRandomPassword());
\In2code\Femanager\Utility\UserUtility::convertPassword($user, '');
}
$this->ldapUserCreateUpdateDelete->updateUser($user, true, $plainTextPassword);
......@@ -222,6 +223,7 @@ class FeManagerHooks
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$userRepository = $objectManager->get(\In2code\Femanager\Domain\Repository\UserRepository::class);
$plainTextPassword = $user->getPassword();
$user->setPassword($this->getRandomPassword());
\In2code\Femanager\Utility\UserUtility::hashPassword($user, '');
$userRepository->add($user);
$this->persistenceManager->persistAll();
......@@ -328,6 +330,11 @@ class FeManagerHooks
return true;
}
private function getRandomPassword(): string
{
return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Crypto\Random::class)->generateRandomHexString(20);
}
/**
* @return string
*/
......
......@@ -32,23 +32,9 @@ $feUsersColumns = [
'type' => 'input'
],
],
'hash_md5' => [
'hash_crypt_sha512' => [
'exclude' => 0,
'label' => 'MD5 Hash of password',
'config' => [
'type' => 'none'
],
],
'hash_sha1' => [
'exclude' => 0,
'label' => 'SHA1 Hash of password',
'config' => [
'type' => 'none'
],
],
'hash_crypt' => [
'exclude' => 0,
'label' => 'Crypt Hash of password',
'label' => 'Crypt SHA512 Hash of password',
'config' => [
'type' => 'none'
],
......@@ -56,7 +42,7 @@ $feUsersColumns = [
];
$fields = 'github, twitter, facebook, terms_version';
$passwordHashFields = 'hash_md5, hash_sha1, hash_crypt';
$passwordHashFields = 'hash_crypt_sha512';
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes(
'fe_users',
......
......@@ -68,7 +68,7 @@
<f:image image="{image}" width="250" />
</f:for>
<div >
<f:link.action controller="Edit" pageUid="24" action="edit" arguments="{user:user}" class="btn btn-primary" >Edit your profile</f:link.action>
<f:link.page pageUid="24" class="btn btn-primary" >Edit your profile</f:link.page>
</div>
</f:then>
<f:else>
......
......@@ -7,9 +7,7 @@ CREATE TABLE fe_users (
facebook VARCHAR (255),
terms_version VARCHAR (255),
hash_md5 VARCHAR(255) DEFAULT '' NOT NULL,
hash_sha1 VARCHAR(255) DEFAULT '' NOT NULL,
hash_crypt VARCHAR(255) DEFAULT '' NOT NULL
hash_crypt_sha512 VARCHAR(255) DEFAULT '' NOT NULL
);
CREATE TABLE old_users (
......
#!/bin/bash
# You need to execute this file, whenever you start a project, where you want to use an ldap connection.
# This script will add the ldap module to the nginx-php container from ddev.
# Afterwards you need to restart your ddev project: ddev stop && ddev start
docker exec -u root -it ddev-my.typo3.org-web apt update
docker exec -u root -it ddev-my.typo3.org-web apt install php7.0-ldap
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment