Revision dd5f8f4d

b/snf-astakos-app/astakos/im/auth_providers.py
42 42
from astakos.im import messages as astakos_messages
43 43

  
44 44
import logging
45
import urllib
45 46

  
46 47
logger = logging.getLogger(__name__)
47 48

  
......
87 88
        msg = 'AUTH_PROVIDER_%s' % msg
88 89
        return override_msg or getattr(astakos_messages, msg, msg) % params
89 90

  
91
    @property
92
    def add_url(self):
93
        return reverse(self.login_view)
94

  
90 95
    def __init__(self, user=None):
91 96
        self.user = user
97
        for tpl in ['login_prompt', 'login', 'signup_prompt']:
98
            tpl_name = '%s_%s' % (tpl, 'template')
99
            override = self.get_setting(tpl_name)
100
            if override:
101
                setattr(self, tpl_name, override)
92 102

  
93 103
    def __getattr__(self, key):
94 104
        if not key.startswith('get_'):
......
141 151
    login_prompt = _('if you already have a username and password')
142 152
    signup_prompt = _('New to ~okeanos ?')
143 153
    signup_link_prompt = _('create an account now')
144

  
145

  
146
    @property
147
    def add_url(self):
148
        return reverse('password_change')
154
    login_view = 'password_change'
149 155

  
150 156
    one_per_user = True
151 157

  
......
171 177
    primary_login_prompt = _('If you are a student/researcher/faculty you can'
172 178
                             ' login using your university-credentials in'
173 179
                             ' the following page')
174

  
175
    @property
176
    def add_url(self):
177
        return reverse('astakos.im.target.shibboleth.login')
180
    login_view = 'astakos.im.target.shibboleth.login'
178 181

  
179 182
    login_template = 'im/auth/shibboleth_login.html'
180
    login_prompt_template = 'im/auth/shibboleth_login_prompt.html'
183
    login_prompt_template = 'im/auth/third_party_provider_generic_login_prompt.html'
181 184

  
182 185

  
183 186
class TwitterAuthProvider(AuthProvider):
......
186 189
    add_prompt = _('Allows you to login to your account using Twitter')
187 190
    details_tpl = _('Twitter screen name: %(info_screen_name)s')
188 191
    user_title = _('Twitter (%(info_screen_name)s)')
189

  
190
    @property
191
    def add_url(self):
192
        return reverse('astakos.im.target.twitter.login')
192
    login_view = 'astakos.im.target.twitter.login'
193 193

  
194 194
    login_template = 'im/auth/third_party_provider_generic_login.html'
195 195
    login_prompt_template = 'im/auth/third_party_provider_generic_login_prompt.html'
......
201 201
    add_prompt = _('Allows you to login to your account using Google')
202 202
    details_tpl = _('Google account: %(info_email)s')
203 203
    user_title = _('Google (%(info_email)s)')
204

  
205
    @property
206
    def add_url(self):
207
        return reverse('astakos.im.target.google.login')
204
    login_view = 'astakos.im.target.google.login'
208 205

  
209 206
    login_template = 'im/auth/third_party_provider_generic_login.html'
210 207
    login_prompt_template = 'im/auth/third_party_provider_generic_login_prompt.html'
......
216 213
    add_prompt = _('Allows you to login to your account using LinkedIn')
217 214
    user_title = _('LinkedIn (%(info_emailAddress)s)')
218 215
    details_tpl = _('LinkedIn account: %(info_emailAddress)s')
219

  
220
    @property
221
    def add_url(self):
222
        return reverse('astakos.im.target.linkedin.login')
216
    login_view = 'astakos.im.target.linkedin.login'
223 217

  
224 218
    login_template = 'im/auth/third_party_provider_generic_login.html'
225 219
    login_prompt_template = 'im/auth/third_party_provider_generic_login_prompt.html'
b/snf-astakos-app/astakos/im/forms.py
468 468

  
469 469
class ExtendedPasswordResetForm(PasswordResetForm):
470 470
    """
471
    Extends PasswordResetForm by overriding save method:
472
    passes a custom from_email in send_mail.
471
    Extends PasswordResetForm by overriding
473 472

  
474
    Since Django 1.3 this is useless since ``django.contrib.auth.views.reset_password``
475
    accepts a from_email argument.
473
    save method: to pass a custom from_email in send_mail.
474
    clean_email: to handle local auth provider checks
476 475
    """
477 476
    def clean_email(self):
478 477
        email = super(ExtendedPasswordResetForm, self).clean_email()
479 478
        try:
480
            user = AstakosUser.objects.get(email__iexact=email)
479
            user = AstakosUser.objects.get_by_identifier(email)
480

  
481
            if not user.is_active:
482
                raise forms.ValidationError(_(astakos_messages.ACCOUNT_INACTIVE))
483

  
481 484
            if not user.has_usable_password():
482 485
                raise forms.ValidationError(_(astakos_messages.UNUSABLE_PASSWORD))
483 486

  
484 487
            if not user.can_change_password():
485
                raise forms.ValidationError(_('Password change for this account'
486
                                              ' is not supported.'))
