aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2025-04-23 10:16:06 +0200
committerAndy Scherzinger <info@andy-scherzinger.de>2025-04-24 00:50:11 +0200
commit3923eaba04acee4b1026f41c73d5a1f44971929d (patch)
treeb1c59f917a9e18195f40d03683fcf0dfba82ed1b
parentb41ba29d19e32c2b78f11169aad91ae1f1547a1f (diff)
downloadnextcloud-server-backport/52366/stable31.tar.gz
nextcloud-server-backport/52366/stable31.zip
feat(profile): Add an API to get the profile field databackport/52366/stable31
Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r--build/integration/features/bootstrap/Provisioning.php79
-rw-r--r--build/integration/features/provisioning-v1.feature17
-rw-r--r--core/Controller/ProfileApiController.php74
-rw-r--r--core/ResponseDefinitions.php25
-rw-r--r--core/openapi-full.json224
-rw-r--r--core/openapi.json224
-rw-r--r--lib/private/Profile/ProfileManager.php6
-rw-r--r--lib/public/Profile/IProfileManager.php4
8 files changed, 622 insertions, 31 deletions
diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php
index 9e3e8ccfd8b..446ffc0318e 100644
--- a/build/integration/features/bootstrap/Provisioning.php
+++ b/build/integration/features/bootstrap/Provisioning.php
@@ -4,6 +4,8 @@
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
+use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Message\ResponseInterface;
use PHPUnit\Framework\Assert;
@@ -124,7 +126,7 @@ trait Provisioning {
* @Then /^user "([^"]*)" has$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
public function userHasSetting($user, $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
@@ -145,12 +147,43 @@ trait Provisioning {
if (isset($value['element']) && in_array($setting[0], ['additional_mail', 'additional_mailScope'], true)) {
$expectedValues = explode(';', $setting[1]);
foreach ($expectedValues as $expected) {
- Assert::assertTrue(in_array($expected, $value['element'], true));
+ Assert::assertTrue(in_array($expected, $value['element'], true), 'Data wrong for field: ' . $setting[0]);
}
} elseif (isset($value[0])) {
- Assert::assertEqualsCanonicalizing($setting[1], $value[0]);
+ Assert::assertEqualsCanonicalizing($setting[1], $value[0], 'Data wrong for field: ' . $setting[0]);
} else {
- Assert::assertEquals('', $setting[1]);
+ Assert::assertEquals('', $setting[1], 'Data wrong for field: ' . $setting[0]);
+ }
+ }
+ }
+
+ /**
+ * @Then /^user "([^"]*)" has the following profile data$/
+ */
+ public function userHasProfileData(string $user, ?TableNode $settings): void {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/profile/$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',
+ 'Accept' => 'application/json',
+ ];
+
+ $response = $client->get($fullUrl, $options);
+ $body = $response->getBody()->getContents();
+ $data = json_decode($body, true);
+ $data = $data['ocs']['data'];
+ foreach ($settings->getRows() as $setting) {
+ Assert::assertArrayHasKey($setting[0], $data, 'Profile data field missing: ' . $setting[0]);
+ if ($setting[1] === 'NULL') {
+ Assert::assertNull($data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]);
+ } else {
+ Assert::assertEquals($setting[1], $data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]);
}
}
}
@@ -159,7 +192,7 @@ trait Provisioning {
* @Then /^group "([^"]*)" has$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
public function groupHasSetting($group, $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/groups/details?search=$group";
@@ -191,7 +224,7 @@ trait Provisioning {
* @Then /^user "([^"]*)" has editable fields$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $fields
+ * @param TableNode|null $fields
*/
public function userHasEditableFields($user, $fields) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/user/fields";
@@ -221,9 +254,9 @@ trait Provisioning {
* @Then /^search users by phone for region "([^"]*)" with$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
- public function searchUserByPhone($region, \Behat\Gherkin\Node\TableNode $searchTable) {
+ public function searchUserByPhone($region, TableNode $searchTable) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/search/by-phone";
$client = new Client();
$options = [];
@@ -624,10 +657,10 @@ trait Provisioning {
/**
* @Then /^users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function theUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRows();
$usersSimplified = $this->simplifyArray($users);
$respondedArray = $this->getArrayOfUsersResponded($this->response);
@@ -637,10 +670,10 @@ trait Provisioning {
/**
* @Then /^phone matches returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function thePhoneUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRowsHash();
$listCheckedElements = simplexml_load_string($this->response->getBody())->data;
$respondedArray = json_decode(json_encode($listCheckedElements), true);
@@ -650,10 +683,10 @@ trait Provisioning {
/**
* @Then /^detailed users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function theDetailedUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRows();
$usersSimplified = $this->simplifyArray($users);
$respondedArray = $this->getArrayOfDetailedUsersResponded($this->response);
@@ -664,10 +697,10 @@ trait Provisioning {
/**
* @Then /^groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theGroupsShouldBe($groupsList) {
- if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($groupsList instanceof TableNode) {
$groups = $groupsList->getRows();
$groupsSimplified = $this->simplifyArray($groups);
$respondedArray = $this->getArrayOfGroupsResponded($this->response);
@@ -677,10 +710,10 @@ trait Provisioning {
/**
* @Then /^subadmin groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theSubadminGroupsShouldBe($groupsList) {
- if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($groupsList instanceof TableNode) {
$groups = $groupsList->getRows();
$groupsSimplified = $this->simplifyArray($groups);
$respondedArray = $this->getArrayOfSubadminsResponded($this->response);
@@ -690,10 +723,10 @@ trait Provisioning {
/**
* @Then /^apps returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $appList
+ * @param TableNode|null $appList
*/
public function theAppsShouldBe($appList) {
- if ($appList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($appList instanceof TableNode) {
$apps = $appList->getRows();
$appsSimplified = $this->simplifyArray($apps);
$respondedArray = $this->getArrayOfAppsResponded($this->response);
@@ -703,7 +736,7 @@ trait Provisioning {
/**
* @Then /^subadmin users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theSubadminUsersShouldBe($groupsList) {
$this->theSubadminGroupsShouldBe($groupsList);
@@ -882,7 +915,7 @@ trait Provisioning {
* @param string $quota
*/
public function userHasAQuotaOf($user, $quota) {
- $body = new \Behat\Gherkin\Node\TableNode([
+ $body = new TableNode([
0 => ['key', 'quota'],
1 => ['value', $quota],
]);
@@ -950,7 +983,7 @@ trait Provisioning {
/**
* @Then /^user "([^"]*)" has not$/
*/
- public function userHasNotSetting($user, \Behat\Gherkin\Node\TableNode $settings) {
+ public function userHasNotSetting($user, TableNode $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
$client = new Client();
$options = [];
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index 32dfebb5c7d..89fe12eaa1b 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -169,6 +169,23 @@ Feature: provisioning
| address | Foo Bar Town |
| website | https://nextcloud.com |
| twitter | Nextcloud |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | organisation |
+ | value | Nextcloud GmbH |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | role |
+ | value | Engineer |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has the following profile data
+ | userId | brand-new-user |
+ | displayname | Brand New User |
+ | organisation | Nextcloud GmbH |
+ | role | Engineer |
+ | address | Foo Bar Town |
+ | timezone | UTC |
+ | timezoneOffset | 0 |
+ | pronouns | NULL |
Scenario: Edit a user account properties scopes
Given user "brand-new-user" exists
diff --git a/core/Controller/ProfileApiController.php b/core/Controller/ProfileApiController.php
index c807ecb72d4..02979cb1649 100644
--- a/core/Controller/ProfileApiController.php
+++ b/core/Controller/ProfileApiController.php
@@ -10,9 +10,11 @@ declare(strict_types=1);
namespace OC\Core\Controller;
use OC\Core\Db\ProfileConfigMapper;
+use OC\Core\ResponseDefinitions;
use OC\Profile\ProfileManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\Attribute\UserRateLimit;
@@ -21,17 +23,27 @@ use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
use OCP\IRequest;
+use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
+use OCP\Share\IManager;
+/**
+ * @psalm-import-type CoreProfileData from ResponseDefinitions
+ */
class ProfileApiController extends OCSController {
public function __construct(
IRequest $request,
+ private IConfig $config,
+ private ITimeFactory $timeFactory,
private ProfileConfigMapper $configMapper,
private ProfileManager $profileManager,
private IUserManager $userManager,
private IUserSession $userSession,
+ private IManager $shareManager,
) {
parent::__construct('core', $request);
}
@@ -57,14 +69,13 @@ class ProfileApiController extends OCSController {
#[ApiRoute(verb: 'PUT', url: '/{targetUserId}', root: '/profile')]
public function setVisibility(string $targetUserId, string $paramId, string $visibility): DataResponse {
$requestingUser = $this->userSession->getUser();
- $targetUser = $this->userManager->get($targetUserId);
-
- if (!$this->userManager->userExists($targetUserId)) {
- throw new OCSNotFoundException('Account does not exist');
+ if ($requestingUser->getUID() !== $targetUserId) {
+ throw new OCSForbiddenException('People can only edit their own visibility settings');
}
- if ($requestingUser !== $targetUser) {
- throw new OCSForbiddenException('People can only edit their own visibility settings');
+ $targetUser = $this->userManager->get($targetUserId);
+ if (!$targetUser instanceof IUser) {
+ throw new OCSNotFoundException('Account does not exist');
}
// Ensure that a profile config is created in the database
@@ -80,4 +91,55 @@ class ProfileApiController extends OCSController {
return new DataResponse();
}
+
+ /**
+ * Get profile fields for another user
+ *
+ * @param string $targetUserId ID of the user
+ * @return DataResponse<Http::STATUS_OK, CoreProfileData, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, null, array{}>
+ *
+ * 200: Profile data returned successfully
+ * 400: Profile is disabled
+ * 404: Account not found or disabled
+ */
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/{targetUserId}', root: '/profile')]
+ #[BruteForceProtection(action: 'user')]
+ #[UserRateLimit(limit: 30, period: 120)]
+ public function getProfileFields(string $targetUserId): DataResponse {
+ $targetUser = $this->userManager->get($targetUserId);
+ if (!$targetUser instanceof IUser) {
+ $response = new DataResponse(null, Http::STATUS_NOT_FOUND);
+ $response->throttle();
+ return $response;
+ }
+ if (!$targetUser->isEnabled()) {
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
+ }
+
+ if (!$this->profileManager->isProfileEnabled($targetUser)) {
+ return new DataResponse(null, Http::STATUS_BAD_REQUEST);
+ }
+
+ $requestingUser = $this->userSession->getUser();
+ if ($targetUser !== $requestingUser) {
+ if (!$this->shareManager->currentUserCanEnumerateTargetUser($requestingUser, $targetUser)) {
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
+ }
+ }
+
+ $profileFields = $this->profileManager->getProfileFields($targetUser, $requestingUser);
+
+ // Extend the profile information with timezone of the user
+ $timezoneStringTarget = $this->config->getUserValue($targetUser->getUID(), 'core', 'timezone') ?: $this->config->getSystemValueString('default_timezone', 'UTC');
+ try {
+ $timezoneTarget = new \DateTimeZone($timezoneStringTarget);
+ } catch (\Throwable) {
+ $timezoneTarget = new \DateTimeZone('UTC');
+ }
+ $profileFields['timezone'] = $timezoneTarget->getName(); // E.g. Europe/Berlin
+ $profileFields['timezoneOffset'] = $timezoneTarget->getOffset($this->timeFactory->now()); // In seconds E.g. 7200
+
+ return new DataResponse($profileFields);
+ }
}
diff --git a/core/ResponseDefinitions.php b/core/ResponseDefinitions.php
index 3b344e6af99..55e27fd927d 100644
--- a/core/ResponseDefinitions.php
+++ b/core/ResponseDefinitions.php
@@ -202,6 +202,31 @@ namespace OC\Core;
* endedAt: ?int,
* }
*
+ * @psalm-type CoreProfileAction = array{
+ * id: string,
+ * icon: string,
+ * title: string,
+ * target: ?string,
+ * }
+ *
+ * @psalm-type CoreProfileFields = array{
+ * userId: string,
+ * address?: string|null,
+ * biography?: string|null,
+ * displayname?: string|null,
+ * headline?: string|null,
+ * isUserAvatarVisible?: bool,
+ * organisation?: string|null,
+ * pronouns?: string|null,
+ * role?: string|null,
+ * actions: list<CoreProfileAction>,
+ * }
+ *
+ * @psalm-type CoreProfileData = CoreProfileFields&array{
+ * timezone: string,
+ * timezoneOffset: int,
+ * }
+ *
*/
class ResponseDefinitions {
}
diff --git a/core/openapi-full.json b/core/openapi-full.json
index 78bf7336ce8..3d6a28f9251 100644
--- a/core/openapi-full.json
+++ b/core/openapi-full.json
@@ -317,6 +317,102 @@
}
}
},
+ "ProfileAction": {
+ "type": "object",
+ "required": [
+ "id",
+ "icon",
+ "title",
+ "target"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "icon": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "target": {
+ "type": "string",
+ "nullable": true
+ }
+ }
+ },
+ "ProfileData": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/ProfileFields"
+ },
+ {
+ "type": "object",
+ "required": [
+ "timezone",
+ "timezoneOffset"
+ ],
+ "properties": {
+ "timezone": {
+ "type": "string"
+ },
+ "timezoneOffset": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ }
+ ]
+ },
+ "ProfileFields": {
+ "type": "object",
+ "required": [
+ "userId",
+ "actions"
+ ],
+ "properties": {
+ "userId": {
+ "type": "string"
+ },
+ "address": {
+ "type": "string",
+ "nullable": true
+ },
+ "biography": {
+ "type": "string",
+ "nullable": true
+ },
+ "displayname": {
+ "type": "string",
+ "nullable": true
+ },
+ "headline": {
+ "type": "string",
+ "nullable": true
+ },
+ "isUserAvatarVisible": {
+ "type": "boolean"
+ },
+ "organisation": {
+ "type": "string",
+ "nullable": true
+ },
+ "pronouns": {
+ "type": "string",
+ "nullable": true
+ },
+ "role": {
+ "type": "string",
+ "nullable": true
+ },
+ "actions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ProfileAction"
+ }
+ }
+ }
+ },
"PublicCapabilities": {
"type": "object",
"required": [
@@ -3095,6 +3191,134 @@
}
}
}
+ },
+ "get": {
+ "operationId": "profile_api-get-profile-fields",
+ "summary": "Get profile fields for another user",
+ "tags": [
+ "profile_api"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "targetUserId",
+ "in": "path",
+ "description": "ID of the user",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Profile data returned successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/ProfileData"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Profile is disabled",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "nullable": true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Account not found or disabled",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "nullable": true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
},
"/ocs/v2.php/references/extract": {
diff --git a/core/openapi.json b/core/openapi.json
index 08631a19118..9a381da7e22 100644
--- a/core/openapi.json
+++ b/core/openapi.json
@@ -317,6 +317,102 @@
}
}
},
+ "ProfileAction": {
+ "type": "object",
+ "required": [
+ "id",
+ "icon",
+ "title",
+ "target"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "icon": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "target": {
+ "type": "string",
+ "nullable": true
+ }
+ }
+ },
+ "ProfileData": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/ProfileFields"
+ },
+ {
+ "type": "object",
+ "required": [
+ "timezone",
+ "timezoneOffset"
+ ],
+ "properties": {
+ "timezone": {
+ "type": "string"
+ },
+ "timezoneOffset": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ }
+ ]
+ },
+ "ProfileFields": {
+ "type": "object",
+ "required": [
+ "userId",
+ "actions"
+ ],
+ "properties": {
+ "userId": {
+ "type": "string"
+ },
+ "address": {
+ "type": "string",
+ "nullable": true
+ },
+ "biography": {
+ "type": "string",
+ "nullable": true
+ },
+ "displayname": {
+ "type": "string",
+ "nullable": true
+ },
+ "headline": {
+ "type": "string",
+ "nullable": true
+ },
+ "isUserAvatarVisible": {
+ "type": "boolean"
+ },
+ "organisation": {
+ "type": "string",
+ "nullable": true
+ },
+ "pronouns": {
+ "type": "string",
+ "nullable": true
+ },
+ "role": {
+ "type": "string",
+ "nullable": true
+ },
+ "actions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ProfileAction"
+ }
+ }
+ }
+ },
"PublicCapabilities": {
"type": "object",
"required": [
@@ -3095,6 +3191,134 @@
}
}
}
+ },
+ "get": {
+ "operationId": "profile_api-get-profile-fields",
+ "summary": "Get profile fields for another user",
+ "tags": [
+ "profile_api"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "targetUserId",
+ "in": "path",
+ "description": "ID of the user",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Profile data returned successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/ProfileData"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Profile is disabled",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "nullable": true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Account not found or disabled",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "nullable": true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
},
"/ocs/v2.php/references/extract": {
diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php
index 9d3d94fab21..13860286356 100644
--- a/lib/private/Profile/ProfileManager.php
+++ b/lib/private/Profile/ProfileManager.php
@@ -12,6 +12,7 @@ namespace OC\Profile;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Core\Db\ProfileConfig;
use OC\Core\Db\ProfileConfigMapper;
+use OC\Core\ResponseDefinitions;
use OC\KnownUser\KnownUserService;
use OC\Profile\Actions\EmailAction;
use OC\Profile\Actions\FediverseAction;
@@ -33,6 +34,9 @@ use Psr\Log\LoggerInterface;
use function array_flip;
use function usort;
+/**
+ * @psalm-import-type CoreProfileFields from ResponseDefinitions
+ */
class ProfileManager implements IProfileManager {
/** @var ILinkAction[] */
private array $actions = [];
@@ -223,7 +227,7 @@ class ProfileManager implements IProfileManager {
/**
* Return the profile parameters of the target user that are visible to the visiting user
* in an associative array
- * @return array{userId: string, address?: string|null, biography?: string|null, displayname?: string|null, headline?: string|null, isUserAvatarVisible?: bool, organisation?: string|null, pronouns?: string|null, role?: string|null, actions: list<array{id: string, icon: string, title: string, target: ?string}>}
+ * @psalm-return CoreProfileFields
*/
public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array {
$account = $this->accountManager->getAccount($targetUser);
diff --git a/lib/public/Profile/IProfileManager.php b/lib/public/Profile/IProfileManager.php
index 0a1e6733e58..f4e90e39d12 100644
--- a/lib/public/Profile/IProfileManager.php
+++ b/lib/public/Profile/IProfileManager.php
@@ -9,10 +9,12 @@ declare(strict_types=1);
namespace OCP\Profile;
+use OC\Core\ResponseDefinitions;
use OCP\Accounts\IAccountManager;
use OCP\IUser;
/**
+ * @psalm-import-type CoreProfileFields from ResponseDefinitions
* @since 28.0.0
*/
interface IProfileManager {
@@ -83,7 +85,7 @@ interface IProfileManager {
* Return the profile parameters of the target user that are visible to the visiting user
* in an associative array
*
- * @return array{userId: string, address?: string|null, biography?: string|null, displayname?: string|null, headline?: string|null, isUserAvatarVisible?: bool, organisation?: string|null, pronouns?: string|null, role?: string|null, actions: list<array{id: string, icon: string, title: string, target: ?string}>}
+ * @psalm-return CoreProfileFields
* @since 28.0.0
*/
public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array;