aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Schiwon <blizzz@arthur-schiwon.de>2021-06-11 16:14:01 +0200
committerArthur Schiwon <blizzz@arthur-schiwon.de>2021-06-30 00:41:11 +0200
commitd109d4f58198b2ac35d590e59a45f948da23ca8e (patch)
tree8569a402433cf348d1734d806f0881e4bb8f425a
parent3da9064f9df9b09c4351bb28255afcbc96156a26 (diff)
downloadnextcloud-server-d109d4f58198b2ac35d590e59a45f948da23ca8e.tar.gz
nextcloud-server-d109d4f58198b2ac35d590e59a45f948da23ca8e.zip
prov api to be able to edit multivalue properties
- adding as usual - deleting and scope setting via additional endpoint Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
-rw-r--r--apps/provisioning_api/appinfo/routes.php1
-rw-r--r--apps/provisioning_api/lib/Controller/UsersController.php98
-rw-r--r--build/integration/features/bootstrap/Provisioning.php49
-rw-r--r--build/integration/features/provisioning-v1.feature75
-rw-r--r--lib/private/Accounts/AccountPropertyCollection.php13
-rw-r--r--lib/public/Accounts/IAccountPropertyCollection.php8
6 files changed, 240 insertions, 4 deletions
diff --git a/apps/provisioning_api/appinfo/routes.php b/apps/provisioning_api/appinfo/routes.php
index 8cf57487d3a..01b6260c1d5 100644
--- a/apps/provisioning_api/appinfo/routes.php
+++ b/apps/provisioning_api/appinfo/routes.php
@@ -54,6 +54,7 @@ return [
['root' => '/cloud', 'name' => 'Users#getEditableFields', 'url' => '/user/fields', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'Users#getEditableFieldsForUser', 'url' => '/user/fields/{userId}', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'Users#editUser', 'url' => '/users/{userId}', 'verb' => 'PUT'],
+ ['root' => '/cloud', 'name' => 'Users#editUserMultiValue', 'url' => '/users/{userId}/{collectionName}', 'verb' => 'PUT', 'requirements' => ['collectionName' => '[^(enable|disable)]']],
['root' => '/cloud', 'name' => 'Users#wipeUserDevices', 'url' => '/users/{userId}/wipe', 'verb' => 'POST'],
['root' => '/cloud', 'name' => 'Users#deleteUser', 'url' => '/users/{userId}', 'verb' => 'DELETE'],
['root' => '/cloud', 'name' => 'Users#enableUser', 'url' => '/users/{userId}/enable', 'verb' => 'PUT'],
diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php
index 6c17648b9ea..709f139d830 100644
--- a/apps/provisioning_api/lib/Controller/UsersController.php
+++ b/apps/provisioning_api/lib/Controller/UsersController.php
@@ -52,6 +52,7 @@ use OC\KnownUser\KnownUserService;
use OC\User\Backend;
use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\Accounts\IAccountManager;
+use OCP\Accounts\IAccountProperty;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
@@ -602,6 +603,85 @@ class UsersController extends AUserData {
}
/**
+ * @throws OCSException
+ */
+ public function editUserMultiValue(
+ string $userId,
+ string $collectionName,
+ string $key,
+ string $value
+ ): DataResponse {
+ $currentLoggedInUser = $this->userSession->getUser();
+ if ($currentLoggedInUser === null) {
+ throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
+ }
+
+ $targetUser = $this->userManager->get($userId);
+ if ($targetUser === null) {
+ throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
+ }
+
+ $permittedFields = [];
+ if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
+ // Editing self (display, email)
+ $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
+ $permittedFields[] = IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX;
+ } else {
+ // Check if admin / subadmin
+ $subAdminManager = $this->groupManager->getSubAdmin();
+ if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())
+ || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
+ // They have permissions over the user
+
+ $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
+ } else {
+ // No rights
+ throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
+ }
+ }
+
+ // Check if permitted to edit this field
+ if (!in_array($collectionName, $permittedFields)) {
+ throw new OCSException('', 103);
+ }
+
+ switch ($collectionName) {
+ case IAccountManager::COLLECTION_EMAIL:
+ $userAccount = $this->accountManager->getAccount($targetUser);
+ $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
+ $mailCollection->removePropertyByValue($key);
+ if ($value !== '') {
+ // "replace on"
+ $mailCollection->addPropertyWithDefaults($value);
+ }
+ $this->accountManager->updateAccount($userAccount);
+ break;
+
+ case IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX:
+ $userAccount = $this->accountManager->getAccount($targetUser);
+ $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
+ $targetProperty = null;
+ foreach ($mailCollection->getProperties() as $property) {
+ if ($property->getValue() === $value) {
+ $targetProperty = $property;
+ break;
+ }
+ }
+ if ($targetProperty instanceof IAccountProperty) {
+ $targetProperty->setScope($value);
+ $this->accountManager->updateAccount($userAccount);
+ } else {
+ throw new OCSException('', 102);
+ }
+ break;
+
+ default:
+ throw new OCSException('', 103);
+ }
+ return new DataResponse();
+ }
+
+ /**
* @NoAdminRequired
* @NoSubAdminRequired
* @PasswordConfirmationRequired
@@ -637,6 +717,8 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
+ $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
+
$permittedFields[] = 'password';
if ($this->config->getSystemValue('force_language', false) === false ||
$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
@@ -675,6 +757,7 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
}
$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
+ $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
$permittedFields[] = 'password';
$permittedFields[] = 'language';
$permittedFields[] = 'locale';
@@ -747,6 +830,21 @@ class UsersController extends AUserData {
throw new OCSException('', 102);
}
break;
+ case IAccountManager::COLLECTION_EMAIL:
+ if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getEMailAddress()) {
+ $userAccount = $this->accountManager->getAccount($targetUser);
+ $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
+ foreach ($mailCollection->getProperties() as $property) {
+ if ($property->getValue() === $value) {
+ break;
+ }
+ }
+ $mailCollection->addPropertyWithDefaults($value);
+ $this->accountManager->updateAccount($userAccount);
+ } else {
+ throw new OCSException('', 102);
+ }
+ break;
case IAccountManager::PROPERTY_PHONE:
case IAccountManager::PROPERTY_ADDRESS:
case IAccountManager::PROPERTY_WEBSITE:
diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php
index 8eab793d66b..3cc1366c52c 100644
--- a/build/integration/features/bootstrap/Provisioning.php
+++ b/build/integration/features/bootstrap/Provisioning.php
@@ -169,13 +169,20 @@ trait Provisioning {
foreach ($settings->getRows() as $setting) {
$value = json_decode(json_encode(simplexml_load_string($response->getBody())->data->{$setting[0]}), 1);
if (isset($value[0])) {
- Assert::assertEquals($setting[1], $value[0], "", 0.0, 10, true);
+ if (in_array($setting[0], ['additional_mail', 'additional_mailScope'], true)) {
+ $expectedValues = explode(';', $setting[1]);
+ foreach ($expectedValues as $expected) {
+ Assert::assertTrue(in_array($expected, $value, true));
+ }
+ } else {
+ Assert::assertEquals($setting[1], $value[0], "", 0.0, 10, true);
+ }
} else {
Assert::assertEquals('', $setting[1]);
}
}
}
-
+
/**
* @Then /^group "([^"]*)" has$/
*
@@ -194,7 +201,7 @@ trait Provisioning {
$options['headers'] = [
'OCS-APIREQUEST' => 'true',
];
-
+
$response = $client->get($fullUrl, $options);
$groupDetails = simplexml_load_string($response->getBody())->data[0]->groups[0]->element;
foreach ($settings->getRows() as $setting) {
@@ -206,7 +213,7 @@ trait Provisioning {
}
}
}
-
+
/**
* @Then /^user "([^"]*)" has editable fields$/
@@ -967,4 +974,38 @@ trait Provisioning {
}
$this->usingServer($previousServer);
}
+
+ /**
+ * @Then /^user "([^"]*)" has not$/
+ */
+ public function userHasNotSetting($user, \Behat\Gherkin\Node\TableNode $settings) {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
+ $client = new Client();
+ $options = [];
+ if ($this->currentUser === 'admin') {
+ $options['auth'] = $this->adminUser;
+ } else {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
+ }
+ $options['headers'] = [
+ 'OCS-APIREQUEST' => 'true',
+ ];
+
+ $response = $client->get($fullUrl, $options);
+ foreach ($settings->getRows() as $setting) {
+ $value = json_decode(json_encode(simplexml_load_string($response->getBody())->data->{$setting[0]}), 1);
+ if (isset($value[0])) {
+ if (in_array($setting[0], ['additional_mail', 'additional_mailScope'], true)) {
+ $expectedValues = explode(';', $setting[1]);
+ foreach ($expectedValues as $expected) {
+ Assert::assertFalse(in_array($expected, $value, true));
+ }
+ } else {
+ Assert::assertNotEquals($setting[1], $value[0], "", 0.0, 10, true);
+ }
+ } else {
+ Assert::assertNotEquals('', $setting[1]);
+ }
+ }
+ }
}
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index d2870e34d48..41041dc91da 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -103,6 +103,16 @@ Feature: provisioning
| value | no-reply@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | no.reply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | noreply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | phone |
| value | +49 711 / 25 24 28-90 |
@@ -127,6 +137,7 @@ Feature: provisioning
| id | brand-new-user |
| displayname | Brand New User |
| email | no-reply@nextcloud.com |
+ | additional_mail | no.reply@nextcloud.com;noreply@nextcloud.com |
| phone | +4971125242890 |
| address | Foo Bar Town |
| website | https://nextcloud.com |
@@ -180,6 +191,33 @@ Feature: provisioning
| displaynameScope | v2-federated |
| avatarScope | v2-local |
+ Scenario: Edit a user account multivalue property scopes
+ Given user "brand-new-user" exists
+ And As an "brand-new-user"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | no.reply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | noreply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user/additional_mailScope" with
+ | key | no.reply@nextcloud.com |
+ | value | v2-federated |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user/additional_mailScope" with
+ | key | noreply@nextcloud.com |
+ | value | v2-published |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has
+ | id | brand-new-user |
+ | additional_mailScope | v2-federated;v2-published |
+
Scenario: Edit a user account properties scopes with invalid or unsupported value
Given user "brand-new-user" exists
And As an "brand-new-user"
@@ -199,6 +237,43 @@ Feature: provisioning
Then the OCS status code should be "102"
And the HTTP status code should be "200"
+ Scenario: Edit a user account multi-value property scopes with invalid or unsupported value
+ Given user "brand-new-user" exists
+ And As an "brand-new-user"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | no.reply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user/additional_mailScope" with
+ | key | no.reply@nextcloud.com |
+ | value | invalid |
+ Then the OCS status code should be "102"
+ And the HTTP status code should be "200"
+
+ Scenario: Delete a user account multi-value property value
+ Given user "brand-new-user" exists
+ And As an "brand-new-user"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | no.reply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | noreply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user/additional_mail" with
+ | key | no.reply@nextcloud.com |
+ | value | |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has
+ | additional_mail | noreply@nextcloud.com |
+ Then user "brand-new-user" has not
+ | additional_mail | no.reply@nextcloud.com |
+
Scenario: An admin cannot edit user account property scopes
Given As an "admin"
And user "brand-new-user" exists
diff --git a/lib/private/Accounts/AccountPropertyCollection.php b/lib/private/Accounts/AccountPropertyCollection.php
index 84e10e6a507..eb92536a6a0 100644
--- a/lib/private/Accounts/AccountPropertyCollection.php
+++ b/lib/private/Accounts/AccountPropertyCollection.php
@@ -27,6 +27,7 @@ declare(strict_types=1);
namespace OC\Accounts;
use InvalidArgumentException;
+use OCP\Accounts\IAccountManager;
use OCP\Accounts\IAccountProperty;
use OCP\Accounts\IAccountPropertyCollection;
@@ -63,6 +64,18 @@ class AccountPropertyCollection implements IAccountPropertyCollection {
return $this;
}
+ public function addPropertyWithDefaults(string $value): IAccountPropertyCollection {
+ $property = new AccountProperty(
+ $this->collectionName,
+ $value,
+ IAccountManager::SCOPE_LOCAL,
+ IAccountManager::NOT_VERIFIED,
+ ''
+ );
+ $this->addProperty($property);
+ return $this;
+ }
+
public function removeProperty(IAccountProperty $property): IAccountPropertyCollection {
$ref = array_search($property, $this->properties, true);
if ($ref !== false) {
diff --git a/lib/public/Accounts/IAccountPropertyCollection.php b/lib/public/Accounts/IAccountPropertyCollection.php
index 9e026f4ce5b..779fb1299b4 100644
--- a/lib/public/Accounts/IAccountPropertyCollection.php
+++ b/lib/public/Accounts/IAccountPropertyCollection.php
@@ -69,6 +69,14 @@ interface IAccountPropertyCollection extends JsonSerializable {
public function addProperty(IAccountProperty $property): IAccountPropertyCollection;
/**
+ * adds a property to this collection with only specifying the value
+ *
+ * @throws InvalidArgumentException
+ * @since 22.0.0
+ */
+ public function addPropertyWithDefaults(string $value): IAccountPropertyCollection;
+
+ /**
* removes a property of this collection
*
* @since 22.0.0