ims.sso 1.0.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ims.sso might be problematic. Click here for more details.
- ims_sso-1.0.0a1/.gitignore +59 -0
- ims_sso-1.0.0a1/PKG-INFO +17 -0
- ims_sso-1.0.0a1/ims/sso/__init__.py +16 -0
- ims_sso-1.0.0a1/ims/sso/browser/__init__.py +1 -0
- ims_sso-1.0.0a1/ims/sso/browser/configure.zcml +142 -0
- ims_sso-1.0.0a1/ims/sso/browser/cron.py +23 -0
- ims_sso-1.0.0a1/ims/sso/browser/login.py +257 -0
- ims_sso-1.0.0a1/ims/sso/browser/password.py +9 -0
- ims_sso-1.0.0a1/ims/sso/browser/personalpreferences.py +1 -0
- ims_sso-1.0.0a1/ims/sso/browser/register.py +161 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/add.pt +46 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/change_password.pt +47 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/linkaccount_anonymous.pt +24 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/linkaccount_disallowed.pt +22 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/linkaccount_finish.pt +21 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/linkaccount_invalid.pt +25 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/login.pt +23 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/logout_warning_viewlet.pt +26 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/newuser_form.pt +69 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/reactivation.pt +26 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/relog.pt +30 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/unauthorized_shib_authenticated.pt +58 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/unauthorized_shib_unauthenticated.pt +57 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/usergroups_usersoverview.pt +568 -0
- ims_sso-1.0.0a1/ims/sso/browser/templates/warnings.pt +44 -0
- ims_sso-1.0.0a1/ims/sso/browser/unauthorized.py +7 -0
- ims_sso-1.0.0a1/ims/sso/browser/usergroups_usersoverview.py +322 -0
- ims_sso-1.0.0a1/ims/sso/browser/viewlets.py +55 -0
- ims_sso-1.0.0a1/ims/sso/configs.py +18 -0
- ims_sso-1.0.0a1/ims/sso/configure.zcml +30 -0
- ims_sso-1.0.0a1/ims/sso/controlpanels/__init__.py +0 -0
- ims_sso-1.0.0a1/ims/sso/controlpanels/configure.zcml +13 -0
- ims_sso-1.0.0a1/ims/sso/controlpanels/settings.pt +24 -0
- ims_sso-1.0.0a1/ims/sso/controlpanels/sso.py +22 -0
- ims_sso-1.0.0a1/ims/sso/errors.py +6 -0
- ims_sso-1.0.0a1/ims/sso/events.py +31 -0
- ims_sso-1.0.0a1/ims/sso/events.zcml +12 -0
- ims_sso-1.0.0a1/ims/sso/indexes.py +9 -0
- ims_sso-1.0.0a1/ims/sso/interfaces.py +168 -0
- ims_sso-1.0.0a1/ims/sso/mailing/__init__.py +1 -0
- ims_sso-1.0.0a1/ims/sso/mailing/configure.zcml +48 -0
- ims_sso-1.0.0a1/ims/sso/mailing/mailers.py +13 -0
- ims_sso-1.0.0a1/ims/sso/mailing/mass_relink.pt +36 -0
- ims_sso-1.0.0a1/ims/sso/mailing/notify.py +101 -0
- ims_sso-1.0.0a1/ims/sso/mailing/relink.py +103 -0
- ims_sso-1.0.0a1/ims/sso/permissions.zcml +40 -0
- ims_sso-1.0.0a1/ims/sso/plugin.py +195 -0
- ims_sso-1.0.0a1/ims/sso/profiles/default/actions.xml +59 -0
- ims_sso-1.0.0a1/ims/sso/profiles/default/browserlayer.xml +6 -0
- ims_sso-1.0.0a1/ims/sso/profiles/default/controlpanel.xml +43 -0
- ims_sso-1.0.0a1/ims/sso/profiles/default/metadata.xml +4 -0
- ims_sso-1.0.0a1/ims/sso/profiles/default/registry/bundle.xml +12 -0
- ims_sso-1.0.0a1/ims/sso/profiles/default/registry/settings.xml +4 -0
- ims_sso-1.0.0a1/ims/sso/profiles/default/rolemap.xml +43 -0
- ims_sso-1.0.0a1/ims/sso/profiles/uninstall/actions.xml +7 -0
- ims_sso-1.0.0a1/ims/sso/profiles/uninstall/browserlayer.xml +6 -0
- ims_sso-1.0.0a1/ims/sso/profiles/uninstall/controlpanel.xml +10 -0
- ims_sso-1.0.0a1/ims/sso/profiles/uninstall/registry.xml +6 -0
- ims_sso-1.0.0a1/ims/sso/profiles.zcml +23 -0
- ims_sso-1.0.0a1/ims/sso/setuphandlers.py +139 -0
- ims_sso-1.0.0a1/ims/sso/static/bundle/chunks/881.f292144b2330c5cc4d06.min.js +2 -0
- ims_sso-1.0.0a1/ims/sso/static/bundle/chunks/881.f292144b2330c5cc4d06.min.js.LICENSE.txt +1 -0
- ims_sso-1.0.0a1/ims/sso/static/bundle/chunks/975.1473fe663da179434339.min.js +2 -0
- ims_sso-1.0.0a1/ims/sso/static/bundle/chunks/975.1473fe663da179434339.min.js.LICENSE.txt +1 -0
- ims_sso-1.0.0a1/ims/sso/static/bundle/ims.sso.min.js +2 -0
- ims_sso-1.0.0a1/ims/sso/static/bundle/ims.sso.min.js.LICENSE.txt +1 -0
- ims_sso-1.0.0a1/ims/sso/static/main.css +0 -0
- ims_sso-1.0.0a1/ims/sso/static/main.less +0 -0
- ims_sso-1.0.0a1/ims/sso/static/multiplugin.gif +0 -0
- ims_sso-1.0.0a1/ims/sso/testing.py +55 -0
- ims_sso-1.0.0a1/ims/sso/upgrades.py +3 -0
- ims_sso-1.0.0a1/ims/sso/upgrades.zcml +8 -0
- ims_sso-1.0.0a1/ims/sso/utilities.zcml +21 -0
- ims_sso-1.0.0a1/ims/sso/utility.py +468 -0
- ims_sso-1.0.0a1/ims/sso/vocabulary.py +22 -0
- ims_sso-1.0.0a1/ims/sso/vocabulary.zcml +15 -0
- ims_sso-1.0.0a1/pyproject.toml +133 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
|
|
5
|
+
# C extensions
|
|
6
|
+
*.so
|
|
7
|
+
|
|
8
|
+
# Distribution / packaging
|
|
9
|
+
.Python
|
|
10
|
+
env/
|
|
11
|
+
build/
|
|
12
|
+
_build/
|
|
13
|
+
public/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
.DS_Store
|
|
27
|
+
|
|
28
|
+
# PyInstaller
|
|
29
|
+
# Usually these files are written by a python script from a template
|
|
30
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.coverage
|
|
42
|
+
.cache
|
|
43
|
+
nosetests.xml
|
|
44
|
+
coverage.xml
|
|
45
|
+
|
|
46
|
+
# Translations
|
|
47
|
+
*.mo
|
|
48
|
+
*.pot
|
|
49
|
+
|
|
50
|
+
# Django stuff:
|
|
51
|
+
*.log
|
|
52
|
+
|
|
53
|
+
# Sphinx documentation
|
|
54
|
+
docs/_build/
|
|
55
|
+
|
|
56
|
+
# PyBuilder
|
|
57
|
+
target/
|
|
58
|
+
|
|
59
|
+
/node_modules/
|
ims_sso-1.0.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ims.sso
|
|
3
|
+
Version: 1.0.0a1
|
|
4
|
+
Summary: Shibboleth integration and security considerations
|
|
5
|
+
Project-URL: homepage, https://github.com/imsweb/ims.sso
|
|
6
|
+
Author-email: Eric Wohnlich <wohnlice@imsweb.com>, Taina Dumay <dumayt@imsweb.com>
|
|
7
|
+
License: GPL
|
|
8
|
+
Classifier: Framework :: Plone :: 6.1
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: plone
|
|
12
|
+
Provides-Extra: test
|
|
13
|
+
Requires-Dist: formencode; extra == 'test'
|
|
14
|
+
Requires-Dist: plone-app-robotframework; extra == 'test'
|
|
15
|
+
Requires-Dist: plone-app-testing; extra == 'test'
|
|
16
|
+
Requires-Dist: plone-mocktestcase; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-plone; extra == 'test'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from Products.PluggableAuthService import registerMultiPlugin
|
|
2
|
+
|
|
3
|
+
from . import plugin
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.0a1"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def initialize(context):
|
|
9
|
+
registerMultiPlugin(plugin.ImsSsoPlugin.meta_type)
|
|
10
|
+
context.registerClass(
|
|
11
|
+
plugin.ImsSsoPlugin,
|
|
12
|
+
permission="Manage portal",
|
|
13
|
+
constructors=(plugin.manage_addImsSsoForm, plugin.manage_addImsSso),
|
|
14
|
+
visibility=None,
|
|
15
|
+
icon="static/multiplugin.gif",
|
|
16
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope"
|
|
3
|
+
xmlns:browser="http://namespaces.zope.org/browser"
|
|
4
|
+
i18n_domain="ims.sso"
|
|
5
|
+
>
|
|
6
|
+
|
|
7
|
+
<!-- Plone override -->
|
|
8
|
+
<browser:page
|
|
9
|
+
name="usergroup-userprefs"
|
|
10
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
11
|
+
class=".usergroups_usersoverview.UsersOverviewControlPanel"
|
|
12
|
+
template="templates/usergroups_usersoverview.pt"
|
|
13
|
+
permission="plone.app.controlpanel.UsersAndGroups"
|
|
14
|
+
layer="..interfaces.IBrowserLayer"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<browser:page
|
|
18
|
+
name="sso-tasks"
|
|
19
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
20
|
+
class=".cron.PeriodicTasks"
|
|
21
|
+
permission="cmf.ManagePortal"
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
<!-- Plone override -->
|
|
25
|
+
<browser:page
|
|
26
|
+
name="new-user"
|
|
27
|
+
for="plone.app.layout.navigation.interfaces.INavigationRoot"
|
|
28
|
+
class=".register.AddUserForm"
|
|
29
|
+
permission="plone.app.controlpanel.UsersAndGroups"
|
|
30
|
+
layer="..interfaces.IBrowserLayer"
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
<!-- login/logout -->
|
|
34
|
+
<browser:page
|
|
35
|
+
name="sso-logout"
|
|
36
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
37
|
+
class=".login.SsoLogout"
|
|
38
|
+
permission="zope.Public"
|
|
39
|
+
/>
|
|
40
|
+
<browser:page
|
|
41
|
+
name="linkaccount"
|
|
42
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
43
|
+
class=".login.SsoLinkaccount"
|
|
44
|
+
permission="zope.Public"
|
|
45
|
+
/>
|
|
46
|
+
<browser:page
|
|
47
|
+
name="request_reactivation"
|
|
48
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
49
|
+
class=".login.RequestReactivation"
|
|
50
|
+
attribute="render"
|
|
51
|
+
permission="zope.Public"
|
|
52
|
+
/>
|
|
53
|
+
<browser:page
|
|
54
|
+
name="reactivation"
|
|
55
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
56
|
+
template="templates/reactivation.pt"
|
|
57
|
+
permission="zope.Public"
|
|
58
|
+
/>
|
|
59
|
+
<browser:page
|
|
60
|
+
name="reactivate_user"
|
|
61
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
62
|
+
class=".login.ReactivateUserAccount"
|
|
63
|
+
permission="zope.Public"
|
|
64
|
+
/>
|
|
65
|
+
<browser:page
|
|
66
|
+
name="get_login_url"
|
|
67
|
+
for="zope.interface.Interface"
|
|
68
|
+
class=".login.LoginUrl"
|
|
69
|
+
permission="zope.Public"
|
|
70
|
+
/>
|
|
71
|
+
<browser:page
|
|
72
|
+
name="unauthorized_shib_unauthenticated"
|
|
73
|
+
for="zope.interface.Interface"
|
|
74
|
+
class=".unauthorized.Unauthorized"
|
|
75
|
+
template="templates/unauthorized_shib_unauthenticated.pt"
|
|
76
|
+
permission="zope.Public"
|
|
77
|
+
/>
|
|
78
|
+
<browser:page
|
|
79
|
+
name="unauthorized_shib_authenticated"
|
|
80
|
+
for="zope.interface.Interface"
|
|
81
|
+
class=".unauthorized.Unauthorized"
|
|
82
|
+
template="templates/unauthorized_shib_authenticated.pt"
|
|
83
|
+
permission="zope.Public"
|
|
84
|
+
/>
|
|
85
|
+
<browser:page
|
|
86
|
+
name="login_condition"
|
|
87
|
+
for="zope.interface.Interface"
|
|
88
|
+
class=".login.LoginCondition"
|
|
89
|
+
permission="zope.Public"
|
|
90
|
+
/>
|
|
91
|
+
<browser:page
|
|
92
|
+
name="logout_url"
|
|
93
|
+
for="zope.interface.Interface"
|
|
94
|
+
class=".login.LogoutUrl"
|
|
95
|
+
permission="zope.Public"
|
|
96
|
+
/>
|
|
97
|
+
<!-- change password -->
|
|
98
|
+
<browser:page
|
|
99
|
+
name="change-password"
|
|
100
|
+
for="plone.app.layout.navigation.interfaces.INavigationRoot"
|
|
101
|
+
class=".password.ChangePassword"
|
|
102
|
+
permission="cmf.SetOwnPassword"
|
|
103
|
+
layer="..interfaces.IBrowserLayer"
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
<browser:page
|
|
108
|
+
name="require_login"
|
|
109
|
+
for="*"
|
|
110
|
+
class=".login.RequireLoginView"
|
|
111
|
+
permission="zope.Public"
|
|
112
|
+
layer="..interfaces.IBrowserLayer"
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
<browser:viewlet
|
|
117
|
+
name="sso_warnings"
|
|
118
|
+
manager="plone.app.layout.viewlets.interfaces.IAboveContent"
|
|
119
|
+
class=".viewlets.SsoWarningsViewlet"
|
|
120
|
+
permission="zope.Public"
|
|
121
|
+
layer="..interfaces.IBrowserLayer"
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
<browser:page
|
|
126
|
+
name="relog-success"
|
|
127
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
128
|
+
template="templates/relog.pt"
|
|
129
|
+
permission="zope2.Public"
|
|
130
|
+
layer="..interfaces.IBrowserLayer"
|
|
131
|
+
/>
|
|
132
|
+
|
|
133
|
+
<!-- this just prevents the default login form -->
|
|
134
|
+
<browser:page
|
|
135
|
+
name="login"
|
|
136
|
+
for="plone.base.interfaces.INavigationRoot"
|
|
137
|
+
class=".login.RedirectLogin"
|
|
138
|
+
permission="zope.Public"
|
|
139
|
+
layer="..interfaces.IBrowserLayer"
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
</configure>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from plone.protect.interfaces import IDisableCSRFProtection
|
|
2
|
+
from Products.Five import BrowserView
|
|
3
|
+
from zope.component import getUtility
|
|
4
|
+
from zope.interface.declarations import alsoProvides
|
|
5
|
+
|
|
6
|
+
from ..interfaces import IReactivationUtility, ISingleSignonUtility
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PeriodicTasks(BrowserView):
|
|
10
|
+
"""This should be called by a cron job. It does three things
|
|
11
|
+
1. Users that have not been linked since X days will be deleted
|
|
12
|
+
2. Users who have not logged in in Y days will have their accounts disabled
|
|
13
|
+
3. Remove expired annotations
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __call__(self, *args, **kwargs):
|
|
17
|
+
alsoProvides(self.request, IDisableCSRFProtection)
|
|
18
|
+
sso = getUtility(ISingleSignonUtility)
|
|
19
|
+
react = getUtility(IReactivationUtility)
|
|
20
|
+
sso.purge_unlinked()
|
|
21
|
+
sso.disable_user_accounts()
|
|
22
|
+
react.purge_activation_keys()
|
|
23
|
+
return "User accounts updated."
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from plone import api
|
|
2
|
+
from plone.protect import CheckAuthenticator, PostOnly
|
|
3
|
+
from plone.protect.interfaces import IDisableCSRFProtection
|
|
4
|
+
from Products.CMFPlone.browser.login.login import LoginForm
|
|
5
|
+
from Products.CMFPlone.PasswordResetTool import InvalidRequestError
|
|
6
|
+
from Products.Five import BrowserView
|
|
7
|
+
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
|
|
8
|
+
from zope.annotation.interfaces import IAnnotations
|
|
9
|
+
from zope.component import getMultiAdapter, getUtility
|
|
10
|
+
from zope.event import notify
|
|
11
|
+
from zope.interface.declarations import alsoProvides, implementer
|
|
12
|
+
from zope.publisher.interfaces import IPublishTraverse
|
|
13
|
+
|
|
14
|
+
from ..configs import AUTHENTICATED_KEY, NOT_LINKED, _
|
|
15
|
+
from ..events import UserIdpUpdated
|
|
16
|
+
from ..interfaces import IReactivationUtility, ISingleSignonUtility
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RedirectLogin(LoginForm):
|
|
20
|
+
"""This is the Plone default login page. SSO sites do not use it."""
|
|
21
|
+
|
|
22
|
+
def __call__(self):
|
|
23
|
+
self.request.response.redirect(api.portal.get().absolute_url())
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SsoLogout(BrowserView):
|
|
28
|
+
@property
|
|
29
|
+
def sso(self):
|
|
30
|
+
return getUtility(ISingleSignonUtility)
|
|
31
|
+
|
|
32
|
+
def __call__(self):
|
|
33
|
+
self.context.restrictedTraverse("browser_id_manager").flushBrowserIdCookie()
|
|
34
|
+
for cookie in self.request.cookies:
|
|
35
|
+
self.request.response.setCookie(
|
|
36
|
+
cookie,
|
|
37
|
+
self.request.cookies[cookie],
|
|
38
|
+
path="/",
|
|
39
|
+
expires="Thu, 01 Jan 1970 00:00:00 GMT",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.request.response.redirect(self.sso.get_url_logout(request=self.request))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@implementer(IPublishTraverse)
|
|
46
|
+
class SsoLinkaccount(BrowserView):
|
|
47
|
+
def __init__(self, context, request):
|
|
48
|
+
self.subpath = []
|
|
49
|
+
super().__init__(context, request)
|
|
50
|
+
|
|
51
|
+
def publishTraverse(self, request, name):
|
|
52
|
+
self.subpath.append(name)
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def __call__(self):
|
|
56
|
+
if not api.user.is_anonymous():
|
|
57
|
+
# most likely a re-click or a tester
|
|
58
|
+
return self.invalid_key()
|
|
59
|
+
alsoProvides(self.request, IDisableCSRFProtection)
|
|
60
|
+
sso = getUtility(ISingleSignonUtility)
|
|
61
|
+
try:
|
|
62
|
+
key = self.subpath[0]
|
|
63
|
+
user_id = self.subpath[1]
|
|
64
|
+
except IndexError:
|
|
65
|
+
return self.invalid_key()
|
|
66
|
+
|
|
67
|
+
usr = api.user.get(userid=user_id)
|
|
68
|
+
is_relink = usr and NOT_LINKED not in usr.getUserName()
|
|
69
|
+
login_name = sso.get_login_from_request(self.request)
|
|
70
|
+
|
|
71
|
+
email = self.request.get(sso.get_setting("shib_header_email"))
|
|
72
|
+
|
|
73
|
+
if login_name and email:
|
|
74
|
+
pw_tool = api.portal.get_tool("portal_password_reset")
|
|
75
|
+
try:
|
|
76
|
+
pw_tool.verifyKey(key)
|
|
77
|
+
password = sso.generate_password(user_id) # random, unknown password
|
|
78
|
+
pw_tool.resetPassword(user_id, key, password)
|
|
79
|
+
except InvalidRequestError:
|
|
80
|
+
return self.invalid_key()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
sso.set_login_name(user_id, login_name)
|
|
84
|
+
except ValueError: # likely duplicate
|
|
85
|
+
return self.disallowed()
|
|
86
|
+
else:
|
|
87
|
+
sso.send_portal_link(email)
|
|
88
|
+
usr = api.user.get(userid=user_id) # we must reinstantiate after change
|
|
89
|
+
if is_relink:
|
|
90
|
+
plone_view = getMultiAdapter((self.context, self.request), name="plone")
|
|
91
|
+
sso.notify_relinked(usr, email, plone_view)
|
|
92
|
+
notify(UserIdpUpdated(usr))
|
|
93
|
+
return self.success()
|
|
94
|
+
else:
|
|
95
|
+
return ViewPageTemplateFile("templates/linkaccount_anonymous.pt")(self)
|
|
96
|
+
|
|
97
|
+
def disallowed(self):
|
|
98
|
+
return ViewPageTemplateFile("templates/linkaccount_disallowed.pt")(self)
|
|
99
|
+
|
|
100
|
+
def success(self):
|
|
101
|
+
return ViewPageTemplateFile("templates/linkaccount_finish.pt")(self)
|
|
102
|
+
|
|
103
|
+
def invalid_key(self):
|
|
104
|
+
username = api.user.get_current().getUserName()
|
|
105
|
+
if username != "Anonymous User":
|
|
106
|
+
api.portal.show_message(
|
|
107
|
+
_(
|
|
108
|
+
"Invalid or expired key. No accounts were linked, but you are already logged in. Please update "
|
|
109
|
+
"your bookmark to this page."
|
|
110
|
+
),
|
|
111
|
+
self.request,
|
|
112
|
+
type="warning",
|
|
113
|
+
)
|
|
114
|
+
return self.request.response.redirect(api.portal.get().absolute_url())
|
|
115
|
+
else:
|
|
116
|
+
return ViewPageTemplateFile("templates/linkaccount_invalid.pt")(self)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class LoginUrl(BrowserView):
|
|
120
|
+
def __call__(self):
|
|
121
|
+
acl = api.portal.get_tool("acl_users")
|
|
122
|
+
if "ims_sso_plugin" not in acl.objectIds():
|
|
123
|
+
return api.portal.get().absolute_url()
|
|
124
|
+
login_url = acl.ims_sso_plugin.login_url(self.request.ACTUAL_URL)
|
|
125
|
+
# stops loop if they try to login to an unauth page
|
|
126
|
+
# if "target=" in login_url and self.request.get("came_from"):
|
|
127
|
+
# return login_url.split("target=")[0] + "target=" + self.request["came_from"]
|
|
128
|
+
# else:
|
|
129
|
+
return login_url
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class LoginCondition(BrowserView):
|
|
133
|
+
def __call__(self):
|
|
134
|
+
if not api.user.is_anonymous(): # sanity fallback
|
|
135
|
+
return False
|
|
136
|
+
sso = getUtility(ISingleSignonUtility)
|
|
137
|
+
return not sso.is_shibboleth_authenticated(self.request)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class LogoutUrl(BrowserView):
|
|
141
|
+
def __call__(self):
|
|
142
|
+
return f"{api.portal.get().absolute_url()}/sso-logout"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class RequireLoginView(BrowserView):
|
|
146
|
+
def __call__(self, *args, **kw):
|
|
147
|
+
|
|
148
|
+
utility = getUtility(ISingleSignonUtility)
|
|
149
|
+
|
|
150
|
+
if utility.is_plone_authenticated():
|
|
151
|
+
return api.content.get_view(
|
|
152
|
+
context=api.portal.get(),
|
|
153
|
+
request=self.request,
|
|
154
|
+
name="insufficient-privileges",
|
|
155
|
+
)()
|
|
156
|
+
else:
|
|
157
|
+
if utility.is_shibboleth_authenticated(self.request):
|
|
158
|
+
return api.content.get_view(
|
|
159
|
+
context=self.context,
|
|
160
|
+
request=self.request,
|
|
161
|
+
name="unauthorized_shib_authenticated",
|
|
162
|
+
)()
|
|
163
|
+
else:
|
|
164
|
+
return api.content.get_view(
|
|
165
|
+
context=self.context,
|
|
166
|
+
request=self.request,
|
|
167
|
+
name="unauthorized_shib_unauthenticated",
|
|
168
|
+
)()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class RequestReactivation(BrowserView):
|
|
172
|
+
def render(self):
|
|
173
|
+
PostOnly(self.request)
|
|
174
|
+
CheckAuthenticator(self.request)
|
|
175
|
+
|
|
176
|
+
util = getUtility(IReactivationUtility)
|
|
177
|
+
portal = api.portal.get()
|
|
178
|
+
portal_url = portal.absolute_url()
|
|
179
|
+
|
|
180
|
+
_key = util.request_reactivation()
|
|
181
|
+
if not _key:
|
|
182
|
+
api.portal.show_message(
|
|
183
|
+
message="An activation key could not be obtained, contact the site administrator",
|
|
184
|
+
type="error",
|
|
185
|
+
)
|
|
186
|
+
self.request.response.redirect(portal_url)
|
|
187
|
+
return
|
|
188
|
+
_key, expiry = _key
|
|
189
|
+
expiry = api.portal.get_tool("translation_service").ulocalized_time(expiry, long_format=1)
|
|
190
|
+
|
|
191
|
+
reg_link = f"{portal_url}/reactivate_user/{_key}"
|
|
192
|
+
portal_title = api.portal.get_registry_record("plone.site_title")
|
|
193
|
+
msg = (
|
|
194
|
+
f"You are receiving this because you requested to have your account on {portal_title} \
|
|
195
|
+
reactivated. To reactivate your account, visit {reg_link}.\n\n"
|
|
196
|
+
f"This link will expire in 1 day (by {expiry}). If the link expires, log into {portal_url} and "
|
|
197
|
+
f"reactivate your account again.\n\n"
|
|
198
|
+
f"If you did not initiate this action, you may ignore this message."
|
|
199
|
+
)
|
|
200
|
+
user_id = IAnnotations(self.request)[AUTHENTICATED_KEY]
|
|
201
|
+
api.portal.send_email(
|
|
202
|
+
recipient=api.user.get(user_id).getProperty("email"),
|
|
203
|
+
subject=f"Reactivate your account for {portal_title}",
|
|
204
|
+
body=msg,
|
|
205
|
+
immediate=False,
|
|
206
|
+
)
|
|
207
|
+
api.portal.show_message(
|
|
208
|
+
"An email with a link to reactivate your account has been sent to the address "
|
|
209
|
+
"associated with your account. If you do not receive the email, contact the "
|
|
210
|
+
f"<a href='{api.portal.get().absolute_url()}/contact-info'>site "
|
|
211
|
+
"administrator</a>."
|
|
212
|
+
)
|
|
213
|
+
self.request.response.redirect(portal_url + "/reactivation")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class ReactivateUserAccount(BrowserView):
|
|
218
|
+
def __init__(self, context, request):
|
|
219
|
+
self.subpath = []
|
|
220
|
+
super().__init__(context, request)
|
|
221
|
+
|
|
222
|
+
def publishTraverse(self, request, name):
|
|
223
|
+
self.subpath.append(name)
|
|
224
|
+
return self
|
|
225
|
+
|
|
226
|
+
def __call__(self):
|
|
227
|
+
util = getUtility(IReactivationUtility)
|
|
228
|
+
try:
|
|
229
|
+
key = self.subpath[0]
|
|
230
|
+
except IndexError:
|
|
231
|
+
api.portal.show_message(
|
|
232
|
+
"Sorry, this appears to be an invalid request. Please make sure you copied "
|
|
233
|
+
"the URL exactly as it appears in your email.",
|
|
234
|
+
type="warning",
|
|
235
|
+
)
|
|
236
|
+
self.request.response.redirect(api.portal.get().absolute_url() + "/reactivation")
|
|
237
|
+
return
|
|
238
|
+
status = util.reactivate_user(key)
|
|
239
|
+
if status:
|
|
240
|
+
api.portal.show_message("Your account has been successfully reactivated.")
|
|
241
|
+
self.request.response.redirect(api.portal.get().absolute_url())
|
|
242
|
+
else:
|
|
243
|
+
if not api.user.is_anonymous():
|
|
244
|
+
# the activation key is invalid but the user is authenticated. They are probably visiting using
|
|
245
|
+
# the reactivation email link, after already doing that step.
|
|
246
|
+
self.request.response.redirect(api.portal.get().absolute_url())
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
api.portal.show_message(
|
|
250
|
+
"Your account could not be activated. This may "
|
|
251
|
+
"be due to an invalid or expired reactivation link, or "
|
|
252
|
+
"your account may have been disabled after the reactivation link was sent. "
|
|
253
|
+
f'Contact the <a href="{api.portal.get().absolute_url()}/contact-info">'
|
|
254
|
+
"site administrator</a> for help with accessing the site.",
|
|
255
|
+
type="error",
|
|
256
|
+
)
|
|
257
|
+
self.request.response.redirect(api.portal.get().absolute_url() + "/reactivation")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from plone.app.users.browser.passwordpanel import PasswordPanel
|
|
2
|
+
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ChangePassword(PasswordPanel):
|
|
6
|
+
cp_template = ViewPageTemplateFile("templates/change_password.pt")
|
|
7
|
+
|
|
8
|
+
def __call__(self):
|
|
9
|
+
return self.cp_template()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|