Enable inactive shibboleth users to change email
[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             urls = {}
119             urls['send_activation'] = reverse(
120                 'send_activation',
121                 kwargs={'user_id':user.id}
122             )
123             urls['signup'] = reverse(
124                 'shibboleth_signup',
125                 args= [user.username]
126             )   
127             message = _(
128                 'You have not followed the activation link. \
129                 <a href="%(send_activation)s">Resend activation email?</a> or \
130                 <a href="%(signup)s">Provide new email?</a>' % urls
131             )
132             messages.error(request, message)
133         return render_response(login_template,
134                                login_form = LoginForm(request=request),
135                                context_instance=RequestContext(request))
136     except AstakosUser.DoesNotExist, e:
137         # First time
138         try:
139             user, created = PendingThirdPartyUser.objects.get_or_create(
140                 third_party_identifier=eppn,
141                 provider='shibboleth',
142                 defaults=dict(
143                     realname=realname,
144                     affiliation=affiliation,
145                     email=email
146                 )
147             )
148             user.save()
149         except BaseException, e:
150             logger.exception(e)
151             template = login_template
152             extra_context['login_form'] = LoginForm(request=request)
153             messages.error(request, _('Something went wrong.'))
154         else:
155             if not ENABLE_LOCAL_ACCOUNT_MIGRATION:
156                 url = reverse(
157                     'shibboleth_signup',
158                     args= [user.username]
159                 )
160                 return HttpResponseRedirect(url)
161             else:
162                 template = signup_template
163                 extra_context['username'] = user.username
164         
165         extra_context['provider']='shibboleth'
166         return render_response(
167             template,
168             context_instance=get_context(request, extra_context)
169         )
170
171 @require_http_methods(["GET"])
172 @requires_anonymous
173 def signup(
174     request,
175     username,
176     backend=None,
177     on_creation_template='im/third_party_registration.html',
178     extra_context=None
179 ):
180     extra_context = extra_context or {}
181     if not username:
182         return HttpResponseBadRequest(_('Missing key parameter.'))
183     try:
184         pending = PendingThirdPartyUser.objects.get(username=username)
185     except PendingThirdPartyUser.DoesNotExist:
186         try:
187             user = AstakosUser.objects.get(username=username)
188         except AstakosUser.DoesNotExist:
189             return HttpResponseBadRequest(_('Invalid key.'))
190     else:
191         d = pending.__dict__
192         d.pop('_state', None)
193         d.pop('id', None)
194         user = AstakosUser(**d)
195     try:
196         backend = backend or get_backend(request)
197     except ImproperlyConfigured, e:
198         messages.error(request, e)
199     else:
200         extra_context['form'] = backend.get_signup_form(
201             provider='shibboleth',
202             instance=user
203         )
204     extra_context['provider']='shibboleth'
205     return render_response(
206             on_creation_template,
207             context_instance=get_context(request, extra_context)
208     )