## $Id: authentication.py 15606 2019-09-24 17:21:28Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
"""
Authenticate students.
"""
import grok
import time
from zope.component import getUtility
from zope.password.interfaces import IPasswordManager
from zope.pluggableauth.interfaces import (
IAuthenticatorPlugin, ICredentialsPlugin)
from zope.pluggableauth.plugins.session import (
SessionCredentialsPlugin, SessionCredentials)
from zope.publisher.interfaces.http import IHTTPRequest
from zope.session.interfaces import ISession
from waeup.kofa.authentication import (
KofaPrincipalInfo, get_principal_role_manager, FailedLoginInfo)
from waeup.kofa.interfaces import (
IAuthPluginUtility, IUserAccount, IPasswordValidator)
from waeup.kofa.students.interfaces import IStudent
[docs]class StudentAccount(grok.Adapter):
"""An adapter to turn student objects into accounts on-the-fly.
"""
grok.context(IStudent)
grok.implements(IUserAccount)
public_name = None
@property
def name(self):
return self.context.student_id
@property
def password(self):
return getattr(self.context, 'password', None)
@property
def title(self):
return self.context.display_fullname
@property
def email(self):
return self.context.email
@property
def phone(self):
return self.context.phone
@property
def user_type(self):
return u'student'
@property
def description(self):
return self.title
[docs] def suspended(self):
return self.context.suspended
@property
def failed_logins(self):
if not hasattr(self.context, 'failed_logins'):
self.context.failed_logins = FailedLoginInfo()
return self.context.failed_logins
[docs] def _get_roles(self):
prm = get_principal_role_manager()
roles = [x[0] for x in prm.getRolesForPrincipal(self.name)
if x[0].startswith('waeup.')]
return roles
[docs] def _set_roles(self, roles):
"""Set roles for principal denoted by this account.
"""
prm = get_principal_role_manager()
old_roles = self.roles
for role in old_roles:
# Remove old roles, not to be set now...
if role.startswith('waeup.') and role not in roles:
prm.unsetRoleForPrincipal(role, self.name)
for role in roles:
prm.assignRoleToPrincipal(role, self.name)
return
roles = property(_get_roles, _set_roles)
[docs] def setPassword(self, password):
"""Set a password (LDAP-compatible) SSHA encoded.
We do not store passwords in plaintext. Encrypted password is
stored as unicode string.
"""
passwordmanager = getUtility(IPasswordManager, 'SSHA')
self.context.password = passwordmanager.encodePassword(password)
[docs] def checkPassword(self, password):
"""Check whether the given `password` matches the one stored by
students, the temporary password was set by officers or
the parents_password was set by parents.
We additionally check if student account has been suspended
or if the portal is blocked.
"""
try:
blocker = grok.getSite()['configuration'].maintmode_enabled_by
if blocker:
return False
except TypeError: # in unit tests
pass
if not isinstance(password, basestring):
return False
passwordmanager = getUtility(IPasswordManager, 'SSHA')
temp_password = self.context.getTempPassword()
if temp_password:
return passwordmanager.checkPassword(temp_password, password)
parents_password = self.context.getParentsPassword()
if parents_password:
return passwordmanager.checkPassword(parents_password, password)
if not getattr(self.context, 'password', None):
# unset/empty passwords do never match
return False
if self.context.suspended == True:
return False
return passwordmanager.checkPassword(self.context.password, password)
[docs]class StudentsAuthenticatorPlugin(grok.GlobalUtility):
grok.implements(IAuthenticatorPlugin)
grok.provides(IAuthenticatorPlugin)
grok.name('students')
[docs] def authenticateCredentials(self, credentials):
"""Authenticate `credentials`.
`credentials` is a tuple (login, password).
We look up students to find out whether a respective student
exists, then check the password and return the resulting
`PrincipalInfo` or ``None`` if no such student can be found.
"""
if not isinstance(credentials, dict):
return None
if not ('login' in credentials and 'password' in credentials):
return None
account = self.getAccount(credentials['login'])
if account is None:
return None
if not account.checkPassword(credentials['password']):
return None
return KofaPrincipalInfo(id=account.name,
title=account.title,
description=account.description,
email=account.email,
phone=account.phone,
public_name=account.public_name,
user_type=account.user_type)
[docs] def principalInfo(self, id):
"""Get a principal identified by `id`.
This one is required by IAuthenticatorPlugin but not needed here
(see respective docstring in applicants package).
"""
return None
[docs] def getAccount(self, login):
"""Look up a student identified by `login`. Returns an account.
Currently, we simply look up the key under which the student
is stored in the portal. That means we hit if login name and
name under which the student is stored match.
Returns not a student but an account object adapted from any
student found.
If no such student exists, ``None`` is returned.
"""
site = grok.getSite()
if site is None:
return None
studentscontainer = site.get('students', None)
if studentscontainer is None:
return None
student = studentscontainer.get(login, None)
if student is None:
return None
return IUserAccount(student)
[docs]class PasswordChangeCredentialsPlugin(grok.GlobalUtility,
SessionCredentialsPlugin):
"""A session credentials plugin that handles the case of a user
changing his/her own password.
When users change their own password they might find themselves
logged out on next request.
To avoid this, we support to use a 'change password' page a bit
like a regular login page. That means, on each request we lookup
the sent data for a login field called 'student_id' and a
password.
If both exist, this means someone sent new credentials.
We then look for the old credentials stored in the user session.
If the new credentials' login (the student_id) matches the old
one's, we set the new credentials in session _but_ we return the
old credentials for the authentication plugins to check as for the
current request (and for the last time) the old credentials apply.
No valid credentials are returned by this plugin if one of the
follwing circumstances is true
- the sent request is not a regular IHTTPRequest
- the credentials to set do not match the old ones
- no credentials are sent with the request
- no credentials were set before (i.e. the user has no session
with credentials set before)
- no session exists already
- password and repeated password do not match
Therefore it is mandatory to put this plugin in the line of all
credentials plugins _before_ other plugins, so that the regular
credentials plugins can drop in as a 'fallback'.
This plugin was designed for students to change their passwords,
but might be used to allow password resets for other types of
accounts as well.
"""
grok.provides(ICredentialsPlugin)
grok.name('student_pw_change')
loginpagename = 'login'
loginfield = 'student_id'
passwordfield = 'change_password'
repeatfield = 'change_password_repeat'
[docs]class StudentsAuthenticatorSetup(grok.GlobalUtility):
"""Register or unregister student authentication for a PAU.
This piece is called when a new site is created.
"""
grok.implements(IAuthPluginUtility)
grok.name('students_auth_setup')
[docs] def register(self, pau):
plugins = list(pau.credentialsPlugins)
# this plugin must come before the regular credentials plugins
plugins.insert(0, 'student_pw_change')
pau.credentialsPlugins = tuple(plugins)
plugins = list(pau.authenticatorPlugins)
plugins.append('students')
pau.authenticatorPlugins = tuple(plugins)
return pau
[docs] def unregister(self, pau):
plugins = [x for x in pau.authenticatorPlugins
if x != 'students']
pau.authenticatorPlugins = tuple(plugins)
return pau