Skip to content

Commit

Permalink
[fc] Repository: plone.app.querystring
Browse files Browse the repository at this point in the history
Branch: refs/heads/master
Date: 2025-01-08T11:20:45+01:00
Author: ale-rt (ale-rt) <[email protected]>
Commit: plone/plone.app.querystring@66db028

Fix a not needed view call

Files changed:
A news/+ffbbb128.bugfix.rst
M plone/app/querystring/results.pt
Repository: plone.app.querystring

Branch: refs/heads/master
Date: 2025-01-19T22:02:18+01:00
Author: Alessandro Pisa (ale-rt) <[email protected]>
Commit: plone/plone.app.querystring@90e6c90

Merge pull request #157 from plone/fix-view-call

Fix a not needed view call

Files changed:
A news/+ffbbb128.bugfix.rst
M plone/app/querystring/results.pt
  • Loading branch information
ale-rt committed Jan 19, 2025
1 parent e0faff6 commit f82832a
Showing 1 changed file with 26 additions and 73 deletions.
99 changes: 26 additions & 73 deletions last_commit.txt
Original file line number Diff line number Diff line change
@@ -1,81 +1,34 @@
Repository: plone.restapi
Repository: plone.app.querystring


Branch: refs/heads/main
Date: 2025-01-17T14:49:28-08:00
Author: Mikel Larreategi (erral) <mlarreategi@codesyntax.com>
Commit: https://github.com/plone/plone.restapi/commit/74f3d72dfbbfb9254821630620c0d12320eefa9e
Branch: refs/heads/master
Date: 2025-01-08T11:20:45+01:00
Author: ale-rt (ale-rt) <alessandro.pisa@gmail.com>
Commit: https://github.com/plone/plone.app.querystring/commit/66db02806a8151de2e710d2dc28271dfec95601f

