mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
feat(profile): Add an API to get the profile field data
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
parent
14868ac0ee
commit
9ed33cf6aa
9 changed files with 854 additions and 31 deletions
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,6 +202,33 @@ 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 identifier like Europe/Berlin or America/North_Dakota/Beulah
|
||||
* timezone: string,
|
||||
* // Offset in seconds, negative when behind UTC, positive otherwise
|
||||
* timezoneOffset: int,
|
||||
* }
|
||||
*
|
||||
*/
|
||||
class ResponseDefinitions {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -317,6 +317,104 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"description": "Timezone identifier like Europe/Berlin or America/North_Dakota/Beulah"
|
||||
},
|
||||
"timezoneOffset": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Offset in seconds, negative when behind UTC, positive otherwise"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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 +3193,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": {
|
||||
|
|
|
|||
|
|
@ -317,6 +317,104 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"description": "Timezone identifier like Europe/Berlin or America/North_Dakota/Beulah"
|
||||
},
|
||||
"timezoneOffset": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Offset in seconds, negative when behind UTC, positive otherwise"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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 +3193,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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
226
openapi.json
226
openapi.json
|
|
@ -355,6 +355,104 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"CoreProfileAction": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"icon",
|
||||
"title",
|
||||
"target"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"CoreProfileData": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/CoreProfileFields"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"timezone",
|
||||
"timezoneOffset"
|
||||
],
|
||||
"properties": {
|
||||
"timezone": {
|
||||
"type": "string",
|
||||
"description": "Timezone identifier like Europe/Berlin or America/North_Dakota/Beulah"
|
||||
},
|
||||
"timezoneOffset": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Offset in seconds, negative when behind UTC, positive otherwise"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CoreProfileFields": {
|
||||
"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/CoreProfileAction"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"CorePublicCapabilities": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -6607,6 +6705,134 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"get": {
|
||||
"operationId": "core-profile_api-get-profile-fields",
|
||||
"summary": "Get profile fields for another user",
|
||||
"tags": [
|
||||
"core/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/CoreProfileData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue