Skip to content

Commit 6e1548a

Browse files
summersabblizzz
authored andcommitted
implement IProvideUserSecretBackend compatibility
Signed-off-by: summersab <[email protected]> perform a little lint cleanup Signed-off-by: summersab <[email protected]> Signed-off-by: Andrew Summers <[email protected]>
1 parent 74241fa commit 6e1548a

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
@@ -23,6 +23,7 @@
2323

2424
use OCP\Authentication\IApacheBackend;
2525
use OCP\EventDispatcher\GenericEvent;
26+
use OCP\Authentication\IProvideUserSecretBackend;
2627
use OCP\EventDispatcher\IEventDispatcher;
2728
use OCP\Files\NotPermittedException;
2829
use OCP\IConfig;
@@ -40,7 +41,7 @@
4041
use OCP\User\Events\UserChangedEvent;
4142
use OCP\UserInterface;
4243

43-
class UserBackend implements IApacheBackend, UserInterface, IUserBackend, IGetDisplayNameBackend {
44+
class UserBackend implements IApacheBackend, UserInterface, IUserBackend, IGetDisplayNameBackend, IProvideUserSecretBackend {
4445
/** @var IConfig */
4546
private $config;
4647
/** @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,22 +249,15 @@ 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) {
201-
$qb = $this->db->getQueryBuilder();
202-
$qb->select('token')
203-
->from('user_saml_auth_token')
204-
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
205-
->setMaxResults(1000);
206-
$result = $qb->execute();
207-
$data = $result->fetchAll();
208-
$result->closeCursor();
209-
210-
foreach ($data as $passwords) {
211-
if (password_verify($password, $passwords['token'])) {
212-
return $uid;
213-
}
259+
if ($this->checkUserSecretHash($uid, $password)) {
260+
return $uid;
214261
}
215262

216263
return false;
@@ -508,6 +555,16 @@ public function getCurrentUserId() {
508555
return '';
509556
}
510557

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

512569
/**
513570
* Backend name to be shown in user management
@@ -608,6 +665,21 @@ private function getAttributeArrayValue($name, array $attributes) {
608665
return $value;
609666
}
610667

668+
private function getUserSecret($uid, array $attributes) {
669+
try {
670+
$userSecret = $this->getAttributeValue('saml-attribute-mapping-user_secret_mapping', $attributes);
671+
if ($userSecret === '') {
672+
$this->logger->debug('Got no user_secret from idp', ['app' => 'user_saml']);
673+
} else {
674+
$this->logger->debug('Got user_secret from idp', ['app' => 'user_saml']);
675+
return $userSecret;
676+
}
677+
} catch (\InvalidArgumentException $e) {
678+
$this->logger->debug('No user_secret mapping configured', ['app' => 'user_saml']);
679+
}
680+
return null;
681+
}
682+
611683
public function updateAttributes($uid,
612684
array $attributes) {
613685
$user = $this->userManager->get($uid);
@@ -679,11 +751,16 @@ public function updateAttributes($uid,
679751
$groupManager->get($group)->removeUser($user);
680752
}
681753
}
754+
755+
$userSecret = $this->getUserSecret($uid, $attributes);
756+
if ($userSecret !== null) {
757+
if (!$this->checkUserSecretHash($uid, $userSecret)) {
758+
$this->updateUserSecretHash($uid, $userSecret);
759+
}
760+
}
682761
}
683762
}
684763

685-
686-
687764
public function countUsers() {
688765
$query = $this->db->getQueryBuilder();
689766
$query->select($query->func()->count('uid'))

0 commit comments

Comments
 (0)