From 1671bf3ef219dd641b51b4d154ea701c6a7cf6b9 Mon Sep 17 00:00:00 2001
From: Jonas <jonas@freesources.org>
Date: Mon, 8 Jul 2024 11:29:26 +0200
Subject: feat(Reference): Add public API endpoints to get references

Calling the public API endpoints will check for matching registered
reference providers that implement `IPublicReferenceProvider` and call
their respective functions. If no matching provider is found, the
default `LinkReferenceProvider` will be used to provide open graph data.

The frontend reference widget components will call these endpoints from
unauthorized sessions, e.g. in public shares.

If present, the sharing token of the origin URL is passed to
`resolveReferencePublic()` as additional information for the reference
provider to determine the access scope. This allows the respective
reference providers to determine whether the origin share has access to
the linked resource.

`getCacheKeyPublic` also gets the sharing token so it can scope the cached
entry to it.

Contributes to #45978

Signed-off-by: Jonas <jonas@freesources.org>
---
 core/Controller/ReferenceApiController.php |  89 ++++++++
 core/openapi-full.json                     | 312 +++++++++++++++++++++++++++++
 core/openapi.json                          | 312 +++++++++++++++++++++++++++++
 3 files changed, 713 insertions(+)

(limited to 'core')

diff --git a/core/Controller/ReferenceApiController.php b/core/Controller/ReferenceApiController.php
index 77b4b43fa3a..a9d15ed4231 100644
--- a/core/Controller/ReferenceApiController.php
+++ b/core/Controller/ReferenceApiController.php
@@ -10,6 +10,7 @@ namespace OC\Core\Controller;
 
 use OC\Core\ResponseDefinitions;
 use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
 use OCP\AppFramework\Http\Attribute\ApiRoute;
 use OCP\AppFramework\Http\DataResponse;
 use OCP\Collaboration\Reference\IDiscoverableReferenceProvider;
@@ -22,6 +23,8 @@ use OCP\IRequest;
  * @psalm-import-type CoreReferenceProvider from ResponseDefinitions
  */
 class ReferenceApiController extends \OCP\AppFramework\OCSController {
+	private const LIMIT_MAX = 15;
+
 	public function __construct(
 		string $appName,
 		IRequest $request,
@@ -62,6 +65,39 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
 		]);
 	}
 
+	/**
+	 * @PublicPage
+	 *
+	 * Extract references from a text
+	 *
+	 * @param string $text Text to extract from
+	 * @param string $sharingToken Token of the public share
+	 * @param bool $resolve Resolve the references
+	 * @param int $limit Maximum amount of references to extract, limited to 15
+	 * @return DataResponse<Http::STATUS_OK, array{references: array<string, CoreReference|null>}, array{}>
+	 *
+	 * 200: References returned
+	 */
+	#[ApiRoute(verb: 'POST', url: '/extractPublic', root: '/references')]
+	#[AnonRateLimit(limit: 10, period: 120)]
+	public function extractPublic(string $text, string $sharingToken, bool $resolve = false, int $limit = 1): DataResponse {
+		$references = $this->referenceManager->extractReferences($text);
+
+		$result = [];
+		$index = 0;
+		foreach ($references as $reference) {
+			if ($index++ >= min($limit, self::LIMIT_MAX)) {
+				break;
+			}
+
+			$result[$reference] = $resolve ? $this->referenceManager->resolveReference($reference, true, $sharingToken)?->jsonSerialize() : null;
+		}
+
+		return new DataResponse([
+			'references' => $result
+		]);
+	}
+
 	/**
 	 * @NoAdminRequired
 	 *
@@ -73,6 +109,7 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
 	 * 200: Reference returned
 	 */
 	#[ApiRoute(verb: 'GET', url: '/resolve', root: '/references')]
