-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement control panel to configure pas.plugins.oidc (#66)
* Implement control panel to configure pas.plugins.oidc (Implements #65) * Fix dependencies * Use PLUGIN_ID instead of hardcoded value for oidc
- Loading branch information
Showing
22 changed files
with
1,051 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Implement control panel to configure pas.plugins.oidc [@ericof] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
from pas.plugins.oidc import _ | ||
from pas.plugins.oidc import PLUGIN_ID | ||
from pas.plugins.oidc.interfaces import IOIDCSettings | ||
from plone import api | ||
from plone.app.registry.browser import controlpanel | ||
from plone.base.interfaces import IPloneSiteRoot | ||
from zope.component import adapter | ||
from zope.interface import implementer | ||
|
||
|
||
@adapter(IPloneSiteRoot) | ||
@implementer(IOIDCSettings) | ||
class OIDCControlPanelAdapter: | ||
|
||
def __init__(self, context): | ||
self.context = context | ||
self.portal = api.portal.get() | ||
self.encoding = "utf-8" | ||
self.settings = self.portal.acl_users[PLUGIN_ID] | ||
|
||
@property | ||
def issuer(self): | ||
return self.settings.issuer | ||
|
||
@issuer.setter | ||
def issuer(self, value): | ||
self.settings.issuer = value | ||
|
||
@property | ||
def client_id(self): | ||
return self.settings.client_id | ||
|
||
@client_id.setter | ||
def client_id(self, value): | ||
self.settings.client_id = value | ||
|
||
@property | ||
def client_secret(self): | ||
return self.settings.client_secret | ||
|
||
@client_secret.setter | ||
def client_secret(self, value): | ||
self.settings.client_secret = value | ||
|
||
@property | ||
def redirect_uris(self): | ||
return self.settings.redirect_uris | ||
|
||
@redirect_uris.setter | ||
def redirect_uris(self, value): | ||
self.settings.redirect_uris = value | ||
|
||
@property | ||
def use_session_data_manager(self): | ||
return self.settings.use_session_data_manager | ||
|
||
@use_session_data_manager.setter | ||
def use_session_data_manager(self, value): | ||
self.settings.use_session_data_manager = value | ||
|
||
@property | ||
def create_user(self): | ||
return self.settings.create_user | ||
|
||
@create_user.setter | ||
def create_user(self, value): | ||
self.settings.create_user = value | ||
|
||
@property | ||
def create_groups(self): | ||
return self.settings.create_groups | ||
|
||
@create_groups.setter | ||
def create_groups(self, value): | ||
self.settings.create_groups = value | ||
|
||
@property | ||
def user_property_as_groupid(self): | ||
return self.settings.user_property_as_groupid | ||
|
||
@user_property_as_groupid.setter | ||
def user_property_as_groupid(self, value): | ||
self.settings.user_property_as_groupid = value | ||
|
||
@property | ||
def create_ticket(self): | ||
return self.settings.create_ticket | ||
|
||
@create_ticket.setter | ||
def create_ticket(self, value): | ||
self.settings.create_ticket = value | ||
|
||
@property | ||
def create_restapi_ticket(self): | ||
return self.settings.create_restapi_ticket | ||
|
||
@create_restapi_ticket.setter | ||
def create_restapi_ticket(self, value): | ||
self.settings.create_restapi_ticket = value | ||
|
||
@property | ||
def scope(self): | ||
return self.settings.scope | ||
|
||
@scope.setter | ||
def scope(self, value): | ||
self.settings.scope = value | ||
|
||
@property | ||
def use_pkce(self): | ||
return self.settings.use_pkce | ||
|
||
@use_pkce.setter | ||
def use_pkce(self, value): | ||
self.settings.use_pkce = value | ||
|
||
@property | ||
def use_deprecated_redirect_uri_for_logout(self): | ||
return self.settings.use_deprecated_redirect_uri_for_logout | ||
|
||
@use_deprecated_redirect_uri_for_logout.setter | ||
def use_deprecated_redirect_uri_for_logout(self, value): | ||
self.settings.use_deprecated_redirect_uri_for_logout = value | ||
|
||
@property | ||
def use_modified_openid_schema(self): | ||
return self.settings.use_modified_openid_schema | ||
|
||
@use_modified_openid_schema.setter | ||
def use_modified_openid_schema(self, value): | ||
self.settings.use_modified_openid_schema = value | ||
|
||
@property | ||
def user_property_as_userid(self): | ||
return self.settings.user_property_as_userid | ||
|
||
@user_property_as_userid.setter | ||
def user_property_as_userid(self, value): | ||
self.settings.user_property_as_userid = value | ||
|
||
|
||
class OIDCSettingsForm(controlpanel.RegistryEditForm): | ||
schema = IOIDCSettings | ||
schema_prefix = "oidc_admin" | ||
label = _("OIDC Plugin Settings") | ||
description = "" | ||
|
||
def getContent(self): | ||
portal = api.portal.get() | ||
return OIDCControlPanelAdapter(portal) | ||
|
||
def applyChanges(self, data): | ||
"""See interfaces.IEditForm""" | ||
content = self.getContent() | ||
changes = {} | ||
for name in data: | ||
current = getattr(content, name) | ||
value = data[name] | ||
if current != value: | ||
setattr(content, name, value) | ||
changes.setdefault(IOIDCSettings, []).append(name) | ||
return changes | ||
|
||
|
||
class OIDCSettingsControlPanel(controlpanel.ControlPanelFormWrapper): | ||
form = OIDCSettingsForm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<configure | ||
xmlns="http://namespaces.zope.org/zope" | ||
xmlns:browser="http://namespaces.zope.org/browser" | ||
i18n_domain="pas.plugins.oidc" | ||
> | ||
|
||
<!-- ClassicUI Control panel --> | ||
<adapter factory=".classic.OIDCControlPanelAdapter" /> | ||
<browser:page | ||
name="oidc-controlpanel" | ||
for="Products.CMFPlone.interfaces.IPloneSiteRoot" | ||
class=".classic.OIDCSettingsControlPanel" | ||
permission="cmf.ManagePortal" | ||
layer="pas.plugins.oidc.interfaces.IDefaultBrowserLayer" | ||
/> | ||
|
||
<!-- Restapi Controlpanel --> | ||
<adapter factory=".serializer.OIDCControlpanelSerializeToJson" /> | ||
<adapter factory=".deserializer.OIDCControlpanelDeserializeFromJson" /> | ||
|
||
<adapter | ||
factory=".oidc.OIDCSettingsConfigletPanel" | ||
provides="pas.plugins.oidc.interfaces.IOIDCControlpanel" | ||
name="oidc_admin" | ||
/> | ||
|
||
</configure> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from pas.plugins.oidc import PLUGIN_ID | ||
from pas.plugins.oidc.interfaces import IOIDCControlpanel | ||
from plone import api | ||
from plone.restapi.deserializer import json_body | ||
from plone.restapi.deserializer.controlpanels import ControlpanelDeserializeFromJson | ||
from plone.restapi.deserializer.controlpanels import FakeDXContext | ||
from plone.restapi.interfaces import IDeserializeFromJson | ||
from plone.restapi.interfaces import IFieldDeserializer | ||
from z3c.form.interfaces import IManagerValidator | ||
from zExceptions import BadRequest | ||
from zope.component import adapter | ||
from zope.component import queryMultiAdapter | ||
from zope.interface import implementer | ||
from zope.interface.exceptions import Invalid | ||
from zope.schema import getFields | ||
from zope.schema.interfaces import ValidationError | ||
|
||
|
||
@implementer(IDeserializeFromJson) | ||
@adapter(IOIDCControlpanel) | ||
class OIDCControlpanelDeserializeFromJson(ControlpanelDeserializeFromJson): | ||
@property | ||
def proxy(self): | ||
portal = api.portal.get() | ||
plugin = portal.acl_users[PLUGIN_ID] | ||
return plugin | ||
|
||
def __call__(self, mask_validation_errors=True): | ||
controlpanel = self.controlpanel | ||
request = controlpanel.request | ||
data = json_body(request) | ||
|
||
proxy = self.proxy | ||
|
||
schema_data = {} | ||
errors = [] | ||
|
||
# Make a fake context | ||
fake_context = FakeDXContext() | ||
|
||
for name, field in getFields(self.schema).items(): | ||
field_data = schema_data.setdefault(self.schema, {}) | ||
|
||
if field.readonly: | ||
continue | ||
|
||
if name in data: | ||
deserializer = queryMultiAdapter( | ||
(field, fake_context, request), IFieldDeserializer | ||
) | ||
try: | ||
# Make it sane | ||
value = deserializer(data[name]) | ||
# Validate required etc | ||
field.validate(value) | ||
# Set the value. | ||
setattr(proxy, name, value) | ||
except ValidationError as e: | ||
errors.append({"message": e.doc(), "field": name, "error": e}) | ||
except (ValueError, Invalid) as e: | ||
errors.append({"message": str(e), "field": name, "error": e}) | ||
else: | ||
field_data[name] = value | ||
|
||
# Validate schemata | ||
for schema, field_data in schema_data.items(): | ||
validator = queryMultiAdapter( | ||
(self.context, request, None, schema, None), IManagerValidator | ||
) | ||
for error in validator.validate(field_data): | ||
errors.append({"error": error, "message": str(error)}) | ||
|
||
if errors: | ||
for error in errors: | ||
if mask_validation_errors: | ||
# Drop Python specific error classes in order to | ||
# be able to better handle errors on front-end | ||
error["error"] = "ValidationError" | ||
error["message"] = api.env.translate(error["message"], context=request) | ||
raise BadRequest(errors) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from pas.plugins.oidc import _ | ||
from pas.plugins.oidc.interfaces import IDefaultBrowserLayer | ||
from pas.plugins.oidc.interfaces import IOIDCControlpanel | ||
from pas.plugins.oidc.interfaces import IOIDCSettings | ||
from plone.restapi.controlpanels import RegistryConfigletPanel | ||
from zope.component import adapter | ||
from zope.interface import implementer | ||
from zope.interface import Interface | ||
|
||
|
||
@adapter(Interface, IDefaultBrowserLayer) | ||
@implementer(IOIDCControlpanel) | ||
class OIDCSettingsConfigletPanel(RegistryConfigletPanel): | ||
"""Control Panel endpoint""" | ||
|
||
schema = IOIDCSettings | ||
configlet_id = "oidc_admin" | ||
configlet_category_id = "plone-users" | ||
title = _("OIDC settings") | ||
group = "" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from pas.plugins.oidc import PLUGIN_ID | ||
from pas.plugins.oidc.interfaces import IOIDCControlpanel | ||
from pas.plugins.oidc.plugins import OIDCPlugin | ||
from plone import api | ||
from plone.restapi.interfaces import ISerializeToJson | ||
from plone.restapi.serializer.controlpanels import ControlpanelSerializeToJson | ||
from plone.restapi.serializer.controlpanels import get_jsonschema_for_controlpanel | ||
from plone.restapi.serializer.controlpanels import SERVICE_ID | ||
from zope.component import adapter | ||
from zope.interface import implementer | ||
|
||
|
||
@implementer(ISerializeToJson) | ||
@adapter(IOIDCControlpanel) | ||
class OIDCControlpanelSerializeToJson(ControlpanelSerializeToJson): | ||
def config_data(self) -> dict: | ||
data = {} | ||
portal = api.portal.get() | ||
plugin = portal.acl_users[PLUGIN_ID] | ||
properties = OIDCPlugin._properties | ||
for prop in properties: | ||
key = prop["id"] | ||
data[key] = getattr(plugin, key, "") | ||
return data | ||
|
||
def __call__(self): | ||
json_data = self.config_data() | ||
controlpanel = self.controlpanel | ||
context = controlpanel.context | ||
request = controlpanel.request | ||
url = context.absolute_url() | ||
json_schema = get_jsonschema_for_controlpanel( | ||
controlpanel, | ||
context, | ||
request, | ||
) | ||
response = { | ||
"@id": f"{url}/{SERVICE_ID}/{controlpanel.__name__}", | ||
"title": "OIDC settings", | ||
"description": "Configure OIDC connection strings", | ||
"group": controlpanel.group, | ||
"data": json_data, | ||
"schema": json_schema, | ||
} | ||
return response |
Oops, something went wrong.