Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
3 changes: 2 additions & 1 deletion buildout.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ extends =
# test_plone43.cfg
# test_plone50.cfg
# test_plone51.cfg
test_plone52.cfg
# test_plone52.cfg
test_plone60.cfg

[instance]
eggs +=
Expand Down
3 changes: 3 additions & 0 deletions dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM plone/plone-backend
COPY . /app/src/pas.plugins.oidc
RUN bin/pip install src/pas.plugins.oidc -c constraints.txt
46 changes: 22 additions & 24 deletions src/pas/plugins/oidc/browser/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def __call__(self):
if api.user.is_anonymous():
# context is our PAS plugin
url = self.context.absolute_url() + "/login"
came_from = self.request.get('came_from', None)
came_from = self.request.get("came_from", None)
if came_from:
url += "?came_from={}".format(quote(came_from))
else:
Expand All @@ -93,7 +93,9 @@ class LoginView(BrowserView):
def __call__(self):
session = Session(
self.request,
use_session_data_manager=self.context.getProperty("use_session_data_manager"),
use_session_data_manager=self.context.getProperty(
"use_session_data_manager"
),
)
# state is used to keep track of responses to outstanding requests (state).
# nonce is a string value used to associate a Client session with an ID Token, and to mitigate replay attacks.
Expand Down Expand Up @@ -126,9 +128,7 @@ def __call__(self):
# Build a random string of 43 to 128 characters
# and send it in the request as a base64-encoded urlsafe string of the sha256 hash of that string
session.set("verifier", rndstr(128))
args["code_challenge"] = self.get_code_challenge(
session.get("verifier")
)
args["code_challenge"] = self.get_code_challenge(session.get("verifier"))
args["code_challenge_method"] = "S256"

try:
Expand All @@ -137,10 +137,7 @@ def __call__(self):
except Exception as e:
logger.error(e)
api.portal.show_message(
_(
"There was an error during the login process. Please try"
" again."
)
_("There was an error during the login process. Please try" " again.")
)
portal_url = api.portal.get_tool("portal_url")
if came_from and portal_url.isURLInPortal(came_from):
Expand All @@ -150,9 +147,7 @@ def __call__(self):

return

self.request.response.setHeader(
"Cache-Control", "no-cache, must-revalidate"
)
self.request.response.setHeader("Cache-Control", "no-cache, must-revalidate")
self.request.response.redirect(login_url)
return

Expand All @@ -162,11 +157,7 @@ def get_code_challenge(self, value):
See https://www.stefaanlippens.net/oauth-code-flow-pkce.html#PKCE-code-verifier-and-challenge
"""
hash_code = sha256(value.encode("utf-8")).digest()
return (
base64.urlsafe_b64encode(hash_code)
.decode("utf-8")
.replace("=", "")
)
return base64.urlsafe_b64encode(hash_code).decode("utf-8").replace("=", "")


class LogoutView(BrowserView):
Expand All @@ -187,7 +178,7 @@ def __call__(self):
redirect_uri = api.portal.get().absolute_url()

# Volto frontend mapping exception
if redirect_uri.endswith('/api'):
if redirect_uri.endswith("/api"):
redirect_uri = redirect_uri[:-4]

args = {
Expand Down Expand Up @@ -216,15 +207,20 @@ def __call__(self):
response = self.request.environ["QUERY_STRING"]
session = Session(
self.request,
use_session_data_manager=self.context.getProperty("use_session_data_manager"),
use_session_data_manager=self.context.getProperty(
"use_session_data_manager"
),
)
client = self.context.get_oauth2_client()
aresp = client.parse_response(
AuthorizationResponse, info=response, sformat="urlencoded"
)
if aresp["state"] != session.get("state"):
logger.error("invalid OAuth2 state response:%s != session:%s",
aresp.get("state"), session.get("state"))
logger.error(
"invalid OAuth2 state response:%s != session:%s",
aresp.get("state"),
session.get("state"),
)
# TODO: need to double check before removing the comment below
# raise ValueError("invalid OAuth2 state")

Expand Down Expand Up @@ -291,13 +287,15 @@ def return_url(self, session=None):
came_from = self.request.get("came_from")
if not came_from and session:
came_from = session.get("came_from")

portal_url = api.portal.get_tool("portal_url")
if not (came_from and portal_url.isURLInPortal(came_from)):
if not came_from:
came_from = api.portal.get().absolute_url()
elif not portal_url.isURLInPortal(came_from):
logger.warning("came_from is not in portal %s", came_from)
came_from = api.portal.get().absolute_url()

# Volto frontend mapping exception
if came_from.endswith('/api'):
if came_from.endswith("/api"):
came_from = came_from[:-4]

return came_from
8 changes: 8 additions & 0 deletions src/pas/plugins/oidc/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
handler=".setuphandlers.activate_challenge_plugin"
/>

<genericsetup:upgradeStep
title="Activate properties plugin"
source="1001"
destination="1002"
profile="pas.plugins.oidc:default"
handler=".setuphandlers.activate_properties_plugin"
/>

<utility
factory=".setuphandlers.HiddenProfiles"
name="pas.plugins.oidc-hiddenprofiles"
Expand Down
Loading