Customize third party signup form fields
[astakos] / snf-astakos-app / astakos / im / target / shibboleth.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.http import HttpResponseBadRequest
35 from django.utils.translation import ugettext as _
36 from django.contrib import messages
37 from django.template import RequestContext
38 from django.views.decorators.http import require_http_methods
39 from django.db.models import Q
40 from django.core.exceptions import ValidationError
41 from django.http import HttpResponseRedirect
42 from django.core.urlresolvers import reverse
43 from urlparse import urlunsplit, urlsplit
44 from django.utils.http import urlencode
45
46 from astakos.im.util import prepare_response, get_context, get_invitation
47 from astakos.im.views import requires_anonymous, render_response
48 from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
49
50 from astakos.im.models import AstakosUser, PendingThirdPartyUser
51 from astakos.im.forms import LoginForm
52 from astakos.im.activation_backends import get_backend, SimpleBackend
53
54 import logging
55
56 logger = logging.getLogger(__name__)
57
58 class Tokens:
59     # these are mapped by the Shibboleth SP software
60     SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName
61     SHIB_NAME = "HTTP_SHIB_INETORGPERSON_GIVENNAME"
62     SHIB_SURNAME = "HTTP_SHIB_PERSON_SURNAME"
63     SHIB_CN = "HTTP_SHIB_PERSON_COMMONNAME"
64     SHIB_DISPLAYNAME = "HTTP_SHIB_INETORGPERSON_DISPLAYNAME"
65     SHIB_EP_AFFILIATION = "HTTP_SHIB_EP_AFFILIATION"
66     SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
67     SHIB_MAIL = "HTTP_SHIB_MAIL"
68
69 @require_http_methods(["GET", "POST"])
70 @requires_anonymous
71 def login(
72     request,
73     on_login_template='im/login.html',
74     on_signup_template='im/third_party_check_local.html',
75     extra_context=None
76 ):
77     extra_context = extra_context or {}
78
79     tokens = request.META
80     
81     try:
82         eppn = tokens[Tokens.SHIB_EPPN]
83     except KeyError:
84         return HttpResponseBadRequest("Missing unique token in request")
85     
86     if Tokens.SHIB_DISPLAYNAME in tokens:
87         realname = tokens[Tokens.SHIB_DISPLAYNAME]
88     elif Tokens.SHIB_CN in tokens:
89         realname = tokens[Tokens.SHIB_CN]
90     elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
91         realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
92     else:
93         return HttpResponseBadRequest("Missing user name in request")
94     
95     affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
96     email = tokens.get(Tokens.SHIB_MAIL, '')
97         
98     try:
99         user = AstakosUser.objects.get(
100             provider='shibboleth',
101             third_party_identifier=eppn
102         )
103         if user.is_active:
104             return prepare_response(request,
105                                     user,
106                                     request.GET.get('next'),
107                                     'renew' in request.GET)
108         else:
109             message = _('Inactive account')
110             messages.add_message(request, messages.ERROR, message)
111             return render_response(on_login_template,
112                                    login_form = LoginForm(request=request),
113                                    context_instance=RequestContext(request))
114     except AstakosUser.DoesNotExist, e:
115         # First time
116         try:
117             user, created = PendingThirdPartyUser.objects.get_or_create(
118                 third_party_identifier=eppn,
119                 provider='shibboleth',
120                 defaults=dict(
121                     realname=realname,
122                     affiliation=affiliation,
123                     email=email
124                 )
125             )
126             user.save()
127         except BaseException, e:
128             logger.exception(e)
129             template = on_login_template
130             extra_context['login_form'] = LoginForm(request=request)
131             messages.error(request, _('Something went wrong.'))
132         else:
133             if not ENABLE_LOCAL_ACCOUNT_MIGRATION:
134                 url = reverse(
135                     'astakos.im.target.shibboleth.signup'
136                 )
137                 parts = list(urlsplit(url))
138                 parts[3] = urlencode({'key': user.username})
139                 url = urlunsplit(parts)
140                 return HttpResponseRedirect(url)
141             else:
142                 template = on_signup_template
143                 extra_context['key'] = user.username
144         
145         extra_context['provider']='shibboleth'
146         return render_response(
147             template,
148             context_instance=get_context(request, extra_context)
149         )
150
151 @require_http_methods(["GET"])
152 @requires_anonymous
153 def signup(
154     request,
155     backend=None,
156     on_creation_template='im/third_party_registration.html',
157     extra_context=None
158 ):
159     extra_context = extra_context or {}
160     username = request.GET.get('key')
161     if not username:
162         return HttpResponseBadRequest(_('Missing key parameter.'))
163     try:
164         pending = PendingThirdPartyUser.objects.get(username=username)
165     except BaseException, e:
166         logger.exception(e)
167         return HttpResponseBadRequest(_('Invalid key.'))
168     else:
169         d = pending.__dict__
170         d.pop('_state', None)
171         d.pop('id', None)
172         user = AstakosUser(**d)
173         try:
174             backend = backend or get_backend(request)
175         except ImproperlyConfigured, e:
176             messages.error(request, e)
177         else:
178             extra_context['form'] = backend.get_signup_form(
179                 provider='shibboleth',
180                 instance=user
181             )
182     extra_context['provider']='shibboleth'
183     return render_response(
184             on_creation_template,
185             context_instance=get_context(request, extra_context)
186     )