isolate slice request view
[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.http                 import HttpResponseRedirect, HttpResponse
28 from django.views.generic.base   import TemplateView
29 from django.shortcuts            import render
30 from django.template.loader      import render_to_string
31 from django.core.mail            import send_mail
32 from django.utils.decorators     import method_decorator
33 from django.contrib.auth.decorators import login_required
34
35 from myslice.viewutils           import topmenu_items, the_user
36
37 from plugins.lists.simplelist    import SimpleList
38 from plugins.hazelnut            import Hazelnut
39 from plugins.pres_view           import PresView
40 from portal.event import Event
41
42 from portal                      import signals
43 from portal.forms                import SliceRequestForm
44 from portal.util                 import RegistrationView, ActivationView
45 from portal.models               import PendingUser, PendingSlice
46 from portal.actions              import authority_get_pi_emails, get_request_by_authority, manifold_add_user, manifold_update_user
47 from manifold.manifoldapi        import execute_query
48 from manifold.core.query         import Query
49 from unfold.page                 import Page
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 class PresViewView(TemplateView):
163     template_name = "view-unfold1.html"
164
165     def get_context_data(self, **kwargs):
166
167         page = Page(self.request)
168
169         pres_view = PresView(page = page)
170
171         context = super(PresViewView, self).get_context_data(**kwargs)
172
173         #context['ALL_STATIC'] = "all_static"
174         context['unfold1_main'] = pres_view.render(self.request)
175
176         # XXX This is repeated in all pages
177         # more general variables expected in the template
178         context['title'] = 'Test view that combines various plugins'
179         # the menu items on the top
180         context['topmenu_items'] = topmenu_items('PresView', self.request)
181         # so we can sho who is logged
182         context['username'] = the_user(self.request)
183
184         prelude_env = page.prelude_env()
185         context.update(prelude_env)
186
187         return context
188
189 def json_me(config_file,type):
190     json_answer = ''
191     for ligne in config_file:
192         if not ligne.startswith('#'):
193             args = ligne.split(';')
194             json_answer += str('{ "name": "' + args[0] + '" ,"id":"' + args[1]  + '" ,"descriptif":"' + args[2]+'"')
195             if type!="dynamic":
196                 json_answer += str(',"contraints":')
197                 if args[3]=="":
198                     json_answer += str('""')
199                 else:
200                     json_answer += str(args[3])
201             json_answer += str('},')
202     return json_answer[:-1]
203
204
205 DIR = '/var/myslice/'
206 STATIC = '%s/config_method_static' % DIR
207 DYNAMIC = '%s/config_method_dynamic' % DIR
208 ANIMATION = '%s/config_method_animation' % DIR
209
210 def pres_view_methods(request, type):
211
212     if type ==None:
213         return 0
214     elif type =="static":
215         config = open(STATIC, "r")
216         json_answer = str('{ "options": [')
217         json_answer += str(json_me(config,"static"))
218         json_answer += str('] }')
219         config.close()
220     elif type =="dynamic":
221         config = open(DYNAMIC, "r")
222         json_answer = str('{ "options": [')
223         json_answer += str(json_me(config,"dynamic"))
224         json_answer += str('] }')
225         config.close()
226     elif type =="animation":
227         config = open(ANIMATION, "r")
228         json_answer = str('{ "options": [')
229         json_answer += str(json_me(config,"animation"))
230         json_answer += str('] }')
231         config.close()
232     elif type =="all":
233         config = open(STATIC, "r")
234         json_answer = str('{ "static": [')
235         json_answer += str(json_me(config,"static"))
236         json_answer += str('],')
237         json_answer += str('"dynamic": [')
238         config.close()
239         config = open(DYNAMIC, "r")
240         json_answer += str(json_me(config,"dynamic"))
241         json_answer += str('],')
242         json_answer += str('"animation": [')
243         config.close()
244         config = open(ANIMATION, "r")
245         json_answer += str(json_me(config,"animation"))
246         json_answer += str('] }')
247         config.close()
248     else:
249         return 0
250     return HttpResponse (json_answer, mimetype="application/json")
251
252 def pres_view_animation(request, constraints, id):
253
254 # sites crees depuis 2008
255 # static.py?contraints=']date_created':1262325600&id='name_id"'
256
257     # method = request.getvalue('method') #ex : GetSites
258     #constraints = "']date_created':1262325600"
259     #id = "2"
260
261     if id == None:
262         return 0
263
264     # method = 'GetSites'#request.getvalue('method') #ex : GetSites
265     # constraints = {}#request.getvalue('constraints') // nul = {}
266     # response_field = "'site_id','name','date_created'"#request.getvalue('response_field')
267
268     config_file = open(ANIMATION, "r")
269     for ligne in config_file:
270         if not ligne.startswith('#'):
271             ligne = ligne.split('\n')
272             first = ligne[0].split(';')
273             if (str(first[1]) == str(id)):
274                 save = first
275     config_file.close()
276
277     #Les print_method, print_option sont definis par le client (js)
278     #Les animations acceptent que les connexions anonymous
279     # args = "postmsg;animation;;;anonymous;https://www.planet-lab.eu/PLCAPI/;"
280     args = ";;"+str(save[8])+";"+str(save[9])+";anonymous;"+str(save[5])+";"+str(save[6])+";{"+str(constraints)+"};"+str(save[7])+";"
281
282
283     #Creation d'un objet event
284     event = Event(args)
285     cmd = [{"params": {
286             "data": {
287                 "print_options": event.print_options,
288                 "print_method": event.print_method,
289                 "message": event.data
290             }
291         }
292     }]
293
294     json_answer = json.dumps(cmd)
295     return HttpResponse (json_answer, mimetype="application/json")
296
297 def pres_view_static(request, constraints, id):
298     #constraints = "']date_created':1262325600"
299     #id = "2"
300
301     # method = 'GetSites'#request.getvalue('method') #ex : GetSites
302     # constraints = {}#request.getvalue('constraints') // nul = {}
303     # response_field = "'site_id','name','date_created'"#request.getvalue('response_field')
304
305     config_file = open(STATIC, "r")
306     for ligne in config_file:
307         if not ligne.startswith('#'):
308             ligne = ligne.split('\n')
309             first = ligne[0].split(';')
310             if (str(first[1]) == str(id)):
311                 save = first
312     config_file.close()
313
314     #Les print_method, print_option sont definis par le client (js)
315     #Les animations acceptent que les connexions anonymous
316     # args = "postmsg;animation;;;anonymous;https://www.planet-lab.eu/PLCAPI/;"
317     args = ";;"+str(save[8])+";"+str(save[9])+";anonymous;"+str(save[5])+";"+str(save[6])+";{"+str(constraints)+"};"+str(save[7])+";"
318
319
320     #Creation d'un objet event
321     event = Event(args)
322     cmd = [{"params": {
323             "data": {
324                 "print_options": event.print_options,
325                 "print_method": event.print_method,
326                 "message": event.data
327             }
328         }
329     }]
330
331     json_answer = json.dumps(cmd)
332     return HttpResponse (json_answer, mimetype="application/json")
333
334 class ValidatePendingView(TemplateView):
335     template_name = "validate_pending.html"
336
337     def get_context_data(self, **kwargs):
338         # We might have slices on different registries with different user accounts 
339         # 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
340         # Different registries mean different identities, unless we identify via SFA HRN or have associated the user email to a single hrn
341
342         #messages.info(self.request, 'You have logged in')
343         page = Page(self.request)
344
345         ctx_my_authorities = {}
346         ctx_delegation_authorities = {}
347
348
349         # The user need to be logged in
350         if the_user(self.request):
351             # Who can a PI validate:
352             # His own authorities + those he has credentials for.
353             # In MySlice we need to look at credentials also.
354             
355
356             # XXX This will have to be asynchroneous. Need to implement barriers,
357             # for now it will be sufficient to have it working statically
358
359             # get user_id to later on query accounts
360             # XXX Having real query plan on local tables would simplify all this
361             # XXX $user_email is still not available for local tables
362             #user_query = Query().get('local:user').filter_by('email', '==', '$user_email').select('user_id')
363             user_query = Query().get('local:user').filter_by('email', '==', the_user(self.request)).select('user_id')
364             user, = execute_query(self.request, user_query)
365             user_id = user['user_id']
366
367             # Query manifold to learn about available SFA platforms for more information
368             # In general we will at least have the portal
369             # For now we are considering all registries
370             all_authorities = []
371             platform_ids = []
372             sfa_platforms_query = Query().get('local:platform').filter_by('gateway_type', '==', 'sfa').select('platform_id', 'platform', 'auth_type')
373             sfa_platforms = execute_query(self.request, sfa_platforms_query)
374             for sfa_platform in sfa_platforms:
375                 print "SFA PLATFORM > ", sfa_platform['platform']
376                 if not 'auth_type' in sfa_platform:
377                     continue
378                 auth = sfa_platform['auth_type']
379                 if not auth in all_authorities:
380                     all_authorities.append(auth)
381                 platform_ids.append(sfa_platform['platform_id'])
382
383             # We can check on which the user has authoritity credentials = PI rights
384             credential_authorities = set()
385             credential_authorities_expired = set()
386
387             # User account on these registries
388             user_accounts_query = Query.get('local:account').filter_by('user_id', '==', user_id).filter_by('platform_id', 'included', platform_ids).select('config')
389             user_accounts = execute_query(self.request, user_accounts_query)
390             #print "=" * 80
391             #print user_accounts
392             #print "=" * 80
393             for user_account in user_accounts:
394                 config = json.loads(user_account['config'])
395                 creds = []
396                 if 'authority_credentials' in config:
397                     for authority_hrn, credential in config['authority_credentials'].items():
398                         #if credential is not expired:
399                         credential_authorities.add(authority_hrn)
400                         #else
401                         #    credential_authorities_expired.add(authority_hrn)
402                 if 'delegated_authority_credentials' in config:
403                     for authority_hrn, credential in config['delegated_authority_credentials'].items():
404                         #if credential is not expired:
405                         credential_authorities.add(authority_hrn)
406                         #else
407                         #    credential_authorities_expired.add(authority_hrn)
408
409             print 'credential_authorities =', credential_authorities
410             print 'credential_authorities_expired =', credential_authorities_expired
411
412             # ** Where am I a PI **
413             # For this we need to ask SFA (of all authorities) = PI function
414             pi_authorities_query = Query.get('user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
415             pi_authorities_tmp = execute_query(self.request, pi_authorities_query)
416             pi_authorities = set()
417             for pa in pi_authorities_tmp:
418                 pi_authorities |= set(pa['pi_authorities'])
419
420             print "pi_authorities =", pi_authorities
421             
422             # My authorities + I have a credential
423             pi_credential_authorities = pi_authorities & credential_authorities
424             pi_no_credential_authorities = pi_authorities - credential_authorities - credential_authorities_expired
425             pi_expired_credential_authorities = pi_authorities & credential_authorities_expired
426             # Authorities I've been delegated PI rights
427             pi_delegation_credential_authorities = credential_authorities - pi_authorities
428             pi_delegation_expired_authorities = credential_authorities_expired - pi_authorities
429
430             print "pi_credential_authorities =", pi_credential_authorities
431             print "pi_no_credential_authorities =", pi_no_credential_authorities
432             print "pi_expired_credential_authorities =", pi_expired_credential_authorities
433             print "pi_delegation_credential_authorities = ", pi_delegation_credential_authorities
434             print "pi_delegation_expired_authorities = ", pi_delegation_expired_authorities
435
436             # Summary intermediary
437             pi_my_authorities = pi_credential_authorities | pi_no_credential_authorities | pi_expired_credential_authorities
438             pi_delegation_authorities = pi_delegation_credential_authorities | pi_delegation_expired_authorities
439
440             print "--"
441             print "pi_my_authorities = ", pi_my_authorities
442             print "pi_delegation_authorities = ", pi_delegation_authorities
443
444             # Summary all
445             queried_pending_authorities = pi_my_authorities | pi_delegation_authorities
446             print "----"
447             print "queried_pending_authorities = ", queried_pending_authorities
448
449             requests = get_request_by_authority(queried_pending_authorities)
450             for request in requests:
451                 auth_hrn = request['authority_hrn']
452
453                 if auth_hrn in pi_my_authorities:
454                     dest = ctx_my_authorities
455
456                     # define the css class
457                     if auth_hrn in pi_credential_authorities:
458                         request['allowed'] = 'allowed'
459                     elif auth_hrn in pi_expired_credential_authorities:
460                         request['allowed'] = 'expired'
461                     else: # pi_no_credential_authorities
462                         request['allowed'] = 'denied'
463
464                 elif auth_hrn in pi_delegation_authorities:
465                     dest = ctx_delegation_authorities
466
467                     if auth_hrn in pi_delegation_credential_authorities:
468                         request['allowed'] = 'allowed'
469                     else: # pi_delegation_expired_authorities
470                         request['allowed'] = 'expired'
471
472                 else:
473                     continue
474
475                 if not auth_hrn in dest:
476                     dest[auth_hrn] = []
477                 dest[auth_hrn].append(request) 
478         
479         context = super(ValidatePendingView, self).get_context_data(**kwargs)
480         context['my_authorities']   = ctx_my_authorities
481         context['delegation_authorities'] = ctx_delegation_authorities
482
483         # XXX This is repeated in all pages
484         # more general variables expected in the template
485         context['title'] = 'Test view that combines various plugins'
486         # the menu items on the top
487         context['topmenu_items'] = topmenu_items('Dashboard', self.request) 
488         # so we can sho who is logged
489         context['username'] = the_user(self.request) 
490
491         # XXX We need to prepare the page for queries
492         #context.update(page.prelude_env())
493
494         return context