487

  
488
                raise forms.ValidationError(_(astakos_messages.AUTH_PROVIDER_CANNOT_CHANGE_PASSWORD))
488 489
        except AstakosUser.DoesNotExist, e:
489 490
            raise forms.ValidationError(_(astakos_messages.EMAIL_UNKNOWN))
490 491
        return email
......
619 620
#     )
620 621
#     desc = forms.CharField(
621 622
#         label= 'Description',
622
#         widget=forms.Textarea, 
623
#         widget=forms.Textarea,
623 624
#         help_text= "Please provide a short but descriptive abstract of your Project, so that anyone searching can quickly understand what this Project is about. "
624 625
#     )
625 626
#     issue_date = forms.DateTimeField(
......
641 642
#         required=True, min_value=1,
642 643
#         help_text="Here you specify the number of members this Project is going to have. This means that this number of people will be granted the resources you will specify in the next step. This can be '1' if you are the only one wanting to get resources. "
643 644
#     )
644
# 
645
#
645 646
#     class Meta:
646 647
#         model = AstakosGroup
647
# 
648
#
648 649
#     def __init__(self, *args, **kwargs):
649 650
#         #update QueryDict
650 651
#         args = list(args)
651 652
#         qd = args.pop(0).copy()
652 653
#         members_unlimited = qd.pop('members_unlimited', False)
653 654
#         members_uplimit = qd.pop('members_uplimit', None)
654
# 
655
#
655 656
#         #substitue QueryDict
656 657
#         args.insert(0, qd)
657
# 
658
#
658 659
#         super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
659
#         
660
#
660 661
#         self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
661 662
#                                 'issue_date', 'expiration_date',
662 663
#                                 'moderation_enabled', 'max_participants']
......
670 671
#         map(add_fields,
671 672
#             ((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
672 673
#         )
673
# 
674
#
674 675
#         def add_fields((k, v)):
675 676
#             self.fields[k] = forms.BooleanField(
676 677
#                 required=False,
......
679 680
#         map(add_fields,
680 681
#             ((k, v) for k,v in qd.iteritems() if k.startswith('is_selected_'))
681 682
#         )
682
# 
683
#
683 684
#     def policies(self):
684 685
#         self.clean()
685 686
#         policies = []
686 687
#         append = policies.append
687 688
#         for name, uplimit in self.cleaned_data.iteritems():
688
# 
689
#
689 690
#             subs = name.split('_uplimit')
690 691
#             if len(subs) == 2:
691 692
#                 prefix, suffix = subs
692 693
#                 s, sep, r = prefix.partition(RESOURCE_SEPARATOR)
693 694
#                 resource = Resource.objects.get(service__name=s, name=r)
694
# 
695
#
695 696
#                 # keep only resource limits for selected resource groups
696 697
#                 if self.cleaned_data.get(
697 698
#                     'is_selected_%s' % resource.group, False
698 699
#                 ):
699 700
#                     append(dict(service=s, resource=r, uplimit=uplimit))
700 701
#         return policies
701
# 
702
#
702 703
# class AstakosGroupCreationSummaryForm(forms.ModelForm):
703 704
#     kind = forms.ModelChoiceField(
704 705
#         queryset=GroupKind.objects.all(),
......
717 718
#     max_participants = forms.IntegerField(
718 719
#         required=False, min_value=1
719 720
#     )
720
# 
721
#
721 722
#     class Meta:
722 723
#         model = AstakosGroup
723
# 
724
#
724 725
#     def __init__(self, *args, **kwargs):
725 726
#         #update QueryDict
726 727
#         args = list(args)
727 728
#         qd = args.pop(0).copy()
728 729
#         members_unlimited = qd.pop('members_unlimited', False)
729 730
#         members_uplimit = qd.pop('members_uplimit', None)
730
# 
731
#
731 732
#         #substitue QueryDict
732 733
#         args.insert(0, qd)
733
# 
734
#
734 735
#         super(AstakosGroupCreationSummaryForm, self).__init__(*args, **kwargs)
735 736
#         self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
736 737
#                                 'issue_date', 'expiration_date',
......
744 745
#         map(add_fields,
745 746
#             ((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
746 747
#         )
747
# 
748
#
748 749
#         def add_fields((k, v)):
749 750
#             self.fields[k] = forms.BooleanField(
750 751
#                 required=False,
......
755 756
#         )
756 757
#         for f in self.fields.values():
757 758
#             f.widget = forms.HiddenInput()
758
# 
759
#
759 760
#     def clean(self):
760 761
#         super(AstakosGroupCreationSummaryForm, self).clean()
761 762
#         self.cleaned_data['policies'] = []
......
769 770
#                 prefix, suffix = subs
770 771
#                 s, sep, r = prefix.partition(RESOURCE_SEPARATOR)
771 772
#                 resource = Resource.objects.get(service__name=s, name=r)
772
# 
773
#
773 774
#                 # keep only resource limits for selected resource groups
774 775
#                 if self.cleaned_data.get(
775 776
#                     'is_selected_%s' % resource.group, False
......
778 779
#         for name in tbd:
779 780
#             self.cleaned_data.pop(name, None)
780 781
#         return self.cleaned_data
781
# 
782
#
782 783
# class AstakosGroupUpdateForm(forms.ModelForm):
783 784
#     class Meta:
784 785
#         model = AstakosGroup
785 786
#         fields = ( 'desc','homepage', 'moderation_enabled')
786
# 
787
# 
787
#
788
#
788 789
# class AddGroupMembersForm(forms.Form):
789 790
#     q = forms.CharField(
790 791
#         max_length=800, widget=forms.Textarea, label=_('Add members'),
791 792
#         help_text=_(astakos_messages.ADD_GROUP_MEMBERS_Q_HELP),
792 793
#         required=True)
793
# 
794
#
794 795
#     def clean(self):
795 796
#         q = self.cleaned_data.get('q') or ''
796 797
#         users = q.split(',')
......
801 802
#             raise forms.ValidationError(_(astakos_messages.UNKNOWN_USERS) % ','.join(unknown))
802 803
#         self.valid_users = db_entries
803 804
#         return self.cleaned_data
804
# 
805
#
805 806
#     def get_valid_users(self):
806 807
#         """Should be called after form cleaning"""
807 808
#         try:
808 809
#             return self.valid_users
809 810
#         except:
810 811
#             return ()
811
# 
812
# 
812
#
813
#
813 814
# class AstakosGroupSearchForm(forms.Form):
814 815
#     q = forms.CharField(max_length=200, label='Search project')
815
# 
816
# 
816
#
817
#
817 818
# class TimelineForm(forms.Form):
818 819
#     entity = forms.ModelChoiceField(
819 820
#         queryset=AstakosUser.objects.filter(is_active=True)
......
830 831
#                  ('charge_usage', 'Charge Usage'),
831 832
#                  ('charge_traffic', 'Charge Traffic'), )
832 833
#     )
833
# 
834
#
834 835
#     def clean(self):
835 836
#         super(TimelineForm, self).clean()
836 837
#         d = self.cleaned_data
......
844 845
#         if 'entity' in d:
845 846
#             d['entity'] = d['entity'].email
846 847
#         return d
847
# 
848
# 
848
#
849
#
849 850
# class AstakosGroupSortForm(forms.Form):
850 851
#     sorting = forms.ChoiceField(
851 852
#         label='Sort by',
......
859 860
#         ),
860 861
#         required=True
861 862
#     )
862
# 
863
#
863 864
# class MembersSortForm(forms.Form):
864 865
#     sorting = forms.ChoiceField(
865 866
#         label='Sort by',
......
869 870
#         ),
870 871
#         required=True
871 872
#     )
872
# 
873
#
873 874
# class PickResourceForm(forms.Form):
874 875
#     resource = forms.ModelChoiceField(
875 876
#         queryset=Resource.objects.select_related().all()
......
920 921
    homepage = forms.URLField(
921 922
        help_text="This should be a URL pointing at your project's site. e.g.: http://myproject.com ",
922 923
        widget=forms.TextInput(attrs={'placeholder': 'http://myproject.com'}),
923
        
924

  
924 925
        required=False
925 926
     )