+	#[AnonRateLimit(limit: 10, period: 120)]
 	public function resolveOne(string $reference): DataResponse {
 		/** @var ?CoreReference $resolvedReference */
 		$resolvedReference = $this->referenceManager->resolveReference(trim($reference))?->jsonSerialize();
@@ -82,6 +119,28 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
 		return $response;
 	}
 
+	/**
+	 * @PublicPage
+	 *
+	 * Resolve from a public page
+	 *
+	 * @param string $reference Reference to resolve
+	 * @param string $sharingToken Token of the public share
+	 * @return DataResponse<Http::STATUS_OK, array{references: array<string, ?CoreReference>}, array{}>
+	 *
+	 * 200: Reference returned
+	 */
+	#[ApiRoute(verb: 'GET', url: '/resolvePublic', root: '/references')]
+	#[AnonRateLimit(limit: 10, period: 120)]
+	public function resolveOnePublic(string $reference, string $sharingToken): DataResponse {
+		/** @var ?CoreReference $resolvedReference */
+		$resolvedReference = $this->referenceManager->resolveReference(trim($reference), true, trim($sharingToken))?->jsonSerialize();
+
+		$response = new DataResponse(['references' => [$reference => $resolvedReference]]);
+		$response->cacheFor(3600, false, true);
+		return $response;
+	}
+
 	/**
 	 * @NoAdminRequired
 	 *
@@ -110,6 +169,36 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
 		]);
 	}
 
+	/**
+	 * @PublicPage
+	 *
+	 * Resolve multiple references from a public page
+	 *
+	 * @param string[] $references References to resolve
+	 * @param string $sharingToken Token of the public share
+	 * @param int $limit Maximum amount of references to resolve, limited to 15
+	 * @return DataResponse<Http::STATUS_OK, array{references: array<string, CoreReference|null>}, array{}>
+	 *
+	 * 200: References returned
+	 */
+	#[ApiRoute(verb: 'POST', url: '/resolvePublic', root: '/references')]
+	#[AnonRateLimit(limit: 10, period: 120)]
+	public function resolvePublic(array $references, string $sharingToken, int $limit = 1): DataResponse {
+		$result = [];
+		$index = 0;
+		foreach ($references as $reference) {
+			if ($index++ >= min($limit, self::LIMIT_MAX)) {
+				break;
+			}
+
+			$result[$reference] = $this->referenceManager->resolveReference($reference, true, $sharingToken)?->jsonSerialize();
+		}
+
+		return new DataResponse([
+			'references' => $result
+		]);
+	}
+
 	/**
 	 * @NoAdminRequired
 	 *
diff --git a/core/openapi-full.json b/core/openapi-full.json
index b70e82c3ee8..a62e587bf06 100644
--- a/core/openapi-full.json
+++ b/core/openapi-full.json
@@ -3059,6 +3059,115 @@
                 }
             }
         },
+        "/ocs/v2.php/references/extractPublic": {
+            "post": {
+                "operationId": "reference_api-extract-public",
+                "summary": "Extract references from a text",
+                "tags": [
+                    "reference_api"
+                ],
+                "security": [
+                    {},
+                    {
+                        "bearer_auth": []
+                    },
+                    {
+                        "basic_auth": []
+                    }
+                ],
+                "requestBody": {
+                    "required": true,
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "type": "object",
+                                "required": [
+                                    "text",
+                                    "sharingToken"
+                                ],
+                                "properties": {
+                                    "text": {
+                                        "type": "string",
+                                        "description": "Text to extract from"
+                                    },
+                                    "sharingToken": {
+                                        "type": "string",
+                                        "description": "Token of the public share"
+                                    },
+                                    "resolve": {
+                                        "type": "boolean",
+                                        "default": false,
+                                        "description": "Resolve the references"
+                                    },
+                                    "limit": {
+                                        "type": "integer",
+                                        "format": "int64",
+                                        "default": 1,
+                                        "description": "Maximum amount of references to extract, limited to 15"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "parameters": [
+                    {
+                        "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": "References returned",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {
+                                                    "type": "object",
+                                                    "required": [
+                                                        "references"
+                                                    ],
+                                                    "properties": {
+                                                        "references": {
+                                                            "type": "object",
+                                                            "additionalProperties": {
+                                                                "$ref": "#/components/schemas/Reference",
+                                                                "nullable": true
+                                                            }
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        },
         "/ocs/v2.php/references/resolve": {
             "get": {
                 "operationId": "reference_api-resolve-one",
@@ -3250,6 +3359,209 @@
                 }
             }
         },
+        "/ocs/v2.php/references/resolvePublic": {
+            "get": {
+                "operationId": "reference_api-resolve-one-public",
+                "summary": "Resolve from a public page",
+                "tags": [
+                    "reference_api"
+                ],
+                "security": [
+                    {},
+                    {
+                        "bearer_auth": []
+                    },
+                    {
+                        "basic_auth": []
+                    }
+                ],
+                "requestBody": {
+                    "required": true,
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "type": "object",
+                                "required": [
+                                    "reference",
+                                    "sharingToken"
+                                ],
+                                "properties": {
+                                    "reference": {
+                                        "type": "string",
+                                        "description": "Reference to resolve"
+                                    },
+                                    "sharingToken": {
+                                        "type": "string",
+                                        "description": "Token of the public share"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "parameters": [
+                    {
+                        "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": "Reference returned",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {
+                                                    "type": "object",
+                                                    "required": [
+                                                        "references"
+                                                    ],
+                                                    "properties": {
+                                                        "references": {
+                                                            "type": "object",
+                                                            "additionalProperties": {
+                                                                "$ref": "#/components/schemas/Reference",
+                                                                "nullable": true
+                                                            }
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "post": {
+                "operationId": "reference_api-resolve-public",
+                "summary": "Resolve multiple references from a public page",
+                "tags": [
+                    "reference_api"
+                ],
+                "security": [
+                    {},
+                    {
+                        "bearer_auth": []
+                    },
+                    {
+                        "basic_auth": []
+                    }
+                ],
+                "requestBody": {
+                    "required": true,
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "type": "object",
+                                "required": [
+                                    "references",
+                                    "sharingToken"
+                                ],
+                                "properties": {
+                                    "references": {
+                                        "type": "array",
+                                        "description": "References to resolve",
+                                        "items": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "sharingToken": {
+                                        "type": "string",
+                                        "description": "Token of the public share"
+                                    },
+                                    "limit": {
+                                        "type": "integer",
+                                        "format": "int64",
+                                        "default": 1,
+                                        "description": "Maximum amount of references to resolve, limited to 15"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "parameters": [
+                    {
+                        "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": "References returned",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {
+                                                    "type": "object",
+                                                    "required": [
+                                                        "references"
+                                                    ],
+                                                    "properties": {
+                                                        "references": {
+                                                            "type": "object",
+                                                            "additionalProperties": {
+                                                                "$ref": "#/components/schemas/Reference",
+                                                                "nullable": true
+                                                            }
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        },
         "/ocs/v2.php/references/providers": {
             "get": {
                 "operationId": "reference_api-get-providers-info",
diff --git a/core/openapi.json b/core/openapi.json
index 3310a03b89d..51698f6d430 100644
--- a/core/openapi.json
+++ b/core/openapi.json
@@ -3059,6 +3059,115 @@
                 }
             }
         },
+        "/ocs/v2.php/references/extractPublic": {
+            "post": {
+                "operationId": "reference_api-extract-public",
+                "summary": "Extract references from a text",
+                "tags": [
+                    "reference_api"
+                ],
+                "security": [
+                    {},
+                    {
+                        "bearer_auth": []
+                    },
+                    {
+                        "basic_auth": []
+                    }
+                ],
+                "requestBody": {
+                    "required": true,
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "type": "object",
+                                "required": [
+                                    "text",
+                                    "sharingToken"
+                                ],
+                                "properties": {
+                                    "text": {
+                                        "type": "string",
+                                        "description": "Text to extract from"
+                                    },
+                                    "sharingToken": {
+                                        "type": "string",
+                                        "description": "Token of the public share"
+                                    },
+                                    "resolve": {
+                                        "type": "boolean",
+                                        "default": false,
+                                        "description": "Resolve the references"
+                                    },
+                                    "limit": {
+                                        "type": "integer",
+                                        "format": "int64",
+                                        "default": 1,
+                                        "description": "Maximum amount of references to extract, limited to 15"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "parameters": [
+                    {
+                        "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": "References returned",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {
+                                                    "type": "object",
+                                                    "required": [
+                                                        "references"
+                                                    ],
+                                                    "properties": {
+                                                        "references": {
+                                                            "type": "object",
+                                                            "additionalProperties": {
+                                                                "$ref": "#/components/schemas/Reference",
+                                                                "nullable": true
+                                                            }
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        },
         "/ocs/v2.php/references/resolve": {
             "get": {
                 "operationId": "reference_api-resolve-one",
@@ -3250,6 +3359,209 @@
                 }
             }
         },
+        "/ocs/v2.php/references/resolvePublic": {
+            "get": {
+                "operationId": "reference_api-resolve-one-public",
+                "summary": "Resolve from a public page",
+                "tags": [
+                    "reference_api"
+                ],
+                "security": [
+                    {},
+                    {
+                        "bearer_auth": []
+                    },
+                    {
+                        "basic_auth": []
+                    }
+                ],
+                "requestBody": {
+                    "required": true,
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "type": "object",
+                                "required": [
+                                    "reference",
+                                    "sharingToken"
+                                ],
+                                "properties": {
+                                    "reference": {
+                                        "type": "string",
+                                        "description": "Reference to resolve"
+                                    },
+                                    "sharingToken": {
+                                        "type": "string",
+                                        "description": "Token of the public share"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "parameters": [
+                    {
+                        "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": "Reference returned",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {
+                                                    "type": "object",
+                                                    "required": [
+                                                        "references"
+                                                    ],
+                                                    "properties": {
+                                                        "references": {
+                                                            "type": "object",
+                                                            "additionalProperties": {
+                                                                "$ref": "#/components/schemas/Reference",
+                                                                "nullable": true
+                                                            }
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "post": {
+                "operationId": "reference_api-resolve-public",
+                "summary": "Resolve multiple references from a public page",
+                "tags": [
+                    "reference_api"
+                ],
+                "security": [
+                    {},
+                    {
+                        "bearer_auth": []
+                    },
+                    {
+                        "basic_auth": []
+                    }
+                ],
+                "requestBody": {
+                    "required": true,
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "type": "object",
+                                "required": [
+                                    "references",
+                                    "sharingToken"
+                                ],
+                                "properties": {
+                                    "references": {
+                                        "type": "array",
+                                        "description": "References to resolve",
+                                        "items": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "sharingToken": {
+                                        "type": "string",
+                                        "description": "Token of the public share"
+                                    },
+                                    "limit": {
+                                        "type": "integer",
+                                        "format": "int64",
+                                        "default": 1,
+                                        "description": "Maximum amount of references to resolve, limited to 15"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "parameters": [
+                    {
+                        "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": "References returned",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {
+                                                    "type": "object",
+                                                    "required": [
+                                                        "references"
+                                                    ],
+                                                    "properties": {
+                                                        "references": {
+                                                            "type": "object",
+                                                            "additionalProperties": {
+                                                                "$ref": "#/components/schemas/Reference",
+                                                                "nullable": true
+                                                            }
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        },
         "/ocs/v2.php/references/providers": {
             "get": {
                 "operationId": "reference_api-get-providers-info",
-- 
cgit v1.2.3