aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/Controller/ProfileApiController.php74
-rw-r--r--core/ResponseDefinitions.php27
-rw-r--r--core/openapi-full.json226
-rw-r--r--core/openapi.json226
4 files changed, 547 insertions, 6 deletions
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..5fb2502c388 100644
--- a/core/ResponseDefinitions.php
+++ b/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 {
}
diff --git a/core/openapi-full.json b/core/openapi-full.json
index 78bf7336ce8..b67bceff1d2 100644
--- a/core/openapi-full.json
+++ b/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": {
diff --git a/core/openapi.json b/core/openapi.json
index 08631a19118..696b674d514 100644
--- a/core/openapi.json
+++ b/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": {