Commit 9e82c0ee authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

astakos: reorganize views

* split project views from the rest views
* move third party authentication views (astakos.im.target)
   under astakos.im.views
* move view decorators to a separate module
* move utility methods utilized by views to a separate module
parent a2e36f86
......@@ -579,7 +579,7 @@ class LocalAuthProvider(AuthProvider):
class ShibbolethAuthProvider(AuthProvider):
module = 'shibboleth'
login_view = 'astakos.im.target.shibboleth.login'
login_view = 'astakos.im.views.target.shibboleth.login'
username_key = 'identifier'
messages = {
......@@ -594,7 +594,7 @@ class ShibbolethAuthProvider(AuthProvider):
class TwitterAuthProvider(AuthProvider):
module = 'twitter'
login_view = 'astakos.im.target.twitter.login'
login_view = 'astakos.im.views.target.twitter.login'
username_key = 'provider_info_screen_name'
messages = {
......@@ -605,7 +605,7 @@ class TwitterAuthProvider(AuthProvider):
class GoogleAuthProvider(AuthProvider):
module = 'google'
login_view = 'astakos.im.target.google.login'
login_view = 'astakos.im.views.target.google.login'
username_key = 'provider_info_email'
messages = {
......@@ -616,7 +616,7 @@ class GoogleAuthProvider(AuthProvider):
class LinkedInAuthProvider(AuthProvider):
module = 'linkedin'
login_view = 'astakos.im.target.linkedin.login'
login_view = 'astakos.im.views.target.linkedin.login'
username_key = 'provider_info_email'
messages = {
......
......@@ -680,7 +680,7 @@ class AstakosUser(User):
return url
def get_password_reset_url(self, token_generator=default_token_generator):
return reverse('astakos.im.target.local.password_reset_confirm',
return reverse('astakos.im.views.target.local.password_reset_confirm',
kwargs={'uidb36':int_to_base36(self.id),
'token':token_generator.make_token(self)})
......
<form action="{% url astakos.im.target.local.login %}" method="post"class="login innerlabels">{% csrf_token %}
<form action="{% url astakos.im.views.target.local.login %}" method="post"class="login innerlabels">{% csrf_token %}
<p>
{{ provider.get_primary_login_prompt_display }}
</p>
......@@ -17,6 +17,6 @@
<div class="form-row submit clearfix">
<input type="submit" class="submit altcol" value="SUBMIT" />
<a class="extra-link" href="{% url astakos.im.target.local.password_reset %}">Forgot your password?</a>
<a class="extra-link" href="{% url astakos.im.views.target.local.password_reset %}">Forgot your password?</a>
</div>
</form>
<form action="{% url astakos.im.target.local.login %}" method="post"
<form action="{% url astakos.im.views.target.local.login %}" method="post"
class="login-form login innerlabels">{% csrf_token %}
<p>
{{ provider.get_login_prompt_msg }}
......@@ -20,7 +20,7 @@
<div class="form-row submit clearfix">
<input type="submit" class="submit altcol" value="SUBMIT" />
<a class="extra-link" href="{% url astakos.im.target.local.password_reset %}">Forgot your password?</a>
<a class="extra-link" href="{% url astakos.im.views.target.local.password_reset %}">Forgot your password?</a>
</div>
</div>
</form>
......
{% extends "im/account_base.html" %}
{% block body %}
<form action="{% url astakos.im.target.local.password_change %}" method="post"
<form action="{% url astakos.im.views.target.local.password_change %}" method="post"
class="withlabels">{% csrf_token %}
{% include "im/form_render.html" %}
......
......@@ -14,7 +14,7 @@ Password reset
{% if "local" in im_modules %}
<form action="{% url astakos.im.target.local.password_reset %}" method="post"
<form action="{% url astakos.im.views.target.local.password_reset %}" method="post"
class="login innerlabels">{% csrf_token %}
<h2 class="formheader"><span>RESET PASSWORD</span></h2>
<p>An email will be sent to the address you specify, containing a link that
......
......@@ -47,7 +47,7 @@ from django.utils.importlib import import_module
from django.utils import simplejson as json
from astakos.im.activation_backends import *
from astakos.im.target.shibboleth import Tokens as ShibbolethTokens
from astakos.im.views.target.shibboleth import Tokens as ShibbolethTokens
from astakos.im.models import *
from astakos.im import functions
from astakos.im import settings as astakos_settings
......
......@@ -91,7 +91,7 @@ if settings.EMAILCHANGE_ENABLED:
if 'local' in settings.IM_MODULES:
urlpatterns += patterns(
'astakos.im.target.local',
'astakos.im.views.target.local',
url(r'^local/?$', 'login'),
url(r'^password_change/?$', 'password_change', {
'post_change_redirect':'profile',
......@@ -119,27 +119,27 @@ if settings.INVITATIONS_ENABLED:
if 'shibboleth' in settings.IM_MODULES:
urlpatterns += patterns(
'astakos.im.target',
'astakos.im.views.target',
url(r'^login/shibboleth/?$', 'shibboleth.login'),
)
if 'twitter' in settings.IM_MODULES:
urlpatterns += patterns(
'astakos.im.target',
'astakos.im.views.target',
url(r'^login/twitter/?$', 'twitter.login'),
url(r'^login/twitter/authenticated/?$',
'twitter.authenticated'))
if 'google' in settings.IM_MODULES:
urlpatterns += patterns(
'astakos.im.target',
'astakos.im.views.target',
url(r'^login/google/?$', 'google.login'),
url(r'^login/google/authenticated/?$',
'google.authenticated'))
if 'linkedin' in settings.IM_MODULES:
urlpatterns += patterns(
'astakos.im.target',
'astakos.im.views.target',
url(r'^login/linkedin/?$', 'linkedin.login'),
url(r'^login/linkedin/authenticated/?$',
'linkedin.authenticated'))
......
from astakos.im.views.im import *
from astakos.im.views.projects import *
......@@ -33,8 +33,14 @@
from functools import wraps
from django.http import HttpResponse
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.utils.http import urlencode
from astakos.im import auth_providers as auth
from astakos.im.cookie import CookieHandler
......@@ -61,3 +67,82 @@ def cookie_fix(func):
cookie.fix(response)
return response
return wrapper
def requires_auth_provider(provider_id, **perms):
"""
"""
def decorator(func, *args, **kwargs):
@wraps(func)
def wrapper(request, *args, **kwargs):
provider = auth.get_provider(provider_id)
if not provider or not provider.is_active():
raise PermissionDenied
for pkey, value in perms.iteritems():
attr = 'get_%s_policy' % pkey.lower()
if getattr(provider, attr) != value:
#TODO: add session message
return HttpResponseRedirect(reverse('login'))
return func(request, *args)
return wrapper
return decorator
def requires_anonymous(func):
"""
Decorator checkes whether the request.user is not Anonymous and
in that case redirects to `logout`.
"""
@wraps(func)
def wrapper(request, *args):
if not request.user.is_anonymous():
next = urlencode({'next': request.build_absolute_uri()})
logout_uri = reverse('astakos.im.views.logout') + '?' + next
return HttpResponseRedirect(logout_uri)
return func(request, *args)
return wrapper
def signed_terms_required(func):
"""
Decorator checks whether the request.user is Anonymous and in that case
redirects to `logout`.
"""
@wraps(func)
def wrapper(request, *args, **kwargs):
if request.user.is_authenticated() and not request.user.signed_terms:
params = urlencode({'next': request.build_absolute_uri(),
'show_form': ''})
terms_uri = reverse('latest_terms') + '?' + params
return HttpResponseRedirect(terms_uri)
return func(request, *args, **kwargs)
return wrapper
def required_auth_methods_assigned(allow_access=False):
"""
Decorator that checks whether the request.user has all
required auth providers assigned.
"""
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if request.user.is_authenticated():
missing = request.user.missing_required_providers()
if missing:
for provider in missing:
messages.error(request,
provider.get_required_msg)
if not allow_access:
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)))
This diff is collapsed.
......@@ -44,7 +44,8 @@ from astakos.im.util import get_query, login_url
from astakos.im import messages as astakos_messages
from astakos.im import auth_providers as auth
from astakos.im.util import prepare_response, get_context
from astakos.im.views import requires_anonymous, render_response
from astakos.im.views.util import render_response
from astakos.im.views.decorators import requires_anonymous
import logging
logger = logging.getLogger(__name__)
......
......@@ -46,17 +46,17 @@ from django.shortcuts import get_object_or_404
from urlparse import urlunsplit, urlsplit
from astakos.im.util import prepare_response, get_context, login_url
from astakos.im.views import requires_anonymous, render_response, \
requires_auth_provider
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
from astakos.im.models import AstakosUser, PendingThirdPartyUser
from astakos.im.forms import LoginForm
from astakos.im.activation_backends import get_backend, SimpleBackend
from astakos.im import settings
from astakos.im import auth_providers
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
from astakos.im.views.target import add_pending_auth_provider, get_pending_key, \
handle_third_party_signup, handle_third_party_login, init_third_party_session
from astakos.im.decorators import cookie_fix
from astakos.im.views.util import render_response
from astakos.im.views.decorators import cookie_fix, requires_anonymous, \
requires_auth_provider
import logging
import time
......@@ -82,7 +82,7 @@ request_token_url = 'https://accounts.google.com/o/oauth2/token'
def get_redirect_uri():
return "%s%s" % (settings.BASEURL,
reverse('astakos.im.target.google.authenticated'))
reverse('astakos.im.views.target.google.authenticated'))
@requires_auth_provider('google')
......
......@@ -46,17 +46,17 @@ from django.shortcuts import get_object_or_404
from urlparse import urlunsplit, urlsplit
from astakos.im.util import prepare_response, get_context, login_url
from astakos.im.views import requires_anonymous, render_response, \
requires_auth_provider
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
from astakos.im.models import AstakosUser, PendingThirdPartyUser
from astakos.im.forms import LoginForm
from astakos.im.activation_backends import get_backend, SimpleBackend
from astakos.im import settings
from astakos.im import auth_providers
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
from astakos.im.views.target import add_pending_auth_provider, get_pending_key, \
handle_third_party_signup, handle_third_party_login, init_third_party_session
from astakos.im.decorators import cookie_fix
from astakos.im.views.util import render_response
from astakos.im.views.decorators import cookie_fix, requires_anonymous, \
requires_auth_provider
import astakos.im.messages as astakos_messages
......
......@@ -44,17 +44,17 @@ from django.contrib.auth.decorators import login_required
import django.contrib.auth.views as django_auth_views
from astakos.im.util import prepare_response, get_query
from astakos.im.views import requires_anonymous, signed_terms_required
from astakos.im.views.decorators import requires_anonymous, \
signed_terms_required, requires_auth_provider
from astakos.im.models import PendingThirdPartyUser
from astakos.im.forms import LoginForm, ExtendedPasswordChangeForm, \
ExtendedSetPasswordForm
from astakos.im.settings import (RATELIMIT_RETRIES_ALLOWED,
ENABLE_LOCAL_ACCOUNT_MIGRATION)
import astakos.im.messages as astakos_messages
from astakos.im.views import requires_auth_provider
from astakos.im import settings
from astakos.im import auth_providers as auth
from astakos.im.decorators import cookie_fix
from astakos.im.views.decorators import cookie_fix, signed_terms_required
from ratelimit.decorators import ratelimit
......@@ -153,14 +153,14 @@ def password_reset_confirm_done(request, *args, **kwargs):
@cookie_fix
def password_reset(request, *args, **kwargs):
kwargs['post_reset_redirect'] = reverse(
'astakos.im.target.local.password_reset_done')
'astakos.im.views.target.local.password_reset_done')
return django_auth_views.password_reset(request, *args, **kwargs)
@cookie_fix
def password_reset_confirm(request, *args, **kwargs):
kwargs['post_reset_redirect'] = reverse(
'astakos.im.target.local.password_reset_complete')
'astakos.im.views.target.local.password_reset_complete')
return django_auth_views.password_reset_confirm(request, *args, **kwargs)
......
......@@ -45,7 +45,7 @@ from urlparse import urlunsplit, urlsplit, parse_qsl
from astakos.im.settings import COOKIE_DOMAIN
from astakos.im.util import restrict_next
from astakos.im.functions import login as auth_login, logout
from astakos.im.decorators import cookie_fix
from astakos.im.views.decorators import cookie_fix
import astakos.im.messages as astakos_messages
......
......@@ -47,17 +47,17 @@ from django.shortcuts import get_object_or_404
from urlparse import urlunsplit, urlsplit
from astakos.im.util import prepare_response, get_context, login_url
from astakos.im.views import (
requires_anonymous, render_response, requires_auth_provider)
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
from astakos.im.models import AstakosUser, PendingThirdPartyUser
from astakos.im.forms import LoginForm
from astakos.im.activation_backends import get_backend, SimpleBackend
from astakos.im import auth_providers
from astakos.im import settings
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
from astakos.im.views.target import add_pending_auth_provider, get_pending_key, \
handle_third_party_signup, handle_third_party_login, init_third_party_session
from astakos.im.decorators import cookie_fix
from astakos.im.views.util import render_response
from astakos.im.views.decorators import cookie_fix, requires_anonymous, \
requires_auth_provider
import astakos.im.messages as astakos_messages
import logging
......
......@@ -47,17 +47,17 @@ from django.shortcuts import get_object_or_404
from urlparse import urlunsplit, urlsplit, parse_qsl
from astakos.im.util import prepare_response, get_context, login_url
from astakos.im.views import requires_anonymous, render_response, \
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
from astakos.im.activation_backends import get_backend, SimpleBackend
from astakos.im import settings
from astakos.im import auth_providers
from astakos.im.target import add_pending_auth_provider, get_pending_key, \
from astakos.im.views.target import add_pending_auth_provider, get_pending_key, \
handle_third_party_signup, handle_third_party_login, init_third_party_session
from astakos.im.decorators import cookie_fix
from astakos.im.views.util import render_response
from astakos.im.views.decorators import cookie_fix, requires_anonymous, \
requires_auth_provider, required_auth_methods_assigned
import astakos.im.messages as astakos_messages
......
# Copyright 2011-2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from django.contrib import messages
from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import PermissionDenied
from django.core.xheaders import populate_xheaders
from django.http import HttpResponse
from django.shortcuts import redirect
from django.template import RequestContext, loader as template_loader
from django.utils.translation import ugettext as _
from django.views.generic.create_update import apply_extra_context, \
get_model_and_form_class, lookup_object
from synnefo.lib.ordereddict import OrderedDict
from snf_django.lib.db.transaction import commit_on_success_strict
from astakos.im import presentation
from astakos.im.util import model_to_dict
from astakos.im.models import Resource
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
"""
Calls ``django.template.loader.render_to_string`` with an additional ``tab``
keyword argument and returns an ``django.http.HttpResponse`` with the
specified ``status``.
"""
if tab is None:
tab = template.partition('_')[0].partition('.html')[0]
kwargs.setdefault('tab', tab)
html = template_loader.render_to_string(
template, kwargs, context_instance=context_instance)
response = HttpResponse(html, status=status)
return response
@commit_on_success_strict()
def _create_object(request, model=None, template_name=None,
template_loader=template_loader, extra_context=None, post_save_redirect=None,
login_required=False, context_processors=None, form_class=None,
msg=None):
"""
Based of django.views.generic.create_update.create_object which displays a
summary page before creating the object.
"""
response = None
if extra_context is None: extra_context = {}
if login_required and not request.user.is_authenticated():
return redirect_to_login(request.path)
try:
model, form_class = get_model_and_form_class(model, form_class)
extra_context['edit'] = 0
if request.method == 'POST':
form = form_class(request.POST, request.FILES)
if form.is_valid():
verify = request.GET.get('verify')
edit = request.GET.get('edit')
if verify == '1':
extra_context['show_form'] = False
extra_context['form_data'] = form.cleaned_data
elif edit == '1':
extra_context['show_form'] = True
else:
new_object = form.save()
if not msg:
msg = _("The %(verbose_name)s was created successfully.")
msg = msg % model._meta.__dict__
messages.success(request, msg, fail_silently=True)
response = redirect(post_save_redirect, new_object)
else:
form = form_class()
except (IOError, PermissionDenied), e:
messages.error(request, e)
return None
else:
if response == None:
# Create the template, context, response
if not template_name:
template_name = "%s/%s_form.html" %\
(model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'form': form
}, context_processors)
apply_extra_context(extra_context, c)
response = HttpResponse(t.render(c))
return response
@commit_on_success_strict()
def _update_object(request, model=None, object_id=None, slug=None,
slug_field='slug', template_name=None, template_loader=template_loader,
extra_context=None, post_save_redirect=None, login_required=False,
context_processors=None, template_object_name='object',
form_class=None, msg=None):
"""
Based of django.views.generic.create_update.update_object which displays a
summary page before updating the object.
"""
response = None
if extra_context is None: extra_context = {}
if login_required and not request.user.is_authenticated():
return redirect_to_login(request.path)
try:
model, form_class = get_model_and_form_class(model, form_class)
obj = lookup_object(model, object_id, slug, slug_field)
if request.method == 'POST':
form = form_class(request.POST, request.FILES, instance=obj)
if form.is_valid():
verify = request.GET.get('verify')
edit = request.GET.get('edit')
if verify == '1':
extra_context['show_form'] = False
extra_context['form_data'] = form.cleaned_data
elif edit == '1':
extra_context['show_form'] = True
else:
obj = form.save()
if not msg:
msg = _("The %(verbose_name)s was created successfully.")
msg = msg % model._meta.__dict__
messages.success(request, msg, fail_silently=True)
response = redirect(post_save_redirect, obj)
else:
form = form_class(instance=obj)
except (IOError, PermissionDenied), e:
messages.error(request, e)
return None
else:
if response == None:
if not template_name:
template_name = "%s/%s_form.html" %\
(model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'form': form,
template_object_name: obj,
}, context_processors)
apply_extra_context(extra_context, c)
response = HttpResponse(t.render(c))
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
return response
def _resources_catalog(for_project=False, for_usage=False):
"""
`resource_catalog` contains a list of tuples. Each tuple contains the group
key the resource is assigned to and resources list of dicts that contain
resource information.