Skip to content

Commit

Permalink
Merge pull request #760 from owncloud/binaryConverter
Browse files Browse the repository at this point in the history
Binary converter
  • Loading branch information
jvillafanez authored Nov 7, 2022
2 parents 7e3d4da + d2c1124 commit 412c3fb
Show file tree
Hide file tree
Showing 11 changed files with 723 additions and 123 deletions.
125 changes: 8 additions & 117 deletions lib/Access.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
use OCA\User_LDAP\User\IUserTools;
use OCA\User_LDAP\User\Manager;
use OCA\User_LDAP\Mapping\AbstractMapping;
use OCA\User_LDAP\Attributes\ConverterHub;
use OCP\Util;

/**
Expand Down Expand Up @@ -327,11 +328,12 @@ public function extractAttributeValuesFromResult($result, $attribute) {
$values = [];
if (isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
$lowercaseAttribute = \strtolower($attribute);
$converterHub = ConverterHub::getDefaultConverterHub();
for ($i=0; $i<$result[$attribute]['count']; $i++) {
if ($this->resemblesDN($attribute)) {
$values[] = Helper::normalizeDN($result[$attribute][$i]);
} elseif ($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
$values[] = self::binGUID2str($result[$attribute][$i]);
} elseif ($converterHub->hasConverter($lowercaseAttribute)) {
$values[] = $converterHub->bin2str($lowercaseAttribute, $result[$attribute][$i]);
} else {
$values[] = $result[$attribute][$i];
}
Expand Down Expand Up @@ -1727,8 +1729,10 @@ public function getUserDnByUuid($uuid) {
}

$uuidAttr = $this->connection->ldapUuidUserAttribute;
if ($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
$uuid = $this->formatGuid2ForFilterUser($uuid);
$lowercaseUuidAttr = \strtolower($uuidAttr);
$converterHub = ConverterHub::getDefaultConverterHub();
if ($converterHub->hasConverter($lowercaseUuidAttr)) {
$uuid = $converterHub->str2filter($lowercaseUuidAttr, $uuid);
}

$filter = $uuidAttr . '=' . $uuid;
Expand Down Expand Up @@ -1828,119 +1832,6 @@ public function getUUID($dn, $isUser = true) {
return $uuid;
}

/**
* converts a binary GUID into a string representation
*
* TODO use shorter version with pack()
*
* General UUID information: @see http://ldapwiki.com/wiki/Universally%20Unique%20Identifier
*
* ## openldap EntryUUID uses RFC4122 see {@link http://ldapwiki.com/wiki/UUID definition}
* see the {@link http://ldapwiki.com/wiki/EntryUUID ldapwiki EntryUUID definition}
*
* ## Microsoft Active Directory objectGUID is defined as 16 byte octet string
* {@link https://msdn.microsoft.com/en-us/library/ms679021(v=vs.85).aspx official objectGUID definition}
* From the {@link http://ldapwiki.com/wiki/ObjectGUID ldapwiki ObjectGUID definition}:
* ObjectGUID is generally a Universally Unique Identifier other than the
* format differs from the UUID standard only in the byte order of the first 3 fields.
* {@link http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B325649 conversion to a string}
*
* ## Novell eDirectory GUID is defined as 16 byte octet string
* From the {@link http://ldapwiki.com/wiki/GUID ldapwiki GUID definition}:
* There are several different methods that are used to display any given GUID
* {@link http://www.novell.com/documentation/developer/ndslib/schm_enu/data/sdk1198.html official GUID definition}
*
* ## 389 Directory Server / Oracle Directory Server Enterprise Edition (ODSEE) is defined as utf string
* {@link https://github.com/leto/389-ds/blob/master/ldap/schema/01core389.ldif#L69 schema definition}
* {@link https://docs.oracle.com/cd/E49437_01/reference.111220/e27801/nsuniqueid-virtual-attribute.html official nsuniqueid definition}
* The nsuniqueid values are generated based on the entryuuid value by moving the "-" to comply with the format of the ODSEE Nsuniqueid Virtual Attribute attribute.
*
* ## RedHat FreeIPA is defined as utf string
* {@link https://github.com/freeipa/freeipa/blob/master/install/share/uuid.ldif ipaUniqueID schema}
*
* This implementation was taken from
* {@link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198 The PHP ldap_get_values_lan doc comments}
*
* @param string $binGuid the ObjectGUID / GUID in it's binary form as retrieved from Microsoft AD / Novell eDirectory
* If you pass an already decoded GUID as string, it will be returned as is.
* @return string
* @throws \OutOfBoundsException
*/
public static function binGUID2str($binGuid) {
$guidLength = \strlen($binGuid);

// The guid should have 16 byte when binary and 36 byte when string (including '-' characters)
if (($guidLength !== 16) && ($guidLength !== 36)) {
throw new \OutOfBoundsException(\sprintf('Invalid GUID with length %d received: <%X>', $guidLength, $binGuid));
}

// If we get a guid in string form we simply return it to prevent double decoding
if ($guidLength === 36) {
return $binGuid;
}

// V = unsigned long (always 32 bit, little endian byte order)
// v = unsigned short (always 16 bit, little endian byte order)
// n = unsigned short (always 16 bit, big endian byte order)
// N = unsigned long (always 32 bit, big endian byte order)
// TODO treat all warnings es error? see https://stackoverflow.com/a/2071048
$unpacked = \unpack('Va/v2b/n2c/Nd', $binGuid); // only throws a warning if it could not parse the input
$uuid = \sprintf('%08X-%04X-%04X-%04X-%04X%08X', $unpacked['a'], $unpacked['b1'], $unpacked['b2'], $unpacked['c1'], $unpacked['c2'], $unpacked['d']);
// make sure this is not a bogus UUID
if ($uuid === '00000000-0000-0000-0000-000000000000') {
throw new \OutOfBoundsException(\sprintf('Invalid binary uuid <%X>', $binGuid));
}
return $uuid;
}

