c536322e732074ff4e36025c8f73160bf3d13d8d
[myslice.git] / portal / views.py
1 # -*- coding: utf-8 -*-
2 #
3 # portal/views.py: views for the portal application
4 # This file is part of the Manifold project.
5 #
6 # Authors:
7 #   Jordan AugĂ© <jordan.auge@lip6.fr>
8 #   Mohammed Yasin Rahman <mohammed-yasin.rahman@lip6.fr>
9 # Copyright 2013, UPMC Sorbonne UniversitĂ©s / LIP6
10 #
11 # This program is free software; you can redistribute it and/or modify it under
12 # the terms of the GNU General Public License as published by the Free Software
13 # Foundation; either version 3, or (at your option) any later version.
14
15 # This program is distributed in the hope that it will be useful, but WITHOUT
16 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18 # details.
19
20 # You should have received a copy of the GNU General Public License along with
21 # this program; see the file COPYING.  If not, write to the Free Software
22 # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 import os.path, re
25 import json
26
27 from django.views.generic.base   import TemplateView
28 from django.shortcuts            import render
29 from django.template.loader      import render_to_string
30 from django.core.mail            import send_mail
31 from django.utils.decorators     import method_decorator
32 from django.contrib.auth.decorators import login_required
33
34 from plugins.lists.simplelist    import SimpleList
35 from plugins.hazelnut            import Hazelnut
36 from plugins.pres_view           import PresView
37 from portal.event import Event
38
39 from portal                      import signals
40 from portal.forms                import SliceRequestForm
41 from portal.util                 import RegistrationView, ActivationView
42 from portal.models               import PendingUser, PendingSlice
43 from portal.actions              import authority_get_pi_emails, get_request_by_authority, manifold_add_user, manifold_update_user
44 from manifold.manifoldapi        import execute_query
45 from manifold.core.query         import Query
46 from unfold.page                 import Page
47 from myslice.viewutils           import topmenu_items, the_user
48 from django.http                 import HttpResponseRedirect, HttpResponse
49
50
51 def register_4m_f4f(request):
52     errors = []
53
54     authorities_query = Query.get('authority').filter_by('authority_hrn', 'included', ['ple.inria', 'ple.upmc']).select('name', 'authority_hrn')
55     #authorities_query = Query.get('authority').select('authority_hrn')
56     authorities = execute_query(request, authorities_query)
57
58     if request.method == 'POST':
59         # We shall use a form here
60
61         #get_email = PendingUser.objects.get(email)
62         reg_fname = request.POST.get('firstname', '')
63         reg_lname = request.POST.get('lastname', '')
64         #reg_aff = request.POST.get('affiliation','')
65         reg_auth = request.POST.get('authority_hrn', '')
66         reg_email = request.POST.get('email','').lower()
67         
68         #POST value validation  
69         if (re.search(r'^[\w+\s.@+-]+$', reg_fname)==None):
70             errors.append('First Name may contain only letters, numbers, spaces and @/./+/-/_ characters.')
71             #return HttpResponse("Only Letters, Numbers, - and _ allowd in First Name")
72             #return render(request, 'register_4m_f4f.html')
73         if (re.search(r'^[\w+\s.@+-]+$', reg_lname) == None):
74             errors.append('Last Name may contain only letters, numbers, spaces and @/./+/-/_ characters.')
75             #return HttpResponse("Only Letters, Numbers, - and _ is allowed in Last name")
76             #return render(request, 'register_4m_f4f.html')
77 #        if (re.search(r'^[\w+\s.@+-]+$', reg_aff) == None):
78 #            errors.append('Affiliation may contain only letters, numbers, spaces and @/./+/-/_ characters.')
79             #return HttpResponse("Only Letters, Numbers and _ is allowed in Affiliation")
80             #return render(request, 'register_4m_f4f.html')
81         # XXX validate authority hrn !!
82         if PendingUser.objects.filter(email__iexact=reg_email):
83             errors.append('Email already registered.Please provide a new email address.')
84             #return HttpResponse("Email Already exists")
85             #return render(request, 'register_4m_f4f.html')
86         if 'generate' in request.POST['question']:
87             # Generate public and private keys using SFA Library
88             from sfa.trust.certificate  import Keypair
89             k = Keypair(create=True)
90             public_key = k.get_pubkey_string()
91             private_key = k.as_pem()
92             private_key = ''.join(private_key.split())
93             public_key = "ssh-rsa " + public_key
94             # Saving to DB
95             keypair = '{"user_public_key":"'+ public_key + '", "user_private_key":"'+ private_key + '"}'
96 #            keypair = re.sub("\r", "", keypair)
97 #            keypair = re.sub("\n", "\\n", keypair)
98 #            #keypair = keypair.rstrip('\r\n')
99 #            keypair = ''.join(keypair.split())
100         else:
101             up_file = request.FILES['user_public_key']
102             file_content =  up_file.read()
103             file_name = up_file.name
104             file_extension = os.path.splitext(file_name)[1]
105             allowed_extension =  ['.pub','.txt']
106             if file_extension in allowed_extension and re.search(r'ssh-rsa',file_content):
107                 keypair = '{"user_public_key":"'+ file_content +'"}'
108                 keypair = re.sub("\r", "", keypair)
109                 keypair = re.sub("\n", "\\n",keypair)
110                 keypair = ''.join(keypair.split())
111             else:
112                 errors.append('Please upload a valid RSA public key [.txt or .pub].')
113
114         #b = PendingUser(first_name=reg_fname, last_name=reg_lname, affiliation=reg_aff, 
115         #                email=reg_email, password=request.POST['password'], keypair=keypair)
116         #b.save()
117         if not errors:
118             b = PendingUser(
119                 first_name=reg_fname, 
120                 last_name=reg_lname, 
121                 #affiliation=reg_aff,
122                 authority_hrn=reg_auth,
123                 email=reg_email, 
124                 password=request.POST['password'],
125                 keypair=keypair
126             )
127             b.save()
128
129             # Send email
130             ctx = {
131                 first_name   : reg_fname, 
132                 last_name    : reg_lname, 
133                 #affiliation  : reg_aff,
134                 authority_hrn: reg_auth,
135                 email        : reg_email, 
136                 keypair      : keypair,
137                 cc_myself    : True # form.cleaned_data['cc_myself']
138             }
139
140             recipients = authority_get_pi_emails(authority_hrn)
141             if ctx['cc_myself']:
142                 recipients.append(ctx['email'])
143
144             msg = render_to_string('user_request_email.txt', ctx)
145             send_mail("Onelab New User request submitted", msg, email, recipients)
146
147             return render(request, 'user_register_complete.html')
148
149     return render(request, 'register_4m_f4f.html',{
150         'topmenu_items': topmenu_items('Register', request),
151         'errors': errors,
152         'firstname': request.POST.get('firstname', ''),
153         'lastname': request.POST.get('lastname', ''),
154         #'affiliation': request.POST.get('affiliation', ''),
155         'authority_hrn': request.POST.get('authority_hrn', ''),
156         'email': request.POST.get('email', ''),
157         'password': request.POST.get('password', ''),           
158         'authorities': authorities
159     })        
160     
161
162 @login_required
163 def slice_request(request):
164     errors = []
165
166     authorities_query = Query.get('authority').filter_by('authority_hrn', 'included', ['ple.inria', 'ple.upmc']).select('name', 'authority_hrn')
167     #authorities_query = Query.get('authority').select('authority_hrn')
168     authorities = execute_query(request, authorities_query)
169
170     authority_hrn_tuple = []
171     for authority in authorities:
172         authority_hrn_tuple.append((authority['authority_hrn'], authority['name']))
173     authority_hrn_initial = {'authority_hrn': authority_hrn_tuple}
174         
175     # request.POST or None ?
176     if request.method == 'POST':
177         # The form has been submitted
178         form = SliceRequestForm(request.POST, initial=authority_hrn_initial) 
179
180         if form.is_valid():
181             slice_name      = form.cleaned_data['slice_name']
182             authority_hrn   = form.cleaned_data['authority_hrn']
183             number_of_nodes = form.cleaned_data['number_of_nodes']
184             type_of_nodes   = form.cleaned_data['type_of_nodes']
185             purpose         = form.cleaned_data['purpose']
186             
187             s = PendingSlice(
188                 slice_name      = slice_name,
189                 authority_hrn   = authority_hrn,
190                 number_of_nodes = number_of_nodes,
191                 type_of_nodes   = type_of_nodes,
192                 purpose         = purpose
193             )
194             s.save()
195
196             # All validation rules pass; process data in form.cleaned_data
197             # slice_name, number_of_nodes, type_of_nodes, purpose
198             email = form.cleaned_data['email'] # email of the sender
199             cc_myself = form.cleaned_data['cc_myself']
200
201             # The recipients are the PI of the authority
202             recipients = authority_get_pi_emails(authority_hrn)
203             #recipients = ['yasin.upmc@gmail.com','jordan.auge@lip6.fr']
204             if cc_myself:
205                 recipients.append(email)
206             msg = render_to_string('slice_request_email.txt', form.cleaned_data)
207             send_mail("Onelab New Slice request form submitted", msg, email, recipients)
208
209             return render(request,'slicereq_recvd.html') # Redirect after POST
210     else:
211         form = SliceRequestForm(initial=authority_hrn_initial)
212
213 #    template_env = {}
214 #    template_env['form'] = form
215 #    template_env['topmenu_items'] = topmenu_items('Request a slice', request) 
216 #    template_env['unfold1_main'] = render(request, 'slice_request_.html', {
217 #        'form': form,
218 #    })
219 #    from django.shortcuts                import render_to_response
220 #    from django.template                 import RequestContext
221 #    return render_to_response ('view-unfold1.html',template_env,
222 #                               context_instance=RequestContext(request))
223
224     return render(request, 'slice_request.html', {
225         'form': form,
226         'topmenu_items': topmenu_items('Request a slice', request),
227         'username': the_user (request) 
228     })
229
230
231 class PresViewView(TemplateView):
232     template_name = "view-unfold1.html"
233
234     def get_context_data(self, **kwargs):
235
236         page = Page(self.request)
237
238         pres_view = PresView(page = page)
239
240         context = super(PresViewView, self).get_context_data(**kwargs)
241
242         #context['ALL_STATIC'] = "all_static"
243         context['unfold1_main'] = pres_view.render(self.request)
244
245         # XXX This is repeated in all pages
246         # more general variables expected in the template
247         context['title'] = 'Test view that combines various plugins'
248         # the menu items on the top
249         context['topmenu_items'] = topmenu_items('PresView', self.request)
250         # so we can sho who is logged
251         context['username'] = the_user(self.request)
252
253         prelude_env = page.prelude_env()
254         context.update(prelude_env)
255
256         return context
257
258 def json_me(config_file,type):
259     json_answer = ''
260     for ligne in config_file:
261         if not ligne.startswith('#'):
262             args = ligne.split(';')
263             json_answer += str('{ "name": "' + args[0] + '" ,"id":"' + args[1]  + '" ,"descriptif":"' + args[2]+'"')
264             if type!="dynamic":
265                 json_answer += str(',"contraints":')
266                 if args[3]=="":
267                     json_answer += str('""')
268                 else:
269                     json_answer += str(args[3])
270             json_answer += str('},')
271     return json_answer[:-1]
272
273
274 DIR = '/var/myslice/'
275 STATIC = '%s/config_method_static' % DIR
276 DYNAMIC = '%s/config_method_dynamic' % DIR
277 ANIMATION = '%s/config_method_animation' % DIR
278
279 def pres_view_methods(request, type):
280
281     if type ==None:
282         return 0
283     elif type =="static":
284         config = open(STATIC, "r")
285         json_answer = str('{ "options": [')
286         json_answer += str(json_me(config,"static"))
287         json_answer += str('] }')
288         config.close()
289     elif type =="dynamic":
290         config = open(DYNAMIC, "r")
291         json_answer = str('{ "options": [')
292         json_answer += str(json_me(config,"dynamic"))
293         json_answer += str('] }')
294         config.close()
295     elif type =="animation":
296         config = open(ANIMATION, "r")
297         json_answer = str('{ "options": [')
298         json_answer += str(json_me(config,"animation"))
299         json_answer += str('] }')
300         config.close()
301     elif type =="all":
302         config = open(STATIC, "r")
303         json_answer = str('{ "static": [')
304         json_answer += str(json_me(config,"static"))
305         json_answer += str('],')
306         json_answer += str('"dynamic": [')
307         config.close()
308         config = open(DYNAMIC, "r")
309         json_answer += str(json_me(config,"dynamic"))
310         json_answer += str('],')
311         json_answer += str('"animation": [')
312         config.close()
313         config = open(ANIMATION, "r")
314         json_answer += str(json_me(config,"animation"))
315         json_answer += str('] }')
316         config.close()
317     else:
318         return 0
319     return HttpResponse (json_answer, mimetype="application/json")
320
321 def pres_view_animation(request, constraints, id):
322
323 # sites crees depuis 2008
324 # static.py?contraints=']date_created':1262325600&id='name_id"'
325
326     # method = request.getvalue('method') #ex : GetSites
327     #constraints = "']date_created':1262325600"
328     #id = "2"
329
330     if id == None:
331         return 0
332
333     # method = 'GetSites'#request.getvalue('method') #ex : GetSites
334     # constraints = {}#request.getvalue('constraints') // nul = {}
335     # response_field = "'site_id','name','date_created'"#request.getvalue('response_field')
336
337     config_file = open(ANIMATION, "r")
338     for ligne in config_file:
339         if not ligne.startswith('#'):
340             ligne = ligne.split('\n')
341             first = ligne[0].split(';')
342             if (str(first[1]) == str(id)):
343                 save = first
344     config_file.close()
345
346     #Les print_method, print_option sont definis par le client (js)
347     #Les animations acceptent que les connexions anonymous
348     # args = "postmsg;animation;;;anonymous;https://www.planet-lab.eu/PLCAPI/;"
349     args = ";;"+str(save[8])+";"+str(save[9])+";anonymous;"+str(save[5])+";"+str(save[6])+";{"+str(constraints)+"};"+str(save[7])+";"
350
351
352     #Creation d'un objet event
353     event = Event(args)
354     cmd = [{"params": {
355             "data": {
356                 "print_options": event.print_options,
357                 "print_method": event.print_method,
358                 "message": event.data
359             }
360         }
361     }]
362
363     json_answer = json.dumps(cmd)
364     return HttpResponse (json_answer, mimetype="application/json")
365
366 def pres_view_static(request, constraints, id):
367     #constraints = "']date_created':1262325600"
368     #id = "2"
369
370     # method = 'GetSites'#request.getvalue('method') #ex : GetSites
371     # constraints = {}#request.getvalue('constraints') // nul = {}
372     # response_field = "'site_id','name','date_created'"#request.getvalue('response_field')
373
374     config_file = open(STATIC, "r")
375     for ligne in config_file:
376         if not ligne.startswith('#'):
377             ligne = ligne.split('\n')
378             first = ligne[0].split(';')
379             if (str(first[1]) == str(id)):
380                 save = first
381     config_file.close()
382
383     #Les print_method, print_option sont definis par le client (js)
384     #Les animations acceptent que les connexions anonymous
385     # args = "postmsg;animation;;;anonymous;https://www.planet-lab.eu/PLCAPI/;"
386     args = ";;"+str(save[8])+";"+str(save[9])+";anonymous;"+str(save[5])+";"+str(save[6])+";{"+str(constraints)+"};"+str(save[7])+";"
387
388
389     #Creation d'un objet event
390     event = Event(args)
391     cmd = [{"params": {
392             "data": {
393                 "print_options": event.print_options,
394                 "print_method": event.print_method,
395                 "message": event.data
396             }
397         }
398     }]
399
400     json_answer = json.dumps(cmd)
401     return HttpResponse (json_answer, mimetype="application/json")
402
403 class ValidatePendingView(TemplateView):
404     template_name = "validate_pending.html"
405
406     def get_context_data(self, **kwargs):
407         # We might have slices on different registries with different user accounts 
408         # We note that this portal could be specific to a given registry, to which we register users, but i'm not sure that simplifies things
409         # Different registries mean different identities, unless we identify via SFA HRN or have associated the user email to a single hrn
410
411         #messages.info(self.request, 'You have logged in')
412         page = Page(self.request)
413
414         ctx_my_authorities = {}
415         ctx_delegation_authorities = {}
416
417
418         # The user need to be logged in
419         if the_user(self.request):
420             # Who can a PI validate:
421             # His own authorities + those he has credentials for.
422             # In MySlice we need to look at credentials also.
423             
424
425             # XXX This will have to be asynchroneous. Need to implement barriers,
426             # for now it will be sufficient to have it working statically
427
428             # get user_id to later on query accounts
429             # XXX Having real query plan on local tables would simplify all this
430             # XXX $user_email is still not available for local tables
431             #user_query = Query().get('local:user').filter_by('email', '==', '$user_email').select('user_id')
432             user_query = Query().get('local:user').filter_by('email', '==', the_user(self.request)).select('user_id')
433             user, = execute_query(self.request, user_query)
434             user_id = user['user_id']
435
436             # Query manifold to learn about available SFA platforms for more information
437             # In general we will at least have the portal
438             # For now we are considering all registries
439             all_authorities = []
440             platform_ids = []
441             sfa_platforms_query = Query().get('local:platform').filter_by('gateway_type', '==', 'sfa').select('platform_id', 'platform', 'auth_type')
442             sfa_platforms = execute_query(self.request, sfa_platforms_query)
443             for sfa_platform in sfa_platforms:
444                 print "SFA PLATFORM > ", sfa_platform['platform']
445                 if not 'auth_type' in sfa_platform:
446                     continue
447                 auth = sfa_platform['auth_type']
448                 if not auth in all_authorities:
449                     all_authorities.append(auth)
450                 platform_ids.append(sfa_platform['platform_id'])
451
452             # We can check on which the user has authoritity credentials = PI rights
453             credential_authorities = set()
454             credential_authorities_expired = set()
455
456             # User account on these registries
457             user_accounts_query = Query.get('local:account').filter_by('user_id', '==', user_id).filter_by('platform_id', 'included', platform_ids).select('config')
458             user_accounts = execute_query(self.request, user_accounts_query)
459             #print "=" * 80
460             #print user_accounts
461             #print "=" * 80
462             for user_account in user_accounts:
463                 config = json.loads(user_account['config'])
464                 creds = []
465                 if 'authority_credentials' in config:
466                     for authority_hrn, credential in config['authority_credentials'].items():
467                         #if credential is not expired:
468                         credential_authorities.add(authority_hrn)
469                         #else
470                         #    credential_authorities_expired.add(authority_hrn)
471                 if 'delegated_authority_credentials' in config:
472                     for authority_hrn, credential in config['delegated_authority_credentials'].items():
473                         #if credential is not expired:
474                         credential_authorities.add(authority_hrn)
475                         #else
476                         #    credential_authorities_expired.add(authority_hrn)
477
478             print 'credential_authorities =', credential_authorities
479             print 'credential_authorities_expired =', credential_authorities_expired
480
481             # ** Where am I a PI **
482             # For this we need to ask SFA (of all authorities) = PI function
483             pi_authorities_query = Query.get('user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
484             pi_authorities_tmp = execute_query(self.request, pi_authorities_query)
485             pi_authorities = set()
486             for pa in pi_authorities_tmp:
487                 pi_authorities |= set(pa['pi_authorities'])
488
489             print "pi_authorities =", pi_authorities
490             
491             # My authorities + I have a credential
492             pi_credential_authorities = pi_authorities & credential_authorities
493             pi_no_credential_authorities = pi_authorities - credential_authorities - credential_authorities_expired
494             pi_expired_credential_authorities = pi_authorities & credential_authorities_expired
495             # Authorities I've been delegated PI rights
496             pi_delegation_credential_authorities = credential_authorities - pi_authorities
497             pi_delegation_expired_authorities = credential_authorities_expired - pi_authorities
498
499             print "pi_credential_authorities =", pi_credential_authorities
500             print "pi_no_credential_authorities =", pi_no_credential_authorities
501             print "pi_expired_credential_authorities =", pi_expired_credential_authorities
502             print "pi_delegation_credential_authorities = ", pi_delegation_credential_authorities
503             print "pi_delegation_expired_authorities = ", pi_delegation_expired_authorities
504
505             # Summary intermediary
506             pi_my_authorities = pi_credential_authorities | pi_no_credential_authorities | pi_expired_credential_authorities
507             pi_delegation_authorities = pi_delegation_credential_authorities | pi_delegation_expired_authorities
508
509             print "--"
510             print "pi_my_authorities = ", pi_my_authorities
511             print "pi_delegation_authorities = ", pi_delegation_authorities
512
513             # Summary all
514             queried_pending_authorities = pi_my_authorities | pi_delegation_authorities
515             print "----"
516             print "queried_pending_authorities = ", queried_pending_authorities
517
518             requests = get_request_by_authority(queried_pending_authorities)
519             for request in requests:
520                 auth_hrn = request['authority_hrn']
521
522                 if auth_hrn in pi_my_authorities:
523                     dest = ctx_my_authorities
524
525                     # define the css class
526                     if auth_hrn in pi_credential_authorities:
527                         request['allowed'] = 'allowed'
528                     elif auth_hrn in pi_expired_credential_authorities:
529                         request['allowed'] = 'expired'
530                     else: # pi_no_credential_authorities
531                         request['allowed'] = 'denied'
532
533                 elif auth_hrn in pi_delegation_authorities:
534                     dest = ctx_delegation_authorities
535
536                     if auth_hrn in pi_delegation_credential_authorities:
537                         request['allowed'] = 'allowed'
538                     else: # pi_delegation_expired_authorities
539                         request['allowed'] = 'expired'
540
541                 else:
542                     continue
543
544                 if not auth_hrn in dest:
545                     dest[auth_hrn] = []
546                 dest[auth_hrn].append(request) 
547         
548         context = super(ValidatePendingView, self).get_context_data(**kwargs)
549         context['my_authorities']   = ctx_my_authorities
550         context['delegation_authorities'] = ctx_delegation_authorities
551
552         # XXX This is repeated in all pages
553         # more general variables expected in the template
554         context['title'] = 'Test view that combines various plugins'
555         # the menu items on the top
556         context['topmenu_items'] = topmenu_items('Dashboard', self.request) 
557         # so we can sho who is logged
558         context['username'] = the_user(self.request) 
559
560         # XXX We need to prepare the page for queries
561         #context.update(page.prelude_env())
562
563         return context