Skip to content

Commit

Permalink
implement IProvideUserSecretBackend compatibility
Browse files Browse the repository at this point in the history
Signed-off-by: summersab <[email protected]>

perform a little lint cleanup

Signed-off-by: summersab <[email protected]>
  • Loading branch information
summersab committed Feb 10, 2023
1 parent 0d0d6e0 commit cd0c669
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 18 deletions.
1 change: 1 addition & 0 deletions lib/SAMLSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class SAMLSettings {
'saml-attribute-mapping-home_mapping',
'saml-attribute-mapping-quota_mapping',
'saml-attribute-mapping-mfa_mapping',
'saml-attribute-mapping-user_secret_mapping',
'saml-user-filter-reject_groups',
'saml-user-filter-require_groups',
'sp-x509cert',
Expand Down
6 changes: 5 additions & 1 deletion lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,11 @@ public function getForm() {
'type' => 'line',
'required' => false,
],

'user_secret_mapping' => [
'text' => $this->l10n->t('Attribute to use as user secret e.g. for the encryption app.'),
'type' => 'line',
'required' => false,
],
];

$userFilterSettings = [
Expand Down
111 changes: 94 additions & 17 deletions lib/UserBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@
use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\ISession;
use OCP\Authentication\IProvideUserSecretBackend;
use Symfony\Component\EventDispatcher\GenericEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\User\Events\UserChangedEvent;

class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
class UserBackend implements IApacheBackend, UserInterface, IUserBackend, IProvideUserSecretBackend {
/** @var IConfig */
private $config;
/** @var IURLGenerator */
Expand Down Expand Up @@ -148,10 +149,63 @@ public function createUserIfNotExists($uid, array $attributes = []) {
}
$qb->execute();

// If we use per-user encryption the keys must be initialized first
$userSecret = $this->getUserSecret($uid, $attributes);
if ($userSecret !== null) {
$this->updateUserSecretHash($uid, $userSecret);
// Emit a post login action to initialize the encryption module with the user secret provided by the idp.
\OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $uid, 'password' => $userSecret, 'isTokenLogin' => false]);
}
$this->initializeHomeDir($uid);
}
}

private function getUserSecretHash($uid) {
$qb = $this->db->getQueryBuilder();
$qb->select('token')
->from('user_saml_auth_token')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->andWhere($qb->expr()->eq('name', $qb->createNamedParameter('sso_secret_hash')))
->setMaxResults(10);
$result = $qb->execute();
$data = $result->fetchAll();
$result->closeCursor();
return $data;
}

private function checkUserSecretHash($uid, $userSecret) {
$data = $this->getUserSecretHash($uid);
foreach($data as $row) {
$storedHash = $row['token'];
if (\OC::$server->getHasher()->verify($userSecret, $storedHash, $newHash)) {
if (!empty($newHash)) {
$this->updateUserSecretHash($uid, $userSecret, true);
}
return true;
}
}
return false;
}

private function updateUserSecretHash($uid, $userSecret, $exists = false) {
$qb = $this->db->getQueryBuilder();
$hash = \OC::$server->getHasher()->hash($userSecret);
if ($exists || count($this->getUserSecretHash($uid)) > 0) {
$qb->update('user_saml_auth_token')
->set('token', $qb->createNamedParameter($hash))
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->andWhere($qb->expr()->eq('name', $qb->createNamedParameter('sso_secret_hash')));
} else {
$qb->insert('user_saml_auth_token')
->values([
'uid' => $qb->createNamedParameter($uid),
'token' => $qb->createNamedParameter($hash),
'name' => $qb->createNamedParameter('sso_secret_hash'),
]);
}
return $qb->execute();
}

/**
* @param string $uid
* @throws \OCP\Files\NotFoundException
Expand Down Expand Up @@ -195,23 +249,16 @@ public function implementsActions($actions) {
* @return string
*
* Check if the password is correct without logging in the user
* returns the user id or false
* returns the user id or false.
*
* By default user_saml tokens are passwordless and this function
* is unused. It is only called if we have tokens with passwords,
* which happens if we have SSO provided user secrets.
*/
public function checkPassword($uid, $password) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('token')
->from('user_saml_auth_token')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->setMaxResults(1000);
$result = $qb->execute();
$data = $result->fetchAll();
$result->closeCursor();

foreach ($data as $passwords) {
if (password_verify($password, $passwords['token'])) {
return $uid;
}
if ($this->checkUserSecretHash($uid, $password)) {
return $uid;
}

return false;
Expand Down Expand Up @@ -512,6 +559,16 @@ public function getCurrentUserId() {
return '';
}

/**
* Optionally returns a stable per-user secret. This secret is for
* instance used to secure file encryption keys.
* @return string|null
* @since 26.0.0
*/
public function getCurrentUserSecret(): string {
$samlData = $this->session->get('user_saml.samlUserData');
return $this->getUserSecret($this->getCurrentUserId(), $samlData);
}

/**
* Backend name to be shown in user management
Expand Down Expand Up @@ -612,6 +669,21 @@ private function getAttributeArrayValue($name, array $attributes) {
return $value;
}

private function getUserSecret($uid, array $attributes) {
try {
$userSecret = $this->getAttributeValue('saml-attribute-mapping-user_secret_mapping', $attributes);
if ($userSecret === '') {
$this->logger->debug('Got no user_secret from idp', ['app' => 'user_saml']);
} else {
$this->logger->debug('Got user_secret from idp', ['app' => 'user_saml']);
return $userSecret;
}
} catch (\InvalidArgumentException $e) {
$this->logger->debug('No user_secret mapping configured', ['app' => 'user_saml']);
}
return null;
}

public function updateAttributes($uid,
array $attributes) {
$user = $this->userManager->get($uid);
Expand Down Expand Up @@ -683,11 +755,16 @@ public function updateAttributes($uid,
$groupManager->get($group)->removeUser($user);
}
}

$userSecret = $this->getUserSecret($uid, $attributes);
if ($userSecret !== null) {
if (!$this->checkUserSecretHash($uid, $userSecret)) {
$this->updateUserSecretHash($uid, $userSecret);
}
}
}
}



public function countUsers() {
$query = $this->db->getQueryBuilder();
$query->select($query->func()->count('uid'))
Expand Down

0 comments on commit cd0c669

Please sign in to comment.