926 927
    comments = forms.CharField(widget=forms.Textarea, required=False)
927
    
928

  
928 929
    class Meta:
929 930
        model = ProjectApplication
930 931
        exclude = (
......
935 936
    def __init__(self, *args, **kwargs):
936 937
        self.precursor_application = kwargs.get('instance')
937 938
        super(ProjectApplicationForm, self).__init__(*args, **kwargs)
938
    
939

  
939 940
    def clean(self):
940 941
        userid = self.data.get('user', None)
941 942
        self.user = None
......
948 949
            raise forms.ValidationError(_(astakos_messages.NO_APPLICANT))
949 950
        super(ProjectApplicationForm, self).clean()
950 951
        return self.cleaned_data
951
    
952

  
952 953
    @property
953 954
    def resource_policies(self):
954 955
        policies = []
......
971 972
                        append(dict(service=s, resource=r, uplimit=uplimit))
972 973
                    else:
973 974
                        append(dict(service=s, resource=r, uplimit=None))
974
                
975

  
975 976
        return policies
976
    
977

  
977 978

  
978 979
    def save(self, commit=True):
979 980
        application = super(ProjectApplicationForm, self).save(commit=False)
b/snf-astakos-app/astakos/im/target/__init__.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
import json
35

  
36
from django.contrib import messages
37
from django.utils.translation import ugettext as _
38
from django.http import HttpResponseRedirect
39
from django.core.urlresolvers import reverse
40

  
41
from astakos.im.models import PendingThirdPartyUser
42
from astakos.im.util import get_query
43
from astakos.im import messages as astakos_messages
44
from astakos.im import auth_providers
45
from astakos.im.util import prepare_response, get_context
46
from astakos.im.views import requires_anonymous, render_response
47

  
48

  
49
def add_pending_auth_provider(request, third_party_token):
50

  
51
    if third_party_token:
52
        # use requests to assign the account he just authenticated with with
53
        # a third party provider account
54
        try:
55
            request.user.add_pending_auth_provider(third_party_token)
56
            messages.success(request, _(astakos_messages.AUTH_PROVIDER_ADDED))
57
        except PendingThirdPartyUser.DoesNotExist:
58
            messages.error(request, _(astakos_messages.AUTH_PROVIDER_ADD_FAILED))
59

  
60

  
61
def get_pending_key(request):
62

  
63
    third_party_token = get_query(request).get('key', False)
64
    if not third_party_token:
65
        third_party_token = request.session.get('pending_key', None)
66
        if third_party_token:
67
          del request.session['pending_key']
68
    return third_party_token
69

  
70

  
71
def handle_third_party_signup(request, userid, provider_module, third_party_key,
72
                              provider_info={},
73
                              pending_user_params={},
74
                              template="im/third_party_check_local.html",
75
                              extra_context={}):
76

  
77
    # user wants to add another third party login method
78
    if third_party_key:
79
        messages.error(request, _(astakos_messages.AUTH_PROVIDER_INVALID_LOGIN))
80
        return HttpResponseRedirect(reverse('login') + "?key=%s" % third_party_key)
81

  
82
    provider = auth_providers.get_provider(provider_module)
83
    if not provider.is_available_for_create():
84
        messages.error(request,
85
                       _(astakos_messages.AUTH_PROVIDER_INVALID_LOGIN))
86
        return HttpResponseRedirect(reverse('login'))
87

  
88
    # eppn not stored in astakos models, create pending profile
89
    user, created = PendingThirdPartyUser.objects.get_or_create(
90
        third_party_identifier=userid,
91
        provider=provider_module,
92
    )
93
    # update pending user
94
    for param, value in pending_user_params.iteritems():
95
        setattr(user, param, value)
96

  
97
    user.info = json.dumps(provider_info)
98
    user.generate_token()
99
    user.save()
100

  
101
    extra_context['provider'] = provider_module
102
    extra_context['provider_title'] = provider.get_title_display
103
    extra_context['token'] = user.token
104
    extra_context['signup_url'] = reverse('signup') + \
105
                                "?third_party_token=%s" % user.token
106
    extra_context['add_url'] = reverse('index') + \
107
                                "?key=%s#other-login-methods" % user.token
108
    extra_context['can_create'] = provider.is_available_for_create()
109
    extra_context['can_add'] = provider.is_available_for_add()
110

  
111

  
112
    return render_response(
113
        template,
114
        context_instance=get_context(request, extra_context)
115
    )
116

  
b/snf-astakos-app/astakos/im/target/google.py
45 45

  
46 46
from urlparse import urlunsplit, urlsplit
47 47

  
48
from astakos.im.util import prepare_response, get_context
48
from astakos.im.util import prepare_response, get_context, login_url
49 49
from astakos.im.views import requires_anonymous, render_response, \
50 50
        requires_auth_provider
51 51
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
......
54 54
from astakos.im.activation_backends import get_backend, SimpleBackend
55 55
from astakos.im import settings
56 56
from astakos.im import auth_providers
57
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
58
    handle_third_party_signup
57 59

  
58 60
import logging
59 61
import time
......
97 99
    if force_login:
98 100
        params['approval_prompt'] = 'force'
99 101

  
102
    if request.GET.get('key', None):
103
        request.session['pending_key'] = request.GET.get('key')
104

  
100 105
    url = "%s?%s" % (authenticate_url, urllib.urlencode(params))
101 106
    return HttpResponseRedirect(url)
102 107

  
......
143 148
    provider_info = access_token_data
144 149
    affiliation = 'Google.com'
145 150

  
151
    third_party_key = get_pending_key(request)
152

  
146 153
    # an existing user accessed the view
147 154
    if request.user.is_authenticated():
148 155
        if request.user.has_auth_provider('google', identifier=userid):
......
172 179
            # authenticate user
173 180
            response = prepare_response(request,
174 181
                                    user,
182
                                    userid,
175 183
                                    request.GET.get('next'),
176 184
                                    'renew' in request.GET)
177 185
            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
186
            add_pending_auth_provider(request, third_party_key)
178 187
            response.set_cookie('astakos_last_login_method', 'google')
179 188
            return response
180 189
        else:
181 190
            message = user.get_inactive_message()
182 191
            messages.error(request, message)
183
            return HttpResponseRedirect(reverse('login'))
192
            return HttpResponseRedirect(login_url(request))
184 193

  
185 194
    except AstakosUser.DoesNotExist, e:
186
        provider = auth_providers.get_provider('google')
187
        if not provider.is_available_for_create():
188
            messages.error(request,
189
                           _(astakos_messages.AUTH_PROVIDER_INVALID_LOGIN))
190
            return HttpResponseRedirect(reverse('login'))
191

  
192
        # eppn not stored in astakos models, create pending profile
193
        user, created = PendingThirdPartyUser.objects.get_or_create(
194
            third_party_identifier=userid,
195
            provider='google',
196
        )
197
        # update pending user
198
        user.affiliation = affiliation
199
        user.info = json.dumps(provider_info)
200
        user.generate_token()
201
        user.save()
202

  
203
        extra_context['provider'] = 'google'
204
        extra_context['provider_title'] = provider.get_title_display
205
        extra_context['token'] = user.token
206
        extra_context['signup_url'] = reverse('signup') + \
207
                                    "?third_party_token=%s" % user.token
208
        extra_context['add_url'] = reverse('index') + \
209
                                    "?key=%s#other-login-methods" % user.token
210
        extra_context['can_create'] = provider.is_available_for_create()
211
        extra_context['can_add'] = provider.is_available_for_add()
212

  
213

  
214
        return render_response(
215
            template,
216
            context_instance=get_context(request, extra_context)
217
        )
218

  
195
        user_info = {'affiliation': affiliation}
196
        return handle_third_party_signup(request, userid, 'google',
197
                                         third_party_key,
198
                                         provider_info,
199
                                         user_info,
200
                                         template,
201
                                         extra_context)
219 202

  
b/snf-astakos-app/astakos/im/target/linkedin.py
45 45

  
46 46
from urlparse import urlunsplit, urlsplit
47 47

  
48
from astakos.im.util import prepare_response, get_context
48
from astakos.im.util import prepare_response, get_context, login_url
49 49
from astakos.im.views import requires_anonymous, render_response, \
50 50
        requires_auth_provider
51 51
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
......
54 54
from astakos.im.activation_backends import get_backend, SimpleBackend
55 55
from astakos.im import settings
56 56
from astakos.im import auth_providers
57
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
58
    handle_third_party_signup
57 59

  
58 60
import astakos.im.messages as astakos_messages
59 61

  
......
85 87

  
86 88
    url = request_token.get('xoauth_request_auth_url') + "?oauth_token=%s" % request_token.get('oauth_token')
87 89

  
90
    if request.GET.get('key', None):
91
        request.session['pending_key'] = request.GET.get('key')
92

  
88 93
    return HttpResponseRedirect(url)
89 94

  
90 95

  
......
116 121
        messages.error(request, 'Invalid linkedin token response')
117 122
        return HttpResponseRedirect(reverse('edit_profile'))
118 123
    access_token = dict(cgi.parse_qsl(content))
119
    print "ACCESS", access_token
120 124

  
121 125
    token = oauth.Token(access_token['oauth_token'],
122 126
        access_token['oauth_token_secret'])
123 127
    client = oauth.Client(consumer, token)
124 128
    resp, content = client.request("http://api.linkedin.com/v1/people/~:(id,first-name,last-name,industry,email-address)?format=json", "GET")
125 129
    if resp['status'] != '200':
126
        print resp, content
127 130
        try:
128 131
            del request.session['request_token']
129 132
        except:
......
136 139
    username = profile_data.get('emailAddress', None)
137 140
    realname = profile_data.get('firstName', '') + ' ' + profile_data.get('lastName', '')
138 141
    provider_info = profile_data
139
    affiliation = 'linkedin.com'
142
    affiliation = 'LinkedIn.com'
143

  
144
    third_party_key = get_pending_key(request)
140 145

  
141 146
    # an existing user accessed the view
142 147
    if request.user.is_authenticated():
......
170 175
                                    request.GET.get('next'),
171 176
                                    'renew' in request.GET)
