Revision 63836eda

b/snf-astakos-app/astakos/im/auth_providers.py
47 47

  
48 48
# providers registry
49 49
PROVIDERS = {}
50
REQUIRED_PROVIDERS = {}
50 51

  
51 52
class AuthProviderBase(type):
52 53

  
......
62 63
        newcls = super(AuthProviderBase, cls).__new__(cls, name, bases, dct)
63 64
        if include:
64 65
            PROVIDERS[type_id] = newcls
66
            if newcls().is_required():
67
                REQUIRED_PROVIDERS[type_id] = newcls
65 68
        return newcls
66 69

  
67 70

  
......
122 125
        return self.is_active() and self.get_setting('CAN_ADD',
123 126
                                                   self.is_active())
124 127

  
128
    def is_required(self):
129
        """Provider required (user cannot remove the last one)"""
130
        return self.is_active() and self.get_setting('REQUIRED', False)
131

  
125 132
    def is_active(self):
126 133
        return self.module in astakos_settings.IM_MODULES
127 134

  
b/snf-astakos-app/astakos/im/messages.py
46 46
ACCOUNT_ACTIVATED                       =   'Congratulations. Your account has' + \
47 47
                                            ' been activated and you have been' + \
48 48
                                            ' automatically signed in to your account.'
49
ALREADY_LOGGED_IN                       =   'You are already signed in to your account.'
49 50
PASSWORD_RESET_DONE                     =   'A mail with details on how to change your password was sent.'
50 51
PASSWORD_RESET_CONFIRM_DONE             =   'Password changed. You can now login using your new password.'
51 52

  
......
156 157
AUTH_PROVIDER_ADD_EXISTS                     =   "Account already assigned to another user."
157 158
AUTH_PROVIDER_LOGIN_TO_ADD                   =   "The new login method will be assigned once you login to your account."
158 159
AUTH_PROVIDER_INVALID_LOGIN                  =   "No account exists."
160
AUTH_PROVIDER_REQUIRED                       =   "%(provider)s login method is required. Add one from your profile page."
159 161

  
160 162

  
161 163
messages = locals().keys()
b/snf-astakos-app/astakos/im/models.py
641 641

  
642 642
        return True
643 643

  
644
    def can_remove_auth_provider(self, provider):
645
        if len(self.get_active_auth_providers()) <= 1:
644
    def can_remove_auth_provider(self, module):
645
        provider = auth_providers.get_provider(module)
646
        existing = self.get_active_auth_providers()
647
        existing_for_provider = self.get_active_auth_providers(module=module)
648

  
649
        if len(existing) <= 1:
650
            return False
651

  
652
        if len(existing_for_provider) == 1 and provider.is_required():
646 653
            return False
654

  
647 655
        return True
648 656

  
649 657
    def can_change_password(self):
650 658
        return self.has_auth_provider('local', auth_backend='astakos')
651 659

  
660
    def has_required_auth_providers(self):
661
        required = auth_providers.REQUIRED_PROVIDERS
662
        for provider in required:
663
            if not self.has_auth_provider(provider):
664
                return False
665
        return True
666

  
652 667
    def has_auth_provider(self, provider, **kwargs):
653 668
        return bool(self.auth_providers.filter(module=provider,
654 669
                                               **kwargs).count())
......
723 738

  
724 739
        return providers
725 740

  
726
    def get_active_auth_providers(self):
741
    def get_active_auth_providers(self, **filters):
727 742
        providers = []
728
        for provider in self.auth_providers.active():
743
        for provider in self.auth_providers.active(**filters):
729 744
            if auth_providers.get_provider(provider.module).is_available_for_login():
730 745
                providers.append(provider)
731 746
        return providers
......
764 779

  
765 780
class AstakosUserAuthProviderManager(models.Manager):
766 781

  
767
    def active(self):
768
        return self.filter(active=True)
782
    def active(self, **filters):
783
        return self.filter(active=True, **filters)
769 784

  
770 785

  
771 786
class AstakosUserAuthProvider(models.Model):
b/snf-astakos-app/astakos/im/target/twitter.py
47 47

  
48 48
from astakos.im.util import prepare_response, get_context
49 49
from astakos.im.views import requires_anonymous, render_response, \
50
        requires_auth_provider
50
        requires_auth_provider, required_auth_methods_assigned
51 51
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
52 52
from astakos.im.models import AstakosUser, PendingThirdPartyUser
53 53
from astakos.im.forms import LoginForm
b/snf-astakos-app/astakos/im/views.py
151 151

  
152 152
def signed_terms_required(func):
153 153
    """
154
    Decorator checkes whether the request.user is Anonymous and in that case
154
    Decorator checks whether the request.user is Anonymous and in that case
155 155
    redirects to `logout`.
156 156
    """
157 157
    @wraps(func)
......
165 165
    return wrapper
166 166

  
167 167

  
168
def required_auth_methods_assigned(only_warn=False):
169
    """
170
    Decorator that checks whether the request.user has all required auth providers
171
    assigned.
172
    """
173
    required_providers = auth_providers.REQUIRED_PROVIDERS.keys()
174

  
175
    def decorator(func):
176
        if not required_providers:
177
            return func
178

  
179
        @wraps(func)
180
        def wrapper(request, *args, **kwargs):
181
            if request.user.is_authenticated():
182
                for required in required_providers:
183
                    if not request.user.has_auth_provider(required):
184
                        provider = auth_providers.get_provider(required)
185
                        if only_warn:
186
                            messages.error(request,
187
                                           _(astakos_messages.AUTH_PROVIDER_REQUIRED  % {
188
                                               'provider': provider.get_title_display}))
189
                        else:
190
                            return HttpResponseRedirect(reverse('edit_profile'))
191
            return func(request, *args, **kwargs)
192
        return wrapper
193
    return decorator
194

  
195

  
196
def valid_astakos_user_required(func):
197
    return signed_terms_required(required_auth_methods_assigned()(login_required(func)))
198

  
199

  
168 200
@require_http_methods(["GET", "POST"])
169 201
@signed_terms_required
170 202
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
......
202 234

  
203 235

  
204 236
@require_http_methods(["GET", "POST"])
205
@login_required
206
@signed_terms_required
237
@valid_astakos_user_required
207 238
@transaction.commit_manually
208 239
def invite(request, template_name='im/invitations.html', extra_context=None):
209 240
    """
......
281 312

  
282 313

  
283 314
@require_http_methods(["GET", "POST"])
315
@required_auth_methods_assigned(only_warn=True)
284 316
@login_required
285 317
@signed_terms_required
286 318
def edit_profile(request, template_name='im/profile.html', extra_context=None):
......
478 510

  
479 511

  
480 512
@require_http_methods(["GET", "POST"])
513
@required_auth_methods_assigned(only_warn=True)
481 514
@login_required
482 515
@signed_terms_required
483 516
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
......
653 686

  
654 687

  
655 688
@require_http_methods(["GET", "POST"])
656
@login_required
657
@signed_terms_required
689
@valid_astakos_user_required
658 690
@transaction.commit_manually
659 691
def change_email(request, activation_key=None,
660 692
                 email_template_name='registration/email_change_email.txt',
......
708 740

  
709 741
def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
710 742

  
743
    if request.user.is_authenticated():
744
        messages.error(request, _(astakos_messages.ALREADY_LOGGED_IN))
745
        return HttpResponseRedirect(reverse('edit_profile'))
746

  
711 747
    if settings.MODERATION_ENABLED:
712 748
        raise PermissionDenied
713 749

  
......
814 850

  
815 851

  
816 852
@require_http_methods(["GET", "POST"])
817
@signed_terms_required
818
@login_required
853
@valid_astakos_user_required
819 854
def group_add(request, kind_name='default'):
820 855

  
821 856
    result = callpoint.list_resources()
......
880 915

  
881 916
#@require_http_methods(["POST"])
882 917
@require_http_methods(["GET", "POST"])
883
@signed_terms_required
884
@login_required
918
@valid_astakos_user_required
885 919
def group_add_complete(request):
886 920
    model = AstakosGroup
887 921
    form = AstakosGroupCreationSummaryForm(request.POST)
......
924 958

  
925 959
#@require_http_methods(["GET"])
926 960
@require_http_methods(["GET", "POST"])
927
@signed_terms_required
928
@login_required
961
@valid_astakos_user_required
929 962
def group_list(request):
930 963
    none = request.user.astakos_groups.none()
931 964
    query = """
