955c7e3427926f931f8f61fedebbf94d14d829a0
[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     extra_context = extra_context or {}
77
78     tokens = request.META
79     
80     try:
81         eppn = tokens[Tokens.SHIB_EPPN]
82     except KeyError:
83         return HttpResponseBadRequest("Missing unique token in request")
84     
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         return HttpResponseBadRequest("Missing user name in request")
93     
94     affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
95     email = tokens.get(Tokens.SHIB_MAIL, '')
96         
97     try:
98         user = AstakosUser.objects.get(
99             provider='shibboleth',
100             third_party_identifier=eppn
101         )
102         if user.is_active:
103             return prepare_response(request,
104                                     user,
105                                     request.GET.get('next'),
106                                     'renew' in request.GET)
107         else:
108             message = _('Inactive account')
109             messages.add_message(request, messages.ERROR, message)
110             return render_response(on_login_template,
111                                    login_form = LoginForm(request=request),
112                                    context_instance=RequestContext(request))
113     except AstakosUser.DoesNotExist, e:
114         # First time
115         try:
116             user, created = PendingThirdPartyUser.objects.get_or_create(
117                 third_party_identifier=eppn,
118                 provider='shibboleth',
119                 defaults=dict(
120                     realname=realname,
121                     affiliation=affiliation,
122                     email=email
123                 )
124             )
125             user.save()
126         except BaseException, e:
127             logger.exception(e)
128             template = on_login_template
129             extra_context['login_form'] = LoginForm(request=request)
130             messages.error(request, _('Something went wrong.'))
131         else:
132             if not ENABLE_LOCAL_ACCOUNT_MIGRATION:
133                 url = reverse(
134                     'astakos.im.target.shibboleth.signup'
135                 )
136                 parts = list(urlsplit(url))
137                 parts[3] = urlencode({'key': user.username})
138                 url = urlunsplit(parts)
139                 return HttpResponseRedirect(url)
140             else:
141                 template = on_signup_template
142                 extra_context['key'] = user.username
143         
144         extra_context['provider']='shibboleth'
145         return render_response(
146             template,
147             context_instance=get_context(request, extra_context)
148         )
149
150 @require_http_methods(["GET"])
151 @requires_anonymous
152 def signup(request,
153            backend=None,
154            on_creation_template='im/third_party_registration.html',
155            extra_context=None
156 ):
157     extra_context = extra_context or {}
158     username = request.GET.get('key')
159     if not username:
160         return HttpResponseBadRequest(_('Missing key parameter.'))
161     try:
162         pending = PendingThirdPartyUser.objects.get(username=username)
163     except BaseException, e:
164         logger.exception(e)
165         return HttpResponseBadRequest(_('Invalid key.'))
166     else:
167         d = pending.__dict__
168         d.pop('_state', None)
169         d.pop('id', None)
170         user = AstakosUser(**d)
171         try:
172             backend = backend or get_backend(request)
173         except ImproperlyConfigured, e:
174             messages.error(request, e)
175         else:
176             extra_context['form'] = backend.get_signup_form(
177                 provider='shibboleth',
178                 instance=user
179             )
180     extra_context['provider']='shibboleth'
181     return render_response(
182             on_creation_template,
183             context_instance=get_context(request, extra_context)
184     )