172 177
            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
178
            add_pending_auth_provider(request, third_party_key)
173 179
            response.set_cookie('astakos_last_login_method', 'linkedin')
174 180
            return response
175 181
        else:
176 182
            message = user.get_inactive_message()
177 183
            messages.error(request, message)
178
            return HttpResponseRedirect(reverse('login'))
184
            return HttpResponseRedirect(login_url(request))
179 185

  
180 186
    except AstakosUser.DoesNotExist, e:
181
        provider = auth_providers.get_provider('linkedin')
182
        if not provider.is_available_for_create():
183
            messages.error(request,
184
                           _(astakos_messages.AUTH_PROVIDER_INVALID_LOGIN))
185
            return HttpResponseRedirect(reverse('login'))
186

  
187
        # eppn not stored in astakos models, create pending profile
188
        user, created = PendingThirdPartyUser.objects.get_or_create(
189
            third_party_identifier=userid,
190
            provider='linkedin',
191
        )
192
        # update pending user
193
        user.realname = realname
194
        user.affiliation = affiliation
195
        user.info = json.dumps(provider_info)
196
        user.generate_token()
197
        user.save()
198

  
199
        extra_context['provider'] = 'linkedin'
200
        extra_context['provider_title'] = provider.get_title_display
201
        extra_context['token'] = user.token
