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.
glasnost/servers/PeopleServer/PeopleServer.py

667 lines
23 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
# Romain Chantereau <rchantereau@entrouvert.com>
# Nicolas Clapiès <nclapies@easter-eggs.org>
# Pierre-Antoine Dejace <padejace@entrouvert.be>
# Thierry Dulieu <tdulieu@easter-eggs.com>
# Florent Monnier <monnier@codelutin.com>
# Cédric Musso <cmusso@easter-eggs.org>
# Frédéric Péters <fpeters@entrouvert.be>
# Benjamin Poussin <poussin@codelutin.com>
# Emmanuel Raviart <eraviart@entrouvert.com>
# Sébastien Régnier <regnier@codelutin.com>
# Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
# Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
# Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
# Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
# Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
# Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
# Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.
__doc__ = """Glasnost People Server"""
__version__ = '$Revision$'[11:-2]
import base64
import copy
import os
import sys
import time
glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
sys.path.insert(0, glasnostPythonDir)
import glasnost
import glasnost.common.context as context
from glasnost.common.PeopleCommon import *
import glasnost.common.faults as faults
import glasnost.common.system as system
import glasnost.common.tools_new as commonTools
import glasnost.common.xhtmlgenerator as X
from glasnost.server.ObjectsServer import register, ObjectServerMixin, \
AdminServerMixin, ObjectsServer, ObjectsVirtualServer
from glasnost.server.tools import *
from glasnost.proxy.CacheProxy import invalidateValue
from glasnost.proxy.DispatcherProxy import getApplicationId
from glasnost.proxy.GroupsProxy import getSetContainedIds
from glasnost.proxy.tools import getProxy
applicationName = 'PeopleServer'
applicationRole = 'people'
dispatcher = None
class AdminPeople(AdminServerMixin, AdminPeopleCommon):
pass
register(AdminPeople)
class Person(ObjectServerMixin, PersonCommon):
def abstainForVote(self, electionId):
"""Drop the person election token.
Keyword arguments:
==================
*electionId*:
The Id of the election to abstain.
"""
if self.voteTokens is not None and self.voteTokens.has_key(electionId):
del self.voteTokens[electionId]
if len(self.voteTokens) == 0:
del self.voteTokens
def checkAddIsPossible(self):
ObjectServerMixin.checkAddIsPossible(self)
virtualServerId = context.getVar('applicationId')
virtualServer = self.getServer().getVirtualServer(virtualServerId)
fullName = self.getFullName()
if fullName:
for person in virtualServer.objects.values():
if person.getFullName() == fullName:
raise faults.DuplicateFullName(fullName)
if self.email:
for person in virtualServer.objects.values():
if person.email == self.email:
raise faults.DuplicateEmail(self.email)
if self.fingerprint:
for person in virtualServer.objects.values():
if person.fingerprint == self.fingerprint:
raise faults.DuplicateFingerprint(self.fingerprint)
def checkModifyIsPossible(self, changes, givenSlotNames = None):
ObjectServerMixin.checkModifyIsPossible(
self, changes, givenSlotNames = givenSlotNames)
virtualServerId = context.getVar('applicationId')
virtualServer = self.getServer().getVirtualServer(virtualServerId)
changesFullName = changes.getFullName()
if (not givenSlotNames or 'firstName' in givenSlotNames
and 'lastName' in givenSlotNames
and 'nickname' in givenSlotNames) \
and changesFullName != self.getFullName() and changesFullName:
for person in virtualServer.objects.values():
if person.getFullName() == changesFullName \
and person.id != changes.id:
raise faults.DuplicateFullName(changesFullName)
if (not givenSlotNames or 'email' in givenSlotNames) \
and changes.email != self.email and changes.email is not None:
for person in virtualServer.objects.values():
if person.email == changes.email \
and person.id != changes.id:
raise faults.DuplicateEmail(changes.email)
if (not givenSlotNames or 'fingerprint' in givenSlotNames) \
and changes.fingerprint != self.fingerprint \
and changes.fingerprint is not None:
for person in virtualServer.objects.values():
if person.fingerprint == changes.fingerprint \
and person.id != changes.id:
raise faults.DuplicateFingerprint(changes.fingerprint)
def exportToXmlRpc(self, requiredSlotNames = None, parentSlot = None):
personExport = PersonCommon.exportToXmlRpc(
self, requiredSlotNames = requiredSlotNames,
parentSlot = parentSlot)
userId = getProxyForServerRole('authentication').getUserId()
if self.id != userId and not self.getServer().isAdmin():
if personExport.has_key('email') and not userId:
del personExport['email']
if personExport.has_key('voteTokens'):
if not userId:
del personExport['voteTokens']
else:
voteTokens = personExport['voteTokens'].copy()
for electionId, voteToken in voteTokens.items():
if not getProxyForServerRole('elections').hasObject(
electionId):
del voteTokens[electionId]
else:
electionBallotKind = getProxyForServerRole(
'elections').getBallotKind(electionId)
if electionBallotKind == 'secret' \
or electionBallotKind == 'voterChoice' \
and getProxyForServerRole(
'ballots').isVoteSecret(voteToken):
voteTokens[electionId] = 'secret'
if len(voteTokens) == 0:
del personExport['voteTokens']
else:
personExport['voteTokens'] = voteTokens
return personExport
def modify(self, changes, givenSlotNames = None):
virtualServerId = context.getVar('applicationId')
virtualServer = self.getServer().getVirtualServer(virtualServerId)
ObjectServerMixin.modify(
self, changes, givenSlotNames = givenSlotNames)
def vote(self, electionId, voteToken):
if self.voteTokens is None:
self.voteTokens = {}
self.voteTokens[electionId] = voteToken
register(Person)
class PeopleVirtualServer(ObjectsVirtualServer):
sendEmailToUsers = None
def __getstate__(self):
state = ObjectsVirtualServer.__getstate__(self)
if state.has_key('sendEmailToUsers'):
del state['sendEmailToUsers']
return state
def init(self):
ObjectsVirtualServer.init(self)
def loadConfigOptions(self):
ObjectsVirtualServer.loadConfigOptions(self)
self.sendEmailToUsers = commonTools.getConfig(
commonTools.extractDispatcherId(self.virtualServerId),
'SendEmailToUsers',
commonTools.getConfig('Misc', 'SendEmailToUsers', 'true')
) == 'true'
class PeopleServer(PeopleCommonMixin, ObjectsServer):
"""Handle the people services.
Attributes:
===========
*VirtualServer*:
The virtual server where the server is.
*dataDirectoryPath*:
The path where the poeple server non core datas are.
*useAdminReaderSet*:
Set if the people server uses readers set to allow reading.
*useAdminWriterSet*:
Set if the people server uses writers set to allow writing.
*version*:
The server version
*objectClassName*:
The class name of the people server.
"""
VirtualServer = PeopleVirtualServer
def abstainForVote(self, electionId):
"""Abstain voting on an election.
Keyword argument:
=================
*electionId*:
The Id of the election to abstain.
Return 0 everytime.
Exceptions:
===========
*faults.MissingItem*:
The specified election does not exist on the specified server.
*AttributeError*:
No authentication proxy found.
"""
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
voterId = getProxyForServerRole('authentication').getUserId()
object = virtualServer.loadObjectCore(voterId)
object.abstainForVote(electionId)
virtualServer.markObjectAsDirty(object)
invalidateValue(object.id)
def addObjectXmlRpc(self, objectImport):
"""Create a new person on the server.
Keyword argument:
=================
*objectImport*:
The new person in XML RPC dictionnary format.
Returns:
========
The new person ID
Exceptions:
===========
*faults.UserAccessDenied*:
The user is not in the admin set.
*standard Exception*:
- The thing category 'object' doesn't exists. (very grave !)
- The adminImport __thingName__ key is not a valid Thing name.
*KeyError*:
The virtual server ID does not correspond to a instanciated
virtual server.
*faults.UnknownApplicationToken*:
The given application token is not in the dispatcher virtual server
ids dictionnary.
*AttributeError*:
+ No groups server proxy found.
*faults.MissingItem*:
The Person object was not found.
*faults.BadEmailAddress*:
The Person email address is invalid.
*faults.SmtpError*:
The user was created, but the mail with his password cannot be
send.
(Problem with the SMTP gateway/server).
"""
objectId = ObjectsServer.addObjectXmlRpc(self, objectImport)
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
object = virtualServer.loadObjectCore(objectId)
if virtualServer.admin.defaultGroupId is not None:
getProxyForServerRole('groups').addObjectMember(
virtualServer.admin.defaultGroupId, object.id)
try:
gpg = Gpg(email = object.email, fingerprint = object.fingerprint)
gpg.addKey()
except: # raise Exception('GnuPG not configured for the user')
# it should be a real fault that could be catched specifically
pass
return object.id
def canDeleteObject(self, objectId):
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
if not virtualServer.canLoadObjectCore(objectId):
return 0
object = virtualServer.loadObjectCore(objectId)
return self.isAdmin() \
or getProxyForServerRole('authentication').setContainsUser(
[object.id])
def canGetObject(self, objectId):
if ObjectsServer.canGetObject(self, objectId):
return 1
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
if not virtualServer.canLoadObjectCore(objectId):
return 0
return getProxyForServerRole('authentication').setContainsUser(
[objectId])
def canModifyObject(self, objectId):
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
if not virtualServer.canLoadObjectCore(objectId):
return 0
object = virtualServer.loadObjectCore(objectId)
return self.isAdmin() \
or getProxyForServerRole('authentication').setContainsUser(
[object.id])
def deleteObject(self, objectId):
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
object = virtualServer.loadObjectCore(objectId)
if not self.canDeleteObject(objectId) \
and not object.canBeDeletedByClient():
raise faults.UserAccessDenied()
try:
gpg = Gpg(object.email)
gpg.deleteKey()
except: # tighter check
pass
object.removeNonCore()
object.clear()
del virtualServer.objects[objectId]
virtualServer.markObjectAsDeleted(objectId)
virtualServer.markCoreAsDirty()
invalidateValue(objectId)
def findObjectIdsXmlRpc(self, text):
"""Find person objects ID corresponding to a specified string.
Keyword argument:
=================
*text*:
The text that must be in the person object (conforming to the
object containstext method).
Return values:
==============
*The corresponding objects IDs sequence*
Exceptions:
===========
*KeyError*:
The virtual server ID does not correspond to a instanciated virtual
server.
"""
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
text = iso8859_15(text)
foundIds = []
for objectId, object in virtualServer.objects.items():
if object.containsText(text):
foundIds.append(objectId)
return foundIds
def getObjectStringFromDigestXmlRpc(self, objectId, path, digest):
"""Retrieve a string in the specified object from its MD5 digest.
Keyword arguments:
==================
*path*:
The path to the string location in the object. It could be the
object itself, or a dictionnary or a sequence, etc.
*digest*:
The MD5 digest of the wanted string.
Return value:
=============
*The the object string representation in UTF-8*
Exceptions:
===========
*faults.UserAccessDenied*:
The user is not in the admin set.
*TypeError*:
The given path is None.
*KeyError*:
The virtual server ID does not correspond to a instanciated virtual
server.
*faults.MissingItem*:
No person object corresponding to the login was not found.
*faults.UnknownStringDigest*:
No string corresponding to the given MD5 digest was found.
"""
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
object = virtualServer.loadObjectCore(objectId)
object.acquireNonCore()
try:
# The objectLabel is accessible from everybody.
if path == 'self.getLabel()':
result = getStringFromDigest(object.getLabel(), digest)
else:
if not self.isAdmin() and not getProxyForServerRole(
'authentication').setContainsUser([objectId]):
raise faults.UserAccessDenied()
result = getStringFromDigest(
eval(path, {'self': object}), digest)
finally:
object.releaseNonCore()
if result is None:
raise faults.UnknownStringDigest(digest)
return utf8(result)
def modifyObjectXmlRpc(self, objectImport):
"""Modify an object.
Keyword argument:
=================
*objectImport*:
The modified person in XML RPC dictionnary format.
Returns:
========
The new version number of the modified object.
Exceptions:
===========
*faults.MissingItem*:
The specified object does not exist.
*faults.UserAccessDenied*:
The client is not allowed to modify the object.
*standard Exception*:
- The thing category 'object' doesn't exists. (very grave !)
- The adminImport __thingName__ key is not a valid Thing name.
*KeyError*:
The virtual server ID does not correspond to a instanciated virtual
server.
*faults.ReadOnlyObject*:
The object is read only.
"""
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
objectChanges = commonTools.importThing(objectImport)
object = virtualServer.loadObjectCore(objectChanges.id)
object.acquireNonCore()
try:
if not object.canBeModified():
raise faults.ReadOnlyObject()
if not self.isAdmin() and not getProxyForServerRole(
'authentication').setContainsUser([object.id]):
if not object.canBeModifiedByClient():
raise faults.UserAccessDenied()
object.checkModifyIsPossible(objectChanges)
if object.fingerprint:
try:
gpg = Gpg(objectChanges.email,
fingerprint = object.fingerprint)
gpg.updateKey()
except: # tighter check
pass
object.modify(objectChanges)
object.saveNonCore()
finally:
object.releaseNonCore()
virtualServer.markObjectAsDirty(object)
invalidateValue(object.id)
return object.version
def registerPublicMethods(self):
"""Register the people server XML RPs."""
ObjectsServer.registerPublicMethods(self)
self.registerPublicMethod('abstainForVote')
self.registerPublicMethod('findObjectIds',
self.findObjectIdsXmlRpc)
self.registerPublicMethod('vote')
def repairVirtualServer(self, virtualServer, version):
"""Handle a descendant compatibily with older server datas.
This method can load datas stored by older server version.
Keyword arguments:
==================
*virtualServer*:
The actual server instance.
*version*:
The datas version number.
"""
changed = 0
if version < 3004:
import time
for object in virtualServer.objects.values():
if object.creationTime is None:
changed = 1
object.modificationTime = object.creationTime = time.time()
if version < 4000:
changed = virtualServer.admin.repair(4000) or changed
for id, object in virtualServer.objects.items():
newId = repairId(id)
if newId:
changed = 1
del virtualServer.objects[id]
virtualServer.objects[newId] = object
changed = object.repair(4000) or changed
if version < 5004:
changed = virtualServer.admin.repair(5004) or changed
for id, object in virtualServer.objects.items():
changed = object.repair(5004) or changed
if version <= 1005000:
changed = 1
virtualServer.objectsByLogin = {}
for id, object in virtualServer.objects.items():
virtualServer.objectsByLogin[object.login] = object
if version <= 1029000:
admin = virtualServer.admin
if admin.id is None:
changed = 1
admin.id = '%s/__admin__' % virtualServer.virtualServerId
if version <= 1041000:
for object in virtualServer.objects.values():
if object.voteTokens is None:
continue
for electionId, voteToken in object.voteTokens.items():
if not voteToken.startswith('glasnost://'):
changed = 1
object.voteTokens[electionId] = '%s/votes/%s' % (
commonTools.extractDispatcherId(
virtualServer.virtualServerId),
voteToken)
if version <= 1123000:
del virtualServer.objectsByLogin
changed = 1
if changed:
virtualServer.markAllAsDirtyFIXME()
def upgrade_0001_0019(self, virtualServer):
ObjectsServer.upgrade_0001_0019(self, virtualServer)
# Fill empty writersSet slots in admin.
admin = virtualServer.admin
if admin.hasSlotName('writersSet') and not admin.writersSet:
admin.writersSet = [system.generalPublicId]
virtualServer.markAdminAsDirty(admin)
def vote(self, electionId, voteToken):
"""Vote to an election.
Keyword arguments:
==================
*electionId*:
The ID of the election to vote.
*voteToken*:
The vote token associated to the vote.
Exceptions:
===========
*faults.MissingItem*:
The specified election was not found.
*KeyError*:
The virtual server ID does not correspond to a instanciated virtual
server.
"""
virtualServerId = context.getVar('applicationId')
virtualServer = self.getVirtualServer(virtualServerId)
voterId = getProxyForServerRole('authentication').getUserId()
object = virtualServer.loadObjectCore(voterId)
object.vote(electionId, voteToken)
virtualServer.markObjectAsDirty(object)
invalidateValue(object.id)
peopleServer = PeopleServer()
if __name__ == "__main__":
peopleServer.launch(applicationName, applicationRole)