This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
expression/src/modules/passwordaccounts.py

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)