cc1  v2.1
CC1 source code docs
 All Classes Namespaces Files Functions Variables Pages
decorators.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 # @COPYRIGHT_begin
3 #
4 # Copyright [2010-2014] Institute of Nuclear Physics PAN, Krakow, Poland
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 # @COPYRIGHT_end
19 
20 ##
21 # @package src.clm.utils.decorators
22 # Here are placed decorators for CLM views functions targeted to specific CLM
23 # role actors (and src.clm.utils.decorators.genericlog() called by all those).
24 #
25 # @par Actor decorators
26 # - src.clm.utils.decorators.guest_log
27 # - src.clm.utils.decorators.user_log
28 # - src.clm.utils.decorators.admin_clm_log
29 #
30 # All those decorators call src.clm.utils.decorators.genericlog().
31 # By default those decorators call src.clm.utils.decorators.genericlog
32 # with logging disabled. You can enable it by giving kwarg \c log=True ,
33 # when decorating, eg.:
34 #
35 # @code
36 # @admin_clm_log(log=True)
37 # def get_by_id(cm_id, caller_id, id):
38 # pass
39 # @endcode
40 #
41 # @author Tomasz Sośnicki <tom.sosnicki@gmail.com>
42 #
43 
44 from clm.utils.cm import CM
45 from clm.utils import log
46 from clm.utils.exception import CLMException
47 from clm.models.user import User
48 from common.signature import Signature
49 from common import response
50 from common.states import user_active_states
51 
52 from functools import wraps
53 import json
54 from django.http import HttpResponse
55 from django.db import transaction
56 
57 
58 # Set of functions decorated by actor decorators
59 # (clm.utils.decorators.guest_log(), src.clm.utils.decorators.user_log(),
60 # src.clm.utils.decorators.admin_clm_log())
61 from common.utils import json_convert
62 
63 global decorated_functions
64 decorated_functions = set([])
65 
66 
67 ##
68 #
69 # Decorator for functions requiring only \b guest's privilidges.
70 #
71 # src.clm.utils.decorators.genericlog() is called with parameters:
72 # - \c is_user=False
73 # - \c is_clm_superuser=False
74 # - \c is_cm_superuser=False
75 #
76 # @par Decorated function's declaration
77 # @code
78 # @guest_log[(log=<False|True>)]
79 # function (**kw)
80 # @endcode
81 #
82 # @par Decorated function's call
83 # @code
84 # function (**kw)
85 # @endcode
86 #
87 def guest_log(*arg, **kw):
88  def logwrapper(fun):
89 
90  @wraps(fun)
91  def wrapper(*args, **kwargs):
92  return genericlog(kw.get('log', False), kw.get('pack', True), False, False, False, fun, args, kwargs)
93 
94  decorated_functions.add(wrapper)
95 
96  return wrapper
97  return logwrapper
98 
99 
100 ##
101 #
102 # Decorator for functions requiring logged in \b user's privilidges.
103 #
104 # src.clm.utils.decorators.genericlog() is called with parameters:
105 # - \c is_user=True
106 # - \c is_clm_superuser=False
107 # - \c is_cm_superuser=False
108 #
109 # @par Decorated function's declaration
110 # @code
111 # @user_log[(log=<False|True>)]
112 # function (cm_id, caller_id, **kw)
113 # @endcode
114 #
115 # @par Decorated function's call
116 # @code
117 # function (cm_id=<cm_id>, login=<login>, password=<password>, **kw)
118 # @endcode
119 #
120 def user_log(*arg, **kw):
121  def logwrapper(fun):
122 
123  @wraps(fun)
124  def wrapper(*args, **kwargs):
125  return genericlog(kw.get('log', False), kw.get('pack', True), True, False, False, fun, args, kwargs)
126 
127  decorated_functions.add(wrapper)
128 
129  return wrapper
130 
131  return logwrapper
132 
133 
134 ##
135 #
136 # Decorator for functions requiring \b admin_cm's privilidges.
137 #
138 # src.clm.utils.decorators.genericlog is called with parameters:
139 # - \c is_user=True
140 # - \c is_clm_superuser=False
141 # - \c is_cm_superuser=True
142 #
143 # @par Decorated function's declaration
144 # @code
145 # @admin_clm_log[(log=<False|True>)]
146 # function (cm_id, caller_id, **kw)
147 # @endcode
148 #
149 # @par Decorated function's call
150 # @code
151 # function (cm_id=<cm_id>, login=<login>, password=<password>, **kw)
152 # @endcode
153 #
154 # \c password argument is removed by \c src.cm.utils.decorators.genericlog(),
155 # so it doesn't appear in formal parameters of the function.
156 #
157 def admin_cm_log(*arg, **kw):
158  def logwrapper(fun):
159 
160  @wraps(fun)
161  def wrapper(*args, **kwargs):
162  return genericlog(kw.get('log', False), kw.get('pack', True), True, False, True, fun, args, kwargs)
163 
164  decorated_functions.add(wrapper)
165 
166  return wrapper
167 
168  return logwrapper
169 
170 
171 ##
172 #
173 # Decorator for functions requiring \b admin_clm's privilidges.
174 #
175 # src.clm.utils.decorators.genericlog is called with parameters:
176 # - \c is_user=True
177 # - \c is_clm_superuser=True
178 # - \c is_cm_superuser=False
179 #
180 # @par Decorated function's declaration
181 # @code
182 # @admin_clm_log[(log=<False|True>)]
183 # function (cm_id, caller_id, *args, **kw)
184 # @endcode
185 #
186 # @par Decorated function's call
187 # @code
188 # function (cm_id, login, password, *arg, **kw)
189 # @endcode
190 #
191 # \c password argument is removed by \c src.cm.utils.decorators.genericlog(),
192 # so it doesn't appear in formal parameters of the function.
193 #
194 def admin_clm_log(*arg, **kw):
195  def logwrapper(fun):
196 
197  @wraps(fun)
198  def wrapper(*args, **kwargs):
199  return genericlog(kw.get('log', False), kw.get('pack', True), True, True, False, fun, args, kwargs)
200 
201  decorated_functions.add(wrapper)
202 
203  return wrapper
204 
205  return logwrapper
206 
207 
208 def auth(is_user, is_clm_superuser, data):
209  if is_user:
210  login = data.pop('login')
211  password = data.get('password')
212 
213 # password = data.pop('password')
214  if password:
215  del data['password']
216  try:
217  user = User.objects.get(login=login)
218  except User.DoesNotExist:
219  raise CLMException('user_get')
220  # if isinstance(args[-1], dict) and 'Signature' in args[-1]: # sigdata will be always the last argument
221  # if Signature.checkSignature(user.password, password, args[-1]):
222  # del args[-1]
223  # else:
224  # resp = response('user_get')
225  # elif user.password != password:
226  # resp = response('user_get')
227  if 'Signature' in data.keys():
228  if not Signature.checkSignature(user.password, data.pop('Signature'), data['parameters']):
229  raise CLMException('user_get')
230  del data['parameters']
231  elif user.password != password:
232  raise CLMException('user_get')
233 
234  data['caller_id'] = user.id
235  if user.is_active != user_active_states['ok']:
236  raise CLMException('user_inactive')
237  if is_clm_superuser and not user.is_superuser:
238  raise CLMException('user_permission')
239 
240  data['cm_id'] = data.pop('cm_id', None)
241  if not data['cm_id']:
242  if user.default_cluster_id is not None:
243  data['cm_id'] = user.default_cluster_id
244  # else:
245  # raise CLMException('missing cm_id')
246  return user.id
247  else:
248  return 0
249 
250 
251 ##
252 #
253 # Generic log is called by actor decorators defined in src.clm.utils.decorators :
254 # - src.clm.utils.decorators.guest_log
255 # - src.clm.utils.decorators.user_log
256 # - src.clm.utils.decorators.admin_cm_log
257 # - src.clm.utils.decorators.admin_clm_log
258 #
259 # It calls decorated functions, additionally performing several tasks.
260 #
261 # Genericlog performes:
262 #
263 # -# <i>if decorated function requires user or admin privilidges</i>: <b>authorization</b>;
264 # -# <b>execution</b> of the decorated function;
265 # -# <b>debug log</b> of the arguments <i>depending on \c log_enabled and function's success</i>;
266 # -# <i>if exception is thrown</i>: <b>general exception log</b>.
267 #
268 # @returns{dict} response; fields:
269 # @dictkey{status,string} 'ok', if succeeded
270 # @dictkey{data,dict} response data
271 #
272 def genericlog(log_enabled, pack_resp, is_user, is_clm_superuser, is_cm_superuser, fun, args, kwargs):
273  #===========================================================================
274  # AUTORIZATION
275  #===========================================================================
276  name = '%s.%s' % (fun.__module__.replace('clm.views.', ''), fun.__name__)
277 
278  request = args[0]
279 
280  data = json.loads(request.body)
281  #===========================================================================
282  # LOG AGRUMENTS
283  #===========================================================================
284  gen_exception = False
285 
286  with transaction.commit_manually():
287  try:
288  # Execute function
289  user_id = auth(is_user, is_clm_superuser, data)
290  resp = fun(**data)
291  if pack_resp and not hasattr(fun, 'packed'): # if function is decorated by cm_request, 'packed' atribbute will be set - response is already packed by cm
292  resp = response('ok', resp)
293  transaction.commit()
294  except CLMException, e:
295  transaction.rollback()
296  user_id = 0
297  resp = e.response
298  except Exception, e:
299  transaction.rollback()
300  gen_exception = True
301  user_id = 0
302  resp = response('clm_error', str(e))
303 
304  if log_enabled or resp['status'] != 'ok':
305  log.debug(user_id, '=' * 100)
306  log.debug(user_id, 'Function: %s' % name)
307  log.debug(user_id, 'ARGS:\n%s' % json.dumps(data, indent=4))
308  if gen_exception:
309  log.exception(user_id, 'General exception')
310  log.debug(user_id, 'Response: %s' % resp or 'None')
311 
312  return HttpResponse(json.dumps(resp, default=json_convert))
313 
314 
315 ##
316 #
317 # Decorator for CM views functions that:
318 # - either are fully transparent and just return CM response,
319 # - or propagate request to CM and further postprocess its response.
320 #
321 # Decorated function ought to be defined like:
322 #
323 # @par Decorated function's declaration
324 # @code
325 # @cm_request
326 # def function (cm_response, <kwargs>):
327 # # postprocess cm_response
328 # return cm_response
329 # @endcode
330 #
331 # @par Decorated function's call
332 # @code
333 # function (cm_id, <kwargs>) # `cm_id` is keyword arg as well, but it's required
334 # @endcode
335 #
336 def cm_request(fun):
337  url = r"%s/%s/" % (fun.__module__.replace("clm.views.", "").replace(".", "/"), fun.__name__)
338 
339  @wraps(fun)
340  def wrapper(**data):
341  log.debug(0, "Forward request to CM: %s" % url)
342  try:
343  cm_response = CM(data.pop('cm_id')).send_request(url, **data)
344  if cm_response['status'] != 'ok':
345  return cm_response
346  except CLMException, e:
347  cm_response = e.response
348  except Exception, e:
349  cm_response = response('clm_error', str(e))
350  fun.packed = True # mark function response to not be packed by genericlog
351  return fun(cm_response, **data)
352  return wrapper
353