source: indico/indico/MaKaC/services/implementation/base.py @ dba09b

hello-world-walkthroughipv6v0.98-seriesv0.98.2v0.98.3v0.99v1.0v1.1
Last change on this file since dba09b was dba09b, checked in by Pedro Ferreira <jose.pedro.ferreira@…>, 16 months ago

[FIX] Restore old time for dropped blocks (undo)

  • Property mode set to 100644
File size: 17.8 KB
RevLine 
[9033fd]1# -*- coding: utf-8 -*-
2##
3##
4## This file is part of CDS Indico.
5## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
6##
7## CDS Indico is free software; you can redistribute it and/or
8## modify it under the terms of the GNU General Public License as
9## published by the Free Software Foundation; either version 2 of the
10## License, or (at your option) any later version.
11##
12## CDS Indico is distributed in the hope that it will be useful, but
13## WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15## General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
[9881d7]20
[9033fd]21from MaKaC.conference import Category
22
23import sys, traceback, time, os
24
25from pytz import timezone
26from datetime import datetime, date
27
28from MaKaC import conference
29from MaKaC.common.timezoneUtils import setAdjustedDate
[3afe015]30from MaKaC.common import security, Config
[bc991f]31from MaKaC.common.externalOperationsManager import ExternalOperationsManager
[13ce65]32from MaKaC.common.utils import parseDateTime
[9881d7]33
[872d48]34from MaKaC.errors import MaKaCError, HtmlScriptError, HtmlForbiddenTag, TimingError
[e83ea5]35from MaKaC.services.interface.rpc.common import ServiceError, ServiceAccessError, HTMLSecurityError, Warning,\
36    ResultWithWarning
[9033fd]37
[6328a5]38from MaKaC.webinterface.rh.base import RequestHandlerBase
[9033fd]39from MaKaC.webinterface.mail import GenericMailer, GenericNotification
40
41from MaKaC.accessControl import AccessWrapper
42
[60f883]43from MaKaC.i18n import _
[ba62ee]44from MaKaC.common.contextManager import ContextManager
[97a9da]45import MaKaC.common.info as info
[9033fd]46
47"""
48base module for asynchronous server requests
49"""
50
51class ExpectedParameterException(ServiceError):
52    """
53    Represents an exception that occurs when a type of parameter was expected
54    but another one was obtained
55    """
[61589d]56
[9033fd]57    def __init__(self, paramName, expected, got):
58        ServiceError.__init__(self, "ERR-P2","'%s': Expected '%s', got instead '%s'" % (paramName, expected, got))
59
[3bc472]60
[9033fd]61class EmptyParameterException(ServiceError):
62    """
63    Thrown when a parameter that should have a value is empty
64    """
65    def __init__(self, paramName=""):
66        ServiceError.__init__(self, "ERR-P3","Expected parameter '%s' is empty"%paramName)
67
[3c0c20]68class DateTimeParameterException(ServiceError):
69    """
70    Thrown when a parameter that should have a value is empty
71    """
72    def __init__(self, paramName, value):
73        ServiceError.__init__(self, "ERR-P4","Date/Time %s = '%s' is not valid " % (paramName, value))
74
[3bc472]75
[9033fd]76class ParameterManager(object):
77
78    """
79    The ParameterManager makes parameter processing a bit easier, by providing
80    some default transformations
81    """
[61589d]82
[9033fd]83    def __init__(self, paramList, allowEmpty=False, timezone=None):
84        self._paramList = paramList
85        self._allowEmpty = allowEmpty
86        self._timezone = timezone
[61589d]87
[9033fd]88    def extract(self, paramName, pType=None, allowEmpty=None, defaultValue=None):
89        """
90        Extracts a parameter, given a parameter name, and optional type
91        """
92
93        # "global" policy applies if allowEmpty not set
94        if (allowEmpty == None):
95            allowEmpty = self._allowEmpty
[61589d]96
[9033fd]97        value = self._paramList.get(paramName)
98
[12296f]99        if (not allowEmpty) and (value == None):
[9033fd]100            raise EmptyParameterException(paramName)
[61589d]101
[9033fd]102        if pType == str:
103            if value != None:
104                value = str(value)
105            else:
106                value = defaultValue
[61589d]107
[9033fd]108            if (value == '' or value == None) and not allowEmpty:
109                raise EmptyParameterException(paramName)
110        elif pType == datetime:
111            # format will possibly be accomodated to different standards,
112            # in the future
113
[3c0c20]114            try:
115                # both strings and objects are accepted
116                if type(value) == str:
117                    naiveDate = datetime.strptime(value, '%Y/%m/%d %H:%M')
[dba09b]118                elif value:
[3c0c20]119                    naiveDate = datetime.strptime(value['date']+' '+value['time'][:5], '%Y/%m/%d %H:%M')
[dba09b]120                elif not allowEmpty:
121                    raise EmptyParameterException(paramName)
122                else:
123                    naiveDate = None
[3c0c20]124            except ValueError:
125                raise DateTimeParameterException(paramName, value)
[61589d]126
[dba09b]127            if self._timezone and naiveDate:
[9033fd]128                value = timezone(self._timezone).localize(naiveDate).astimezone(timezone('utc'))
129            else:
130                value = naiveDate
131        elif pType == date:
132            # format will possibly be accomodated to different standards,
133            # in the future
[3c0c20]134            value = datetime.strptime(value,'%Y/%m/%d').date()
[9033fd]135        elif pType == int:
136            if value == None and allowEmpty:
137                value = None
138            else:
139                value = int(value)
140        elif pType == float:
141            if value == None and allowEmpty:
142                value = None
143            else:
144                value = float(value)
145        elif pType == dict:
146            if not (type(value) == dict or (allowEmpty and value == None)):
147                raise ExpectedParameterException(paramName, dict, type(value))
148        elif pType == list:
149            if not (type(value) == list or (allowEmpty and value == None)):
150                raise ExpectedParameterException(paramName, list, type(value))
[12296f]151        elif pType == bool:
152            if not (type(value) == bool or (allowEmpty and value == None)):
[f7f228]153                raise ExpectedParameterException(paramName, bool, type(value))
[9033fd]154
155        return value
156
157    def setTimezone(self, tz):
158        self._timezone = tz
159
[3bc472]160
[12296f]161class ServiceBase(RequestHandlerBase):
[9033fd]162    """
163    The ServiceBase class is the basic class for services.
[12296f]164    """
165
[59dfb4]166    def __init__(self, params, session, req):
[9033fd]167        """
168        Constructor.  Initializes provate variables
169        @param req: HTTP Request provided by the previous layer
170        """
[59dfb4]171        RequestHandlerBase.__init__(self, req)
[490053]172        self._reqParams = self._params = params
[9033fd]173        self._requestStarted = False
174        self._websession = session
175        # Fill in the aw instance with the current information
176        self._aw = AccessWrapper()
[59dfb4]177        self._aw.setIP(self.getHostIP())
[9033fd]178        self._aw.setSession(session)
179        self._aw.setUser(session.getUser())
180        self._target = None
181        self._startTime = None
[175fad]182        self._tohttps = self._req.is_https()
[9033fd]183        self._endTime = None
184        self._doProcess = True  #Flag which indicates whether the RH process
185                                #   must be carried out; this is useful for
186                                #   the checkProtection methods
187        self._tempFilesToDelete = []
[928829]188
[9033fd]189    # Methods =============================================================
[928829]190
[9033fd]191    def _getSession( self ):
192        """
[928829]193        Returns the web session associated to the received mod_python
[9033fd]194        request.
195        """
196        return self._websession
[928829]197
[9033fd]198    def _checkParams(self):
199        """
200        Checks the request parameters (normally overloaded)
201        """
202        pass
[928829]203
[9033fd]204    def _checkProtection( self ):
205        """
206        Checks protection when accessing resources (normally overloaded)
207        """
208        pass
209
210    def _processError(self):
211        """
212        Treats errors occured during the process of a RH, returning an error string.
213        @param e: the exception
214        @type e: An Exception-derived type
215        """
[928829]216
[9033fd]217        trace = traceback.format_exception(*sys.exc_info())
[928829]218
[9033fd]219        return ''.join(trace)
220
221    def _deleteTempFiles( self ):
222        if len(self._tempFilesToDelete) > 0:
223            for file in self._tempFilesToDelete:
224                os.remove(file)
[928829]225
[9033fd]226    def process(self):
227        """
228        Processes the request, analyzing the parameters, and feeding them to the
229        _getAnswer() method (implemented by derived classes)
230        """
231
[ba62ee]232        ContextManager.set('currentRH', self)
233
[f25f07]234        self._setLang()
[9033fd]235        self._checkParams()
236        self._checkProtection()
237
238        try:
[310d46]239            security.Sanitization.sanitizationCheck(self._target,
[9033fd]240                                   self._params,
241                                   self._aw)
[872d48]242        except (HtmlScriptError, HtmlForbiddenTag), e:
[40cfd6]243            raise HTMLSecurityError('ERR-X0','HTML Security problem. %s ' % str(e))
[928829]244
[9033fd]245        if self._doProcess:
[3afe015]246            if Config.getInstance().getProfile():
247                import profile, pstats, random
248                proffilename = os.path.join(Config.getInstance().getTempDir(), "service%s.prof" % random.random())
249                result = [None]
250                profile.runctx("result[0] = self._getAnswer()", globals(), locals(), proffilename)
251                answer = result[0]
252                rep = Config.getInstance().getTempDir()
253                stats = pstats.Stats(proffilename)
254                stats.strip_dirs()
255                stats.sort_stats('cumulative', 'time', 'calls')
256                stats.dump_stats(os.path.join(rep, "IndicoServiceRequestProfile.log"))
257                os.remove(proffilename)
258            else:
259                answer = self._getAnswer()
[9033fd]260            self._deleteTempFiles()
[928829]261
262            return answer
[9033fd]263
264    def _getAnswer(self):
265        """
266        To be overloaded. It should contain the code that does the actual
267        business logic and returns a result (python JSON-serializable object).
268        If this method is not overloaded, an exception will occur.
269        If you don't want to return an answer, you should still implement this method with 'pass'.
270        """
271        # This exception will happen if the _getAnswer method is not implemented in a derived class
272        raise MaKaCError("No answer was returned")
273
274
275class ProtectedService(ServiceBase):
276    """
[f78b0c]277    ProtectedService is a parent class for ProtectedDisplayService and ProtectedModificationService
[9033fd]278    """
279
[928829]280    def _checkSessionUser(self):
281        """
282        Checks that the current user exists (is authenticated)
283        """
284
285        if self._getUser() == None:
286            self._doProcess = False
[a755c1]287            raise ServiceAccessError("You are currently not authenticated. Please log in again.")
[928829]288
[9033fd]289
290class ProtectedDisplayService(ProtectedService):
291    """
292    A ProtectedDisplayService can only be accessed by users that
293    are authorized to "see" the target resource
294    """
[f78b0c]295
[9033fd]296    def _checkProtection( self ):
297        """
298        Overloads ProtectedService._checkProtection, assuring that
299        the user is authorized to view the target resource
300        """
[f7f228]301        if not self._target.canAccess( self.getAW() ):
[f78b0c]302
[9033fd]303            from MaKaC.conference import Link, LocalFile
304
305            # in some cases, the target does not directly have an owner
306            if (isinstance(self._target, Link) or
307                isinstance(self._target, LocalFile)):
308                target = self._target.getOwner()
309            else:
310                target = self._target
311            if not isinstance(target, Category):
312                if target.getAccessKey() != "" or target.getConference().getAccessKey() != "":
[a755c1]313                    raise ServiceAccessError("You are currently not authenticated or cannot access this service. Please log in again if necessary.")
[9033fd]314            if self._getUser() == None:
315                self._checkSessionUser()
316            else:
[a755c1]317                raise ServiceAccessError("You cannot access this service. Please log in again if necessary.")
[9033fd]318
319
320class LoggedOnlyService(ProtectedService):
321    """
[f78b0c]322    Only accessible to users who are logged in (access keys not allowed)
[9033fd]323    """
324
[f78b0c]325    def _checkProtection( self ):
326        self._checkSessionUser()
327
[9033fd]328
329class ProtectedModificationService(ProtectedService):
330    """
331    A ProtectedModificationService can only be accessed by users that
332    are authorized to modify the target resource
333    """
334    def _checkProtection( self ):
335        """
336        Overloads ProtectedService._checkProtection, so that it is
337        verified if the user has modification access to the resource
338        """
339
340        target = self._target
341        if (type(target) == conference.SessionSlot):
342            target = target.getSession()
[928829]343
[9033fd]344        if not target.canModify( self.getAW() ):
345            if target.getModifKey() != "":
[a755c1]346                raise ServiceAccessError("You don't have the rights to modify this object")
[9033fd]347            if self._getUser() == None:
348                self._checkSessionUser()
349            else:
[a755c1]350                raise ServiceAccessError("You don't have the rights to modify this object")
[cbf814]351        if hasattr(self._target, "getConference") and hasattr(self._target, "isClosed"):
[9033fd]352            if target.getConference().isClosed():
[a755c1]353                raise ServiceAccessError("Conference %s is closed"%target.getConference().getId())
[9033fd]354
[f78b0c]355class AdminService(LoggedOnlyService):
[9033fd]356    """
357    A AdminService can only be accessed by administrators
358    """
359    def _checkProtection( self ):
360        """
361        Overloads ProtectedService._checkProtection
362        """
363
[f78b0c]364        LoggedOnlyService._checkProtection(self)
[9033fd]365
366        if not self._getUser().isAdmin():
[a755c1]367            raise ServiceAccessError(_("Only administrators can perform this operation"))
[9033fd]368
[3bc472]369class TextModificationBase:
[9033fd]370    """
371    Base class for text field modification
372    """
[41eb27]373
[9033fd]374    def _getAnswer( self ):
[e83ea5]375        """ Calls _handleGet() or _handleSet() on the derived classes, in order to make it happen. Provides
376            them with self._value.
[928829]377
[e83ea5]378            When calling _handleGet(), it will return the value to return.
379            When calling _handleSet(), it will return:
380            -either self._value if there were no problems
381            -either a FieldModificationWarning object (pickled) if there are warnings to give to the user
[9033fd]382        """
[41eb27]383
384        # fetch the 'value' parameter (default for text)
[9033fd]385        if self._params.has_key('value'):
386            self._value = self._params['value']
387        else:
[41eb27]388            # None if not passed
[9033fd]389            self._value = None
[41eb27]390
[9033fd]391        if self._value == None:
392            return self._handleGet()
393        else:
[e83ea5]394            setResult = self._handleSet()
395            if isinstance(setResult, Warning):
[d25c08]396                return ResultWithWarning(self._value, setResult).fossilize()
[e83ea5]397            else:
398                return self._value
[9033fd]399
[3bc472]400class HTMLModificationBase:
[9033fd]401    """
402    Base class for HTML field modification
403    """
404    def _getAnswer( self ):
405        """
406        Calls _handle() on the derived classes, in order to make it happen. Provides
407        them with self._value.
408        """
[928829]409
[9033fd]410        if self._params.has_key('value'):
411            self._value = self._params['value']
412        else:
413            self._value = None
[928829]414
[9033fd]415        if self._value == None:
416            return self._handleGet()
417        else:
418            self._handleSet()
[928829]419
[9033fd]420        return self._value
421
422
423class DateTimeModificationBase( TextModificationBase ):
[e83ea5]424    """ Date and time modification base class
425        Its _handleSet method is called by TextModificationBase's _getAnswer method.
426        DateTimeModificationBase's _handletSet method will call the _setParam method
427        from the classes that inherits from DateTimeModificationBase.
428        _handleSet will return whatever _setParam returns (usually None if there were no problems,
[928829]429        or a FieldModificationWarning object with information about a problem / warning to give to the user)
[9033fd]430    """
431    def _handleSet(self):
432        try:
[13ce65]433            naiveDate = parseDateTime(self._value)
[9033fd]434        except ValueError:
435            raise ServiceError("ERR-E2",
436                               "Date/time is not in the correct format")
[9881d7]437
[ef4d2c]438        try:
439            self._pTime = setAdjustedDate(naiveDate, self._conf)
[9881d7]440            return self._setParam()
[ef4d2c]441        except TimingError,e:
442            raise ServiceError("ERR-E2", e.getMsg())
443
[fe1e0a]444class ListModificationBase:
[9033fd]445    """ Base class for a list modification.
446        The class that inherits from this must have:
447        -a _handleGet() method that returns a list.
448        -a _handleSet() method which can use self._value to process the input. self._value will be a list.
449    """
[928829]450
[9033fd]451    def _getAnswer(self):
452        if self._params.has_key('value'):
453            pm = ParameterManager(self._params)
454            self._value = pm.extract("value", pType=list, allowEmpty=True)
455        else:
456            self._value = None
[928829]457
[9033fd]458        if self._value == None:
459            return self._handleGet()
460        else:
461            self._handleSet()
[928829]462
[9033fd]463        return self._value
464
[fe1e0a]465class TwoListModificationBase:
[9033fd]466    """ Base class for two lists modification.
467        The class that inherits from this must have:
468        -a _handleGet() method that returns a list, given self._destination
469        -a _handleSet() method which can use self._value and self._destination to process the input.
470        self._value will be a list. self._destination will be 'left' or 'right'
471    """
[928829]472
[9033fd]473    def _getAnswer(self):
474        self._destination = self._params.get('destination', None)
475        if self._destination == None or (self._destination != 'right' and self._destination != 'left'):
476            #TODO: add this error to the wiki
477            raise ServiceError("ERR-E4", 'Destination list not set to "right" or "left"')
[928829]478
[9033fd]479        if self._params.has_key('value'):
480            pm = ParameterManager(self._params)
481            self._value = pm.extract("value", pType=list, allowEmpty=False)
482        else:
483            self._value = None
[928829]484
[9033fd]485        if self._value == None:
486            return self._handleGet()
487        else:
488            self._handleSet()
489
[928829]490        return self._value
[97a9da]491
[b4a8e5]492
493class ExportToICalBase(object):
[97a9da]494
495    def _checkParams(self):
496        minfo = info.HelperMaKaCInfo.getMaKaCInfoInstance()
497        self._apiMode = minfo.getAPIMode()
[b4a8e5]498        user = self._getUser()
[97a9da]499        if not user:
[b4a8e5]500            raise ServiceError("ERR-U0", "User is not logged in!")
[97a9da]501        apiKey = user.getAPIKey()
502        if not apiKey:
[b4a8e5]503            raise ServiceError("ERR-U1", "User has no API key!")
[97a9da]504        elif apiKey.isBlocked():
[b4a8e5]505            raise ServiceError("ERR-U1", "This API key is blocked!")
[97a9da]506        self._apiKey = apiKey
Note: See TracBrowser for help on using the repository browser.