Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new @login endpoint to return available external login options #1757

Merged
merged 33 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cf79461
add new @login endpoint to return available external login options
erral Feb 28, 2024
3ff293b
changelog
erral Feb 28, 2024
783a079
lint
erral Feb 28, 2024
cc1347c
lint
erral Feb 28, 2024
6cf2636
lint
erral Feb 28, 2024
98c0cf5
lint
erral Feb 28, 2024
68ce3be
lint
erral Feb 28, 2024
46f891e
Merge branch 'main' into erral-login-options
erral Feb 28, 2024
879752e
Update news/1757.feature
erral Feb 29, 2024
53002d4
Update news/1757.feature
erral Feb 29, 2024
37c47e7
Update news/1757.feature
erral Feb 29, 2024
06be87c
add docs
erral Feb 29, 2024
8168d4a
yaml
erral Mar 1, 2024
961def7
yaml
erral Mar 1, 2024
3cd4daa
docs
erral Mar 1, 2024
0eccce4
docs
erral Mar 1, 2024
499d74a
Review of docs
stevepiercy Mar 1, 2024
c97c97b
Revert `'` to `"`
stevepiercy Mar 1, 2024
4cc4288
properly implement the adapter in tests
erral Mar 3, 2024
fd67f94
add docs rsults
erral Mar 3, 2024
1a0c196
Merge branch 'main' into erral-login-options
erral Mar 3, 2024
861673f
black
erral Mar 3, 2024
7a9e378
fix response
erral Mar 3, 2024
83c802e
Merge branch 'main' into erral-login-options
erral Mar 11, 2024
f52a73d
Merge branch 'main' into erral-login-options
erral Mar 15, 2024
92ae900
Merge branch 'main' into erral-login-options
erral Aug 20, 2024
49a018f
Merge branch 'main' into erral-login-options
erral Sep 6, 2024
b5c5899
Merge branch 'main' into erral-login-options
erral Sep 16, 2024
730e8d2
Merge branch 'main' into erral-login-options
erral Nov 13, 2024
3468580
Merge branch 'main' into erral-login-options
erral Jan 10, 2025
a57b3ac
rename the interface to ILoginProviders
erral Jan 12, 2025
a0df4a1
Apply suggestions from code review
davisagli Jan 17, 2025
501cb61
Merge branch 'main' into erral-login-options
davisagli Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/1757.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a @login endpoint to get external login services' links. @erral
erral marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 11 additions & 0 deletions src/plone/restapi/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,14 @@ class IBlockVisitor(Interface):

def __call__(self, block):
"""Return an iterable of sub-blocks found inside `block`."""


class IExternalLoginProviders(Interface):
davisagli marked this conversation as resolved.
Show resolved Hide resolved
"""An interface needed to be implemented by providers that want to be listed
in the @login endpoint
"""

def get_providers():
"""
return a list of login providers, with its id, title, plugin and url
"""
davisagli marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions src/plone/restapi/services/auth/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
xmlns:plone="http://namespaces.plone.org/plone"
xmlns:zcml="http://namespaces.zope.org/zcml"
>
<plone:service
method="GET"
factory=".get.Login"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="zope.Public"
name="@login"
/>

<plone:service
method="POST"
Expand Down
14 changes: 14 additions & 0 deletions src/plone/restapi/services/auth/get.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from plone.restapi.interfaces import IExternalLoginProviders
from plone.restapi.services import Service
from zope.component import getAdapters


class Login(Service):
def reply(self):
adapters = getAdapters(self.context, IExternalLoginProviders)
external_providers = []
for adapter in adapters:
external_providers.extend(adapter.get_providers())

return {"options": external_providers}
50 changes: 50 additions & 0 deletions src/plone/restapi/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from zExceptions import Unauthorized
from zope.event import notify
from ZPublisher.pubevents import PubStart
from zope.component import provideAdapter
from plone.restapi.interfaces import IExternalLoginProviders
from Products.CMFPlone.interfaces import IPloneSiteRoot


class TestLogin(TestCase):
Expand Down Expand Up @@ -208,3 +211,50 @@ def test_renew_fails_on_invalid_token(self):
self.assertEqual(
res["error"]["type"], "Invalid or expired authentication token"
)


class MyExternalLinks:
def get_providers(self):
return [
{
"id": "myprovider",
"title": "Provider",
"plugin": "myprovider",
"url": "https://some.example.com/login-url",
},
{
"id": "github",
"title": "GitHub",
"plugin": "github",
"url": "https://some.example.com/login-authomatic/github",
},
]


class TestExternalLoginServices(TestCase):
layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING

def setUp(self):
self.portal = self.layer["portal"]
self.request = self.layer["request"]

provideAdapter(
MyExternalLinks, adapts=(IPloneSiteRoot,), provides=IExternalLoginProviders
)

def traverse(self, path="/plone/@login", accept="application/json", method="GET"):
request = self.layer["request"]
request.environ["PATH_INFO"] = path
request.environ["PATH_TRANSLATED"] = path
request.environ["HTTP_ACCEPT"] = accept
request.environ["REQUEST_METHOD"] = method
notify(PubStart(request))
return request.traverse(path)

def test_provider_returns_list(self):
service = self.traverse()
res = service.reply()
self.assertEqual(service.request.response.status, 200)
self.assertTrue(isinstance(res, dict))
self.assertIn("options", res)
self.assertTrue(isinstance(res.get("options"), list))
Loading