534 lines
18 KiB
Python
Executable File
534 lines
18 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 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)
|
|
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)
|
|
|
|
|
|
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 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 then 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('identities').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('identities').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('identities').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(
|
|
'identities').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(
|
|
'identities').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('findObjectIds', self.findObjectIdsXmlRpc)
|
|
|
|
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 upgradeVirtualServer_0001_0019(self, virtualServer):
|
|
ObjectsServer.upgradeVirtualServer_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)
|
|
|
|
|
|
peopleServer = PeopleServer()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
peopleServer.launch(applicationName, applicationRole)
|
|
|