Add group backend to separate SAML groups from system/other groups

Signed-off-by: Maximilian Ruta <mr@xtain.net>
This commit is contained in:
Maximilian Ruta 2019-08-05 14:30:09 +02:00 committed by Sven Seeberg
parent 15a262cb24
commit b8e68dd5ea
Signed by: sven.seeberg
GPG key ID: 29559DD5A83806B5
7 changed files with 446 additions and 30 deletions

View file

@ -38,6 +38,22 @@ try {
\OC::$server->getLogger()->logException($e);
return;
}
\OC::$server->registerService('SAMLGroupDuplicateChecker', function() use($config) {
return new OCA\User_SAML\GroupDuplicateChecker(
$config,
\OC::$server->getGroupManager(),
\OC::$server->getLogger()
);
});
\OC::$server->registerService('SAMLGroupManager', function() {
return new OCA\User_SAML\GroupManager(
\OC::$server->getDatabaseConnection(),
\OC::$server->query('SAMLGroupDuplicateChecker')
);
});
$samlSettings = new \OCA\User_SAML\SAMLSettings(
$urlGenerator,
$config,
@ -51,13 +67,16 @@ $userBackend = new \OCA\User_SAML\UserBackend(
\OC::$server->getSession(),
\OC::$server->getDatabaseConnection(),
\OC::$server->getUserManager(),
\OC::$server->getGroupManager(),
\OC::$server->query('SAMLGroupManager'),
$samlSettings,
\OC::$server->getLogger()
);
$userBackend->registerBackends(\OC::$server->getUserManager()->getBackends());
OC_User::useBackend($userBackend);
$groupBackend = new \OCA\User_SAML\GroupBackend($config, \OC::$server->query('SAMLGroupManager'));
\OC::$server->getGroupManager()->addBackend($groupBackend);
$params = [];
// Setting up the one login config may fail, if so, do not catch the requests later.

View file

@ -78,4 +78,34 @@
</field>
</declaration>
</table>
<table>
<name>*dbprefix*user_saml_group_members</name>
<declaration>
<field>
<name>uid</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>group</name>
<type>text</type>
<notnull>true</notnull>
<length>128</length>
<default></default>
</field>
<index>
<name>idx_group_members</name>
<unique>true</unique>
<field>
<name>group</name>
<name>uid</name>
</field>
</index>
</declaration>
</table>
</database>

129
lib/GroupBackend.php Normal file
View file

@ -0,0 +1,129 @@
<?php
namespace OCA\User_SAML;
use OCP\Group\Backend\ABackend;
use OCP\IConfig;
class GroupBackend extends ABackend {
/**
* @var IConfig
*/
protected $config;
/**
* @var GroupManager
*/
protected $groupManager;
public function __construct(
IConfig $config,
GroupManager $groupManager
) {
$this->config = $config;
$this->groupManager = $groupManager;
}
/**
* @return string
*/
protected function getPrefix() {
return $this->config->getAppValue('user_saml', 'saml-attribute-mapping-group_mapping_prefix', '');
}
protected function hasPrefix($string) {
return mb_substr($string, 0, mb_strlen($this->getPrefix())) === $this->getPrefix();
}
protected function removePrefix($query = null) {
if ($query === null || $query === '') {
return null;
}
$pattern = '';
foreach (preg_split('//u', $this->getPrefix(), -1, PREG_SPLIT_NO_EMPTY) as $char) {
$pattern .= preg_quote($char, '/') . '?';
}
$result = preg_replace('/^' . $pattern . '/', '', $query);
if ($result === '') {
return null;
}
return $result;
}
protected function prefixedList($items) {
$newList = array();
foreach ($items as $item) {
$newList[] = $this->getPrefix() . $item;
}
return $newList;
}
/**
* @return bool
*/
public function inGroup($uid, $gid) {
if (!$this->hasPrefix($gid)) {
return false;
}
return $this->groupManager->userInGroup($uid, $this->removePrefix($gid));
}
/**
* @return string[] Group names
*/
public function getUserGroups($uid) {
return $this->prefixedList($this->groupManager->userGroups($uid));
}
/**
* @return string[] Group names
*/
public function getGroups($search = '', $limit = -1, $offset = 0) {
if ($search === '') {
$search = null;
} else {
if (!$this->hasPrefix($search)) {
return [];
}
}
return $this->prefixedList($this->groupManager->findGroups($this->removePrefix($search), $limit, $offset));
}
/**
* @return bool
*/
public function groupExists($gid) {
if (!$this->hasPrefix($gid)) {
return false;
}
return $this->groupManager->hasGroup(
$this->removePrefix($gid)
);
}
/**
* @return string[] User ids
*/
public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
if ($search === '') {
$search = null;
} else {
if (!$this->hasPrefix($search)) {
return [];
}
}
return $this->prefixedList(
$this->groupManager->findGroups($gid, $this->removePrefix($search), $limit, $offset)
);
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace OCA\User_SAML;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\ILogger;
class GroupDuplicateChecker
{
/**
* @var IConfig
*/
protected $config;
/**
* @var IGroupManager
*/
protected $groupManager;
/**
* @var ILogger
*/
protected $logger;
public function __construct(
IConfig $config,
IGroupManager $groupManager,
ILogger $logger
) {
$this->config = $config;
$this->groupManager = $groupManager;
$this->logger = $logger;
}
/**
* @return string
*/
protected function getPrefix() {
return $this->config->getAppValue('user_saml', 'saml-attribute-mapping-group_mapping_prefix', '');
}
public function checkForDuplicates($group) {
$realGroupName = $this->getPrefix() . $group;
$existingGroup = $this->groupManager->get($realGroupName);
if ($existingGroup !== null) {
$reflection = new \ReflectionClass($existingGroup);
$property = $reflection->getProperty('backends');
$property->setAccessible(true);
$backends = $property->getValue($existingGroup);
if ($backends) {
foreach ($backends as $backend) {
if ($backend instanceof GroupBackend) {
return;
}
}
}
$this->logger->warning(
'Group {name} already existing in other backend',
[
'name' => $realGroupName
]
);
}
}
}

181
lib/GroupManager.php Normal file
View file

@ -0,0 +1,181 @@
<?php
namespace OCA\User_SAML;
use OCP\IDBConnection;
class GroupManager
{
/**
* @var IDBConnection $db
*/
protected $db;
/**
* @var GroupDuplicateChecker
*/
protected $duplicateChecker;
public function __construct(IDBConnection $db, GroupDuplicateChecker $duplicateChecker) {
$this->db = $db;
$this->duplicateChecker = $duplicateChecker;
}
/**
* @return string[]
*/
public function findGroups($query = null, $limit = -1, $offset = 0) {
$sql = '
SELECT DISTINCT `group`
FROM `*PREFIX*user_saml_group_members`
';
$params = array();
if ($query !== null) {
$sql .= ' WHERE `group` LIKE ?';
$params = [
'%' . $query . '%'
];
}
if ($limit === -1) {
$limit = null;
}
if ($offset === 0) {
$offset = null;
}
$stmt = $this->db->prepare($sql, $limit, $offset);
$stmt->execute($params);
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* @return bool
*/
public function hasGroup($group) {
$sql = '
SELECT DISTINCT `group`
FROM `*PREFIX*user_saml_group_members`
WHERE `group` = ?
';
$stmt = $this->db->prepare($sql);
$stmt->execute(array($group));
return $stmt->rowCount() > 0;
}
/**
* @return bool
*/
public function userInGroup($uid, $group) {
$sql = '
SELECT DISTINCT `group`
FROM `*PREFIX*user_saml_group_members`
WHERE `uid` = ? AND `group` = ?
';
$stmt = $this->db->prepare($sql);
$stmt->execute(array($uid, $group));
return $stmt->rowCount() > 0;
}
/**
* @return string[]
*/
public function userGroups($uid) {
$sql = '
SELECT DISTINCT `group`
FROM `*PREFIX*user_saml_group_members`
WHERE `uid` = ?
';
$stmt = $this->db->prepare($sql);
$stmt->execute(array($uid));
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* @return string[]
*/
public function usersInGroup($gid, $query = null, $limit = -1, $offset = 0) {
$sql = '
SELECT DISTINCT `uid`
FROM `*PREFIX*user_saml_group_members`
WHERE `group` = ?
';
$params = array($gid);
if ($query !== null) {
$sql .= ' AND `uid` LIKE ?';
$params = [
'%' . $query . '%'
];
}
if ($limit === -1) {
$limit = null;
}
if ($offset === 0) {
$offset = null;
}
$stmt = $this->db->prepare($sql, $limit, $offset);
$stmt->execute($params);
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
public function replaceGroups($uid, $saml) {
$assgined = $this->userGroups($uid);
$this->removeGroups($uid, array_diff($assgined, $saml));
$this->addGroups($uid, array_diff($saml, $assgined));
}
public function removeGroup($uid, $group) {
$this->removeGroups($uid, array($group));
}
public function removeGroups($uid, $groups) {
if (count($groups) === 0) {
return;
}
$groups = array_values($groups);
$inQuery = implode(',', array_fill(0, count($groups), '?'));
$sql = '
DELETE FROM `*PREFIX*user_saml_group_members`
WHERE `uid` = ? AND `group` IN (' . $inQuery . ')
';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(1, $uid);
foreach ($groups as $k => $id) {
$stmt->bindValue(($k + 2), $id);
}
return $stmt->execute();
}
public function addGroups($uid, $groups) {
foreach ($groups as $group) {
$this->addGroup($uid, $group);
}
}
public function addGroup($uid, $group) {
$this->duplicateChecker->checkForDuplicates($group);
$sql = '
INSERT INTO `*PREFIX*user_saml_group_members`
(`uid`, `group`)
VALUES (?, ?)
';
$stmt = $this->db->prepare($sql);
return $stmt->execute(array($uid, $group));
}
}

View file

@ -126,7 +126,11 @@ class Admin implements ISettings {
'type' => 'line',
'required' => true,
],
'group_mapping_prefix' => [
'text' => $this->l10n->t('Group Mapping Prefix'),
'type' => 'line',
'required' => true,
],
];
$selectedNameIdFormat = $this->config->getAppValue('user_saml', 'sp-name-id-format', Constants::NAMEID_UNSPECIFIED);

View file

@ -24,16 +24,16 @@ namespace OCA\User_SAML;
use OCP\Authentication\IApacheBackend;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\NotPermittedException;
use OCP\IDBConnection;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IGroupManager;
use OCP\UserInterface;
use OCP\IUserBackend;
use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserBackend;
use OCP\IUserManager;
use OCP\UserInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
@ -47,7 +47,7 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
private $db;
/** @var IUserManager */
private $userManager;
/** @var IGroupManager */
/** @var GroupManager */
private $groupManager;
/** @var \OCP\UserInterface[] */
private static $backends = [];
@ -62,7 +62,7 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
* @param ISession $session
* @param IDBConnection $db
* @param IUserManager $userManager
* @param IGroupManager $groupManager
* @param GroupManager $groupManager
* @param SAMLSettings $settings
* @param ILogger $logger
*/
@ -71,7 +71,7 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
ISession $session,
IDBConnection $db,
IUserManager $userManager,
IGroupManager $groupManager,
GroupManager $groupManager,
SAMLSettings $settings,
ILogger $logger) {
$this->config = $config;
@ -671,24 +671,10 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
$user->setQuota($newQuota);
}
if ($newGroups !== null) {
$groupManager = $this->groupManager;
$oldGroups = $groupManager->getUserGroupIds($user);
$groupsToAdd = array_unique(array_diff($newGroups, $oldGroups));
$groupsToRemove = array_diff($oldGroups, $newGroups);
foreach ($groupsToAdd as $group) {
if (!($groupManager->groupExists($group))) {
$groupManager->createGroup($group);
}
$groupManager->get($group)->addUser($user);
}
foreach ($groupsToRemove as $group) {
$groupManager->get($group)->removeUser($user);
}
if ($newGroups === null) {
$newGroups = [];
}
$this->groupManager->replaceGroups($user->getUID(), $newGroups);
}
}
}