Browse Source

feat(profile): Add an API to get the profile field data

Signed-off-by: Joas Schilling <coding@schilljs.com>
pull/52366/head
Joas Schilling 6 months ago
parent
commit
9ed33cf6aa
No known key found for this signature in database GPG Key ID: F72FA5B49FFA96B0
  1. 79
      build/integration/features/bootstrap/Provisioning.php
  2. 17
      build/integration/features/provisioning-v1.feature
  3. 74
      core/Controller/ProfileApiController.php
  4. 27
      core/ResponseDefinitions.php
  5. 226
      core/openapi-full.json
  6. 226
      core/openapi.json
  7. 6
      lib/private/Profile/ProfileManager.php
  8. 4
      lib/public/Profile/IProfileManager.php
  9. 226
      openapi.json

79
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 = [];

17
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

74
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);
}
}

27
core/ResponseDefinitions.php

@ -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 {
}

226
core/openapi-full.json

@ -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": {

226
core/openapi.json

@ -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": {

6
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);

4
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;

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…
Cancel
Save