202
        extra_context['signup_url'] = reverse('signup') + \
203
                                    "?third_party_token=%s" % user.token
204
        extra_context['add_url'] = reverse('index') + \
205
                                    "?key=%s#other-login-methods" % user.token
206
        extra_context['can_create'] = provider.is_available_for_create()
207
        extra_context['can_add'] = provider.is_available_for_add()
208

  
209

  
210
        return render_response(
211
            template,
212
            context_instance=get_context(request, extra_context)
213
        )
214

  
215

  
187
        user_info = {'affiliation': affiliation, 'realname': realname}
188
        return handle_third_party_signup(request, userid, 'linkedin',
189
                                         third_party_key,
190
                                         provider_info,
191
                                         user_info,
192
                                         template,
193
                                         extra_context)
216 194

  
b/snf-astakos-app/astakos/im/target/redirect.py
138 138
        response['Location'] = url
139 139
        response.status_code = 302
140 140
        return response
141

  
b/snf-astakos-app/astakos/im/target/shibboleth.py
45 45

  
46 46
from urlparse import urlunsplit, urlsplit
47 47

  
48
from astakos.im.util import prepare_response, get_context
48
from astakos.im.util import prepare_response, get_context, login_url
49 49
from astakos.im.views import (
50 50
    requires_anonymous, render_response, requires_auth_provider)
