Skip to content

Commit cd0c669

Browse files
committed
implement IProvideUserSecretBackend compatibility
Signed-off-by: summersab <[email protected]> perform a little lint cleanup Signed-off-by: summersab <[email protected]>
1 parent 0d0d6e0 commit cd0c669

File tree

3 files changed

+100
-18
lines changed

3 files changed

+100
-18
lines changed

lib/SAMLSettings.php

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class SAMLSettings {
6666
'saml-attribute-mapping-home_mapping',
6767
'saml-attribute-mapping-quota_mapping',
6868
'saml-attribute-mapping-mfa_mapping',
69+
'saml-attribute-mapping-user_secret_mapping',
6970
'saml-user-filter-reject_groups',
7071
'saml-user-filter-require_groups',
7172
'sp-x509cert',

lib/Settings/Admin.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,11 @@ public function getForm() {
140140
'type' => 'line',
141141
'required' => false,
142142
],
143-
143+
'user_secret_mapping' => [
144+
'text' => $this->l10n->t('Attribute to use as user secret e.g. for the encryption app.'),
145+
'type' => 'line',
146+
'required' => false,
147+
],
144148
];
145149

146150
$userFilterSettings = [

lib/UserBackend.php

+94-17
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@
3434
use OCP\IConfig;
3535
use OCP\IURLGenerator;
3636
use OCP\ISession;
37+
use OCP\Authentication\IProvideUserSecretBackend;
3738
use Symfony\Component\EventDispatcher\GenericEvent;
3839
use OCP\EventDispatcher\IEventDispatcher;
3940
use OCP\User\Events\UserChangedEvent;
4041

41-
class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
42+
class UserBackend implements IApacheBackend, UserInterface, IUserBackend, IProvideUserSecretBackend {
4243
/** @var IConfig */
4344
private $config;
4445
/** @var IURLGenerator */
@@ -148,10 +149,63 @@ public function createUserIfNotExists($uid, array $attributes = []) {
148149
}
149150
$qb->execute();
150151

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

163+
private function getUserSecretHash($uid) {
164+
$qb = $this->db->getQueryBuilder();
165+
$qb->select('token')
166+
->from('user_saml_auth_token')
167+
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
168+
->andWhere($qb->expr()->eq('name', $qb->createNamedParameter('sso_secret_hash')))
169+
->setMaxResults(10);
170+
$result = $qb->execute();
171+
$data = $result->fetchAll();
172+
$result->closeCursor();
173+
return $data;
174+
}
175+
176+
private function checkUserSecretHash($uid, $userSecret) {
177+
$data = $this->getUserSecretHash($uid);
178+
foreach($data as $row) {
179+
$storedHash = $row['token'];
180+
if (\OC::$server->getHasher()->verify($userSecret, $storedHash, $newHash)) {
181+
if (!empty($newHash)) {
182+
$this->updateUserSecretHash($uid, $userSecret, true);
183+
}
184+
return true;
185+
}
186+
}
187+
return false;
188+
}
189+
190+
private function updateUserSecretHash($uid, $userSecret, $exists = false) {
191+
$qb = $this->db->getQueryBuilder();
192+
$hash = \OC::$server->getHasher()->hash($userSecret);
193+
if ($exists || count($this->getUserSecretHash($uid)) > 0) {
194+
$qb->update('user_saml_auth_token')
195+
->set('token', $qb->createNamedParameter($hash))
196+
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
197+
->andWhere($qb->expr()->eq('name', $qb->createNamedParameter('sso_secret_hash')));
198+
} else {
199+
$qb->insert('user_saml_auth_token')
200+
->values([
201+
'uid' => $qb->createNamedParameter($uid),
202+
'token' => $qb->createNamedParameter($hash),
203+
'name' => $qb->createNamedParameter('sso_secret_hash'),
204+
]);
205+
}
206+
return $qb->execute();
207+
}
208+
155209
/**
156210
* @param string $uid
157211
* @throws \OCP\Files\NotFoundException
@@ -195,23 +249,16 @@ public function implementsActions($actions) {
195249
* @return string
196250
*
197251
* Check if the password is correct without logging in the user
198-
* returns the user id or false
252+
* returns the user id or false.
253+
*
254+
* By default user_saml tokens are passwordless and this function
255+
* is unused. It is only called if we have tokens with passwords,
256+
* which happens if we have SSO provided user secrets.
199257
*/
200258
public function checkPassword($uid, $password) {
201259
/* @var $qb IQueryBuilder */
202-
$qb = $this->db->getQueryBuilder();
203-
$qb->select('token')
204-
->from('user_saml_auth_token')
205-
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
206-
->setMaxResults(1000);
207-
$result = $qb->execute();
208-
$data = $result->fetchAll();
209-
$result->closeCursor();
210-
211-
foreach ($data as $passwords) {
212-
if (password_verify($password, $passwords['token'])) {
213-
return $uid;
214-
}
260+
if ($this->checkUserSecretHash($uid, $password)) {
261+
return $uid;
215262
}
216263

217264
return false;
@@ -512,6 +559,16 @@ public function getCurrentUserId() {
512559
return '';
513560
}
514561

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

516573
/**
517574
* Backend name to be shown in user management
@@ -612,6 +669,21 @@ private function getAttributeArrayValue($name, array $attributes) {
612669
return $value;
613670
}
614671

672+
private function getUserSecret($uid, array $attributes) {
673+
try {
674+
$userSecret = $this->getAttributeValue('saml-attribute-mapping-user_secret_mapping', $attributes);
675+
if ($userSecret === '') {
676+
$this->logger->debug('Got no user_secret from idp', ['app' => 'user_saml']);
677+
} else {
678+
$this->logger->debug('Got user_secret from idp', ['app' => 'user_saml']);
679+
return $userSecret;
680+
}
681+
} catch (\InvalidArgumentException $e) {
682+
$this->logger->debug('No user_secret mapping configured', ['app' => 'user_saml']);
683+
}
684+
return null;
685+
}
686+
615687
public function updateAttributes($uid,
616688
array $attributes) {
617689
$user = $this->userManager->get($uid);
@@ -683,11 +755,16 @@ public function updateAttributes($uid,
683755
$groupManager->get($group)->removeUser($user);
684756
}
685757
}
758+
759+
$userSecret = $this->getUserSecret($uid, $attributes);
760+
if ($userSecret !== null) {
761+
if (!$this->checkUserSecretHash($uid, $userSecret)) {
762+
$this->updateUserSecretHash($uid, $userSecret);
763+
}
764+
}
686765
}
687766
}
688767

689-
690-
691768
public function countUsers() {
692769
$query = $this->db->getQueryBuilder();
693770
$query->select($query->func()->count('uid'))

0 commit comments

Comments
 (0)