=============================================================== loops - Linked Objects for Organization and Processing Services =============================================================== ($Id$) Note: This packages depends on cybertools.organize. Let's do some basic setup >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) >>> from zope import component, interface and set up a simple loops site with a concept manager and some concepts (with all the type machinery, what in real life is done via standard ZCML setup): >>> from loops.organize.setup import SetupManager >>> component.provideAdapter(SetupManager, name='organize') >>> from loops.tests.setup import TestSite >>> t = TestSite(site) >>> concepts, resources, views = t.setup() >>> from loops import util >>> loopsRoot = site['loops'] >>> loopsId = util.getUidForObject(loopsRoot) >>> from loops.organize.tests import setupUtilitiesAndAdapters >>> setupData = setupUtilitiesAndAdapters(loopsRoot) >>> type = concepts['type'] >>> person = concepts['person'] >>> from loops.concept import Concept >>> johnC = concepts['john'] = Concept(u'John') >>> johnC.conceptType = person Organizations: Persons (and Users), Institutions, Addresses... ============================================================== The classes used in this package are just adapters to IConcept. >>> from loops.organize.interfaces import IPerson >>> john = IPerson(johnC) >>> john.title u'John' >>> john.firstName = u'John' >>> johnC._firstName u'John' >>> john.lastName is None True >>> john.someOtherAttribute Traceback (most recent call last): ... AttributeError: ... someOtherAttribute We can use the age calculations from the base Person class: >>> from datetime import date >>> john.birthDate = date(1980, 3, 26) >>> john.ageAt(date(2006, 5, 12)) 26 >>> john.age >= 26 True A person can be associated with a user of the system by setting the userId attribute; this will also register the person concept in the corresponding principal annotations so that there is a fast way to find the person(s) belonging to a user/principal. For testing, we first have to provide the needed utilities and settings (in real life this is all done during Zope startup): >>> auth = setupData.auth >>> principalAnnotations = setupData.principalAnnotations >>> principal = auth.definePrincipal('users.john', u'John', login='john') >>> john.userId = 'users.john' >>> annotations = principalAnnotations.getAnnotationsById('users.john') >>> from loops.organize.party import ANNOTATION_KEY >>> annotations[ANNOTATION_KEY][loopsId] == johnC True Change a userId assignment: >>> principal = auth.definePrincipal('users.johnny', u'Johnny', login='johnny') >>> john.userId = 'users.johnny' >>> annotations = principalAnnotations.getAnnotationsById('users.johnny') >>> annotations[ANNOTATION_KEY][loopsId] == johnC True >>> annotations = principalAnnotations.getAnnotationsById('users.john') >>> annotations[ANNOTATION_KEY][loopsId] is None True >>> john.userId = None >>> annotations = principalAnnotations.getAnnotationsById('users.johnny') >>> annotations[ANNOTATION_KEY][loopsId] is None True Deleting a person with a userId assigned removes the corresponding principal annotation: >>> from zope.app.container.interfaces import IObjectRemovedEvent >>> from zope.app.container.contained import ObjectRemovedEvent >>> from zope.event import notify >>> from zope.interface import Interface >>> from loops.interfaces import IConcept >>> from loops.organize.party import removePersonReferenceFromPrincipal >>> from zope.app.testing import ztapi >>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None, ... removePersonReferenceFromPrincipal) >>> john.userId = 'users.john' >>> annotations = principalAnnotations.getAnnotationsById('users.john') >>> annotations[ANNOTATION_KEY][loopsId] == johnC True >>> del concepts['john'] >>> annotations = principalAnnotations.getAnnotationsById('users.john') >>> annotations[ANNOTATION_KEY][loopsId] is None True If we try to assign a userId of a principal that already has a person concept assigned we should get an error: >>> johnC = concepts['john'] = Concept(u'John') >>> johnC.conceptType = person >>> john = IPerson(johnC) >>> john.userId = 'users.john' >>> marthaC = concepts['martha'] = Concept(u'Martha') >>> marthaC.conceptType = person >>> martha = IPerson(marthaC) >>> martha.userId = 'users.john' Traceback (most recent call last): ... ValueError: ... Member Registrations ==================== The member registration needs the whole pluggable authentication stuff with a principal folder: >>> from zope.app.appsetup.bootstrap import ensureUtility >>> from zope.app.authentication.authentication import PluggableAuthentication >>> from zope.app.security.interfaces import IAuthentication >>> ensureUtility(site, IAuthentication, '', PluggableAuthentication, ... copy_to_zlog=False, asObject=True) <...PluggableAuthentication...> >>> pau = component.getUtility(IAuthentication, context=site) >>> from zope.app.authentication.principalfolder import PrincipalFolder >>> from zope.app.authentication.interfaces import IAuthenticatorPlugin >>> pFolder = PrincipalFolder('loops.') >>> pau['loops'] = pFolder >>> pau.authenticatorPlugins = ('loops',) In addition, we have to create at least one node in the view space and register an IMemberRegistrationManager adapter for the loops root object: >>> from loops.view import Node >>> menu = views['menu'] = Node('Home') >>> menu.nodeType = 'menu' >>> from loops.organize.member import MemberRegistrationManager >>> component.provideAdapter(MemberRegistrationManager) Now we can enter the registration info for a new member (after having made sure that a principal object can be served by a corresponding factory): >>> from zope.app.authentication.principalfolder import FoundPrincipalFactory >>> component.provideAdapter(FoundPrincipalFactory) >>> data = {'loginName': u'newuser', ... 'password': u'quack', ... 'passwordConfirm': u'quack', ... 'lastName': u'Sawyer', ... 'firstName': u'Tom', ... 'email': u'tommy@sawyer.com', ... 'action': 'update',} and register it. >>> from zope.publisher.browser import TestRequest >>> request = TestRequest(form=data) >>> from loops.organize.browser.member import MemberRegistration >>> regView = MemberRegistration(menu, request) >>> regView.update() False >>> from loops.common import adapted >>> person = concepts['person.newuser'] >>> pa = adapted(person) >>> pa.lastName, pa.userId (u'Sawyer', u'loops.newuser') Now we can also retrieve it from the authentication utility: >>> pau.getPrincipal('loops.newuser').title u'Tom Sawyer' Change Password --------------- >>> data = {'oldPassword': u'tiger', ... 'password': u'lion', ... 'passwordConfirm': u'lion', ... 'action': 'update'} >>> request = TestRequest(form=data) We need a principal for testing the login stuff: >>> from zope.app.authentication.principalfolder import InternalPrincipal >>> principal = InternalPrincipal('scott', 'tiger', 'Scotty') >>> request.setPrincipal(principal) >>> from loops.organize.browser.member import PasswordChange >>> pwcView = PasswordChange(menu, request) >>> pwcView.update() False Security ======== Automatic security settings on persons -------------------------------------- >>> from zope.traversing.api import getName >>> list(sorted(getName(c) for c in concepts['person'].getChildren())) [u'john', u'martha', u'person.newuser'] Person objects that have a user assigned to them receive this user (principal) as their owner. >>> from zope.app.securitypolicy.interfaces import IPrincipalRoleMap >>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles() [('loops.Owner', 'users.john', PermissionSetting: Allow)] >>> IPrincipalRoleMap(concepts['person.newuser']).getPrincipalsAndRoles() [('loops.Owner', u'loops.newuser', PermissionSetting: Allow)] The person ``martha`` hasn't got a user id, so there is no role assigned to it. >>> IPrincipalRoleMap(concepts['martha']).getPrincipalsAndRoles() [] Only the owner (and a few other privileged people) should be able to edit a person object. We also need an interaction with a participation based on the principal whose permissions we want to check. >>> from zope.app.authentication.principalfolder import Principal >>> pJohn = Principal('users.john', 'xxx', u'John') >>> from loops.tests.auth import login >>> login(pJohn) We also want to grant some global permissions and roles, i.e. on the site or loops root level. >>> rolePermissions = setupData.rolePermissions >>> rolePermissions.grantPermissionToRole('zope.View', 'zope.Member') >>> principalRoles = setupData.principalRoles >>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.john') Now we are ready to look for the real stuff - what John is allowed to do. >>> from zope.security import canAccess, canWrite, checkPermission >>> john = concepts['john'] >>> canAccess(john, 'title') True Person objects that have an owner may be modified by this owner. >>> canWrite(john, 'title') True So let's try with another user with another role setting. >>> rolePermissions.grantPermissionToRole('zope.ManageContent', 'loops.Staff') >>> principalRoles.assignRoleToPrincipal('loops.Staff', 'users.martha') >>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.martha') >>> pMartha = Principal('users.martha', 'xxx', u'Martha') >>> login(pMartha) >>> canAccess(john, 'title') True >>> canWrite(john, 'title') False If we clear the userId attribute from a person object others may be allowed again to edit it... >>> adapted(john).userId = '' >>> canWrite(john, 'title') True ... but John no more... >>> login(pJohn) >>> canWrite(john, 'title') False Tasks and Events ================ Task view with edit action -------------------------- >>> from loops.setup import addAndConfigureObject >>> from loops.organize.interfaces import ITask >>> task = addAndConfigureObject(concepts, Concept, 'task', title=u'Task', ... conceptType=type, typeInterface=ITask) >>> from loops.organize.task import Task >>> component.provideAdapter(Task) >>> task01 = addAndConfigureObject(concepts, Concept, 'task01', ... title=u'Task #1', conceptType=task) >>> from loops.organize.browser.task import TaskView >>> view = TaskView(task01, TestRequest()) >>> view.getActions('portlet') [] Events listing -------------- >>> event = addAndConfigureObject(concepts, Concept, 'event', title=u'Event', ... conceptType=type, typeInterface=ITask) >>> event01 = addAndConfigureObject(concepts, Concept, 'event01', ... title=u'Event #1', conceptType=event, ... ) >>> from loops.organize.browser.event import Events >>> listing = Events(johnC, TestRequest()) >>> listing.getActions('portlet') [] >>> list(listing.events()) [] Fin de partie ============= >>> placefulTearDown()