......
986 1019

  
987 1020

  
988 1021
@require_http_methods(["GET", "POST"])
989
@signed_terms_required
990
@login_required
1022
@valid_astakos_user_required
991 1023
def group_detail(request, group_id):
992 1024
    q = AstakosGroup.objects.select_related().filter(pk=group_id)
993 1025
    q = q.extra(select={
......
1084 1116

  
1085 1117

  
1086 1118
@require_http_methods(["GET", "POST"])
1087
@signed_terms_required
1088
@login_required
1119
@valid_astakos_user_required
1089 1120
def group_search(request, extra_context=None, **kwargs):
1090 1121
    q = request.GET.get('q')
1091 1122
    if request.method == 'GET':
......
1160 1191

  
1161 1192

  
1162 1193
@require_http_methods(["GET", "POST"])
1163
@signed_terms_required
1164
@login_required
1194
@valid_astakos_user_required
1165 1195
def group_all(request, extra_context=None, **kwargs):
1166 1196
    q = AstakosGroup.objects.select_related()
1167 1197
    q = q.filter(~Q(kind__name='default'))
......
1216 1246

  
1217 1247
#@require_http_methods(["POST"])
1218 1248
@require_http_methods(["POST", "GET"])
1219
@signed_terms_required
1220
@login_required
1249
@valid_astakos_user_required
1221 1250
def group_join(request, group_id):
1222 1251
    m = Membership(group_id=group_id,
1223 1252
                   person=request.user,
......
1236 1265

  
1237 1266

  
1238 1267
@require_http_methods(["POST"])
1239
@signed_terms_required
1240
@login_required
1268
@valid_astakos_user_required
1241 1269
def group_leave(request, group_id):
1242 1270
    try:
1243 1271
        m = Membership.objects.select_related().get(
......
1276 1304

  
1277 1305
#@require_http_methods(["POST"])
1278 1306
@require_http_methods(["POST", "GET"])
1279
@signed_terms_required
1280
@login_required
1307
@valid_astakos_user_required
1281 1308
@handle_membership
1282 1309
def approve_member(request, membership):
1283 1310
    try:
......
1295 1322
        messages.error(request, msg)
1296 1323

  
1297 1324

  
1298
@signed_terms_required
1299
@login_required
1325
@valid_astakos_user_required
1300 1326
@handle_membership
1301 1327
def disapprove_member(request, membership):
1302 1328
    try:
......
1312 1338

  
1313 1339
#@require_http_methods(["GET"])
1314 1340
@require_http_methods(["POST", "GET"])
1315
@signed_terms_required
1316
@login_required
1341
@valid_astakos_user_required
1317 1342
def resource_usage(request):
1318 1343
    def with_class(entry):
1319 1344
        entry['load_class'] = 'red'
......
1426 1451

  
1427 1452
#@require_http_methods(["GET"])
1428 1453
@require_http_methods(["POST", "GET"])
1429
@signed_terms_required
1430
@login_required
1454
@valid_astakos_user_required
1431 1455
def timeline(request):
1432 1456
#    data = {'entity':request.user.email}
1433 1457
    timeline_body = ()

Also available in: Unified diff