From e3989934a2c537b0a6e7c556c9163332bd973e79 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 29 Jan 2025 13:56:02 +0100 Subject: [PATCH 01/23] First create some structure to fill in the content in next commits Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 66 ++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index 34bb1ddc314..3ec00021972 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -339,7 +339,71 @@ Cookies can be set or modified directly on the response class: Responses --------- -Similar to how every controller receives a request object, every controller method has to return a Response. This can be in the form of a Response subclass or in the form of a value that can be handled by a registered responder. +Similar to how every controller receives a request object, every controller method has to return a Response. +This can be in the form of a Response subclass or in the form of a value that can be handled by a registered responder. + +.. _controller_html_responses: + +HTML-based Responses +-------------------- + +Templates +^^^^^^^^^ + +Public page templates +^^^^^^^^^^^^^^^^^^^^^ + + +Data-based responses +-------------------- + +OCS +^^^ + +JSON +^^^^ + +Handling errors +^^^^^^^^^^^^^^^ + +Responders +^^^^^^^^^^ + +Miscellaneous responses +----------------------- + +Redirects +^^^^^^^^^ + +Downloads +^^^^^^^^^ + +Creating custom responses +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Streamed and lazily rendered responses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Security considerations +----------------------- + +Authentication +^^^^^^^^^^^^^^ + +Loosening the default restrictions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rate limiting +^^^^^^^^^^^^^ + +Brute-force protection +^^^^^^^^^^^^^^^^^^^^^^ + +Modifying the content security policy +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +--- JSON ^^^^ From 2f1bc359c41da82bb5f19a4709134299ed2835b1 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 28 Feb 2025 13:02:53 +0100 Subject: [PATCH 02/23] Move section html templates Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 69 ++++++++++++------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index 3ec00021972..c6654be9dd2 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -347,9 +347,43 @@ This can be in the form of a Response subclass or in the form of a value that ca HTML-based Responses -------------------- +.. _controller_template: + Templates ^^^^^^^^^ +A :doc:`template ` can be rendered by returning a TemplateResponse. A TemplateResponse takes the following parameters: + +* **appName**: tells the template engine in which app the template should be located +* **templateName**: the name of the template inside the templates/ folder without the .php extension +* **parameters**: optional array parameters that are available in the template through $_, e.g.:: + + array('key' => 'something') + + can be accessed through:: + + $_['key'] + +* **renderAs**: defaults to *user*, tells Nextcloud if it should include it in the web interface, or in case *blank* is passed solely render the template + +.. code-block:: php + + 'hi'); + return new TemplateResponse($this->appName, $templateName, $parameters); + } + + } + Public page templates ^^^^^^^^^^^^^^^^^^^^^ @@ -532,41 +566,6 @@ Because returning values works fine in case of a success but not in case of fail } -Templates -^^^^^^^^^ - -A :doc:`template ` can be rendered by returning a TemplateResponse. A TemplateResponse takes the following parameters: - -* **appName**: tells the template engine in which app the template should be located -* **templateName**: the name of the template inside the templates/ folder without the .php extension -* **parameters**: optional array parameters that are available in the template through $_, e.g.:: - - array('key' => 'something') - - can be accessed through:: - - $_['key'] - -* **renderAs**: defaults to *user*, tells Nextcloud if it should include it in the web interface, or in case *blank* is passed solely render the template - -.. code-block:: php - - 'hi'); - return new TemplateResponse($this->appName, $templateName, $parameters); - } - - } - Public page templates ^^^^^^^^^^^^^^^^^^^^^ From 9e95840608dd415ff8343b888bb017ab2d64e120 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 28 Feb 2025 13:04:51 +0100 Subject: [PATCH 03/23] Moved section public html templates Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 79 ++++++++++++------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index c6654be9dd2..0a6eecca147 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -387,6 +387,43 @@ A :doc:`template ` can be rendered by returning a TemplateR Public page templates ^^^^^^^^^^^^^^^^^^^^^ +For public pages, that are rendered to users who are not logged in to the +Nextcloud instance, a ``OCP\\AppFramework\\Http\\Template\\PublicTemplateResponse`` should be used, to load the +correct base template. It also allows adding an optional set of actions that +will be shown in the top right corner of the public page. + + +.. code-block:: php + + appName, 'main', []); + $template->setHeaderTitle('Public page'); + $template->setHeaderDetails('some details'); + $template->setHeaderActions([ + new SimpleMenuAction('download', 'Label 1', 'icon-css-class1', 'link-url', 0), + new SimpleMenuAction('share', 'Label 2', 'icon-css-class2', 'link-url', 10), + ]); + return $template; + } + + } + +The header title and subtitle will be rendered in the header, next to the logo. +The action with the highest priority (lowest number) will be used as the +primary action, others will shown in the popover menu on demand. + +A ``OCP\\AppFramework\\Http\\Template\\SimpleMenuAction`` will be a link with an icon added to the menu. App +developers can implement their own types of menu renderings by adding a custom +class implementing the ``OCP\\AppFramework\\Http\\Template\\IMenuAction`` interface. Data-based responses -------------------- @@ -566,48 +603,6 @@ Because returning values works fine in case of a success but not in case of fail } -Public page templates -^^^^^^^^^^^^^^^^^^^^^ - -For public pages, that are rendered to users who are not logged in to the -Nextcloud instance, a ``OCP\\AppFramework\\Http\\Template\\PublicTemplateResponse`` should be used, to load the -correct base template. It also allows adding an optional set of actions that -will be shown in the top right corner of the public page. - - -.. code-block:: php - - appName, 'main', []); - $template->setHeaderTitle('Public page'); - $template->setHeaderDetails('some details'); - $template->setHeaderActions([ - new SimpleMenuAction('download', 'Label 1', 'icon-css-class1', 'link-url', 0), - new SimpleMenuAction('share', 'Label 2', 'icon-css-class2', 'link-url', 10), - ]); - return $template; - } - - } - -The header title and subtitle will be rendered in the header, next to the logo. -The action with the highest priority (lowest number) will be used as the -primary action, others will shown in the popover menu on demand. - -A ``OCP\\AppFramework\\Http\\Template\\SimpleMenuAction`` will be a link with an icon added to the menu. App -developers can implement their own types of menu renderings by adding a custom -class implementing the ``OCP\\AppFramework\\Http\\Template\\IMenuAction`` interface. - - Redirects ^^^^^^^^^ From af0cad8bf47f9a99cdd960ec48fe477917b429a7 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 28 Feb 2025 13:26:49 +0100 Subject: [PATCH 04/23] Move section OCS Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 98 ++++++++++++------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index 0a6eecca147..ad133f55db1 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -428,9 +428,57 @@ class implementing the ``OCP\\AppFramework\\Http\\Template\\IMenuAction`` interf Data-based responses -------------------- +.. _ocscontroller: + OCS ^^^ +.. note:: + This is purely for compatibility reasons. If you are planning to offer an external API, go for a :ref:`REST APIs ` instead. + +In order to ease migration from OCS API routes to the App Framework, an additional controller and response have been added. +To migrate your API you can use the **OCP\\AppFramework\\OCSController** base class and return your data in the form of a DataResponse in the following way: + +.. code-block:: php + + [ + [ + 'name' => 'Share#getShares', + 'url' => '/api/v1/shares', + 'verb' => 'GET', + ], + ], + ]; + +Now your method will be reachable via ``/ocs/v2.php/apps//api/v1/shares`` + JSON ^^^^ @@ -785,56 +833,6 @@ The following policy for instance allows images, audio and videos from other dom } -.. _ocscontroller: - -OCS -^^^ - -.. note:: This is purely for compatibility reasons. If you are planning to offer an external API, go for a :doc:`../digging_deeper/rest_apis` instead. - -In order to ease migration from OCS API routes to the App Framework, an additional controller and response have been added. To migrate your API you can use the **OCP\\AppFramework\\OCSController** base class and return your data in the form of a DataResponse in the following way: - - -.. code-block:: php - - [ - [ - 'name' => 'Share#getShares', - 'url' => '/api/v1/shares', - 'verb' => 'GET', - ], - ], - ]; - -Now your method will be reachable via ``/ocs/v2.php/apps//api/v1/shares`` Handling errors ^^^^^^^^^^^^^^^ From 578cbcef439f95afacedd65183778f61cf7f1ee2 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 28 Feb 2025 15:21:41 +0100 Subject: [PATCH 05/23] Move section JSON Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 79 ++++++++++++------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index ad133f55db1..fcecdabb85e 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -482,6 +482,44 @@ Now your method will be reachable via ``/ocs/v2.php/apps//api/v JSON ^^^^ +Returning JSON is simple, just pass an array to a JSONResponse: + +.. code-block:: php + + 'hi'); + return new JSONResponse($params); + } + + } + +Because returning JSON is such a common task, there's even a shorter way to do this: + +.. code-block:: php + + 'hi'); + } + + } + +Why does this work? Because the dispatcher sees that the controller did not return a subclass of a Response and asks the controller to turn the value into a Response. That's where responders come in. + Handling errors ^^^^^^^^^^^^^^^ @@ -524,47 +562,6 @@ Modifying the content security policy --- -JSON -^^^^ - -Returning JSON is simple, just pass an array to a JSONResponse: - -.. code-block:: php - - 'hi'); - return new JSONResponse($params); - } - - } - -Because returning JSON is such a common task, there's even a shorter way to do this: - -.. code-block:: php - - 'hi'); - } - - } - -Why does this work? Because the dispatcher sees that the controller did not return a subclass of a Response and asks the controller to turn the value into a Response. That's where responders come in. - Responders ^^^^^^^^^^ From e58d1ff44ee00bcb4ec2c4a673aeead6624631f1 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 28 Feb 2025 15:23:19 +0100 Subject: [PATCH 06/23] Move section Responders Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 73 ++++++++++++------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index fcecdabb85e..e46bb14dd49 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -523,44 +523,7 @@ Why does this work? Because the dispatcher sees that the controller did not retu Handling errors ^^^^^^^^^^^^^^^ -Responders -^^^^^^^^^^ - -Miscellaneous responses ------------------------ - -Redirects -^^^^^^^^^ - -Downloads -^^^^^^^^^ - -Creating custom responses -^^^^^^^^^^^^^^^^^^^^^^^^^ - -Streamed and lazily rendered responses -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Security considerations ------------------------ - -Authentication -^^^^^^^^^^^^^^ - -Loosening the default restrictions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Rate limiting -^^^^^^^^^^^^^ - -Brute-force protection -^^^^^^^^^^^^^^^^^^^^^^ - -Modifying the content security policy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - ---- +.. _controller-responders: Responders ^^^^^^^^^^ @@ -647,7 +610,41 @@ Because returning values works fine in case of a success but not in case of fail } +Miscellaneous responses +----------------------- + +Redirects +^^^^^^^^^ + +Downloads +^^^^^^^^^ + +Creating custom responses +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Streamed and lazily rendered responses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Security considerations +----------------------- + +Authentication +^^^^^^^^^^^^^^ + +Loosening the default restrictions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rate limiting +^^^^^^^^^^^^^ + +Brute-force protection +^^^^^^^^^^^^^^^^^^^^^^ +Modifying the content security policy +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +--- Redirects ^^^^^^^^^ From 1693e6ef10a7ca0439a37d26427dd9b951d89bac Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 28 Feb 2025 13:29:15 +0100 Subject: [PATCH 07/23] Moving section handling errors Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 51 ++++++++++++------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index e46bb14dd49..022631d5bfb 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -523,6 +523,31 @@ Why does this work? Because the dispatcher sees that the controller did not retu Handling errors ^^^^^^^^^^^^^^^ +Sometimes a request should fail, for instance if an author with id 1 is requested but does not exist. In that case use an appropriate `HTTP error code `_ to signal the client that an error occurred. + +Each response subclass has access to the **setStatus** method which lets you set an HTTP status code. To return a JSONResponse signaling that the author with id 1 has not been found, use the following code: + +.. code-block:: php + + `_ to signal the client that an error occurred. - -Each response subclass has access to the **setStatus** method which lets you set an HTTP status code. To return a JSONResponse signaling that the author with id 1 has not been found, use the following code: - -.. code-block:: php - Date: Fri, 28 Feb 2025 13:38:35 +0100 Subject: [PATCH 08/23] Moving section Authentication Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 100 ++++++++++++------------ 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index 022631d5bfb..d1dc2a0ea5d 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -653,12 +653,60 @@ Streamed and lazily rendered responses Security considerations ----------------------- +.. _controller_authentication: + Authentication ^^^^^^^^^^^^^^ +By default every controller method enforces the maximum security, which is: + +* Ensure that the user is admin +* Ensure that the user is logged in +* Ensure that the user has passed the two-factor challenge, if applicable +* Check the CSRF token + Loosening the default restrictions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Most of the time though it makes sense to also allow normal users to access the page and the ``PageController->index()`` method should not check the CSRF token because it has not yet been sent to the client and because of that can't work. + +To turn off checks the following *Attributes* can be added before the controller: + +* ``#[NoAdminRequired]``: Also users that are not admins can access the page +* ``#[PublicPage]``: Everyone can access the page without having to log in +* ``#[NoTwoFactorRequired]``: A user can access the page before the two-factor challenge has been passed (use this wisely and only in two-factor auth apps, e.g. to allow setup during login) +* ``#[NoCSRFRequired]``: Don't check the CSRF token (use this wisely since you might create a security hole; to understand what it does see `CSRF in the security section <../prologue/security.html#cross-site-request-forgery>`__) + +.. note:: + + The attributes are only available in Nextcloud 27 or later. In older versions annotations with the same names exist: + + * ``@NoAdminRequired`` instead of ``#[NoAdminRequired]`` + * ``@PublicPage``` instead of ``#[PublicPage]`` + * ``@NoTwoFactorRequired``` instead of ``#[NoTwoFactorRequired]`` + * ``@NoCSRFRequired``` instead of ``#[NoCSRFRequired]`` + +A controller method that turns off all checks would look like this: + +.. code-block:: php + :emphasize-lines: 6-7,10-11 + + index() method should not check the CSRF token because it has not yet been sent to the client and because of that can't work. - -To turn off checks the following *Attributes* can be added before the controller: - -* ``#[NoAdminRequired]``: Also users that are not admins can access the page -* ``#[PublicPage]``: Everyone can access the page without having to log in -* ``#[NoTwoFactorRequired]``: A user can access the page before the two-factor challenge has been passed (use this wisely and only in two-factor auth apps, e.g. to allow setup during login) -* ``#[NoCSRFRequired]``: Don't check the CSRF token (use this wisely since you might create a security hole; to understand what it does see `CSRF in the security section <../prologue/security.html#cross-site-request-forgery>`__) - -.. note:: - - The attributes are only available in Nextcloud 27 or later. In older versions annotations with the same names exist: - - * ``@NoAdminRequired`` instead of ``#[NoAdminRequired]`` - * ``@PublicPage``` instead of ``#[PublicPage]`` - * ``@NoTwoFactorRequired``` instead of ``#[NoTwoFactorRequired]`` - * ``@NoCSRFRequired``` instead of ``#[NoCSRFRequired]`` - -A controller method that turns off all checks would look like this: - -.. code-block:: php - :emphasize-lines: 6-7,10-11 - - Date: Fri, 28 Feb 2025 15:01:55 +0100 Subject: [PATCH 09/23] Move section Content security policy Signed-off-by: Christian Wolf --- developer_manual/basics/controllers.rst | 119 ++++++++++++------------ 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/developer_manual/basics/controllers.rst b/developer_manual/basics/controllers.rst index d1dc2a0ea5d..210ef1aa153 100644 --- a/developer_manual/basics/controllers.rst +++ b/developer_manual/basics/controllers.rst @@ -716,6 +716,64 @@ Brute-force protection Modifying the content security policy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +By default Nextcloud disables all resources which are not served on the same domain, forbids cross domain requests and disables inline CSS and JavaScript by setting a `Content Security Policy `_. +However if an app relies on third-party media or other features which are forbidden by the current policy the policy can be relaxed. + +.. note:: Double check your content and edge cases before you relax the policy! Also read the `documentation provided by MDN `_ + +To relax the policy pass an instance of the ContentSecurityPolicy class to your response. The methods on the class can be chained. + +The following methods turn off security features by passing in **true** as the **$isAllowed** parameter + +* **allowInlineScript** (bool $isAllowed) +* **allowInlineStyle** (bool $isAllowed) +* **allowEvalScript** (bool $isAllowed) +* **useStrictDynamic** (bool $isAllowed) + + Trust all scripts that are loaded by a trusted script, see 'script-src' and 'strict-dynamic' + +* **useStrictDynamicOnScripts** (bool $isAllowed) + + Trust all scripts that are loaded by a trusted script which was loaded using a ``