work in progress: access tracking - loading access log basically OK
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2964 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
bb6a3b3d7b
commit
bf453ace5a
6 changed files with 189 additions and 39 deletions
|
@ -22,10 +22,13 @@ Recording changes to loops objects.
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import transaction
|
||||||
from zope.app.publication.interfaces import IEndRequestEvent
|
from zope.app.publication.interfaces import IEndRequestEvent
|
||||||
from zope.interface import Interface
|
from zope.interface import Interface, implements
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.component import adapter
|
from zope.component import adapter
|
||||||
from zope.security.proxy import removeSecurityProxy
|
from zope.security.proxy import removeSecurityProxy
|
||||||
|
@ -35,13 +38,14 @@ from cybertools.tracking.btree import Track, getTimeStamp
|
||||||
from cybertools.tracking.interfaces import ITrack
|
from cybertools.tracking.interfaces import ITrack
|
||||||
from cybertools.tracking.logfile import Logger, loggers
|
from cybertools.tracking.logfile import Logger, loggers
|
||||||
from loops.interfaces import ILoopsObject
|
from loops.interfaces import ILoopsObject
|
||||||
from loops.organize.party import getPersonForUser
|
from loops.organize.tracking.base import BaseRecordManager
|
||||||
from loops.security.common import getCurrentPrincipal
|
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
# logging
|
||||||
|
|
||||||
|
logfile_option = 'organize.tracking.logfile'
|
||||||
request_key = 'loops.organize.tracking.access'
|
request_key = 'loops.organize.tracking.access'
|
||||||
loggers_key = 'loops.access'
|
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
'001': ('principal', 'node', 'target', 'view', 'params'),
|
'001': ('principal', 'node', 'target', 'view', 'params'),
|
||||||
|
@ -71,11 +75,12 @@ def logAccess(event):
|
||||||
logger = loggers.get(loggers_key)
|
logger = loggers.get(loggers_key)
|
||||||
if not logger:
|
if not logger:
|
||||||
options = IOptions(context.getLoopsRoot())
|
options = IOptions(context.getLoopsRoot())
|
||||||
logfile = options('organize.tracking.logfile')
|
logfile = options(logfile_option)
|
||||||
if not logfile:
|
if not logfile:
|
||||||
return
|
return
|
||||||
path = os.path.join(util.getVarDirectory(), logfile[0])
|
fn = logfile[0]
|
||||||
logger = loggers[loggers_key] = Logger(loggers_key, path)
|
path = os.path.join(util.getVarDirectory(), fn)
|
||||||
|
logger = loggers[fn] = Logger(fn, path)
|
||||||
logger.log(marshall(data))
|
logger.log(marshall(data))
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,3 +90,88 @@ def marshall(data):
|
||||||
for key in fields[version]:
|
for key in fields[version]:
|
||||||
values.append(data.get(key) or '')
|
values.append(data.get(key) or '')
|
||||||
return ';'.join(values)
|
return ';'.join(values)
|
||||||
|
|
||||||
|
|
||||||
|
# record manager
|
||||||
|
|
||||||
|
class AccessRecordManager(BaseRecordManager):
|
||||||
|
|
||||||
|
storageName = 'access'
|
||||||
|
|
||||||
|
def __init__(self, context, request):
|
||||||
|
self.context = context
|
||||||
|
self.request = request
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def logfile(self):
|
||||||
|
value = self.options(logfile_option)
|
||||||
|
return value and value[0] or None
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def valid(self):
|
||||||
|
return self.storage is not None and self.logfile
|
||||||
|
|
||||||
|
def loadRecordsFromLog(self):
|
||||||
|
if not self.valid:
|
||||||
|
return
|
||||||
|
fn = self.logfile
|
||||||
|
path = os.path.join(util.getVarDirectory(), fn)
|
||||||
|
logger = loggers.get(fn)
|
||||||
|
if not logger:
|
||||||
|
logger = loggers[fn] = Logger(fn, path)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return
|
||||||
|
lf = open(path, 'r')
|
||||||
|
for idx, line in enumerate(lf):
|
||||||
|
self.processLogRecord(idx, line)
|
||||||
|
lf.close()
|
||||||
|
transaction.commit()
|
||||||
|
logger.doRollover()
|
||||||
|
|
||||||
|
def processLogRecord(self, idx, line):
|
||||||
|
if not line:
|
||||||
|
return
|
||||||
|
values = line.split(';')
|
||||||
|
timeString = values.pop(0)
|
||||||
|
version = values.pop(0)
|
||||||
|
if version not in fields:
|
||||||
|
logging.getLogger('AccessRecordManager').warn(
|
||||||
|
'Undefined logging record version %r on record %i.'
|
||||||
|
% (version, idx))
|
||||||
|
return
|
||||||
|
if len(values) != len(fields[version]):
|
||||||
|
logging.getLogger('AccessRecordManager').warn(
|
||||||
|
'Length of record %i does not match version %r.'
|
||||||
|
% (idx, version))
|
||||||
|
return
|
||||||
|
data = {}
|
||||||
|
for idx, field in enumerate(fields[version]):
|
||||||
|
data[field] = values[idx]
|
||||||
|
timeStamp = timeStringToTimeStamp(timeString)
|
||||||
|
personId = self.getPersonId(data['principal'])
|
||||||
|
taskId = data['target'] or data['node']
|
||||||
|
existing = self.storage.query(taskId=taskId, userName=self.personId,
|
||||||
|
timeStamp=timeStamp)
|
||||||
|
for track in existing:
|
||||||
|
if track.data == data: # has been recorded already
|
||||||
|
return
|
||||||
|
self.storage.saveUserTrack(taskId, 0, personId, data,
|
||||||
|
timeStamp=timeStamp)
|
||||||
|
|
||||||
|
|
||||||
|
class IAccessRecord(ITrack):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AccessRecord(Track):
|
||||||
|
|
||||||
|
implements(IAccessRecord)
|
||||||
|
|
||||||
|
typeName = 'AccessRecord'
|
||||||
|
|
||||||
|
|
||||||
|
def timeStringToTimeStamp(timeString):
|
||||||
|
s, decimal = timeString.split(',')
|
||||||
|
t = time.strptime(s, '%Y-%m-%d %H:%M:%S')
|
||||||
|
return int(time.mktime(t))
|
||||||
|
|
71
organize/tracking/base.py
Normal file
71
organize/tracking/base.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base class(es) for track/record managers.
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
|
||||||
|
from cybertools.meta.interfaces import IOptions
|
||||||
|
from loops.organize.party import getPersonForUser
|
||||||
|
from loops.organize.util import getPrincipalForUserId
|
||||||
|
from loops.security.common import getCurrentPrincipal
|
||||||
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
class BaseRecordManager(object):
|
||||||
|
|
||||||
|
context = None
|
||||||
|
valid = True
|
||||||
|
storageName = None
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def options(self):
|
||||||
|
#return IOptions(self.context)
|
||||||
|
return IOptions(self.loopsRoot)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def loopsRoot(self):
|
||||||
|
return self.context.getLoopsRoot()
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def storage(self):
|
||||||
|
records = self.loopsRoot.getRecordManager()
|
||||||
|
if records is not None:
|
||||||
|
return records.get(self.storageName)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def personId(self):
|
||||||
|
return self.getPersonId()
|
||||||
|
|
||||||
|
def getPersonId(self, userId=None):
|
||||||
|
if userId is None:
|
||||||
|
principal = getCurrentPrincipal()
|
||||||
|
else:
|
||||||
|
principal = getPrincipalForUserId(userId, context=self.context)
|
||||||
|
if principal is not None:
|
||||||
|
person = getPersonForUser(self.context, principal=principal)
|
||||||
|
if person is None:
|
||||||
|
return principal.id
|
||||||
|
return util.getUidForObject(person)
|
||||||
|
return None
|
||||||
|
|
|
@ -37,24 +37,20 @@ from loops.resource import ResourceManager
|
||||||
from loops.interfaces import IAssignmentEvent, IDeassignmentEvent
|
from loops.interfaces import IAssignmentEvent, IDeassignmentEvent
|
||||||
from loops.interfaces import ILoopsObject
|
from loops.interfaces import ILoopsObject
|
||||||
from loops.organize.party import getPersonForUser
|
from loops.organize.party import getPersonForUser
|
||||||
|
from loops.organize.tracking.base import BaseRecordManager
|
||||||
from loops.security.common import getCurrentPrincipal
|
from loops.security.common import getCurrentPrincipal
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
class ChangeManager(object):
|
class ChangeManager(BaseRecordManager):
|
||||||
|
|
||||||
context = None
|
storageName = 'changes'
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
if isinstance(context, (ConceptManager, ResourceManager)):
|
if isinstance(context, (ConceptManager, ResourceManager)):
|
||||||
return
|
return
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def options(self):
|
|
||||||
#return IOptions(self.context)
|
|
||||||
return IOptions(self.loopsRoot)
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def valid(self):
|
def valid(self):
|
||||||
return (not (self.context is None or
|
return (not (self.context is None or
|
||||||
|
@ -62,27 +58,6 @@ class ChangeManager(object):
|
||||||
self.personId is None)
|
self.personId is None)
|
||||||
and self.options('organize.tracking.changes'))
|
and self.options('organize.tracking.changes'))
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def loopsRoot(self):
|
|
||||||
return self.context.getLoopsRoot()
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def storage(self):
|
|
||||||
records = self.loopsRoot.getRecordManager()
|
|
||||||
if records is not None:
|
|
||||||
return records.get('changes')
|
|
||||||
return None
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def personId(self):
|
|
||||||
principal = getCurrentPrincipal()
|
|
||||||
if principal is not None:
|
|
||||||
person = getPersonForUser(self.context, principal=principal)
|
|
||||||
if person is None:
|
|
||||||
return principal.id
|
|
||||||
return util.getUidForObject(person)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def recordModification(self, action='modify', **kw):
|
def recordModification(self, action='modify', **kw):
|
||||||
if not self.valid:
|
if not self.valid:
|
||||||
return
|
return
|
||||||
|
@ -117,10 +92,6 @@ class ChangeRecord(Track):
|
||||||
def recordModification(obj, event):
|
def recordModification(obj, event):
|
||||||
ChangeManager(obj).recordModification()
|
ChangeManager(obj).recordModification()
|
||||||
|
|
||||||
#@adapter(ILoopsObject, IObjectCreatedEvent)
|
|
||||||
#def recordCreation(obj, event):
|
|
||||||
# ChangeManager(obj).recordModification('create')
|
|
||||||
|
|
||||||
@adapter(ILoopsObject, IObjectAddedEvent)
|
@adapter(ILoopsObject, IObjectAddedEvent)
|
||||||
def recordAdding(obj, event):
|
def recordAdding(obj, event):
|
||||||
ChangeManager(obj).recordModification('add')
|
ChangeManager(obj).recordModification('add')
|
||||||
|
|
|
@ -26,6 +26,13 @@
|
||||||
|
|
||||||
<zope:subscriber handler="loops.organize.tracking.access.logAccess" />
|
<zope:subscriber handler="loops.organize.tracking.access.logAccess" />
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
name="load_access_records"
|
||||||
|
for="loops.interfaces.ILoops"
|
||||||
|
class="loops.organize.tracking.access.AccessRecordManager"
|
||||||
|
attribute="loadRecordsFromLog"
|
||||||
|
permission="zope.Public" />
|
||||||
|
|
||||||
<zope:adapter factory="loops.organize.tracking.setup.SetupManager"
|
<zope:adapter factory="loops.organize.tracking.setup.SetupManager"
|
||||||
name="organize.tracking" />
|
name="organize.tracking" />
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ from zope.interface import implements, Interface
|
||||||
|
|
||||||
from cybertools.tracking.btree import TrackingStorage
|
from cybertools.tracking.btree import TrackingStorage
|
||||||
from loops.organize.tracking.change import ChangeRecord
|
from loops.organize.tracking.change import ChangeRecord
|
||||||
|
from loops.organize.tracking.access import AccessRecord
|
||||||
from loops.setup import SetupManager as BaseSetupManager
|
from loops.setup import SetupManager as BaseSetupManager
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,3 +37,5 @@ class SetupManager(BaseSetupManager):
|
||||||
records = self.context.getRecordManager()
|
records = self.context.getRecordManager()
|
||||||
changes = self.addObject(records, TrackingStorage, 'changes',
|
changes = self.addObject(records, TrackingStorage, 'changes',
|
||||||
trackFactory=ChangeRecord)
|
trackFactory=ChangeRecord)
|
||||||
|
access = self.addObject(records, TrackingStorage, 'access',
|
||||||
|
trackFactory=AccessRecord)
|
||||||
|
|
|
@ -84,6 +84,14 @@ def getInternalPrincipal(id, context=None):
|
||||||
raise PrincipalLookupError(id)
|
raise PrincipalLookupError(id)
|
||||||
|
|
||||||
|
|
||||||
|
def getPrincipalForUserId(id, context=None):
|
||||||
|
auth = component.getUtility(IAuthentication, context=context)
|
||||||
|
try:
|
||||||
|
return auth.getPrincipal(id)
|
||||||
|
except PrincipalLookupError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def getTrackingStorage(obj, name):
|
def getTrackingStorage(obj, name):
|
||||||
records = obj.getLoopsRoot().getRecordManager()
|
records = obj.getLoopsRoot().getRecordManager()
|
||||||
if records is not None:
|
if records is not None:
|
||||||
|
|
Loading…
Add table
Reference in a new issue