add new @login endpoint to return available external login options (#1757)
Fix a not needed view call

* add new @login endpoint to return available external login options

* changelog

* lint

* lint

* lint

* lint

* lint

* Update news/1757.feature

Co-authored-by: Steve Piercy &lt;[email protected]&gt;

* Update news/1757.feature

Co-authored-by: Steve Piercy &lt;[email protected]&gt;

* Update news/1757.feature

Co-authored-by: Steve Piercy &lt;[email protected]&gt;

* add docs

* yaml

* yaml

* docs

* docs

* Review of docs

* Revert `'` to `"`

* properly implement the adapter in tests

* add docs rsults

* black

* fix response

* rename the interface to ILoginProviders

* Apply suggestions from code review

---------

Co-authored-by: Steve Piercy &lt;[email protected]&gt;
Co-authored-by: David Glick &lt;[email protected]&gt;
Files changed:
A news/+ffbbb128.bugfix.rst
M plone/app/querystring/results.pt

b'diff --git a/news/+ffbbb128.bugfix.rst b/news/+ffbbb128.bugfix.rst\nnew file mode 100644\nindex 0000000..f68dbe2\n--- /dev/null\n+++ b/news/+ffbbb128.bugfix.rst\n@@ -0,0 +1 @@\n+Fix a not needed view call\ndiff --git a/plone/app/querystring/results.pt b/plone/app/querystring/results.pt\nindex dc6ef19..2232a38 100644\n--- a/plone/app/querystring/results.pt\n+++ b/plone/app/querystring/results.pt\n@@ -8,7 +8,7 @@\n limited_results python:context[:25];\n original_context nocall:options/original_context|nocall:context;\n toLocalizedTime nocall:original_context/@@plone/toLocalizedTime;\n- pas_member original_context/@@pas_member;\n+ pas_member nocall: original_context/@@pas_member;\n normalize nocall: context/@@plone/normalizeString;\n "\n i18n:domain="plone"\n'

Repository: plone.app.querystring


Branch: refs/heads/master
Date: 2025-01-19T22:02:18+01:00
Author: Alessandro Pisa (ale-rt) <[email protected]>
Commit: https://github.com/plone/plone.app.querystring/commit/90e6c9019e52f0e0c8e9d4244ac188b4603d65e0

Merge pull request #157 from plone/fix-view-call

Fix a not needed view call

Files changed:
A docs/source/endpoints/login.md
A news/1757.feature
A src/plone/restapi/services/auth/get.py
A src/plone/restapi/tests/http-examples/external_authentication_links.req
A src/plone/restapi/tests/http-examples/external_authentication_links.resp
M docs/source/endpoints/index.md
M src/plone/restapi/interfaces.py
M src/plone/restapi/services/auth/configure.zcml
M src/plone/restapi/tests/test_auth.py
M src/plone/restapi/tests/test_documentation.py
A news/+ffbbb128.bugfix.rst
M plone/app/querystring/results.pt

b'diff --git a/docs/source/endpoints/index.md b/docs/source/endpoints/index.md\nindex 800b9f5286..2eb88c4de5 100644\n--- a/docs/source/endpoints/index.md\n+++ b/docs/source/endpoints/index.md\n@@ -1,10 +1,10 @@\n ---\n myst:\n html_meta:\n- "description": "Usage of the Plone REST API."\n- "property=og:description": "Usage of the Plone REST API."\n- "property=og:title": "Usage of the Plone REST API"\n- "keywords": "Plone, plone.restapi, REST, API, Usage"\n+ "description": "Endpoints of the Plone REST API."\n+ "property=og:description": "Endpoints of the Plone REST API."\n+ "property=og:title": "Endpoints of the Plone REST API"\n+ "keywords": "Plone, plone.restapi, REST, API, endpoints"\n ---\n \n (restapi-endpoints)=\n@@ -33,6 +33,7 @@ groups\n history\n linkintegrity\n locking\n+login\n navigation\n navroot\n actions\ndiff --git a/docs/source/endpoints/login.md b/docs/source/endpoints/login.md\nnew file mode 100644\nindex 0000000000..8541bb91be\n--- /dev/null\n+++ b/docs/source/endpoints/login.md\n@@ -0,0 +1,71 @@\n+---\n+myst:\n+ html_meta:\n+ "description": "The @login endpoint exposes the list of external authentication services that may be used in the Plone site."\n+ "property=og:description": "The @login endpoint exposes the list of external authentication services that may be used in the Plone site."\n+ "property=og:title": "@login for external authentication links"\n+ "keywords": "Plone, plone.restapi, REST, API, login, authentication, external services"\n+---\n+\n+# Login for external authentication links\n+\n+It is common to use add-ons that allow logging in to your site using third party services.\n+Such add-ons include using authentication services provided by KeyCloak, GitHub, or other OAuth2 or OpenID Connect enabled services.\n+\n+When you install one of these add-ons, it modifies the login process, directing the user to third party services.\n+\n+To expose the links provided by these add-ons, `plone.restapi` provides an adapter based service registration.\n+It lets those add-ons know that the REST API can use those services to authenticate users.\n+This will mostly be used by frontends that need to show the end user the links to those services.\n+\n+To achieve that, third party products need to register one or more adapters for the Plone site root object, providing the `plone.restapi.interfaces.IExternalLoginProviders` interface.\n+\n+In the adapter, the add-on needs to return the list of external links and some metadata, including the `id`, `title`, and name of the `plugin`.\n+\n+An example adapter would be the following, in a file named {file}`adapter.py`:\n+\n+```python\n+from zope.component import adapter\n+from zope.interface import implementer\n+\n+@adapter(IPloneSiteRoot)\n+@implementer(IExternalLoginProviders)\n+class MyExternalLinks:\n+ def __init__(self, context):\n+ self.context = context\n+\n+ def get_providers(self):\n+ return [\n+ {\n+ "id": "myprovider",\n+ "title": "Provider",\n+ "plugin": "pas.plugins.authomatic",\n+ "url": "https://some.example.com/login-url",\n+ },\n+ {\n+ "id": "github",\n+ "title": "GitHub",\n+ "plugin": "pas.plugins.authomatic",\n+ "url": "https://some.example.com/login-authomatic/github",\n+ },\n+ ]\n+```\n+\n+With the corresponding ZCML stanza, in the corresponding {file}`configure.zcml` file:\n+\n+```xml\n+<adapter factory=".adapter.MyExternalLinks" name="my-external-links"/>\n+```\n+\n+The API request would be as follows:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/external_authentication_links.req\n+```\n+\n+The server will respond with a `Status 200` and the list of external providers:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/external_authentication_links.resp\n+:language: http\n+```\ndiff --git a/news/1757.feature b/news/1757.feature\nnew file mode 100644\nindex 0000000000..c678441b4e\n--- /dev/null\n+++ b/news/1757.feature\n@@ -0,0 +1 @@\n+Add a `@login` endpoint to get external login services\' links. @erral\ndiff --git a/src/plone/restapi/interfaces.py b/src/plone/restapi/interfaces.py\nindex 5c2aa337e6..9d5c2bcede 100644\n--- a/src/plone/restapi/interfaces.py\n+++ b/src/plone/restapi/interfaces.py\n@@ -240,3 +240,14 @@ class IBlockVisitor(Interface):\n \n def __call__(self, block):\n """Return an iterable of sub-blocks found inside `block`."""\n+\n+\n+class ILoginProviders(Interface):\n+ """An interface needed to be implemented by providers that want to be listed\n+ in the @login endpoint\n+ """\n+\n+ def get_providers():\n+ """\n+ return a list of login providers, with its id, title, plugin and url\n+ """\ndiff --git a/src/plone/restapi/services/auth/configure.zcml b/src/plone/restapi/services/auth/configure.zcml\nindex dec5304c50..f5604d81f2 100644\n--- a/src/plone/restapi/services/auth/configure.zcml\n+++ b/src/plone/restapi/services/auth/configure.zcml\n@@ -3,6 +3,13 @@\n xmlns:plone="http://namespaces.plone.org/plone"\n xmlns:zcml="http://namespaces.zope.org/zcml"\n >\n+ <plone:service\n+ method="GET"\n+ factory=".get.Login"\n+ for="Products.CMFPlone.interfaces.IPloneSiteRoot"\n+ permission="zope.Public"\n+ name="@login"\n+ />\n \n <plone:service\n method="POST"\ndiff --git a/src/plone/restapi/services/auth/get.py b/src/plone/restapi/services/auth/get.py\nnew file mode 100644\nindex 0000000000..d4cae56500\n--- /dev/null\n+++ b/src/plone/restapi/services/auth/get.py\n@@ -0,0 +1,14 @@\n+# -*- coding: utf-8 -*-\n+from plone.restapi.interfaces import ILoginProviders\n+from plone.restapi.services import Service\n+from zope.component import getAdapters\n+\n+\n+class Login(Service):\n+ def reply(self):\n+ adapters = getAdapters((self.context,), ILoginProviders)\n+ external_providers = []\n+ for name, adapter in adapters:\n+ external_providers.extend(adapter.get_providers())\n+\n+ return {"options": external_providers}\ndiff --git a/src/plone/restapi/tests/http-examples/external_authentication_links.req b/src/plone/restapi/tests/http-examples/external_authentication_links.req\nnew file mode 100644\nindex 0000000000..92469012d8\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/external_authentication_links.req\n@@ -0,0 +1,3 @@\n+GET /plone/@login HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/external_authentication_links.resp b/src/plone/restapi/tests/http-examples/external_authentication_links.resp\nnew file mode 100644\nindex 0000000000..b88f62ab5e\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/external_authentication_links.resp\n@@ -0,0 +1,19 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "options": [\n+ {\n+ "id": "myprovider",\n+ "plugin": "myprovider",\n+ "title": "Provider",\n+ "url": "https://some.example.com/login-url"\n+ },\n+ {\n+ "id": "github",\n+ "plugin": "github",\n+ "title": "GitHub",\n+ "url": "https://some.example.com/login-authomatic/github"\n+ }\n+ ]\n+}\ndiff --git a/src/plone/restapi/tests/test_auth.py b/src/plone/restapi/tests/test_auth.py\nindex ece162b21e..e7df8470bb 100644\n--- a/src/plone/restapi/tests/test_auth.py\n+++ b/src/plone/restapi/tests/test_auth.py\n@@ -7,6 +7,9 @@\n from zExceptions import Unauthorized\n from zope.event import notify\n from ZPublisher.pubevents import PubStart\n+from zope.component import provideAdapter\n+from plone.restapi.interfaces import ILoginProviders\n+from Products.CMFPlone.interfaces import IPloneSiteRoot\n \n \n class TestLogin(TestCase):\n@@ -208,3 +211,57 @@ def test_renew_fails_on_invalid_token(self):\n self.assertEqual(\n res["error"]["type"], "Invalid or expired authentication token"\n )\n+\n+\n+class MyExternalLinks:\n+ def __init__(self, context):\n+ self.context = context\n+\n+ def get_providers(self):\n+ return [\n+ {\n+ "id": "myprovider",\n+ "title": "Provider",\n+ "plugin": "myprovider",\n+ "url": "https://some.example.com/login-url",\n+ },\n+ {\n+ "id": "github",\n+ "title": "GitHub",\n+ "plugin": "github",\n+ "url": "https://some.example.com/login-authomatic/github",\n+ },\n+ ]\n+\n+\n+class TestExternalLoginServices(TestCase):\n+ layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING\n+\n+ def setUp(self):\n+ self.portal = self.layer["portal"]\n+ self.request = self.layer["request"]\n+\n+ provideAdapter(\n+ MyExternalLinks,\n+ adapts=(IPloneSiteRoot,),\n+ provides=ILoginProviders,\n+ name="test-external-links",\n+ )\n+\n+ def traverse(self, path="/plone/@login", accept="application/json", method="GET"):\n+ request = self.layer["request"]\n+ request.environ["PATH_INFO"] = path\n+ request.environ["PATH_TRANSLATED"] = path\n+ request.environ["HTTP_ACCEPT"] = accept\n+ request.environ["REQUEST_METHOD"] = method\n+ notify(PubStart(request))\n+ return request.traverse(path)\n+\n+ def test_provider_returns_list(self):\n+ service = self.traverse()\n+ res = service.reply()\n+ self.assertEqual(service.request.response.status, 200)\n+ self.assertTrue(isinstance(res, dict))\n+ self.assertIn("options", res)\n+ self.assertTrue(isinstance(res.get("options"), list))\n+ self.assertTrue(len(res.get("options")), 2)\ndiff --git a/src/plone/restapi/tests/test_documentation.py b/src/plone/restapi/tests/test_documentation.py\nindex 774bbd94b5..574873ce6e 100644\n--- a/src/plone/restapi/tests/test_documentation.py\n+++ b/src/plone/restapi/tests/test_documentation.py\n@@ -42,6 +42,10 @@\n from plone.app.testing import popGlobalRegistry\n from plone.app.testing import pushGlobalRegistry\n from plone.restapi.testing import register_static_uuid_utility\n+from zope.component import provideAdapter\n+from plone.restapi.interfaces import ILoginProviders\n+from Products.CMFPlone.interfaces import IPloneSiteRoot\n+\n \n import collections\n import json\n@@ -86,6 +90,27 @@\n open_kw = {"newline": "\\n"}\n \n \n+class MyExternalLinks:\n+ def __init__(self, context):\n+ self.context = context\n+\n+ def get_providers(self):\n+ return [\n+ {\n+ "id": "myprovider",\n+ "title": "Provider",\n+ "plugin": "myprovider",\n+ "url": "https://some.example.com/login-url",\n+ },\n+ {\n+ "id": "github",\n+ "title": "GitHub",\n+ "plugin": "github",\n+ "url": "https://some.example.com/login-authomatic/github",\n+ },\n+ ]\n+\n+\n def normalize_test_port(value):\n # When you run these tests in the Plone core development buildout,\n # the port number is random. Normalize this to the default port.\n@@ -227,6 +252,13 @@ def setUp(self):\n super().setUp()\n self.document = self.create_document()\n alsoProvides(self.document, ITTWLockable)\n+ provideAdapter(\n+ MyExternalLinks,\n+ adapts=(IPloneSiteRoot,),\n+ provides=ILoginProviders,\n+ name="test-external-links",\n+ )\n+\n transaction.commit()\n \n def tearDown(self):\n@@ -787,6 +819,12 @@ def test_documentation_jwt_logout(self):\n )\n save_request_and_response_for_docs("jwt_logout", response)\n \n+ def test_documentation_external_doc_links(self):\n+ response = self.api_session.get(\n+ f"{self.portal.absolute_url()}/@login",\n+ )\n+ save_request_and_response_for_docs("external_authentication_links", response)\n+\n def test_documentation_batching(self):\n folder = self.portal[\n self.portal.invokeFactory("Folder", id="folder", title="Folder")\n'
b'diff --git a/news/+ffbbb128.bugfix.rst b/news/+ffbbb128.bugfix.rst\nnew file mode 100644\nindex 0000000..f68dbe2\n--- /dev/null\n+++ b/news/+ffbbb128.bugfix.rst\n@@ -0,0 +1 @@\n+Fix a not needed view call\ndiff --git a/plone/app/querystring/results.pt b/plone/app/querystring/results.pt\nindex dc6ef19..2232a38 100644\n--- a/plone/app/querystring/results.pt\n+++ b/plone/app/querystring/results.pt\n@@ -8,7 +8,7 @@\n limited_results python:context[:25];\n original_context nocall:options/original_context|nocall:context;\n toLocalizedTime nocall:original_context/@@plone/toLocalizedTime;\n- pas_member original_context/@@pas_member;\n+ pas_member nocall: original_context/@@pas_member;\n normalize nocall: context/@@plone/normalizeString;\n "\n i18n:domain="plone"\n'

0 comments on commit f82832a

Please sign in to comment.