51 51
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
......
54 54
from astakos.im.activation_backends import get_backend, SimpleBackend
55 55
from astakos.im import auth_providers
56 56
from astakos.im import settings
57
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
58
    handle_third_party_signup
57 59

  
58 60
import astakos.im.messages as astakos_messages
59

  
60 61
import logging
61 62

  
62 63
logger = logging.getLogger(__name__)
63 64

  
65

  
64 66
class Tokens:
65 67
    # these are mapped by the Shibboleth SP software
66 68
    SHIB_EPPN = "HTTP_EPPN"  # eduPersonPrincipalName
......
72 74
    SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
73 75
    SHIB_MAIL = "HTTP_SHIB_MAIL"
74 76

  
77

  
75 78
@requires_auth_provider('shibboleth', login=True)
76 79
@require_http_methods(["GET", "POST"])
77 80
def login(
......
82 85
    extra_context = extra_context or {}
83 86

  
84 87
    tokens = request.META
88
    third_party_key = get_pending_key(request)
85 89

  
86 90
    try:
87 91
        eppn = tokens.get(Tokens.SHIB_EPPN)
......
101 105
                raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_NAME))
102 106
            else:
103 107
                realname = ''
108

  
104 109
    except KeyError, e:
105 110
        # invalid shibboleth headers, redirect to login, display message
106 111
        messages.error(request, e.message)
107
        return HttpResponseRedirect(reverse('login'))
112
        return HttpResponseRedirect(login_url(request))
108 113

  
109 114
    affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
110 115
    email = tokens.get(Tokens.SHIB_MAIL, '')
111 116
    provider_info = {'eppn': eppn, 'email': email, 'name': realname}
117
    userid = eppn
112 118

  
113 119
    # an existing user accessed the view
114 120
    if request.user.is_authenticated():
121

  
115 122
        if request.user.has_auth_provider('shibboleth', identifier=eppn):
116 123
            return HttpResponseRedirect(reverse('edit_profile'))
117 124

  
......
136 143
            identifier=eppn
137 144
        )
138 145
        if user.is_active:
146

  
139 147
            # authenticate user
140 148
            response = prepare_response(request,
141 149
                                    user,
142 150
                                    request.GET.get('next'),
143 151
                                    'renew' in request.GET)
144 152
            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
153
            add_pending_auth_provider(request, third_party_key)
145 154
            response.set_cookie('astakos_last_login_method', 'local')
146 155
            return response
147 156
        else:
148 157
            message = user.get_inactive_message()
149 158
            messages.error(request, message)
150
            return HttpResponseRedirect(reverse('login'))
159
            return HttpResponseRedirect(login_url(request))
151 160

  
152 161
    except AstakosUser.DoesNotExist, e:
153
        provider = auth_providers.get_provider('shibboleth')
154
        if not provider.is_available_for_create():
155
            messages.error(request,
156
                           _(astakos_messages.AUTH_PROVIDER_INVALID_LOGIN))
157
            return HttpResponseRedirect(reverse('login'))
158

  
159
        # eppn not stored in astakos models, create pending profile
160
        user, created = PendingThirdPartyUser.objects.get_or_create(
161
            third_party_identifier=eppn,
162
            provider='shibboleth'
163
        )
164
        # update pending user
165
        user.realname = realname
166
        user.affiliation = affiliation
167
        user.email = email
168
        user.info = json.dumps(provider_info)
169
        user.generate_token()
170
        user.save()
171

  
172
        extra_context['provider'] = 'shibboleth'
173
        extra_context['provider_title'] = provider.get_title_display
174
        extra_context['token'] = user.token
175
        extra_context['signup_url'] = reverse('signup') + \
176
                                    "?third_party_token=%s" % user.token
177
        extra_context['add_url'] = reverse('index') + \
178
                                    "?key=%s#other-login-methods" % user.token
179
        extra_context['can_create'] = provider.is_available_for_create()
180
        extra_context['can_add'] = provider.is_available_for_add()
181

  
182

  
183
        return render_response(
184
            template,
185
            context_instance=get_context(request, extra_context)
186
        )
162
        user_info = {'affiliation': affiliation, 'realname': realname}
163
        return handle_third_party_signup(request, userid, 'shibboleth',
164
                                         third_party_key,
165
                                         provider_info,
166
                                         user_info,
167
                                         template,
168
                                         extra_context)
187 169

  
b/snf-astakos-app/astakos/im/target/twitter.py
45 45

  
46 46
from urlparse import urlunsplit, urlsplit
47 47

  
48
from astakos.im.util import prepare_response, get_context
48
from astakos.im.util import prepare_response, get_context, login_url
49 49
from astakos.im.views import requires_anonymous, render_response, \
50 50
        requires_auth_provider, required_auth_methods_assigned
