Commit ae019418 authored by Kostas Papadimitriou's avatar Kostas Papadimitriou
Browse files

Merge branch 'feature-astakos-authpolicies' into develop

Conflicts:
	snf-cyclades-app/synnefo/api/management/commands/flavor-create.py
	version
parents 7b207c76 17fcbf28
This diff is collapsed.
......@@ -140,7 +140,7 @@ class ActivationBackend(object):
else:
send_account_creation_notification(
template_name=admin_email_template_name,
dictionary={'user': user.__dict__, 'group_creation': True}
dictionary={'user': user, 'group_creation': True}
)
return NotificationSent()
except BaseException, e:
......@@ -222,9 +222,8 @@ class SimpleBackend(ActivationBackend):
def _is_preaccepted(self, user):
if super(SimpleBackend, self)._is_preaccepted(user):
return True
if astakos_settings.MODERATION_ENABLED:
return False
return True
return user.get_auth_provider().get_automoderate_policy
class ActivationResult(object):
......
......@@ -271,19 +271,19 @@ class ThirdPartyUserCreationForm(forms.ModelForm, StoreUserMixin):
terms_link_html = '<a href="%s" target="_blank">%s</a>' \
% (reverse('latest_terms'), _("the terms"))
self.fields['has_signed_terms'].label = \
mark_safe("I agree with %s" % terms_link_html)
mark_safe("I agree with %s" % terms_link_html)
def clean_email(self):
email = self.cleaned_data['email']
if not email:
raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
if reserved_verified_email(email):
provider = auth_providers.get_provider(self.request.REQUEST.get('provider', 'local'))
extra_message = _(astakos_messages.EXISTING_EMAIL_THIRD_PARTY_NOTIFICATION) % \
(provider.get_title_display, reverse('edit_profile'))
provider_id = self.request.REQUEST.get('provider', 'local')
provider = auth_providers.get_provider(provider_id)
extra_message = provider.get_add_to_existing_account_msg
raise forms.ValidationError(_(astakos_messages.EMAIL_USED) + ' ' + \
extra_message)
raise forms.ValidationError(mark_safe(_(astakos_messages.EMAIL_USED) + ' ' +
extra_message))
return email
def clean_has_signed_terms(self):
......@@ -294,10 +294,12 @@ class ThirdPartyUserCreationForm(forms.ModelForm, StoreUserMixin):
def post_store_user(self, user, request):
pending = PendingThirdPartyUser.objects.get(
token=request.POST.get('third_party_token'),
third_party_identifier= \
self.cleaned_data.get('third_party_identifier'))
return user.add_pending_auth_provider(pending)
token=request.POST.get('third_party_token'),
third_party_identifier=
self.cleaned_data.get('third_party_identifier'))
provider = pending.get_provider(user)
provider.add_to_user()
pending.delete()
def save(self, commit=True):
user = super(ThirdPartyUserCreationForm, self).save(commit=False)
......@@ -406,9 +408,9 @@ class LoginForm(AuthenticationForm):
try:
user = AstakosUser.objects.get_by_identifier(username)
if not user.has_auth_provider('local'):
provider = auth_providers.get_provider('local')
provider = auth_providers.get_provider('local', user)
raise forms.ValidationError(
_(provider.get_message('NOT_ACTIVE_FOR_USER')))
provider.get_login_disabled_msg)
except AstakosUser.DoesNotExist:
pass
......@@ -418,7 +420,8 @@ class LoginForm(AuthenticationForm):
if self.user_cache is None:
raise
if not self.user_cache.is_active:
raise forms.ValidationError(self.user_cache.get_inactive_message())
msg = self.user_cache.get_inactive_message('local')
raise forms.ValidationError(msg)
if self.request:
if not self.request.session.test_cookie_worked():
raise
......@@ -505,25 +508,25 @@ class ExtendedPasswordResetForm(PasswordResetForm):
clean_email: to handle local auth provider checks
"""
def clean_email(self):
email = super(ExtendedPasswordResetForm, self).clean_email()
# we override the default django auth clean_email to provide more
# detailed messages in case of inactive users
email = self.cleaned_data['email']
try:
user = AstakosUser.objects.get_by_identifier(email)
self.users_cache = [user]
if not user.is_active:
raise forms.ValidationError(_(astakos_messages.ACCOUNT_INACTIVE))
raise forms.ValidationError(user.get_inactive_message('local'))
provider = auth_providers.get_provider('local', user)
if not user.has_usable_password():
provider = auth_providers.get_provider('local')
available_providers = user.auth_providers.all()
available_providers = ",".join(p.settings.get_title_display for p in \
available_providers)
message = astakos_messages.UNUSABLE_PASSWORD % \
(provider.get_method_prompt_display, available_providers)
raise forms.ValidationError(message)
msg = provider.get_unusable_password_msg
raise forms.ValidationError(msg)
if not user.can_change_password():
raise forms.ValidationError(_(astakos_messages.AUTH_PROVIDER_CANNOT_CHANGE_PASSWORD))
except AstakosUser.DoesNotExist, e:
msg = provider.get_cannot_change_password_msg
raise forms.ValidationError(msg)
except AstakosUser.DoesNotExist:
raise forms.ValidationError(_(astakos_messages.EMAIL_UNKNOWN))
return email
......@@ -661,9 +664,10 @@ class ExtendedSetPasswordForm(SetPasswordForm):
self.user = AstakosUser.objects.get(id=self.user.id)
if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
self.user.renew_token()
#self.user.flush_sessions()
if not self.user.has_auth_provider('local'):
self.user.add_auth_provider('local', auth_backend='astakos')
provider = auth_providers.get_provider('local', self.user)
if provider.get_add_policy:
provider.add_to_user()
except BaseException, e:
logger.exception(e)
......
......@@ -155,14 +155,14 @@ def _send_admin_notification(template_name,
logger.exception(e)
raise SendNotificationError()
else:
msg = 'Sent admin notification for user %s' % \
(dictionary.get('user', {}).get('email', None), )
user = dictionary.get('user')
msg = 'Sent admin notification for user %s' % user.log_display
logger.log(LOGGING_LEVEL, msg)
def send_account_creation_notification(template_name, dictionary=None):
user = dictionary.get('user', AnonymousUser())
subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
user = dictionary.get('user')
subject = _(ACCOUNT_CREATION_SUBJECT) % {'user': user.email}
return _send_admin_notification(template_name, dictionary, subject=subject)
......@@ -241,7 +241,7 @@ def send_greeting(user, email_template_name='im/welcome_email.txt'):
logger.exception(e)
raise SendGreetingError()
else:
msg = 'Sent greeting %s' % user.email
msg = 'Sent greeting %s' % user.log_display
logger.log(LOGGING_LEVEL, msg)
......@@ -260,7 +260,7 @@ def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
logger.exception(e)
raise SendFeedbackError()
else:
msg = 'Sent feedback from %s' % user.email
msg = 'Sent feedback from %s' % user.log_display
logger.log(LOGGING_LEVEL, msg)
......@@ -280,7 +280,7 @@ def send_change_email(
logger.exception(e)
raise ChangeEmailError()
else:
msg = 'Sent change email for %s' % ec.user.email
msg = 'Sent change email for %s' % ec.user.log_display
logger.log(LOGGING_LEVEL, msg)
......@@ -738,7 +738,7 @@ def dismiss_application(application_id, request_user=None):
application.dismiss()
def deny_application(application_id):
def deny_application(application_id, reason=None):
application = get_application_for_update(application_id)
if not application.can_deny():
......@@ -746,7 +746,9 @@ def deny_application(application_id):
application.id, application.state_display()))
raise PermissionDenied(m)
application.deny()
if reason is None:
reason = ""
application.deny(reason)
application_deny_notify(application)
def approve_application(app_id):
......
......@@ -70,21 +70,25 @@ class Command(BaseCommand):
def handle(self, *args, **options):
sync = options['sync']
verify = options['verify'] or sync
verify = options['verify']
user_ident = options['user']
list_only = not sync and not verify
if user_ident is not None:
log = self.run_sync_user(user_ident, sync)
else:
log = self.run(sync)
ex, nonex, qh_l, qh_c, astakos_i, astakos_q, info = log
if verify:
self.print_verify(nonex, qh_l, astakos_q)
ex, nonex, qh_l, qh_c, astakos_i, diff_q, info = log
else:
if list_only:
self.list_quotas(qh_l, qh_c, astakos_i, info)
else:
if verify:
self.print_verify(nonex, qh_l, diff_q)
if sync:
self.print_sync(diff_q)
@transaction.commit_on_success
def run_sync_user(self, user_ident, sync):
......@@ -150,10 +154,20 @@ class Command(BaseCommand):
line = ' '.join(output)
self.stdout.write(line + '\n')
def print_sync(self, diff_quotas):
size = len(diff_quotas)
if size == 0:
self.stdout.write("No sync needed.\n")
else:
self.stdout.write("Synced %s users:\n" % size)
for holder in diff_quotas.keys():
user = get_user_by_uuid(holder)
self.stdout.write("%s (%s)\n" % (holder, user.username))
def print_verify(self,
nonexisting,
qh_limits,
astakos_quotas):
diff_quotas):
if nonexisting:
self.stdout.write("Users not registered in quotaholder:\n")
......@@ -161,21 +175,21 @@ class Command(BaseCommand):
self.stdout.write("%s\n" % (user))
self.stdout.write("\n")
diffs = 0
for holder, local in astakos_quotas.iteritems():
for holder, local in diff_quotas.iteritems():
registered = qh_limits.pop(holder, None)
user = get_user_by_uuid(holder)
if registered is None:
diffs += 1
self.stdout.write("No quotas for %s in quotaholder.\n\n" %
(get_user_by_uuid(holder)))
elif local != registered:
diffs += 1
self.stdout.write("Quotas differ for %s:\n" %
(get_user_by_uuid(holder)))
self.stdout.write(
"No quotas for %s (%s) in quotaholder.\n" %
(holder, user.username))
else:
self.stdout.write("Quotas differ for %s (%s):\n" %
(holder, user.username))
self.stdout.write("Quotas according to quotaholder:\n")
self.stdout.write("%s\n" % (registered))
self.stdout.write("Quotas according to astakos:\n")
self.stdout.write("%s\n\n" % (local))
diffs = len(diff_quotas)
if diffs:
self.stdout.write("Quotas differ for %d users.\n" % (diffs))
......@@ -73,10 +73,17 @@ class Command(BaseCommand):
dest='terminate_expired',
default=False,
help="Terminate all expired projects"),
make_option('--message', '-m',
dest='message',
metavar='<msg>',
help=("Specify reason of action, "
"e.g. when denying a project")),
)
def handle(self, *args, **options):
message = options['message']
pid = options['terminate']
if pid is not None:
self.run_command(terminate, pid)
......@@ -99,7 +106,7 @@ class Command(BaseCommand):
appid = options['deny']
if appid is not None:
self.run_command(deny_application, appid)
self.run_command(deny_application, appid, message)
return
if options['check_expired']:
......@@ -109,14 +116,14 @@ class Command(BaseCommand):
if options['terminate_expired']:
self.expire(execute=True)
@cmd_project_transaction_context(sync=True)
def run_command(self, func, id, ctx=None):
try:
func(id)
except BaseException as e:
if ctx:
ctx.mark_rollback()
raise CommandError(e)
def run_command(self, func, *args):
with cmd_project_transaction_context(sync=True) as ctx:
try:
func(*args)
except BaseException as e:
if ctx:
ctx.mark_rollback()
raise CommandError(e)
def print_expired(self, projects, execute):
length = len(projects)
......
# Copyright 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.
import string
from optparse import make_option
from django.core.management.base import BaseCommand, CommandError
from astakos.im.models import AuthProviderPolicyProfile as Profile
option_list = list(BaseCommand.option_list) + [
make_option('--update',
action='store_true',
dest='update',
default=False,
help="Update an existing profile."),
make_option('--exclusive',
action='store_true',
dest='exclusive',
default=False,
help="Apply policies to all authentication providers "
"except the one provided."),
]
POLICIES = ['add', 'remove', 'create', 'login', 'limit', 'required',
'automoderate']
for p in POLICIES:
option_list.append(make_option('--unset-%s-policy' % p,
action='store_true',
dest='unset_policy_%s' % p,
help="Unset %s policy (only when --update)"
% p.title()))
option_list.append(make_option('--%s-policy' % p,
action='store',
dest='policy_%s' % p,
help="%s policy" % p.title()))
class Command(BaseCommand):
args = "<name> <provider_name>"
help = "Create a new authentication provider policy profile"
option_list = option_list
def handle(self, *args, **options):
if len(args) < 2:
raise CommandError("Invalid number of arguments")
profile = None
update = options.get('update')
name = args[0].strip()
provider = args[1].strip()
if Profile.objects.filter(name=name).count():
if update:
profile = Profile.objects.get(name=name)
else:
raise CommandError("Profile with the same name already exists")
if not profile:
profile = Profile()
profile.name = name
profile.provider = provider
profile.is_exclusive = options.get('exclusive')
for policy, value in options.iteritems():
if policy.startswith('policy_') and value is not None:
if isinstance(value, basestring) and value[0] in string.digits:
value = int(value)
if value == 'False' or value == '0':
value = False
if value == 'True' or value == '1':
value = True
setattr(profile, policy, value)
if update and policy.startswith('unset_'):
policy = policy.replace('unset_', '')
setattr(profile, policy, None)
profile.save()
if update:
print "Profile updated"
else:
print "Profile stored"
# Copyright 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 optparse import make_option
from django.core.management.base import NoArgsCommand
from astakos.im.models import AuthProviderPolicyProfile
from ._common import format
class Command(NoArgsCommand):
help = "List existing authentication provider policy profiles"
option_list = NoArgsCommand.option_list + (
make_option('-c',
action='store_true',
dest='csv',
default=False,
help="Use pipes to separate values"),
)
def handle_noargs(self, **options):
profiles = AuthProviderPolicyProfile.objects.all()
labels = ['id', 'name', 'provider', 'exclusive', 'groups', 'users']
columns = [3, 20, 13, 7, 50, 50]
if not options['csv']:
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
self.stdout.write(line + '\n')
sep = '-' * len(line)
self.stdout.write(sep + '\n')
for profile in profiles:
id = str(profile.pk)
name = profile.name
provider = profile.provider
exclusive = str(profile.is_exclusive)
groups = ",".join([g.name for g in profile.groups.all()])
users = ",".join([u.email for u in profile.users.all()])
field_values = [id, name, provider, exclusive, groups, users]
fields = (format(elem) for elem in field_values)
if options['csv']:
line = '|'.join(fields)
else:
line = ' '.join(f.rjust(w) for f, w in zip(fields, columns))
self.stdout.write(line + '\n')
# Copyright 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.core.management.base import BaseCommand, CommandError