Temp commit to switch to new branch ( based on dcd1c38335966c77df02c49f39ef7224b91ed8b3 )
[astakos] / snf-astakos-app / astakos / im / api / backends / lib / django / __init__.py~
1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 from django.db import IntegrityError, transaction
35 from django.core.exceptions import ObjectDoesNotExist
36
37 from functools import wraps
38 from smtplib import SMTPException
39
40 from astakos.im.models import (
41     AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR
42 )
43 from astakos.im.api.backends.base import BaseBackend, SuccessResult, FailureResult
44 from astakos.im.api.backends.errors import (
45     ItemNotExists, ItemExists, MissingIdentifier, MultipleItemsExist
46 )
47 from astakos.im.util import reserved_email, model_to_dict
48 from astakos.im.endpoints.quotaholder import get_quota
49
50 import logging
51
52 logger = logging.getLogger(__name__)
53
54 DEFAULT_CONTENT_TYPE = None
55
56
57 def safe(func):
58     """Decorator function for views that implement an API method."""
59     @transaction.commit_manually
60     @wraps(func)
61     def wrapper(self, *args, **kwargs):
62         logger.debug('%s %s %s' % (func, args, kwargs))
63         try:
64             data = func(self, *args, **kwargs) or ()
65         except Exception, e:
66             logger.exception(e)
67             transaction.rollback()
68             return FailureResult(getattr(e, 'message', e))
69         else:
70             transaction.commit()
71             return SuccessResult(data)
72     return wrapper
73
74
75 class DjangoBackend(BaseBackend):
76     def _lookup_object(self, model, **kwargs):
77         """
78         Returns an object of the specific model matching the given lookup
79         parameters.
80         """
81         if not kwargs:
82             raise MissingIdentifier
83         try:
84             return model.objects.get(**kwargs)
85         except model.DoesNotExist:
86             raise ItemNotExists(model._meta.verbose_name, **kwargs)
87         except model.MultipleObjectsReturned:
88             raise MultipleItemsExist(model._meta.verbose_name, **kwargs)
89
90     def _lookup_user(self, id):
91         """
92         Returns an AstakosUser having this id.
93         """
94         if not isinstance(id, int):
95             raise TypeError('User id should be of type int')
96         return self._lookup_object(AstakosUser, id=id)
97
98     def _lookup_service(self, id):
99         """
100         Returns an Service having this id.
101         """
102         if not isinstance(id, int):
103             raise TypeError('Service id should be of type int')
104         return self._lookup_object(Service, id=id)
105
106     def _list(self, model, filter=()):
107         q = model.objects.all()
108         if filter:
109             q = q.filter(id__in=filter)
110         return map(lambda o: model_to_dict(o, exclude=[]), q)
111
112     def _create_object(self, model, **kwargs):
113         o = model.objects.create(**kwargs)
114         o.save()
115         return o
116
117     def _update_object(self, model, id, save=True, **kwargs):
118         o = self._lookup_object(model, id=id)
119         if kwargs:
120             o.__dict__.update(kwargs)
121         if save:
122             o.save()
123         return o
124
125     @safe
126     def update_user(self, user_id, renew_token=False, **kwargs):
127         user = self._update_object(AstakosUser, user_id, save=False, **kwargs)
128         if renew_token:
129             user.renew_token()
130         if kwargs or renew_token:
131             user.save()
132
133     @safe
134     def create_user(self, **kwargs):
135         policies = kwargs.pop('policies', ())
136         permissions = kwargs.pop('permissions', ())
137         groups = kwargs.pop('groups', ())
138         password = kwargs.pop('password', None)
139
140         u = self._create_object(AstakosUser, **kwargs)
141
142         if password:
143             u.set_password(password)
144         u.permissions = permissions
145         u.policies = policies
146         u.extended_groups = groups
147         return self._list(AstakosUser, filter=(u.id,))
148
149     @safe
150     def add_policies(self, user_id, update=False, policies=()):
151         user = self._lookup_user(user_id)
152         rejected = []
153         append = rejected.append
154         for p in policies:
155             service = p.get('service')
156             resource = p.get('resource')
157             uplimit = p.get('uplimit')
158             try:
159                 user.add_policy(service, resource, uplimit, update)
160             except (ObjectDoesNotExist, IntegrityError), e:
161                 append((service, resource, e))
162         return rejected
163     
164     @safe
165     def remove_policies(self, user_id, policies=()):
166         user = self._lookup_user(user_id)
167         if not user:
168             return user_id
169         rejected = []
170         append = rejected.append
171         for p in policies:
172             service = p.get('service')
173             resource = p.get('resource')
174             try:
175                 user.delete_policy(service, resource)
176             except ObjectDoesNotExist, e:
177                 append((service, resource, e))
178         return rejected
179     @safe
180     def add_permissions(self, user_id, permissions=()):
181         user = self._lookup_user(user_id)
182         rejected = []
183         append = rejected.append
184         for p in permissions:
185             try:
186                 user.add_permission(p)
187             except IntegrityError, e:
188                 append((p, e))
189         return rejected
190     
191     @safe
192     def remove_permissions(self, user_id, permissions=()):
193         user = self._lookup_user(user_id)
194         rejected = []
195         append = rejected.append
196         for p in permissions:
197             try:
198                 user.remove_permission(p)
199             except (ObjectDoesNotExist, IntegrityError), e:
200                 append((p, e))
201         return rejected
202     
203     @safe
204     def invite_users(self, senderid, recipients=()):
205         user = self._lookup_user(senderid)
206         rejected = []
207         append = rejected.append
208         for r in recipients:
209             try:
210                 user.invite(r.get('email'), r.get('realname'))
211             except (IntegrityError, SMTPException), e:
212                 append((email, e))
213         return rejected
214     
215     @safe
216     def list_users(self, filter=()):
217         return self._list(AstakosUser, filter=filter)
218
219     @safe
220     def get_resource_usage(self, user_id):
221         user = self._lookup_user(user_id)
222         c, data = get_quota((user,))
223         resources = []
224         append = resources.append
225         for t in data:
226             t = (i if i else 0 for i in t)
227             (entity, name, quantity, capacity, importLimit, exportLimit,
228              imported, exported, returned, released, flags) = t
229             service, sep, resource = name.partition(RESOURCE_SEPARATOR)
230             resource = Resource.objects.select_related().get(
231                 service__name=service, name=resource)
232             d = dict(name=name,
233                      description=resource.desc,
234                      unit=resource.unit or '',
235                      maxValue=quantity + capacity,
236                      currValue=quantity + imported - released - exported + returned)
237             append(d)
238         return resources
239
240     @safe
241     def list_resources(self, filter=()):
242         return self._list(Resource, filter=filter)
243
244     @safe
245     def create_service(self, **kwargs):
246         resources = kwargs.pop('resources', ())
247         s = self._create_object(Service, **kwargs)
248         s.resources = resources
249         return self._list(Service, filter=(s.id,))
250
251     @safe
252     def remove_services(self, ids=()):
253         # TODO return information for unknown ids
254         q = Service.objects.filter(id__in=ids)
255         q.delete()
256     
257     @safe
258     def update_service(self, service_id, renew_token=False, **kwargs):
259         s = self._update_object(Service, service_id, save=False, **kwargs)
260         if renew_token:
261             s.renew_token()
262
263         if kwargs or renew_token:
264             s.save()
265
266     @safe
267     def add_resources(self, service_id, update=False, resources=()):
268         s = self._lookup_service(service_id)
269         rejected = []
270         append = rejected.append
271         for r in resources:
272             try:
273                 rr = r.copy()
274                 resource_id = rr.pop('id', None)
275                 if update:
276                     if not resource_id:
277                         raise MissingIdentifier
278                     resource = self._update_object(Resource, resource_id, **rr)
279                 else:
280                     resource = self._create_object(Resource, service=s, **rr)
281             except Exception, e:
282                 append((r, e))
283         return rejected
284     
285     @safe
286     def remove_resources(self, service_id, ids=()):
287         # TODO return information for unknown ids
288         q = Resource.objects.filter(service__id=service_id,
289                                 id__in=ids)
290         q.delete()
291     
292     @safe
293     def create_group(self, **kwargs):
294         policies = kwargs.pop('policies', ())
295         permissions = kwargs.pop('permissions', ())
296         members = kwargs.pop('members', ())
297         owners = kwargs.pop('owners', ())
298         kwargs['kind'] = self._lookup_object(
299             GroupKind, name=kwargs.get('kind', 'course')
300         )
301
302         g = self._create_object(AstakosGroup, **kwargs)
303
304         g.permissions = permissions
305         g.policies = policies
306 #         g.members = members
307         g.owners = owners
308         return self._list(AstakosGroup, filter=(g.id,))