Commit 2c024afd authored by Kostas Papadimitriou's avatar Kostas Papadimitriou
Browse files

Required auth providers functionality

if one of auth providers is set to be required, user with no such
provider can only view his profile page and is prompted to add a
new login method.
parent 92620596
......@@ -47,6 +47,7 @@ logger = logging.getLogger(__name__)
# providers registry
PROVIDERS = {}
REQUIRED_PROVIDERS = {}
class AuthProviderBase(type):
......@@ -62,6 +63,8 @@ class AuthProviderBase(type):
newcls = super(AuthProviderBase, cls).__new__(cls, name, bases, dct)
if include:
PROVIDERS[type_id] = newcls
if newcls().is_required():
REQUIRED_PROVIDERS[type_id] = newcls
return newcls
......@@ -122,6 +125,10 @@ class AuthProvider(object):
return self.is_active() and self.get_setting('CAN_ADD',
self.is_active())
def is_required(self):
"""Provider required (user cannot remove the last one)"""
return self.is_active() and self.get_setting('REQUIRED', False)
def is_active(self):
return self.module in astakos_settings.IM_MODULES
......
......@@ -46,6 +46,7 @@ ACCOUNT_PENDING_ACTIVATION_HELP = 'If you haven\'t received activation
ACCOUNT_ACTIVATED = 'Congratulations. Your account has' + \
' been activated and you have been' + \
' automatically signed in to your account.'
ALREADY_LOGGED_IN = 'You are already signed in to your account.'
PASSWORD_RESET_DONE = 'A mail with details on how to change your password was sent.'
PASSWORD_RESET_CONFIRM_DONE = 'Password changed. You can now login using your new password.'
......@@ -156,6 +157,7 @@ AUTH_PROVIDER_ADD_FAILED = "Failed to add new login method
AUTH_PROVIDER_ADD_EXISTS = "Account already assigned to another user."
AUTH_PROVIDER_LOGIN_TO_ADD = "The new login method will be assigned once you login to your account."
AUTH_PROVIDER_INVALID_LOGIN = "No account exists."
AUTH_PROVIDER_REQUIRED = "%(provider)s login method is required. Add one from your profile page."
messages = locals().keys()
......
......@@ -641,14 +641,29 @@ class AstakosUser(User):
return True
def can_remove_auth_provider(self, provider):
if len(self.get_active_auth_providers()) <= 1:
def can_remove_auth_provider(self, module):
provider = auth_providers.get_provider(module)
existing = self.get_active_auth_providers()
existing_for_provider = self.get_active_auth_providers(module=module)
if len(existing) <= 1:
return False
if len(existing_for_provider) == 1 and provider.is_required():
return False
return True
def can_change_password(self):
return self.has_auth_provider('local', auth_backend='astakos')
def has_required_auth_providers(self):
required = auth_providers.REQUIRED_PROVIDERS
for provider in required:
if not self.has_auth_provider(provider):
return False
return True
def has_auth_provider(self, provider, **kwargs):
return bool(self.auth_providers.filter(module=provider,
**kwargs).count())
......@@ -723,9 +738,9 @@ class AstakosUser(User):
return providers
def get_active_auth_providers(self):
def get_active_auth_providers(self, **filters):
providers = []
for provider in self.auth_providers.active():
for provider in self.auth_providers.active(**filters):
if auth_providers.get_provider(provider.module).is_available_for_login():
providers.append(provider)
return providers
......@@ -764,8 +779,8 @@ class AstakosUser(User):
class AstakosUserAuthProviderManager(models.Manager):
def active(self):
return self.filter(active=True)
def active(self, **filters):
return self.filter(active=True, **filters)
class AstakosUserAuthProvider(models.Model):
......
......@@ -47,7 +47,7 @@ from urlparse import urlunsplit, urlsplit
from astakos.im.util import prepare_response, get_context
from astakos.im.views import requires_anonymous, render_response, \
requires_auth_provider
requires_auth_provider, required_auth_methods_assigned
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
from astakos.im.models import AstakosUser, PendingThirdPartyUser
from astakos.im.forms import LoginForm
......
......@@ -151,7 +151,7 @@ def requires_anonymous(func):
def signed_terms_required(func):
"""
Decorator checkes whether the request.user is Anonymous and in that case
Decorator checks whether the request.user is Anonymous and in that case
redirects to `logout`.
"""
@wraps(func)
......@@ -165,6 +165,38 @@ def signed_terms_required(func):
return wrapper
def required_auth_methods_assigned(only_warn=False):
"""
Decorator that checks whether the request.user has all required auth providers
assigned.
"""
required_providers = auth_providers.REQUIRED_PROVIDERS.keys()
def decorator(func):
if not required_providers:
return func
@wraps(func)
def wrapper(request, *args, **kwargs):
if request.user.is_authenticated():
for required in required_providers:
if not request.user.has_auth_provider(required):
provider = auth_providers.get_provider(required)
if only_warn:
messages.error(request,
_(astakos_messages.AUTH_PROVIDER_REQUIRED % {
'provider': provider.get_title_display}))
else:
return HttpResponseRedirect(reverse('edit_profile'))
return func(request, *args, **kwargs)
return wrapper
return decorator
def valid_astakos_user_required(func):
return signed_terms_required(required_auth_methods_assigned()(login_required(func)))
@require_http_methods(["GET", "POST"])
@signed_terms_required
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
......@@ -202,8 +234,7 @@ def index(request, login_template_name='im/login.html', profile_template_name='i
@require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
@valid_astakos_user_required
@transaction.commit_manually
def invite(request, template_name='im/invitations.html', extra_context=None):
"""
......@@ -281,6 +312,7 @@ def invite(request, template_name='im/invitations.html', extra_context=None):
@require_http_methods(["GET", "POST"])
@required_auth_methods_assigned(only_warn=True)
@login_required
@signed_terms_required
def edit_profile(request, template_name='im/profile.html', extra_context=None):
......@@ -478,6 +510,7 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
@require_http_methods(["GET", "POST"])
@required_auth_methods_assigned(only_warn=True)
@login_required
@signed_terms_required
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
......@@ -653,8 +686,7 @@ def approval_terms(request, term_id=None, template_name='im/approval_terms.html'
@require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
@valid_astakos_user_required
@transaction.commit_manually
def change_email(request, activation_key=None,
email_template_name='registration/email_change_email.txt',
......@@ -708,6 +740,10 @@ def change_email(request, activation_key=None,
def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
if request.user.is_authenticated():
messages.error(request, _(astakos_messages.ALREADY_LOGGED_IN))
return HttpResponseRedirect(reverse('edit_profile'))
if settings.MODERATION_ENABLED:
raise PermissionDenied
......@@ -814,8 +850,7 @@ class ResourcePresentation():
@require_http_methods(["GET", "POST"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_add(request, kind_name='default'):
result = callpoint.list_resources()
......@@ -880,8 +915,7 @@ def group_add(request, kind_name='default'):
#@require_http_methods(["POST"])
@require_http_methods(["GET", "POST"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_add_complete(request):
model = AstakosGroup
form = AstakosGroupCreationSummaryForm(request.POST)
......@@ -924,8 +958,7 @@ def group_add_complete(request):
#@require_http_methods(["GET"])
@require_http_methods(["GET", "POST"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_list(request):
none = request.user.astakos_groups.none()
query = """
......@@ -986,8 +1019,7 @@ def group_list(request):
@require_http_methods(["GET", "POST"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_detail(request, group_id):
q = AstakosGroup.objects.select_related().filter(pk=group_id)
q = q.extra(select={
......@@ -1084,8 +1116,7 @@ def group_detail(request, group_id):
@require_http_methods(["GET", "POST"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_search(request, extra_context=None, **kwargs):
q = request.GET.get('q')
if request.method == 'GET':
......@@ -1160,8 +1191,7 @@ def group_search(request, extra_context=None, **kwargs):
@require_http_methods(["GET", "POST"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_all(request, extra_context=None, **kwargs):
q = AstakosGroup.objects.select_related()
q = q.filter(~Q(kind__name='default'))
......@@ -1216,8 +1246,7 @@ def group_all(request, extra_context=None, **kwargs):
#@require_http_methods(["POST"])
@require_http_methods(["POST", "GET"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_join(request, group_id):
m = Membership(group_id=group_id,
person=request.user,
......@@ -1236,8 +1265,7 @@ def group_join(request, group_id):
@require_http_methods(["POST"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def group_leave(request, group_id):
try:
m = Membership.objects.select_related().get(
......@@ -1276,8 +1304,7 @@ def handle_membership(func):
#@require_http_methods(["POST"])
@require_http_methods(["POST", "GET"])
@signed_terms_required
@login_required
@valid_astakos_user_required
@handle_membership
def approve_member(request, membership):
try:
......@@ -1295,8 +1322,7 @@ def approve_member(request, membership):
messages.error(request, msg)
@signed_terms_required
@login_required
@valid_astakos_user_required
@handle_membership
def disapprove_member(request, membership):
try:
......@@ -1312,8 +1338,7 @@ def disapprove_member(request, membership):
#@require_http_methods(["GET"])
@require_http_methods(["POST", "GET"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def resource_usage(request):
def with_class(entry):
entry['load_class'] = 'red'
......@@ -1426,8 +1451,7 @@ def group_create_list(request):
#@require_http_methods(["GET"])
@require_http_methods(["POST", "GET"])
@signed_terms_required
@login_required
@valid_astakos_user_required
def timeline(request):
# data = {'entity':request.user.email}
timeline_body = ()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment