Commit 5ee598ce authored by Jérome Perrin's avatar Jérome Perrin Committed by Levin Zimmermann

AlarmTool: handle automatic solve with alarms owned by system user

Business templates are installed by system user, which is a special
user not returned by getWrappedOwner. Because of this, the "fixing
problems or activating a disabled alarm is not allowed" error was
raised when checking if the owner of the alarm has manage portal
permission on the alarm.

This switches the implementation to explicit creation of the user
when user id is the system user, so that we have a user with the
permission to solve the alarm.
parent 5dd53c20
......@@ -119,7 +119,7 @@ class Alarm(XMLObject, PeriodicityMixin):
if not checkPermission(Permissions.ManagePortal, self):
raise Unauthorized('fixing problems or activating a disabled alarm is not allowed')
# Use UnrestrictedMethod, so that the behaviour would not
# Use UnrestrictedMethod, so that the behavior would not
# change even if this method is invoked by random users.
@UnrestrictedMethod
def _activeSense():
......@@ -135,7 +135,41 @@ class Alarm(XMLObject, PeriodicityMixin):
# able to notify the user after all processes are ended
# We do some inspection to keep compatibility
# (because fixit and tag were not set previously)
tag = self.__getTag(True)
if 'tag' not in activate_kw:
# If a given alarm is running more than once at a given point in
# time, there is a risk that the same random value will have been
# produced.
# In such event, notify() will be delayed until all conflicting
# invocations are done executing.
# Also, all notifications will as a result refer to a single
# ActiveProcess, so all but one notification content will be lost to
# someone only checking notification content.
# This is expected to be highly unlikely to have any meaningful
# effect, because:
# - if a single alarm can be running multiple times in parallel and
# has notifications enabled, there is anyway no guarantee that each
# payload will actually be properly notified (independently from
# any tag collision)
# - if a single alarm is not usually happening in parallel and
# notifications are enabled (hence, there is a reasonable
# expectation that each notification will properly happen),
# parallel execution means a former invocation failed, so
# administrator attention should already be attracted to the issue.
# - and overall, alarm concurrency should be low enough that
# collision should stay extremely low: it should be extremely rare
# for a notification-enabled alarm to run even 10 times in parallel
# (as alarms can at most be spawned every minute), and even in such
# case the probability of a collision is about 2e-9 (10 / 2**32).
# Assuming 10 alarms spawned every second with a one-second duration
# it takes a bit under 7 years for a single collision to be more
# likely to have occurred than not: 50% / (10 / 2**32) = 6.8 years
# On the other hand, using a completely constant tag per alarm would
# mean notify() would be blocked by any isolated failure event, which
# increases the pressure on a timely resolution of what could be a
# frequent occurrence (ex: an alarm pulling data from a 3rd-party
# server with poor availability).
activate_kw['tag'] = '%s_%x' % (self.getRelativeUrl(), getrandbits(32))
tag = activate_kw['tag']
method = getattr(self, method_id)
func_code = method.func_code
try:
......
......@@ -29,7 +29,7 @@
import time
import threading
from AccessControl import ClassSecurityInfo
from AccessControl import ClassSecurityInfo, SpecialUsers
from AccessControl.SecurityManagement import getSecurityManager, \
newSecurityManager, setSecurityManager
from Products.ERP5Type.Globals import InitializeClass, DTMLFile, PersistentMapping
......@@ -49,7 +49,7 @@ class AlarmTool(TimerServiceMixin, BaseTool):
"""
This tool manages alarms.
It is used as a central managment point for all alarms.
It is used as a central management point for all alarms.
Inside this tool we have a way to retrieve all reports coming
from Alarms,...
......@@ -131,10 +131,19 @@ class AlarmTool(TimerServiceMixin, BaseTool):
if so then we will activate them.
"""
security_manager = getSecurityManager()
system_user = None
try:
for alarm in self.getAlarmList(to_active=1):
if alarm is not None:
user = alarm.getWrappedOwner()
udb, owner_user_id = alarm.getOwnerTuple()
if owner_user_id == SpecialUsers.system.getUserName():
if system_user is None:
# build this wrapped system user only once per tic
system_user = SpecialUsers.system.__of__(
self.getPhysicalRoot().unrestrictedTraverse(udb, None))
user = system_user
else:
user = alarm.getWrappedOwner()
newSecurityManager(self.REQUEST, user)
if alarm.isActive() or not alarm.isEnabled():
# do nothing if already active, or not enabled
......
......@@ -26,30 +26,17 @@
#
##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.tests.utils import DummyMailHost
from Products.ERP5Type.UnrestrictedMethod import super_user
from AccessControl.SecurityManagement import newSecurityManager, \
getSecurityManager, setSecurityManager
from AccessControl.User import nobody
from AccessControl import Unauthorized
from DateTime import DateTime
from Products.ERP5Type.DateUtils import addToDate
class TestAlarm(ERP5TypeTestCase):
"""
This is the list of test
test setNextStartDate :
- every hour
- at 6, 10, 15, 21 every day
- every day at 10
- every 3 days at 14 and 15 and 17
- every monday and friday, at 6 and 15
- every 1st and 15th every month, at 12 and 14
- every 1st day of every 2 month, at 6
"""
# year/month/day hour:minute:second
date_format = '%i/%i/%i %i:%i:%d GMT+0100'
......@@ -72,10 +59,17 @@ class TestAlarm(ERP5TypeTestCase):
def newAlarm(self, **kw):
"""
Create an empty alarm
Create an empty alarm, owned by system user, like when the alarm is
installed from a business template.
"""
a_tool = self.getAlarmTool()
return a_tool.newContent(**kw)
sm = getSecurityManager()
newSecurityManager(None, nobody)
try:
with super_user():
return self.getAlarmTool().newContent(**kw)
finally:
setSecurityManager(sm)
def test_01_HasEverything(self):
# Test if portal_alarms was created
......@@ -614,9 +608,3 @@ class TestAlarm(ERP5TypeTestCase):
self.tic()
alarm_list = alarm.Alarm_zGetAlarmDate(uid=alarm.getUid())
self.assertEqual(date.toZone('UTC'), alarm_list[0].alarm_date)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestAlarm))
return suite
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment