Special handling for login failure messages
[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     login_template='im/login.html',
74     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.get(Tokens.SHIB_EPPN)
83         if not eppn:
84             raise KeyError(_('Missing unique token in request'))
85         if Tokens.SHIB_DISPLAYNAME in tokens:
86             realname = tokens[Tokens.SHIB_DISPLAYNAME]
87         elif Tokens.SHIB_CN in tokens:
88             realname = tokens[Tokens.SHIB_CN]
89         elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
90             realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
91         else:
92             raise KeyError(_('Missing user name in request'))
93     except KeyError, e:
94         extra_context['login_form'] = LoginForm(request=request)
95         messages.error(request, e)
96         return render_response(
97             login_template,
98             context_instance=get_context(request, extra_context)
99         )
100     
101     affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
102     email = tokens.get(Tokens.SHIB_MAIL, '')
103     
104     try:
105         user = AstakosUser.objects.get(
106             provider='shibboleth',
107             third_party_identifier=eppn
108         )
109         if user.is_active:
110             return prepare_response(request,
111                                     user,
112                                     request.GET.get('next'),
113                                     'renew' in request.GET)
114         elif not user.activation_sent:
115             message = _('Your request is pending activation')
116             messages.error(request, message)
117         else:
118             url = reverse('send_activation', kwargs={'user_id':user.id})
119             message = _('You have not followed the activation link. \
120             <a href="%s">Provide new email?</a>' % url)
121             messages.error(request, message)
122         return render_response(login_template,
123                                login_form = LoginForm(request=request),
124                                context_instance=RequestContext(request))
125     except AstakosUser.DoesNotExist, e:
126         # First time
127         try:
128             user, created = PendingThirdPartyUser.objects.get_or_create(
129                 third_party_identifier=eppn,
130                 provider='shibboleth',
131                 defaults=dict(
132                     realname=realname,
133                     affiliation=affiliation,
134                     email=email
135                 )
136             )
137             user.save()
138         except BaseException, e:
139             logger.exception(e)
140             template = login_template
141             extra_context['login_form'] = LoginForm(request=request)
142             messages.error(request, _('Something went wrong.'))
143         else:
144             if not ENABLE_LOCAL_ACCOUNT_MIGRATION:
145                 url = reverse(
146                     'astakos.im.target.shibboleth.signup'
147                 )
148                 parts = list(urlsplit(url))
149                 parts[3] = urlencode({'key': user.username})
150                 url = urlunsplit(parts)
151                 return HttpResponseRedirect(url)
152             else:
153                 template = signup_template
154                 extra_context['key'] = user.username
155         
156         extra_context['provider']='shibboleth'
157         return render_response(
158             template,
159             context_instance=get_context(request, extra_context)
160         )
161
162 @require_http_methods(["GET"])
163 @requires_anonymous
164 def signup(
165     request,
166     backend=None,
167     on_creation_template='im/third_party_registration.html',
168     extra_context=None
169 ):
170     extra_context = extra_context or {}
171     username = request.GET.get('key')
172     if not username:
173         return HttpResponseBadRequest(_('Missing key parameter.'))
174     try:
175         pending = PendingThirdPartyUser.objects.get(username=username)
176     except BaseException, e:
177         logger.exception(e)
178         return HttpResponseBadRequest(_('Invalid key.'))
179     else:
180         d = pending.__dict__
181         d.pop('_state', None)
182         d.pop('id', None)
183         user = AstakosUser(**d)
184         try:
185             backend = backend or get_backend(request)
186         except ImproperlyConfigured, e:
187             messages.error(request, e)
188         else:
189             extra_context['form'] = backend.get_signup_form(
190                 provider='shibboleth',
191                 instance=user
192             )
193     extra_context['provider']='shibboleth'
194     return render_response(
195             on_creation_template,
196             context_instance=get_context(request, extra_context)
197     )