51 51
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
......
54 54
from astakos.im.activation_backends import get_backend, SimpleBackend
55 55
from astakos.im import settings
56 56
from astakos.im import auth_providers
57
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
58
    handle_third_party_signup
57 59

  
58 60
import astakos.im.messages as astakos_messages
59 61

  
......
89 91
    if force_login:
90 92
        params['force_login'] = 1
91 93

  
94
    if request.GET.get('key', None):
95
        request.session['pending_key'] = request.GET.get('key')
96

  
97

  
92 98
    url = "%s?%s" % (authenticate_url, urllib.urlencode(params))
93 99

  
94 100
    return HttpResponseRedirect(url)
......
128 134
    provider_info = {'screen_name': username}
129 135
    affiliation = 'Twitter.com'
130 136

  
137
    third_party_key = get_pending_key(request)
138

  
131 139
    # an existing user accessed the view
132 140
    if request.user.is_authenticated():
133 141
        if request.user.has_auth_provider('twitter', identifier=userid):
......
160 168
                                    request.GET.get('next'),
161 169
                                    'renew' in request.GET)
162 170
            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
171
            add_pending_auth_provider(request, third_party_key)
163 172
            response.set_cookie('astakos_last_login_method', 'twitter')
164 173
            return response
165 174
        else:
166 175
            message = user.get_inactive_message()
167 176
            messages.error(request, message)
168
            return HttpResponseRedirect(reverse('login'))
177
            return HttpResponseRedirect(login_url(request))
169 178

  
170 179
    except AstakosUser.DoesNotExist, e:
171
        provider = auth_providers.get_provider('twitter')
172
        if not provider.is_available_for_create() and not provider.is_available_for_add():
173
            messages.error(request,
174
                           _(astakos_messages.AUTH_PROVIDER_INVALID_LOGIN))
175
            return HttpResponseRedirect(reverse('login'))
176

  
177
        # eppn not stored in astakos models, create pending profile
178
        user, created = PendingThirdPartyUser.objects.get_or_create(
179
            third_party_identifier=userid,
180
            provider='twitter',
181
        )
182
        # update pending user
183
        user.affiliation = affiliation
184
        user.info = json.dumps(provider_info)
185
        user.generate_token()
186
        user.save()
187

  
188
        extra_context['provider'] = 'twitter'
189
        extra_context['provider_title'] = provider.get_title_display
190
        extra_context['token'] = user.token
191
        extra_context['signup_url'] = reverse('signup') + \
192
                                    "?third_party_token=%s" % user.token
193
        extra_context['add_url'] = reverse('index') + \
194
                                    "?key=%s#other-login-methods" % user.token
195
        extra_context['can_create'] = provider.is_available_for_create()
196
        extra_context['can_add'] = provider.is_available_for_add()
197

  
198

  
199
        return render_response(
200
            template,
201
            context_instance=get_context(request, extra_context)
202
        )
180
        user_info = {'affiliation': affiliation}
181
        return handle_third_party_signup(request, userid, 'twitter',
182
                                         third_party_key,
183
                                         provider_info,
184
                                         user_info,
185
                                         template,
186
                                         extra_context)
203 187

  
b/snf-astakos-app/astakos/im/templates/im/auth/shibboleth_login.html
1
{% load astakos_tags %}
1 2
<p>
2 3
{{ master_auth_provider.get_primary_login_prompt_display }}
3 4
</p>
4 5
<br />
5
<a class="submit" href="/im/login/shibboleth">ACADEMIC LOGIN</a>
6
<a class="submit" href="{% provider_login_url master_auth_provider %}">ACADEMIC LOGIN</a>
/dev/null
1
<br />
2
{{ provider.get_login_prompt }}
3
<a href="/im/login/shibboleth?{% ifnotequal next "" %}&next={{ next|urlencode }}{% endifnotequal %}{% ifnotequal code ""%}{% if next != "" %}&{% else %}?{% endif %}code={{ code }}{% endifnotequal %}"
4
  alt="{{ provider.get_title_display }}">{{ provider.get_title_display }}</a>
b/snf-astakos-app/astakos/im/templates/im/auth/third_party_provider_generic_login.html
1
<h2><a href="{{ provider.add_url }}">{{ provider.get_primary_login_prompt_display|safe }} {{ provider.get_title_display }}</a></h2>
1
{% load astakos_tags %}
2
<h2><a href="{% provider_login_url provider %}">
3
    {{ provider.get_primary_login_prompt_display|safe }} {{ provider.get_title_display }}
4
</a></h2>
b/snf-astakos-app/astakos/im/templates/im/auth/third_party_provider_generic_login_prompt.html
1
<Br />{{ provider.get_login_prompt_display|safe }}
2
<a href="{{ provider.add_url }}?{% ifnotequal next "" %}&next={{ next|urlencode }}{% endifnotequal %}{% ifnotequal code ""%}{% if next != "" %}&{% else %}?{% endif %}code={{ code }}{% endifnotequal %}"
3
  alt="{{ provider.get_title_display }}">{{ provider.get_title_display }}</a>
