Added logging ingo and examples on top of file
[flowspy] / rapi / api.py
1 from tastypie.resources import ModelResource
2 from flowspy.flowspec.models import *
3 from tastypie import fields
4 from django.contrib.auth.models import User
5 from flowspy.accounts.models import *
6 from tastypie.authentication import BasicAuthentication
7 from tastypie.authorization import Authorization
8 from flowspy.djangobackends.restauthBackend import restauthBackend
9 from django.template.loader import render_to_string
10 from django import forms
11 from flowspy.flowspec.forms import *
12 from ipaddr import *
13 from flowspy.flowspec.views import send_new_mail as new_mail
14 from flowspy.flowspec.views import get_peer_techc_mails as get_peer_techcs_contact
15 from django.conf import settings
16 from django.core.exceptions import ObjectDoesNotExist
17 from tastypie.exceptions import *
18 from tastypie import http
19 import logging
20 import datetime
21 from django.shortcuts import get_object_or_404
22 from copy import deepcopy
23 import os
24
25 LOG_FILENAME = os.path.join(settings.LOG_FILE_LOCATION, 'celery_jobs.log')
26 #FORMAT = '%(asctime)s %(levelname)s: %(message)s'
27 #logging.basicConfig(format=FORMAT)
28 formatter = logging.Formatter('%(asctime)s %(levelname)s %(clientip)s %(user)s: %(message)s')
29
30 logger = logging.getLogger(__name__)
31 logger.setLevel(logging.DEBUG)
32 handler = logging.FileHandler(LOG_FILENAME)
33 handler.setFormatter(formatter)
34 logger.addHandler(handler)
35
36
37 '''Usage example:
38 GET all: curl --user username:APIKEY  --dump-header - -H "Content-Type: application/json" -X GET https://example.com/fod/api/v1/rule/?format=json
39 GET rule_by_pk: curl --user username:APIKEY  --dump-header - -H "Content-Type: application/json" -X GET https://example.com/fod/api/v1/rule/{rule_pk}?format=json
40 UPDATE RULE (PUT) : curl --user username:APIKEY  --dump-header - -H "Content-Type: application/json" -X PUT --data '{"comments": "", "destination": "", "destinationport": [{"port": "9090"}], "expires": "2012-02-21", "id": "11", "port": [], "source": "5.6.7.8/32", "sourceport": [{"port": "9090"}], "status": "ACTIVE", "then": [{"action": "discard", "action_value": ""}]}' https://example.com/fod/api/v1/rule/11/?format=json
41 DELETE RULE: curl --user username:APIKEY  --dump-header - -H "Content-Type: application/json" -X DELETE https://example.com/fod/api/v1/rule/11/?format=json
42 CREATE RULE (POST): curl --user username:APIKEY  --dump-header - -H "Content-Type: application/json" -X POST --data '{"name":"", "comments": "", "destination": "", "destinationport": [{"port": "9090"}], "expires": "2012-02-21", "port": [], "source": "5.6.7.8/32", "sourceport": [{"port": "9090"}], "status": "ACTIVE", "then": [{"action": "discard", "action_value": ""}]}' https://example.com/fod/api/v1/rule/?format=json 
43 '''
44 class SourcePortRes(ModelResource):
45     class Meta:
46         queryset = MatchPort.objects.all()
47         resource_name = 'sourceport'
48         fields = ['port']
49         allowed_methods = ['get']
50
51 class DestPortRes(ModelResource):
52     class Meta:
53         queryset = MatchPort.objects.all()
54         resource_name = 'destinationport'
55         fields = ['port']
56         allowed_methods = ['get']
57         
58 class PortRes(ModelResource):
59     class Meta:
60         queryset = MatchPort.objects.all()
61         resource_name = 'port'
62         fields = ['port']
63         allowed_methods = ['get']        
64
65 class ThenResource(ModelResource):
66     class Meta:
67         queryset = ThenAction.objects.all()
68         resource_name = 'then'
69         allowed_methods = ['get']
70         fields = ['action', 'action_value']
71
72 class RuleResource(ModelResource):
73     name = fields.CharField(attribute='name', help_text="A slug field (no spaces)")
74     sourceport = fields.ManyToManyField(SourcePortRes, 'sourceport', full=True, null=True)
75     destinationport = fields.ManyToManyField(DestPortRes, 'destinationport', full=True, null=True)
76     then = fields.ManyToManyField(ThenResource, 'then', full=True, null=True)
77     port = fields.ManyToManyField(PortRes, 'port', full=True, null=True)
78        
79     def get_object_list(self, request):
80         logger.info('Got REST GET list request %s' %request)
81         group_routes = []
82         user = request.user
83         peer = user.get_profile().peer
84         if peer:
85             peer_members = UserProfile.objects.filter(peer=peer)
86             users = [prof.user for prof in peer_members]
87         return super(RuleResource, self).get_object_list(request).filter(applier__in=users)
88     
89     def obj_create(self, bundle, request=None, **kwargs):
90         logger.info('Got REST POST request %s' %kwargs)
91         then_list = []
92         sourceport_list = []
93         destport_list = []
94         port_list = []
95         bundle.obj = self._meta.object_class()
96
97         for key, value in kwargs.items():
98             setattr(bundle.obj, key, value)
99
100         bundle = self.full_hydrate(bundle)
101         m2m_bundle = self.hydrate_m2m(bundle)
102         
103         then_action_dict = m2m_bundle.data['then'][0].obj.__dict__
104         then_action_pk = ThenAction.objects.get(action=then_action_dict['action'], action_value=then_action_dict['action_value']).pk
105         then_list.append(then_action_pk)
106         try:
107             sourceport_dict = m2m_bundle.data['sourceport']
108         except:
109             sourceport_dict = None
110         if sourceport_dict:
111             for sp in sourceport_dict:
112                 sourceport_pk = get_port_pk_or_create(sp)
113                 sourceport_list.append(sourceport_pk)
114
115         try:
116             destport_dict = m2m_bundle.data['destinationport']
117         except:
118             destport_dict = None
119         if destport_dict:
120             for dp in destport_dict:
121                 destport_pk = get_port_pk_or_create(dp)
122                 destport_list.append(destport_pk)
123
124         try:
125             port_dict = m2m_bundle.data['port']
126         except:
127             port_dict = None
128         if port_dict:
129             for prt in port_dict:
130                 port_pk = get_port_pk_or_create(prt) 
131                 port_list.append(port_pk)
132
133         datadict = bundle.obj.__dict__
134         datadict['applier'] =  request.user.pk
135         datadict['comments'] =  "%s: %s" %("Submitted via REST API", datadict['comments'])
136         datadict['then'] =  then_list
137         datadict['sourceport'] =  sourceport_list
138         datadict['destinationport'] =  destport_list
139         datadict['port'] =  port_list
140         form = RouteForm(data=datadict)
141
142         if form.is_valid():
143             route=form.save(commit=False)
144             route.applier = request.user
145             route.status = "PENDING"
146             route.source = IPNetwork("%s/%s" %(IPNetwork(route.source).network.compressed, IPNetwork(route.source).prefixlen)).compressed
147             route.destination = IPNetwork("%s/%s" %(IPNetwork(route.destination).network.compressed, IPNetwork(route.destination).prefixlen)).compressed
148             route.save()
149             form.save_m2m()
150             route.commit_add()
151             requesters_address = request.META['HTTP_X_FORWARDED_FOR']
152             mail_body = render_to_string("rule_add_mail.txt",
153                                              {"route": route, "address": requesters_address})
154             user_mail = "%s" %route.applier.email
155             user_mail = user_mail.split(';')
156             new_mail(settings.EMAIL_SUBJECT_PREFIX + "Rule %s creation request submitted by %s via REST api" %(route.name, route.applier.username),
157                               mail_body, settings.SERVER_EMAIL, user_mail,
158                               get_peer_techcs_contact(route.applier))
159             d = { 'clientip' : "REST_API: %s" %requesters_address, 'user' : route.applier.username }
160             logger.info(mail_body, extra=d)
161         else:
162             raise ApiFieldError("ERRORS %s %s" %(form.errors, form.__dict__))
163
164         return bundle
165     
166     def obj_delete(self, request=None, **kwargs):
167         logger.info('Got REST DELETE request %s' %kwargs)
168         obj = kwargs.pop('pk', None)
169         if obj:
170             try:
171                 route = Route.objects.get(pk=obj)
172             except:
173                 raise NotFound("A model instance matching the provided arguments could not be found.")
174             applier_peer = route.applier.get_profile().peer
175             requester_peer = request.user.get_profile().peer
176             if applier_peer == requester_peer:
177                 route.status = "PENDING"
178                 route.expires = datetime.date.today()
179                 route.save()
180                 route.commit_delete()
181                 requesters_address = request.META['HTTP_X_FORWARDED_FOR']
182                 mail_body = render_to_string("rule_delete_mail.txt",
183                                                  {"route": route, "address": requesters_address})
184                 user_mail = "%s" %route.applier.email
185                 user_mail = user_mail.split(';')
186                 new_mail(settings.EMAIL_SUBJECT_PREFIX + "Rule %s removal request submitted by %s" %(route.name, route.applier.username), 
187                                   mail_body, settings.SERVER_EMAIL, user_mail,
188                                  get_peer_techcs_contact(route.applier))
189                 d = { 'clientip' : "REST_API: %s" %requesters_address, 'user' : route.applier.username }
190                 logger.info(mail_body, extra=d)
191             else:
192                 raise NotFound("A model instance matching the provided arguments could not be found.")
193         else:
194             raise NotFound("A model instance matching the provided arguments could not be found.")
195         
196     def delete_list(self, request, **kwargs):
197         return http.HttpForbidden()
198
199     def obj_update(self, bundle, request=None, **kwargs):
200         logger.info('Got REST PUT request %s' %kwargs)
201         then_list = []
202         sourceport_list = []
203         destport_list = []
204         port_list = []
205         applier = request.user.pk
206         applier_peer = request.user.get_profile().peer
207         route_edit = get_object_or_404(Route, pk=kwargs['pk'])
208         route_edit_applier_peer = route_edit.applier.get_profile().peer
209         if applier_peer != route_edit_applier_peer:
210             return http.HttpForbidden()
211
212         route_original = deepcopy(route_edit)
213         if not bundle.obj or not bundle.obj.pk:
214
215             try:
216                 bundle.obj = self.get_object_list(request).model()
217                 bundle.data.update(kwargs)
218                 bundle = self.full_hydrate(bundle)
219                 lookup_kwargs = kwargs.copy()
220
221                 for key in kwargs.keys():
222                     if key == 'pk':
223                         continue
224                     elif getattr(bundle.obj, key, NOT_AVAILABLE) is not NOT_AVAILABLE:
225                         lookup_kwargs[key] = getattr(bundle.obj, key)
226                     else:
227                         del lookup_kwargs[key]
228             except:
229                 # if there is trouble hydrating the data, fall back to just
230                 # using kwargs by itself (usually it only contains a "pk" key
231                 # and this will work fine.
232                 lookup_kwargs = kwargs
233
234             try:
235                 bundle.obj = self.obj_get(request, **lookup_kwargs)
236             except ObjectDoesNotExist:
237                 raise NotFound("A model instance matching the provided arguments could not be found.")
238
239         bundle = self.full_hydrate(bundle)
240         m2m_bundle = self.hydrate_m2m(bundle)
241         try:
242             then_action_dict = m2m_bundle.data['then'][0].obj.__dict__
243             then_action_pk = ThenAction.objects.get(action=then_action_dict['action'], action_value=then_action_dict['action_value']).pk
244             then_list.append(then_action_pk)
245         except:
246             then_list.extend([x.pk for x in route_edit.then.all() if x])
247
248         try:
249             sourceport_dict = m2m_bundle.data['sourceport']
250         except:
251             sourceport_dict = None
252         if sourceport_dict:
253             for sp in sourceport_dict:
254                 sourceport_pk = get_port_pk_or_create(sp)
255                 sourceport_list.append(sourceport_pk)
256         else:
257              sourceport_list.extend([x.pk for x in route_edit.sourceport.all() if x])
258
259         try:
260             destport_dict = m2m_bundle.data['destinationport']
261         except:
262             destport_dict = None
263         if destport_dict:
264             for dp in destport_dict:
265                 destport_pk = get_port_pk_or_create(dp)
266                 destport_list.append(destport_pk)
267         else:
268              destport_list.extend([x.pk for x in route_edit.destinationport.all() if x])
269         try:
270             port_dict = m2m_bundle.data['port']
271         except:
272             port_dict = None
273         if port_dict:
274             for prt in port_dict:
275                 port_pk = get_port_pk_or_create(prt)
276                 port_list.append(port_pk)
277         else:
278             port_list.extend([x.pk for x in route_edit.port.all() if x])
279         
280     
281         datadict = bundle.obj.__dict__
282         datadict['applier'] =  request.user.pk
283         comment_text = "Submitted via REST API"
284         if comment_text not in datadict['comments']:
285             datadict['comments'] =  "%s: %s" %("Submitted via REST API", datadict['comments'])
286         datadict['then'] =  then_list
287         datadict['sourceport'] =  sourceport_list
288         datadict['destinationport'] =  destport_list
289         datadict['port'] =  port_list
290         datadict['name'] =  route_edit.name
291         datadict['expires'] = datetime.date.today() + datetime.timedelta(days = settings.EXPIRATION_DAYS_OFFSET)
292         form = RouteForm(data=datadict, instance = route_edit)
293
294         if form.is_valid():
295             route=form.save(commit=False)
296             route.name = route_original.name
297             route.applier = request.user
298             route.status = "PENDING"
299             route.source = IPNetwork("%s/%s" %(IPNetwork(route.source).network.compressed, IPNetwork(route.source).prefixlen)).compressed
300             route.destination = IPNetwork("%s/%s" %(IPNetwork(route.destination).network.compressed, IPNetwork(route.destination).prefixlen)).compressed
301             route.save()
302             form.save_m2m()
303             route.commit_edit()
304             requesters_address = request.META['HTTP_X_FORWARDED_FOR']
305             mail_body = render_to_string("rule_edit_mail.txt",
306                                              {"route": route, "address": requesters_address})
307             user_mail = "%s" %route.applier.email
308             user_mail = user_mail.split(';')
309             new_mail(settings.EMAIL_SUBJECT_PREFIX + "Rule %s edit request submitted by %s via REST api" %(route.name, route.applier.username),
310                               mail_body, settings.SERVER_EMAIL, user_mail,
311                               get_peer_techcs_contact(route.applier))
312             d = { 'clientip' : "REST_API: %s" %requesters_address, 'user' : route.applier.username }
313             logger.info(mail_body, extra=d)
314         else:
315             raise ApiFieldError("ERRORS %s %s" %(form.errors, form.__dict__))
316         return bundle
317     
318     
319     class Meta:
320         queryset = Route.objects.all()
321         default_format = "application/json"
322         resource_name = 'rule'
323         allowed_methods = ['get', 'post', 'delete', 'put']
324         fields = ['comments', 'source', 'destination', 'expires', 'status', 'id', 'sourceport', 'destinationport', 'then']
325         authentication = BasicAuthentication(restauthBackend())
326         authorization= Authorization()
327         
328 def get_port_pk_or_create(prt):
329     try:
330         port_pk = MatchPort.objects.get(port=prt.obj.__dict__['port']).pk
331     except:
332         try:
333             assert (int(prt.obj.__dict__['port']))
334             mp = MatchPort(port=int(int(prt.obj.__dict__['port'])))
335             mp.save()
336             port_pk=mp.pk
337         except:
338             raise ApiFieldError("Port should be an integer")
339     return port_pk