329 lines
13 KiB
Python
329 lines
13 KiB
Python
# -*- coding: UTF-8 -*-
|
|
|
|
|
|
# Expression
|
|
# By: Frederic Peters <fpeters@entrouvert.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
#
|
|
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
|
|
#
|
|
# 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.
|
|
|
|
|
|
"""Login/Password Accounts Module"""
|
|
|
|
|
|
import errno
|
|
import os
|
|
|
|
import expression.core.dataholders as dataholders
|
|
import expression.core.directories as directories
|
|
import expression.core.elements as elements
|
|
import expression.core.environs as environs
|
|
import expression.core.faults as faults
|
|
import expression.core.locations as locations
|
|
import expression.core.logs as logs
|
|
import expression.core.modules as modules
|
|
import expression.core.namespaces as namespaces
|
|
import expression.core.sessions as sessions
|
|
import expression.core.stations as stations
|
|
import expression.core.strings as strings
|
|
import expression.core.things as things
|
|
import emails
|
|
import xforms.descriptions as descriptions
|
|
|
|
from identities import Identity
|
|
|
|
|
|
class LoginPage(descriptions.Page):
|
|
def newContext(self, specimen, *attributes, **keywords):
|
|
return LoginPageContext(self, specimen, *attributes, **keywords)
|
|
|
|
def processSubmittedContext(self, context):
|
|
specimen = context.specimen
|
|
nextUrl = specimen.nextUrl
|
|
if not nextUrl:
|
|
nextUrl = "/"
|
|
submission = environs.getVar("submission")
|
|
if submission.getField("emailPassword"):
|
|
# Alert the user that its password has been sent by email.
|
|
nextUrl = specimen.constructUri(nextUrl, preserveAbsolutePath = True)
|
|
return environs.getVar("httpRequestHandler").outputAlert(
|
|
N_("An email containing your password as been sent to %s") % specimen.email,
|
|
title = N_("Password sent by email"), url = nextUrl)
|
|
else:
|
|
# Log the user in.
|
|
passwordAccountLocalId = strings.simplify(specimen.email)
|
|
passwordAccounts = specimen.previous.previous
|
|
passwordAccountHolder = passwordAccounts.walk([passwordAccountLocalId])
|
|
passwordAccount = passwordAccountHolder.getRootElement()
|
|
user = passwordAccount.getUser()
|
|
session = sessions.getOrCreateSession()
|
|
session.publishToken = True
|
|
session.setAccountAbsolutePath(passwordAccount.getAbsolutePath())
|
|
user.setSessionToken(session.getToken())
|
|
user.getDocument().save()
|
|
baseEnviron = environs.get(_level = "handleHttpCommand")
|
|
baseEnviron.setVar("user", user)
|
|
logs.debug("""Setting session user = "%s" at location "%s".""" % (
|
|
user.simpleLabel, session.getAccountAbsolutePath()))
|
|
# Note: constructUri must be called after getOrCreateSession, so
|
|
# that nextUrl contains the sessionToken.
|
|
nextUrl = specimen.constructUri(nextUrl, preserveAbsolutePath = True)
|
|
return environs.getVar('httpRequestHandler').outputRedirect(
|
|
nextUrl, station = specimen)
|
|
|
|
def validateSubmittedContext(self, context):
|
|
modelContext = context.xformsDefaultModel
|
|
submission = environs.getVar("submission")
|
|
if submission.getField("emailPassword"):
|
|
# Try to send the user's password by email.
|
|
if modelContext.errors and len(modelContext.errors) == 1 \
|
|
and "yep_password" in modelContext.errors:
|
|
del modelContext.errors
|
|
if not modelContext.errors:
|
|
specimen = context.specimen
|
|
passwordAccountLocalId = strings.simplify(specimen.email)
|
|
passwordAccounts = specimen.previous.previous
|
|
accountExists = passwordAccounts.walk([passwordAccountLocalId], None, "exists")
|
|
if not accountExists:
|
|
modelContext.setErrorLabel(
|
|
"yep_email", "badValue", N_("Unknown email address"))
|
|
return
|
|
messageHolder = passwordAccounts.walk(
|
|
[passwordAccountLocalId, "sendPasswordEmail"])
|
|
message = messageHolder.getRootElement()
|
|
try:
|
|
message.send()
|
|
except: # TODO: tighter check
|
|
logs.exception("SMTP send failed")
|
|
modelContext.setErrorLabel("yep_email", "badValue", N_("Bad email address"))
|
|
return
|
|
elif not modelContext.errors:
|
|
# Check that the email and password are correct.
|
|
specimen = context.specimen
|
|
passwordAccountLocalId = strings.simplify(specimen.email)
|
|
passwordAccounts = specimen.previous.previous
|
|
try:
|
|
passwordAccountHolder = passwordAccounts.walk([passwordAccountLocalId])
|
|
except faults.PathNotFound:
|
|
modelContext.setErrorLabel("yep_email", "badValue", N_("Unknown email address"))
|
|
return
|
|
passwordAccount = passwordAccountHolder.getRootElement()
|
|
if specimen.password != passwordAccount.password:
|
|
modelContext.setErrorLabel("yep_password", "badValue", N_("Wrong password"))
|
|
return
|
|
user = passwordAccount.getUser()
|
|
if user is None:
|
|
logs.warning('Account "%s" without user.' % passwordAccount)
|
|
modelContext.setErrorLabel("yep_email", "badValue", N_("Unknown user"))
|
|
return
|
|
|
|
|
|
class LoginPageContext(descriptions.PageContext):
|
|
def styled(self):
|
|
submission = environs.getVar("submission")
|
|
nextUrl = submission.getField("nextUrl")
|
|
if nextUrl:
|
|
self.specimen.nextUrl = nextUrl
|
|
super(LoginPageContext, self).styled()
|
|
|
|
def unstyled(self):
|
|
submission = environs.getVar("submission")
|
|
nextUrl = submission.getField("nextUrl")
|
|
if nextUrl:
|
|
self.specimen.nextUrl = nextUrl
|
|
super(LoginPageContext, self).unstyled()
|
|
|
|
def getHttpPostUri(self):
|
|
uri = self.getUri()
|
|
virtualHost = environs.getVar("virtualHost")
|
|
if not virtualHost.isSsl \
|
|
and virtualHost.brotherVirtualHost is not None:
|
|
# We are currently in HTTP => Direct the POST to HTTPS.
|
|
uriAuthority = environs.getVar("uriAuthority")
|
|
i = uriAuthority.find("@") + 1
|
|
j = uriAuthority.find(":", i)
|
|
if j >= 0:
|
|
uriAuthority = uriAuthority[:j]
|
|
httpsPort = virtualHost.brotherVirtualHost.getPort()
|
|
if httpsPort != 443:
|
|
uriAuthority = "%s:%d" % (uriAuthority, httpsPort)
|
|
uri = "https://%s%s" % (uriAuthority, uri)
|
|
return uri
|
|
|
|
|
|
class NewPage(descriptions.Page):
|
|
def validateSubmittedContext(self, context):
|
|
modelContext = context.xformsDefaultModel
|
|
if not modelContext.errors or "yep_email" not in modelContext.errors:
|
|
specimen = context.specimen
|
|
accountExists = specimen.getUriPrevious().walk(
|
|
[strings.simplify(specimen.email)], None, "exists")
|
|
if accountExists:
|
|
modelContext.setErrorLabel(
|
|
"yep_email", "valueNotUnique",
|
|
N_("Email address already used"))
|
|
|
|
def validateSubmittedContextOnceAgain(self, context):
|
|
specimen = context.specimen
|
|
specimen.password = strings.makePassword(length = 6)
|
|
messageHolder = specimen.walk(["newAccountEmail"])
|
|
message = messageHolder.getRootElement()
|
|
try:
|
|
message.send()
|
|
except: # TODO: tighter check
|
|
logs.exception("SMTP send failed")
|
|
modelContext = context.xformsDefaultModel
|
|
modelContext.setErrorLabel(
|
|
"yep_email", "badValue", N_("Bad email address"))
|
|
|
|
|
|
class PasswordAccount(things.Thing):
|
|
_user = None
|
|
|
|
def deleteNextUrl(self):
|
|
nodes = self.evaluateXpath("yep:nextUrl")
|
|
if nodes:
|
|
node = nodes[0]
|
|
node.unlinkNode()
|
|
node.freeNode()
|
|
|
|
def deleteUserPath(self):
|
|
nodes = self.evaluateXpath("yep:user")
|
|
if nodes:
|
|
node = nodes[0]
|
|
node.unlinkNode()
|
|
node.freeNode()
|
|
|
|
def getAuthenticationMethod(self):
|
|
return "password"
|
|
|
|
def getEmail(self):
|
|
# For HTTP authentication, a user id (ie a login) can not contain ":", because ":" is used
|
|
# as a separator between login and password.
|
|
nodes = self.evaluateXpath("yep:email")
|
|
if nodes:
|
|
return nodes[0].content
|
|
else:
|
|
return None
|
|
|
|
def getNextUrl(self):
|
|
nodes = self.evaluateXpath("yep:nextUrl")
|
|
if nodes:
|
|
return nodes[0].content
|
|
else:
|
|
return None
|
|
|
|
def getPassword(self):
|
|
nodes = self.evaluateXpath("yep:password")
|
|
if nodes:
|
|
return nodes[0].content
|
|
else:
|
|
return None
|
|
|
|
def getUser(self):
|
|
if self._user is None:
|
|
userAbsolutePath = self.getUserAbsolutePath()
|
|
if userAbsolutePath:
|
|
try:
|
|
userHolder = self.walkToLocation(userAbsolutePath)
|
|
except IOError, error:
|
|
if error.errno == errno.ENOENT:
|
|
logs.debug("""User at path "%s" doesn't exist.""" % userAbsolutePath)
|
|
self._user = "none"
|
|
else:
|
|
raise
|
|
else:
|
|
# Some times, users are stored in independant files; some times they are
|
|
# embedded inside accounts.
|
|
accountOrUser = userHolder.getRootElement()
|
|
self._user = accountOrUser.getUser()
|
|
else:
|
|
userNodes = self.evaluateXpath("yep:user")
|
|
if userNodes:
|
|
self._user = elements.newElement(userNodes[0], previous = self, owner = self)
|
|
else:
|
|
self._user = "none"
|
|
if self._user == "none":
|
|
return None
|
|
return self._user
|
|
|
|
def getUserAbsolutePath(self):
|
|
nodes = self.evaluateXpath("yep:user/@src")
|
|
if nodes:
|
|
userPath = nodes[0].content
|
|
return self.convertPathToAbsolute(userPath)
|
|
else:
|
|
return None
|
|
|
|
def setNextUrl(self, nextUrl):
|
|
self.setElementContent("yep:nextUrl", nextUrl)
|
|
|
|
def setPassword(self, password):
|
|
self.setElementContent("yep:password", password)
|
|
|
|
def setUserAbsolutePath(self, userAbsolutePath):
|
|
userBaseRelativePath = self.convertAbsolutePathToBaseRelative(userAbsolutePath)
|
|
self.setElementAttribute("yep:user", "src", userBaseRelativePath)
|
|
|
|
email = property(getEmail)
|
|
nextUrl = property(getNextUrl, setNextUrl, deleteNextUrl)
|
|
password = property(getPassword, setPassword)
|
|
|
|
|
|
class PasswordAccountHolder(dataholders.XmlHolder):
|
|
def deletePublicName(self):
|
|
pass
|
|
|
|
def getPublicName(self):
|
|
email = self.getRootElement().email
|
|
if email is None:
|
|
return None
|
|
publicName = strings.simplify(email)
|
|
if self.isRootElder:
|
|
publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName)
|
|
return publicName
|
|
|
|
def setPublicName(self, publicName):
|
|
pass
|
|
|
|
publicName = property(getPublicName, setPublicName, deletePublicName)
|
|
|
|
|
|
class PasswordAccountsHolder(directories.DirectoryHolder):
|
|
def login(self, *uriPathFragments):
|
|
"""
|
|
Handles HTTP GET & POST.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command not in ("GET", "POST"):
|
|
raise faults.PathNotFound("")
|
|
|
|
try:
|
|
elementFeature = modules.getElementFeature(namespaces.yep.uri, "passwordAccount")
|
|
except (KeyError, ValueError):
|
|
raise faults.PathNotFound("")
|
|
xmlHolder = elementFeature.newXmlHolder(self, pathFragment = "login")
|
|
return xmlHolder.checkAccessAndWalk(
|
|
("login", ) + uriPathFragments, command, environs.getVar("instruction"))
|
|
|
|
|
|
elements.registerElement(
|
|
namespaces.yep.uri, "passwordAccount", PasswordAccount,
|
|
"http://www.entrouvert.org/expression/schemas/PasswordAccount.xsd",
|
|
"http://www.entrouvert.org/expression/descriptions/PasswordAccount.xml", PasswordAccountHolder)
|