1
{% load astakos_tags %}
2
<br />{{ provider.get_login_prompt_display|safe }}
3
<a href="{% provider_login_url provider %}" alt="{{ provider.get_title_display }}">{{ provider.get_title_display }}</a>
/dev/null
1
<h2><a href="/im/login/{{ provider.add_url }}">{{ provider.get_login_prompt_display }}</a></h2>
/dev/null
1
<br />{{ provider.get_login_prompt_display }}
2
<a href="/im/login/twitter?{% ifnotequal next "" %}&next={{ next|urlencode }}{% endifnotequal %}{% ifnotequal code ""%}{% if next != "" %}&{% else %}?{% endif %}code={{ code }}{% endifnotequal %}" alt="{{ provider.get_title_display }}">{{ provider.get_title_display }}</a>
b/snf-astakos-app/astakos/im/templatetags/astakos_tags.py
1
# Copyright 2011 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
import urllib
35

  
36
from inspect import getargspec
37

  
1 38
from django import template
2 39
from django.core.urlresolvers import resolve
3 40
from django.conf import settings
41
from django.template import TemplateSyntaxError, Variable
4 42

  
5 43
register = template.Library()
6 44

  
......
16 54
})
17 55

  
18 56

  
57
# helper tag decorator
58
# https://github.com/djblets/djblets/blob/master/djblets/util/decorators.py#L96
59
def basictag(takes_context=False):
60
    """
61
    A decorator similar to Django's @register.simple_tag that optionally
62
    takes a context parameter. This condenses many tag implementations down
63
    to a few lines of code.
64

  
65
    Example:
66
        @register.tag
67
        @basictag(takes_context=True)
68
        def printuser(context):
69
            return context['user']
70
    """
71
    class BasicTagNode(template.Node):
72
        def __init__(self, take_context, tag_name, tag_func, args):
73
            self.takes_context = takes_context
74
            self.tag_name = tag_name
75
            self.tag_func = tag_func
76
            self.args = args
77

  
78
        def render(self, context):
79
            args = [Variable(var).resolve(context) for var in self.args]
80

  
81
            if self.takes_context:
82
                return self.tag_func(context, *args)
83
            else:
84
                return self.tag_func(*args)
85

  
86
    def basictag_func(tag_func):
87
        def _setup_tag(parser, token):
88
            bits = token.split_contents()
89
            tag_name = bits[0]
90
            del(bits[0])
91

  
92
            params, xx, xxx, defaults = getargspec(tag_func)
93
            max_args = len(params)
94

  
95
            if takes_context:
96
                if params[0] == 'context':
97
                    max_args -= 1 # Ignore context
98
                else:
99
                    raise TemplateSyntaxError, \
100
                        "Any tag function decorated with takes_context=True " \
101
                        "must have a first argument of 'context'"
102

  
103
            min_args = max_args - len(defaults or [])
104

  
105
            if not min_args <= len(bits) <= max_args:
106
                if min_args == max_args:
107
                    raise TemplateSyntaxError, \
108
                        "%r tag takes %d arguments." % (tag_name, min_args)
109
                else:
110
                    raise TemplateSyntaxError, \
111
                        "%r tag takes %d to %d arguments, got %d." % \
112
                        (tag_name, min_args, max_args, len(bits))
113

  
114
            return BasicTagNode(takes_context, tag_name, tag_func, bits)
115

  
116
        _setup_tag.__name__ = tag_func.__name__
117
        _setup_tag.__doc__ = tag_func.__doc__
118
        _setup_tag.__dict__.update(tag_func.__dict__)
119
        return _setup_tag
120

  
121
    return basictag_func
122

  
123

  
19 124
@register.tag(name='display_messages')
20 125
def display_messages(parser, token):
21 126
    return MessagesNode()
......
67 172

  
68 173

  
69 174
@register.simple_tag
70
def olga(v):
71
    return v+'a'
72

  
73
@register.simple_tag
74 175
def get_grant_value(rname, form):
75 176
    grants = form.instance.grants
76 177
    service_name, resource_name = rname.split('.',1)
......
78 179
        return form.instance.projectresourcegrant_set.get(resource__name=resource_name,
79 180
                                                           resource__service__name=service_name).member_capacity
80 181
    except:
81
        return ''
182
        return ''
183

  
184
@register.tag(name="provider_login_url")
185
@basictag(takes_context=True)
186
def provider_login_url(context, provider):
187
    request = context['request'].REQUEST
188
    next = request.get('next', None)
189
    code = request.get('code', None)
190
    key = request.get('key', None)
191

  
192
    attrs = {}
193
    if next:
194
        attrs['next'] = next
195
    if code:
196
        attrs['code'] = code
197
    if key:
198
        attrs['key'] = key
199

  
200
    url = provider.add_url
201

  
202
    joinchar = "?"
203
    if "?" in url:
204
        joinchar = "&"
205

  
206
    return "%s%s%s" % (provider.add_url, joinchar, urllib.urlencode(attrs))
207

  

Also available in: Unified diff