Commit b59476ba authored by root's avatar root
Browse files

Merge branch 'devel-0.13' of https://code.grnet.gr/git/astakos into devel-0.13

parents ac53ac67 cb416c36
......@@ -41,8 +41,9 @@ from astakos.im.functions import (
send_activation, send_account_creation_notification, activate
)
from astakos.im.settings import (
INVITATIONS_ENABLED, MODERATION_ENABLED, RE_USER_EMAIL_PATTERNS
INVITATIONS_ENABLED, RE_USER_EMAIL_PATTERNS
)
from astakos.im import settings as astakos_settings
from astakos.im.forms import *
import astakos.im.messages as astakos_messages
......@@ -102,7 +103,7 @@ class ActivationBackend(object):
if provider == request.POST.get('provider', ''):
initial_data = request.POST
return globals()[formclass](initial_data, instance=instance, request=request)
def handle_activation(
self, user, activation_template_name='im/activation_email.txt',
greeting_template_name='im/welcome_email.txt',
......@@ -119,7 +120,7 @@ class ActivationBackend(object):
try:
if user.is_active:
return RegistationCompleted()
if self._is_preaccepted(user):
if user.email_verified:
activate(user, greeting_template_name)
......@@ -196,7 +197,7 @@ class InvitationsBackend(ActivationBackend):
return True
invitation = self.invitation
if not invitation:
return False
return not astakos_settings.MODERATION_ENABLED
if invitation.username == user.email and not invitation.is_consumed:
invitation.consume()
return True
......@@ -212,7 +213,7 @@ class SimpleBackend(ActivationBackend):
def _is_preaccepted(self, user):
if super(SimpleBackend, self)._is_preaccepted(user):
return True
if MODERATION_ENABLED:
if astakos_settings.MODERATION_ENABLED:
return False
return True
......@@ -236,4 +237,4 @@ class NotificationSent(ActivationResult):
class RegistationCompleted(ActivationResult):
def __init__(self):
message = _(astakos_messages.REGISTRATION_COMPLETED)
super(RegistationCompleted, self).__init__(message)
\ No newline at end of file
super(RegistationCompleted, self).__init__(message)
......@@ -144,6 +144,10 @@ class DjangoBackend(BaseBackend):
u.permissions = permissions
u.policies = policies
u.extended_groups = groups
if not u.has_auth_provider('local'):
u.add_auth_provider('local')
return self._list(AstakosUser, filter=(u.id,))
@safe
......@@ -160,7 +164,7 @@ class DjangoBackend(BaseBackend):
except (ObjectDoesNotExist, IntegrityError), e:
append((service, resource, e))
return rejected
@safe
def remove_policies(self, user_id, policies=()):
user = self._lookup_user(user_id)
......@@ -187,7 +191,7 @@ class DjangoBackend(BaseBackend):
except IntegrityError, e:
append((p, e))
return rejected
@safe
def remove_permissions(self, user_id, permissions=()):
user = self._lookup_user(user_id)
......@@ -199,7 +203,7 @@ class DjangoBackend(BaseBackend):
except (ObjectDoesNotExist, IntegrityError), e:
append((p, e))
return rejected
@safe
def invite_users(self, senderid, recipients=()):
user = self._lookup_user(senderid)
......@@ -213,7 +217,7 @@ class DjangoBackend(BaseBackend):
except (IntegrityError, SMTPException), e:
append((email, e))
return rejected
@safe
def list_users(self, filter=()):
return self._list(AstakosUser, filter=filter)
......@@ -255,7 +259,7 @@ class DjangoBackend(BaseBackend):
# TODO return information for unknown ids
q = Service.objects.filter(id__in=ids)
q.delete()
@safe
def update_service(self, service_id, renew_token=False, **kwargs):
s = self._update_object(Service, service_id, save=False, **kwargs)
......@@ -283,14 +287,14 @@ class DjangoBackend(BaseBackend):
except Exception, e:
append((r, e))
return rejected
@safe
def remove_resources(self, service_id, ids=()):
# TODO return information for unknown ids
q = Resource.objects.filter(service__id=service_id,
id__in=ids)
q.delete()
@safe
def create_group(self, **kwargs):
policies = kwargs.pop('policies', ())
......
......@@ -34,15 +34,18 @@
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.utils.datastructures import SortedDict
from astakos.im import settings
from django.conf import settings
from astakos.im import settings as astakos_settings
import logging
logger = logging.getLogger(__name__)
# providers registry
PROVIDERS = {}
PROVIDERS = SortedDict()
_PROVIDERS = {}
class AuthProviderBase(type):
......@@ -52,12 +55,12 @@ class AuthProviderBase(type):
type_id = dct.get('module')
if type_id:
include = True
if type_id in settings.IM_MODULES:
if type_id in astakos_settings.IM_MODULES:
dct['module_enabled'] = True
newcls = super(AuthProviderBase, cls).__new__(cls, name, bases, dct)
if include:
PROVIDERS[type_id] = newcls
_PROVIDERS[type_id] = newcls
return newcls
......@@ -73,8 +76,21 @@ class AuthProvider(object):
def __init__(self, user=None):
self.user = user
def __getattr__(self, key):
if not key.startswith('get_'):
return super(AuthProvider, self).__getattr__(key)
if key.endswith('_display') or key.endswith('template'):
attr = key.replace('_display', '').replace('get_','')
settings_attr = self.get_setting(attr.upper())
if not settings_attr:
return getattr(self, attr)
return settings_attr
else:
return super(AuthProvider, self).__getattr__(key)
def get_setting(self, name, default=None):
attr = 'AUTH_PROVIDER_%s_%s' % (self.module.upper(), name.upper())
attr = 'ASTAKOS_AUTH_PROVIDER_%s_%s' % (self.module.upper(), name.upper())
return getattr(settings, attr, default)
def is_available_for_login(self):
......@@ -93,23 +109,26 @@ class AuthProvider(object):
self.is_active())
def is_active(self):
return self.module in settings.IM_MODULES
return self.module in astakos_settings.IM_MODULES
class LocalAuthProvider(AuthProvider):
module = 'local'
title = _('Local password')
description = _('Create a local password for your account')
create_prompt = _('Create an account')
add_prompt = _('Create a local password for your account')
@property
def add_url(self):
return reverse('password_change')
add_description = _('Create a local password for your account')
login_template = 'auth/local_login_form.html'
add_template = 'auth/local_add_action.html'
one_per_user = True
login_template = 'im/auth/local_login_form.html'
login_prompt_template = 'im/auth/local_login_prompt.html'
signup_prompt_template = 'im/auth/local_signup_prompt.html'
details_tpl = _('You can login to your account using your'
' %(auth_backend)s password.')
......@@ -123,17 +142,14 @@ class ShibbolethAuthProvider(AuthProvider):
title = _('Academic credentials (Shibboleth)')
description = _('Allows you to login to your account using your academic '
'credentials')
add_prompt = _('Add academic credentials to your account.')
@property
def add_url(self):
return reverse('astakos.im.target.shibboleth.login')
add_description = _('Allows you to login to your account using your academic '
'credentials')
login_template = 'auth/shibboleth_login_form.html'
add_template = 'auth/shibboleth_add_action.html'
details_tpl = _('You can login to your account using your'
' shibboleth credentials. Shibboleth id: %(identifier)s')
login_template = 'im/auth/shibboleth_login.html'
login_prompt_template = 'im/auth/shibboleth_login_prompt.html'
def get_provider(id, user_obj=None, default=None):
......@@ -142,3 +158,8 @@ def get_provider(id, user_obj=None, default=None):
"""
return PROVIDERS.get(id, default)(user_obj)
for module in astakos_settings.IM_MODULES:
if module in _PROVIDERS:
PROVIDERS[module] = _PROVIDERS[module]
......@@ -48,8 +48,11 @@ def im_modules(request):
return {'im_modules': IM_MODULES}
def auth_providers(request):
return {'auth_providers': filter(lambda p:p.module_enabled,
AUTH_PROVIDERS.itervalues())}
active_auth_providers = filter(lambda p:p.module_enabled,
AUTH_PROVIDERS.itervalues())
auth_providers = map(lambda p: p(), active_auth_providers)
return {'auth_providers': auth_providers,
'master_auth_provider': auth_providers[0]}
def next(request):
return {'next': get_query(request).get('next', '')}
......
......@@ -60,7 +60,6 @@ from astakos.im.settings import (
EMAIL_CHANGE_EMAIL_SUBJECT
)
import astakos.im.messages as astakos_messages
import astakos.im.models
logger = logging.getLogger(__name__)
......@@ -84,9 +83,11 @@ def logged(func, msg):
def login(request, user):
auth_login(request, user)
astakos.im.models.SessionCatalog(
from astakos.im.models import SessionCatalog
SessionCatalog(
session_key=request.session.session_key,
user=user).save()
user=user
).save()
login = logged(login, '%s logged in.')
logout = logged(auth_logout, '%s logged out.')
......@@ -155,7 +156,9 @@ def send_account_creation_notification(template_name, dictionary=None):
def send_group_creation_notification(template_name, dictionary=None):
group = dictionary.get('group', astakos.im.models.AstakosGroup())
group = dictionary.get('group')
if not group:
return
subject = _(GROUP_CREATION_SUBJECT) % {'group':group.get('name', '')}
return _send_admin_notification(template_name, dictionary, subject=subject)
......
......@@ -730,8 +730,9 @@ class AstakosUserAuthProvider(models.Model):
def delete(self, *args, **kwargs):
ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs)
self.user.set_unusable_password()
self.user.save()
if self.module == 'local':
self.user.set_unusable_password()
self.user.save()
return ret
def __repr__(self):
......
......@@ -199,13 +199,13 @@ RESOURCES_PRESENTATION_DATA = getattr(
'help_text':'group compute help text',
'is_abbreviation':False,
'report_desc':'',
'verbose_name':'compute',
'verbose_name':'compute',
},
'storage': {
'help_text':'group storage help text',
'is_abbreviation':False,
'report_desc':'',
'verbose_name':'storage',
'verbose_name':'storage',
},
},
'resources': {
......@@ -214,14 +214,14 @@ RESOURCES_PRESENTATION_DATA = getattr(
'is_abbreviation':False,
'report_desc':'Pithos+ Diskspace',
'placeholder':'eg. 10GB',
'verbose_name':'diskspace',
'verbose_name':'diskspace',
},
'cyclades.vm': {
'help_text':'resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text',
'is_abbreviation':True,
'report_desc':'Virtual Machines',
'placeholder':'eg. 2',
'verbose_name':'vm',
'verbose_name':'vm',
},
'cyclades.disk': {
'help_text':'resource cyclades.disk help text',
......@@ -251,9 +251,9 @@ RESOURCES_PRESENTATION_DATA = getattr(
'placeholder':'eg. 1',
'verbose_name':'private network'
}
}
})
# Permit local account migration
......
<form action="{% url astakos.im.target.local.login %}" method="post"class="login innerlabels">{% csrf_token %}
<h2>LOGIN</h2>
{% with login_form as form %}
{% include "im/form_render.html" %}
{% endwith %}
<input type="hidden" name="next" value="{{ next }}">
{% if key %}
<input type="hidden" name="key" value="{{key}}">
{% else %}
{% if request.GET.key %}
<input type="hidden" name="key" value="{{request.GET.key}}">
{% endif %}
{% endif %}
<div class="form-row submit clearfix">
<input type="submit" class="submit altcol" value="SUBMIT" />
<a class="extra-link" href="{% url django.contrib.auth.views.password_reset %}">Forgot your password?</a>
</div>
</form>
<form action="{% url astakos.im.target.local.login %}" method="post"class="login innerlabels">{% csrf_token %}
<h2 class="form-toggler"><a href="#">LOGIN USING YOUR LOCAL ACCOUNT</a></h2>
<div class="form" style="display:none">
{% with login_form as form %}
{% include "im/form_render.html" %}
{% endwith %}
<input type="hidden" name="next" value="{{ next }}">
{% if key %}
<input type="hidden" name="key" value="{{key}}">
{% else %}
{% if request.GET.key %}
<input type="hidden" name="key" value="{{request.GET.key}}">
{% endif %}
{% endif %}
<div class="form-row submit clearfix">
<input type="submit" class="submit altcol" value="SUBMIT" />
<a class="extra-link" href="{% url django.contrib.auth.views.password_reset %}">Forgot your password?</a>
</div>
</div>
</form>
<script>
$(document).ready(function(){
$("h2.form-toggler a").click(function(e) {
e.preventDefault();
$("div.form").slideToggle();
})
})
</script>
new to okeanos ?
<a href="{% url astakos.im.views.signup %}{% ifnotequal code "" %}?code={{ code|urlencode }}{% endifnotequal %}">
{{ provider.get_create_prompt_display }}
</a>
<h2><a href="/im/login/shibboleth">LOGIN OR CREATE ACCOUNT USING SHIBBOLETH</a></h2>
LOGIN using
<a href="/im/login/shibboleth?{% 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>
LOGIN using 11
<a href="/im/login/shibboleth?{% ifnotequal next "" %}&next={{ next|urlencode }}{% endifnotequal %}{% ifnotequal code ""%}{% if next != "" %}&{% else %}?{% endif %}code={{ code }}{% endifnotequal %}"
alt="{{ o|title }}">{{ provider }}</a>
......@@ -9,50 +9,22 @@
{% endblock body.left %}
{% block body.right %}
{% if "local" in im_modules %}
<form action="{% url astakos.im.target.local.login %}" method="post"class="login innerlabels">{% csrf_token %}
<h2>LOGIN</h2>
{% with login_form as form %}
{% include "im/form_render.html" %}
{% endwith %}
<input type="hidden" name="next" value="{{ next }}">
{% if key %}
<input type="hidden" name="key" value="{{key}}">
{% else %}
{% if request.GET.key %}
<input type="hidden" name="key" value="{{request.GET.key}}">
{% endif %}
{% endif %}
<div class="form-row submit clearfix">
<input type="submit" class="submit altcol" value="SUBMIT" />
<a class="extra-link" href="{% url django.contrib.auth.views.password_reset %}">Forgot your password?</a>
</div>
</form>
{% endif %}
{% include master_auth_provider.login_template %}
<div class="extralogin">
{% for o in im_modules %}
<div>
{% if o != 'local' %}
LOGIN using
<a href="/im/login/{{ o }}?{% ifnotequal next "" %}&next={{ next|urlencode }}{% endifnotequal %}{% ifnotequal code ""%}{% if next != "" %}&{% else %}?{% endif %}code={{ code }}{% endifnotequal %}"
alt="{{ o|title }}">{{ o }}</a>
{% endif %}
</div>
{% endfor %}
{% for provider in auth_providers %}
{% if not provider == master_auth_provider %}
{% include provider.login_prompt_template %}
{% endif %}
{% endfor %}
</div><br />
{% block body.signup %}
{% for o in im_modules %}
{% if o != 'local' %}
{% endif %}
{% endfor %}
<div class="bottom">
{% block body.login.signup %}
new to okeanos ? <a href="{% url astakos.im.views.signup %}{% ifnotequal code "" %}?code={{ code|urlencode }}{% endifnotequal %}">CREATE ACCOUNT</a>
{% endblock %}
</div>
<div class="bottom">
{% block body.signup %}
{% for provider in auth_providers %}
{% include provider.signup_prompt_template %}
{% endfor %}
</div>
{% endblock body.signup %}
{% endblock body.right%}
......@@ -60,7 +60,7 @@
</form>
<div class="two-cols-links">
<p><a href="{% url password_change %}">Change Password</a></p>
<p></p>
<p>
<a href="https://okeanos.grnet.gr/home/">Back to ~okeanos</a>
<a href="https://cyclades.okeanos.grnet.gr/ui/">Take me to cyclades</a>
......
......@@ -171,7 +171,7 @@ class ShibbolethTests(TestCase):
r = client.get('/im/shibboleth/signup/%s' % token)
form = r.context['form']
post_data = {'email': 'kpap@grnet.gr',
post_data = {'email': 'kpap',
'third_party_identifier': pending_user.third_party_identifier,
'first_name': 'Kostas',
'third_party_token': token,
......@@ -180,6 +180,9 @@ class ShibbolethTests(TestCase):
'provider': 'shibboleth'
}
r = client.post('/im/signup', post_data)
self.assertContains(r, token)
post_data['email'] = 'kpap@grnet.gr'
r = client.post('/im/signup', post_data)
self.assertEqual(r.status_code, 200)
self.assertEqual(AstakosUser.objects.count(), 1)
self.assertEqual(PendingThirdPartyUser.objects.count(), 0)
......@@ -336,10 +339,29 @@ class LocalUserTests(TestCase):
settings.ADMINS = (('admin', 'support@cloud.grnet.gr'),)
settings.SERVER_EMAIL = 'no-reply@grnet.gr'
def test_invitations(self):
return
def test_no_moderation(self):
astakos_settings.MODERATION_ENABLED = False
r = self.client.get("/im/signup")
self.assertEqual(r.status_code, 200)
data = {'email':'kpap@grnet.gr', 'password1':'password',
'password2':'password', 'first_name': 'Kostas',
'last_name': 'Mitroglou', 'provider': 'local'}
r = self.client.post("/im/signup", data)
self.assertEqual(AstakosUser.objects.count(), 1)
user = AstakosUser.objects.get(username="kpap@grnet.gr",
email="kpap@grnet.gr")
self.assertEqual(user.username, 'kpap@grnet.gr')
self.assertEqual(user.has_auth_provider('local'), True)
self.assertFalse(user.is_active)
# user (not admin) gets notified
self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 0)
self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
astakos_settings.MODERATION_ENABLED = True
def test_local_provider(self):
astakos_settings.MODERATION_ENABLED = True
r = self.client.get("/im/signup")
self.assertEqual(r.status_code, 200)
......@@ -353,6 +375,8 @@ class LocalUserTests(TestCase):
self.assertEqual(user.username, 'kpap@grnet.gr')
self.assertEqual(user.has_auth_provider('local'), True)
self.assertFalse(user.is_active)
self.assertFalse(user.email_verified)
self.assertFalse(user.activation_sent)
# admin gets notified
self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 1)
......
......@@ -407,6 +407,8 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
except AstakosUser.DoesNotExist:
instance = None
third_party_token = request.REQUEST.get('third_party_token', None)
try:
if not backend:
backend = get_backend(request)
......@@ -461,6 +463,7 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
transaction.rollback()
return render_response(template_name,
signup_form=form,
third_party_token=third_party_token,
provider=provider,
context_instance=get_context(request, extra_context))
......
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