/**
* the first three blocks of the string-converted GUID happen to be in
* reverse order. In order to use it in a filter, this needs to be
* corrected. Furthermore the dashes need to be replaced and \\ preprended
* to every two hax figures.
*
* If an invalid string is passed, it will be returned without change.
*
* @param string $guid
* @return string
* @throws \InvalidArgumentException
*/
public function formatGuid2ForFilterUser($guid) {
if (!\is_string($guid)) {
throw new \InvalidArgumentException('String expected');
}
$blocks = \explode('-', $guid);
if (\count($blocks) !== 5) {
/*
* Why not throw an Exception instead? This method is a utility
* called only when trying to figure out whether a "missing" known
* LDAP user was or was not renamed on the LDAP server. And this
* even on the use case that a reverse lookup is needed (UUID known,
* not DN), i.e. when finding users (search dialog, users page,
* login, …) this will not be fired. This occurs only if shares from
* a users are supposed to be mounted who cannot be found. Throwing
* an exception here would kill the experience for a valid, acting
* user. Instead we write a log message.
*/
\OC::$server->getLogger()->info(
'Passed string does not resemble a valid GUID. Known UUID ' .
'({uuid}) probably does not match UUID configuration.',
[ 'app' => 'user_ldap', 'uuid' => $guid ]
);
return $guid;
}
for ($i=0; $i < 3; $i++) {
$pairs = \str_split($blocks[$i], 2);
$pairs = \array_reverse($pairs);
$blocks[$i] = \implode('', $pairs);
}
for ($i=0; $i < 5; $i++) {
$pairs = \str_split($blocks[$i], 2);
$blocks[$i] = '\\' . \implode('\\', $pairs);
}
return \implode('', $blocks);
}

/**
* gets a SID of the domain of the given dn
*
Expand Down
23 changes: 23 additions & 0 deletions lib/Attributes/ConverterException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* @copyright Copyright (c) 2022, ownCloud GmbH.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\User_LDAP\Attributes;

class ConverterException extends \Exception {
}
138 changes: 138 additions & 0 deletions lib/Attributes/ConverterHub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* @copyright Copyright (c) 2022, ownCloud GmbH.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\User_LDAP\Attributes;

class ConverterHub {
/** @var ConverterHub */
private static $defaultConverterHub;

/** @var array<string,IConverter> */
private $attrConverterMap = [];

/**
* Get the default converted hub. If no hub has been created yet,
* or the $fresh parameter is true, a new ConverterHub will be
* created and set as default, the `registerDefaultConverters`
* method will be called on it, and finally it will be returned.
* @param bool $fresh whether a new instance should be created and set
* as default
* @return ConverterHub
*/
public static function getDefaultConverterHub($fresh = false) {
if (self::$defaultConverterHub === null || $fresh) {
$converterHub = new ConverterHub();
$converterHub->registerDefaultConverters();
self::$defaultConverterHub = $converterHub;
}
return self::$defaultConverterHub;
}

/**
* Register a new IConverter instance for the chosen attribute.
* This will overwrite any previous IConverter register for that attribute
* @param string $attr the attribute that will be converted. The attribute
* should be lowercase
* @param IConverter $converter the converter to be used for that attribute
*/
public function registerConverter(string $attr, IConverter $converter) {
$this->attrConverterMap[$attr] = $converter;
}

/**
* Remove all registered converters in this instance
*/
public function clearConverters() {
$this->attrConverterMap = [];
}

/**
* Register the default converters. This method won't remove any existing
* converter, but it will overwrite them if they overlap
*/
public function registerDefaultConverters() {
$this->attrConverterMap['objectguid'] = new GUIDConverter();
$this->attrConverterMap['guid'] = new GUIDConverter();
$this->attrConverterMap['objectsid'] = new SIDConverter();
}

/**
* Check if this instance has a converter set for the target attribute
* @param string $attr the attribute to check
* @return bool true if there is a converter for the attribute, false otherwise
*/
public function hasConverter(string $attr): bool {
return isset($this->attrConverterMap[$attr]);
}

/**
* Convert the binary value to its string representation using the
* converter registered for the target attribute.
* If there is no converter registered for the target attribute, a
* ConverterException will be thrown.
* Additional ConverterException might be thrown from the converters
* if there is a problem dealing with the conversion.
* @param string $attr the attribute name for the value
* @param string $binValue the binary value for the associated attribute
* @return string the string representation of the binary value
* accordingly to the associated converter
* @throws ConverterException if no converter is registered, or the
* associated converter can't deal with the input.
*/
public function bin2str(string $attr, string $binValue) {
if (!isset($this->attrConverterMap[$attr])) {
throw new ConverterException("No converter found for attr {$attr}");
}

$converter = $this->attrConverterMap[$attr];
return $converter->bin2str($binValue);
}

/**
* Convert the string representation of the attribute into a string
* suitable to be used in a LDAP filter.
* The string representation should come from the `bin2str` method
* of this instance (it might have been stored anywhere).
* Since the filter will likely contain binary data, the converters
* are expected to escape the result accordinly, so results such as
* '\11\f8\30\73\7f\f4\bc\41\a4\ff\e7\92\d0\73\f4\1f' are expected
* to be returned.
* The filter is expected to be built like
* ```
* $strRepr = 'S-1-5-21-12032599-1214884855-3286145327-1103';
* $filter = "objectsid=" . $hub->str2filter('objectsid', $strRepr);
* ```
* A ConverterException will be thrown if there is no converter
* associated to the target attribute
* @param string $attr the attribute associated to the string representation
* @param string $strValue the string representation of the value of
* the attribute
* @return string a string suitable to be used as filter
* @throws ConverterException if no converter is registered, or the
* associated converter can't deal with the input.
*/
public function str2filter(string $attr, string $strValue) {
if (!isset($this->attrConverterMap[$attr])) {
throw new ConverterException("No converter found for attr {$attr}");
}

$converter = $this->attrConverterMap[$attr];
return $converter->str2filter($strValue);
}
}
Loading

0 comments on commit 412c3fb

Please sign in to comment.