ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT '%s alpha2 testing account activated (%%(user)s)' % SITENAME Account activation helpdesk notification email subject
ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT 'Email change on %s alpha2 testing' % SITENAME Email change subject
ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT 'Password reset on %s alpha2 testing' % SITENAME Password change email subject
+
+ASTAKOS_QUOTA_HOLDER_URL '' The quota holder URI
+ e.g. ``http://localhost:8080/api/quotaholder/v``
+ASTAKOS_SERVICES {'cyclades': {'resources': [{'desc': 'Number of virtual machines', Default cloud service information
+ 'group': 'compute',
+ 'name': 'vm',
+ 'uplimit': 2},
+ {'desc': 'Virtual machine disk size',
+ 'group': 'compute',
+ 'name': 'diskspace',
+ 'unit': 'GB',
+ 'uplimit': 5},
+ {'desc': 'Number of virtual machine processors',
+ 'group': 'compute',
+ 'name': 'cpu',
+ 'uplimit': 1},
+ {'desc': 'Virtual machines',
+ 'group': 'compute',
+ 'name': 'ram',
+ 'unit': 'MB',
+ 'uplimit': 1024}],
+ 'url': 'https://node1.example.com/ui/'},
+ 'pithos+': {'resources': [{'desc': 'Pithos account diskspace',
+ 'group': 'storage',
+ 'name': 'diskspace',
+ 'unit': 'bytes',
+ 'uplimit': 5368709120}],
+ 'url': 'https://node2.example.com/ui/'}}
+ASTAKOS_AQUARIUM_URL '' The billing (aquarium) URI
+ e.g. ``http://localhost:8888/user``
+ASTAKOS_PAGINATE_BY 10 Number of object to be displayed per page
+
ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN True Enforce token renewal on password change/reset. If set to False, user can optionally decide
whether to renew the token or not.
+ ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION True Permit local account migration to third party account
=========================================== ============================================================================= ===========================================================================================
Administrator functions
from django.utils.importlib import import_module
from django.core.exceptions import ImproperlyConfigured
-from django.core.mail import send_mail
-from django.template.loader import render_to_string
-from django.contrib import messages
-from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
-from django.db import transaction
-from urlparse import urljoin
-
-from astakos.im.models import AstakosUser, Invitation
-from astakos.im.forms import *
+from astakos.im.models import AstakosUser
- from astakos.im.forms import LocalUserCreationForm, ShibbolethUserCreationForm
from astakos.im.util import get_invitation
- from astakos.im.functions import (send_verification, send_activation,
- send_account_creation_notification,
- send_group_creation_notification, activate)
- from astakos.im.settings import (INVITATIONS_ENABLED, MODERATION_ENABLED,
- SITENAME, RE_USER_EMAIL_PATTERNS
-from astakos.im.functions import send_verification, send_activation, \
- send_admin_notification, activate, SendMailError
-from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, \
- DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
++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
+)
+
+import astakos.im.messages as astakos_messages
-import socket
import logging
import re
def __init__(self, message):
self.message = message
+
class VerificationSent(ActivationResult):
def __init__(self):
- message = _('Verification sent.')
+ message = _(astakos_messages.VERIFICATION_SENT)
super(VerificationSent, self).__init__(message)
-
- class SwitchAccountsVerificationSent(ActivationResult):
- def __init__(self, email):
- message = _(astakos_messages.SWITCH_ACCOUNT_LINK_SENT)
- super(SwitchAccountsVerificationSent, self).__init__(message)
-
-
class NotificationSent(ActivationResult):
def __init__(self):
- message = _(astakos_messages.NOTIFACATION_SENT)
- message = _('Your request for an account was successfully received and is now pending \
- approval. You will be notified by email in the next few days. Thanks for \
- your interest in ~okeanos! The GRNET team.')
++ message = _(astakos_messages.NOTIFICATION_SENT)
super(NotificationSent, self).__init__(message)
+
class RegistationCompleted(ActivationResult):
def __init__(self):
- message = _('Registration completed. You can now login.')
+ message = _(astakos_messages.REGISTRATION_COMPLETED)
- super(RegistationCompleted, self).__init__(message)
+ super(RegistationCompleted, self).__init__(message)
response.content = json.dumps(user_info)
response['Content-Type'] = 'application/json; charset=UTF-8'
response['Content-Length'] = len(response.content)
- return response
+ return response
+
+
+@api_method(http_method='GET')
+def get_services(request):
+ callback = request.GET.get('callback', None)
+ services = Service.objects.all()
+ data = tuple({'id': s.pk, 'name': s.name, 'url': s.url, 'icon':
+ s.icon} for s in services)
+ data = json.dumps(data)
+ mimetype = 'application/json'
+
+ if callback:
+ mimetype = 'application/javascript'
+ data = '%s(%s)' % (callback, data)
+
+ return HttpResponse(content=data, mimetype=mimetype)
+
+
+@api_method()
+def get_menu(request, with_extra_links=False, with_signout=True):
+ user = request.user
- if not isinstance(user, AstakosUser):
- cookie = unquote(request.COOKIES.get(COOKIE_NAME, ''))
- email = cookie.partition('|')[0]
- try:
- if email:
- user = AstakosUser.objects.get(email=email, is_active=True)
- except AstakosUser.DoesNotExist:
- pass
- if not isinstance(user, AstakosUser):
- index_url = reverse('index')
- l = [{'url': absolute(request, index_url), 'name': "Sign in"}]
- else:
++ index_url = reverse('index')
++ l = [{'url': absolute(request, index_url), 'name': "Sign in"}]
++ if user.is_authenticated():
+ l = []
+ append = l.append
+ item = MenuItem
+ item.current_path = absolute(request, request.path)
+ append(item(
+ url=absolute(request, reverse('index')),
+ name=user.email))
+ append(item(url=absolute(request, reverse('edit_profile')),
+ name="My account"))
+ if with_extra_links:
- """
+ if user.has_usable_password() and user.provider in ('local', ''):
+ append(item(
+ url=absolute(request, reverse('password_change')),
+ name="Change password"))
- """
+ if EMAILCHANGE_ENABLED:
+ append(item(
+ url=absolute(request, reverse('email_change')),
+ name="Change email"))
- """
+ if INVITATIONS_ENABLED:
+ append(item(
+ url=absolute(request, reverse('invite')),
+ name="Invitations"))
- """
+
+ append(item(
+ url=absolute(request, reverse('group_list')),
+ name="Groups",
+ submenu=(item(
+ url=absolute(request,
+ reverse('group_list')),
+ name="Overview"),
+ item(
+ url=absolute(request,
+ reverse('group_create_list')),
+ name="Create"),
+ item(
+ url=absolute(request,
+ reverse('group_search')),
+ name="Join"),)))
+ append(item(
+ url=absolute(request, reverse('resource_list')),
+ name="Report"))
+ append(item(
+ url=absolute(request, reverse('feedback')),
+ name="Feedback"))
- """
+ append(item(
+ url=absolute(request, reverse('billing')),
+ name="Billing"))
+ append(item(
+ url=absolute(request, reverse('timeline')),
+ name="Timeline"))
- """
+ if with_signout:
+ append(item(
+ url=absolute(request, reverse('logout')),
+ name="Sign out"))
+
+ callback = request.GET.get('callback', None)
+ data = json.dumps(tuple(l))
+ mimetype = 'application/json'
+
+ if callback:
+ mimetype = 'application/javascript'
+ data = '%s(%s)' % (callback, data)
+
+ return HttpResponse(content=data, mimetype=mimetype)
+
+
+class MenuItem(dict):
+ current_path = ''
+
+ def __init__(self, *args, **kwargs):
+ super(MenuItem, self).__init__(*args, **kwargs)
+ if kwargs.get('url') or kwargs.get('submenu'):
+ self.__set_is_active__()
+
+ def __setitem__(self, key, value):
+ super(MenuItem, self).__setitem__(key, value)
+ if key in ('url', 'submenu'):
+ self.__set_is_active__()
+
+ def __set_is_active__(self):
+ if self.get('is_active'):
+ return
+ if self.current_path == self.get('url'):
+ self.__setitem__('is_active', True)
+ else:
+ submenu = self.get('submenu', ())
+ current = (i for i in submenu if i.get('url') == self.current_path)
+ try:
+ current_node = current.next()
+ if not current_node.get('is_active'):
+ current_node.__setitem__('is_active', True)
+ self.__setitem__('is_active', True)
+ except StopIteration:
+ return
+
+ def __setattribute__(self, name, value):
+ super(MenuItem, self).__setattribute__(name, value)
+ if name == 'current_path':
+ self.__set_is_active__()
# or implied, of GRNET S.A.
import logging
-import urllib
from functools import wraps
-from traceback import format_exc
from time import time, mktime
-from urllib import quote
-from urlparse import urlparse
-from collections import defaultdict
-from django.conf import settings
from django.http import HttpResponse
from django.utils import simplejson as json
-from django.core.urlresolvers import reverse
-from astakos.im.api.faults import *
-from astakos.im.models import AstakosUser, Service
-from astakos.im.settings import INVITATIONS_ENABLED, EMAILCHANGE_ENABLED
+from astakos.im.api.faults import (
+ Fault, Unauthorized, InternalServerError, BadRequest,
- Forbidden)
++ Forbidden
++)
+from astakos.im.api import render_fault, _get_user_by_email, _get_user_by_username
+from astakos.im.models import AstakosUser
from astakos.im.util import epoch
-from astakos.im.api import _get_user_by_email, _get_user_by_username
logger = logging.getLogger(__name__)
format = ('%a, %d %b %Y %H:%M:%S GMT')
--- /dev/null
+# 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.db import IntegrityError, transaction
+from django.core.exceptions import ObjectDoesNotExist
+
+from functools import wraps
+from smtplib import SMTPException
+
+from astakos.im.models import (
+ AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR
+)
+from astakos.im.api.backends.base import BaseBackend, SuccessResult, FailureResult
+from astakos.im.api.backends.errors import (
+ ItemNotExists, ItemExists, MissingIdentifier, MultipleItemsExist
+)
+from astakos.im.util import reserved_email, model_to_dict
+from astakos.im.endpoints.qh import get_quota
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+DEFAULT_CONTENT_TYPE = None
+
+
+def safe(func):
+ """Decorator function for views that implement an API method."""
+ @transaction.commit_manually
+ @wraps(func)
+ def wrapper(self, *args, **kwargs):
+ logger.debug('%s %s %s' % (func, args, kwargs))
+ try:
+ data = func(self, *args, **kwargs) or ()
+ except Exception, e:
+ logger.exception(e)
+ transaction.rollback()
+ return FailureResult(e)
+ else:
+ transaction.commit()
+ return SuccessResult(data)
+ return wrapper
+
+
+class DjangoBackend(BaseBackend):
+ def _lookup_object(self, model, **kwargs):
+ """
+ Returns an object of the specific model matching the given lookup
+ parameters.
+ """
+ if not kwargs:
+ raise MissingIdentifier
+ try:
+ return model.objects.get(**kwargs)
+ except model.DoesNotExist:
+ raise ItemNotExists(model._meta.verbose_name, **kwargs)
+ except model.MultipleObjectsReturned:
+ raise MultipleItemsExist(model._meta.verbose_name, **kwargs)
+
+ def _lookup_user(self, id):
+ """
+ Returns an AstakosUser having this id.
+ """
+ if not isinstance(id, int):
+ raise TypeError('User id should be of type int')
+ return self._lookup_object(AstakosUser, id=id)
+
+ def _lookup_service(self, id):
+ """
+ Returns an Service having this id.
+ """
+ if not isinstance(id, int):
+ raise TypeError('Service id should be of type int')
+ return self._lookup_object(Service, id=id)
+
+ def _list(self, model, filter=()):
+ q = model.objects.all()
+ if filter:
+ q = q.filter(id__in=filter)
+ return map(lambda o: model_to_dict(o, exclude=[]), q)
+
+ def _create_object(self, model, **kwargs):
+ o = model.objects.create(**kwargs)
+ o.save()
+ return o
+
+ def _update_object(self, model, id, save=True, **kwargs):
+ o = self._lookup_object(model, id=id)
+ if kwargs:
+ o.__dict__.update(kwargs)
+ if save:
+ o.save()
+ return o
+
+ @safe
+ def update_user(self, user_id, renew_token=False, **kwargs):
+ user = self._update_object(AstakosUser, user_id, save=False, **kwargs)
+ if renew_token:
+ user.renew_token()
+ if kwargs or renew_token:
+ user.save()
+
+ @safe
+ def create_user(self, **kwargs):
+ policies = kwargs.pop('policies', ())
+ permissions = kwargs.pop('permissions', ())
+ groups = kwargs.pop('groups', ())
+ password = kwargs.pop('password', None)
+
+ u = self._create_object(AstakosUser, **kwargs)
+
+ if password:
+ u.set_password(password)
+ u.permissions = permissions
+ u.policies = policies
+ u.extended_groups = groups
+ return self._list(AstakosUser, filter=(u.id,))
+
+ @safe
+ def add_policies(self, user_id, update=False, policies=()):
+ user = self._lookup_user(user_id)
+ rejected = []
+ append = rejected.append
+ for p in policies:
+ service = p.get('service')
+ resource = p.get('resource')
+ uplimit = p.get('uplimit')
+ try:
+ user.add_policy(service, resource, uplimit, update)
+ except (ObjectDoesNotExist, IntegrityError), e:
+ append((service, resource, e))
+ return rejected
+
+ @safe
+ def remove_policies(self, user_id, policies=()):
+ user = self._lookup_user(user_id)
+ if not user:
+ return user_id
+ rejected = []
+ append = rejected.append
+ for p in policies:
+ service = p.get('service')
+ resource = p.get('resource')
+ try:
+ user.delete_policy(service, resource)
+ except ObjectDoesNotExist, e:
+ append((service, resource, e))
+ return rejected
+ @safe
+ def add_permissions(self, user_id, permissions=()):
+ user = self._lookup_user(user_id)
+ rejected = []
+ append = rejected.append
+ for p in permissions:
+ try:
+ user.add_permission(p)
+ except IntegrityError, e:
+ append((p, e))
+ return rejected
+
+ @safe
+ def remove_permissions(self, user_id, permissions=()):
+ user = self._lookup_user(user_id)
+ rejected = []
+ append = rejected.append
+ for p in permissions:
+ try:
+ user.remove_permission(p)
+ except (ObjectDoesNotExist, IntegrityError), e:
+ append((p, e))
+ return rejected
+
+ @safe
+ def invite_users(self, senderid, recipients=()):
+ user = self._lookup_user(senderid)
+ rejected = []
+ append = rejected.append
+ for r in recipients:
++ email = r.get('email')
++ realname = r.get('realname')
+ try:
- user.invite(r.get('email'), r.get('realname'))
++ user.invite(email, realname)
+ except (IntegrityError, SMTPException), e:
+ append((email, e))
+ return rejected
+
+ @safe
+ def list_users(self, filter=()):
+ return self._list(AstakosUser, filter=filter)
+
+ @safe
+ def get_resource_usage(self, user_id):
+ user = self._lookup_user(user_id)
+ c, data = get_quota((user,))
+ resources = []
+ append = resources.append
+ for t in data:
+ t = (i if i else 0 for i in t)
+ (entity, name, quantity, capacity, importLimit, exportLimit,
+ imported, exported, returned, released, flags) = t
+ service, sep, resource = name.partition(RESOURCE_SEPARATOR)
+ resource = Resource.objects.select_related().get(
+ service__name=service, name=resource)
+ d = dict(name=name,
+ description=resource.desc,
+ unit=resource.unit or '',
+ maxValue=quantity + capacity,
+ currValue=quantity + imported - released - exported + returned)
+ append(d)
+ return resources
+
+ @safe
+ def list_resources(self, filter=()):
+ return self._list(Resource, filter=filter)
+
+ @safe
+ def create_service(self, **kwargs):
+ resources = kwargs.pop('resources', ())
+ s = self._create_object(Service, **kwargs)
+ s.resources = resources
+ return self._list(Service, filter=(s.id,))
+
+ @safe
+ def remove_services(self, ids=()):
+ # 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)
+ if renew_token:
+ s.renew_token()
+
+ if kwargs or renew_token:
+ s.save()
+
+ @safe
+ def add_resources(self, service_id, update=False, resources=()):
+ s = self._lookup_service(service_id)
+ rejected = []
+ append = rejected.append
+ for r in resources:
+ try:
+ rr = r.copy()
+ resource_id = rr.pop('id', None)
+ if update:
+ if not resource_id:
+ raise MissingIdentifier
+ resource = self._update_object(Resource, resource_id, **rr)
+ else:
+ resource = self._create_object(Resource, service=s, **rr)
+ 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', ())
+ permissions = kwargs.pop('permissions', ())
+ members = kwargs.pop('members', ())
+ owners = kwargs.pop('owners', ())
+
+ g = self._create_object(AstakosGroup, **kwargs)
+
+ g.permissions = permissions
+ g.policies = policies
+# g.members = members
+ g.owners = owners
+ return self._list(AstakosGroup, filter=(g.id,))
--- /dev/null
++# 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.
++
++import logging
++import socket
++
++from smtplib import SMTPException
++
++from django.conf import settings
++from django.core.mail import send_mail
++from django.template.loader import render_to_string
++
++import astakos.im.messages as astakos_messages
++
++from astakos.im.settings import LOGGING_LEVEL
++
++logger = logging.getLogger(__name__)
++
+def _send_admin_notification(template_name,
+ dictionary=None,
+ subject='alpha2 testing notification',):
+ """
+ Send notification email to settings.ADMINS.
+
+ Raises SendNotificationError
+ """
+ if not settings.ADMINS:
+ return
+ dictionary = dictionary or {}
+ message = render_to_string(template_name, dictionary)
+ sender = settings.SERVER_EMAIL
+ try:
+ send_mail(subject,
+ message, sender, [i[1] for i in settings.ADMINS])
+ except (SMTPException, socket.error) as e:
+ logger.exception(e)
+ raise SendNotificationError()
+ else:
+ msg = 'Sent admin notification for user %s' % dictionary
+ logger.log(LOGGING_LEVEL, msg)
+
+class EmailNotification(Notification):
+ def send(self):
+ send_mail(
+ subject,
+ message,
+ sender,
+ recipients
+ )
- )
+
+class Notification(object):
+ def __init__(self, sender, recipients, subject, message):
+ self.sender = sender
+ self.recipients = recipients
+ self.subject = subject
+ self.message = message
+
+ def send(self):
- pass
++ pass
++
++class SendMailError(Exception):
++ pass
++
++class SendNotificationError(SendMailError):
++ def __init__(self):
++ self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
++ super(SendNotificationError, self).__init__()
--- /dev/null
+# 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 astakos.im.api.spec import AstakosAPI
+from astakos.im.api.backends import get_backend
+
- from commissioning import (Callpoint,
- # CommissionException,
- # CorruptedError, InvalidDataError,
- # InvalidKeyError, NoEntityError,
- # NoQuantityError, NoCapacityError,
- # ExportLimitError, ImportLimitError
- )
++from commissioning import CorruptedError
+
-
- # from commissioning.utils.newname import newname
- # from django.db.models import Model, BigIntegerField, CharField, ForeignKey, Q
- # from django.db import transaction, IntegrityError
- # from .models import (Holder, Entity, Policy, Holding,
- # Commission, Provision, ProvisionLog, now)
++from django.db import transaction
+
+class AstakosCallpoint():
+
+ api_spec = AstakosAPI()
+
+# http_exc_lookup = {
+# CorruptedError: 550,
+# InvalidDataError: 400,
+# InvalidKeyError: 401,
+# NoEntityError: 404,
+# NoQuantityError: 413,
+# NoCapacityError: 413,
+# }
+
+ def init_connection(self, connection):
+ if connection is not None:
+ raise ValueError("Cannot specify connection args with %s" %
+ type(self).__name__)
+ pass
+
+ def commit(self):
+ transaction.commit()
+
+ def rollback(self):
+ transaction.rollback()
+
+ def do_make_call(self, call_name, data):
+ call_fn = getattr(self, call_name, None)
+ if not call_fn:
+ m = "cannot find call '%s'" % (call_name,)
+ raise CorruptedError(m)
+
+ return call_fn(**data)
+
+ def create_users(self, users=()):
+ b = get_backend()
+ rejected = (b.create_user(**u) for u in users)
+ return rejected
+
+ def update_users(self, users=()):
+ b = get_backend()
+ rejected = (b.update_user(**u) for u in users)
+ return rejected
+
+ def add_user_policies(self, user_id, update=False, policies=()):
+ b = get_backend()
+ rejected = b.add_policies(user_id, update, policies)
+ return rejected
+
+ def remove_user_policies(self, user_id, policies=()):
+ b = get_backend()
+ rejected = b.remove_policies(user_id, policies)
+ return rejected
+
+ def add_user_permissions(self, user_id, permissions=()):
+ b = get_backend()
+ rejected = b.add_permissions(user_id, permissions)
+ return rejected
+
+ def remove_user_permissions(self, user_id, permissions=()):
+ b = get_backend()
+ rejected = b.remove_permissions(user_id, permissions)
+ return rejected
+
+ def invite_users(self, sender_id, recipients=()):
+ b = get_backend()
+ rejected = b.invite_users(sender_id, recipients)
+ return rejected
+
+ def list_users(self, filter=()):
+ b = get_backend()
+ return b.list_users(filter)
+
+ def get_user_status(self, user_id):
+ b = get_backend()
+ return b.get_resource_usage(user_id)
+
+ def list_resources(self, filter=()):
+ b = get_backend()
+ return b.list_resources(filter)
+
+ def add_services(self, services=()):
+ b = get_backend()
+ rejected = (b.create_service(**s) for s in services)
+ return rejected
+
+ def update_services(self, services=()):
+ b = get_backend()
+ rejected = (b.update_service(**s) for s in services)
+ return rejected
+
+ def remove_services(self, ids=()):
+ b = get_backend()
+ rejected = b.remove_services(ids)
+ return rejected
+
+ def add_resources(self, service_id, update=False, resources=()):
+ b = get_backend()
+ rejected = b.add_resources(service_id, update, resources)
+ return rejected
+
+ def remove_resources(self, service_id, ids=()):
+ b = get_backend()
+ rejected = b.remove_resources(service_id, ids)
+ return rejected
+
+ def create_groups(self, groups=()):
+ b = get_backend()
+ rejected = (b.create_group(**g) for g in groups)
+ return rejected
+
+API_Callpoint = AstakosCallpoint
--- /dev/null
+from commissioning.specificator import (
- CanonifyException, SpecifyException,
- Specificator, Null, Integer, Text,
- Tuple, ListOf, Dict, Args)
++ Specificator, Integer, Text, ListOf
++)
+
+
+class Name(Text):
+ def init(self):
+ self.opts.update({'regex': "[\w.:]+", 'maxlen': 512})
+Name = Name()
+
+
+class Email(Text):
+ def init(self):
+ pattern = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
+ self.opts.update({'regex': pattern, 'maxlen': 512})
+Email = Email()
+
+
+class Url(Text):
+ def init(self):
+ pattern = "(((f|ht){1}tp://)[-a-zA-Z0-9@:%_\+.~#?&//=]+)"
+ self.opts.update({'regex': pattern, 'maxlen': 512})
+Url = Url()
+
+
+class Filepath(Text):
+ def init(self):
+ self.opts.update({'regex': "", 'maxlen': 512})
+Filepath = Filepath()
+
+
+class Nonnegative(Integer):
+ def init(self):
+ self.opts.update({'minimum': 0})
+Nonnegative = Nonnegative()
+
+
+class Boolean(Integer):
+ def init(self):
+ self.opts.update({'minimum': 0, 'maximum': 1})
+Boolean = Boolean()
+
+
+# class GroupKind(Integer):
+# def init(self):
+# self.opts.update({'minimum': 1, 'maximum': 5})
+# GroupKind = GroupKind()
+
+Timepoint = Text(classname='Timepoint', maxlen=24)
+
+
+class AstakosAPI(Specificator):
+ def create_users(
+ self,
+ users=ListOf(
+ email=Email,
+ first_name=Name,
+ last_name=Name,
+ is_active=Boolean,
+ is_superuser=Boolean,
+ affiliation=Name,
+ password=Name,
+ provider=Name,
+ level=Nonnegative,
+ invitations=Nonnegative,
+ is_verified=Boolean,
+ third_party_identifier=Name,
+ email_verified=Boolean),
+ policies=ListOf(resource=Name, supimit=Nonnegative),
+ groups=ListOf(Name),
+ permissions=ListOf(Name)
+ ):
+ rejected = ListOf(user=Email, reason=Text())
+ return rejected
+
+ def update_users(
+ self,
+ users=ListOf(
+ pk=Nonnegative,
+ renew_token=Boolean,
+ data=ListOf(
+ first_name=Name,
+ last_name=Name,
+ is_active=Boolean,
+ is_superuser=Boolean,
+ affiliation=Name,
+ password=Name,
+ provider=Name,
+ level=Nonnegative,
+ invitations=Nonnegative,
+ is_verified=Boolean,
+ third_party_identifier=Name,
+ email_verified=Boolean
+ )
+ )
+ ):
+ rejected = ListOf(user_id=Nonnegative, reason=Text())
+ return rejected
+
+ def add_user_policies(
+ self,
+ pk=Nonnegative,
+ update=Boolean,
+ policies=ListOf(service=Name, resource=Name, upimit=Nonnegative)
+ ):
+ rejected = ListOf(resource=Name, reason=Text())
+ return rejected
+
+ def remove_user_policies(
+ self,
+ pk=Nonnegative,
+ policies=ListOf(service=Name, resource=Name)
+ ):
+ rejected = ListOf(service=Name, resource=Name)
+ return rejected
+
+ def add_user_permissions(
+ self,
+ pk=Nonnegative,
+ permissions=ListOf(permission=Name)
+ ):
+ rejected = ListOf(permission=Name)
+ return rejected
+
+ def remove_user_permissions(
+ self,
+ pk=Nonnegative,
+ permissions=ListOf(permission=Name)
+ ):
+ rejected = ListOf(permission=Name)
+ return rejected
+
+ def invite_users(
+ self,
+ sender=Email,
+ data=ListOf(email=Email, realname=Name)
+ ):
+ rejected = ListOf(receiver=Email)
+ return rejected
+
+ def list_users(
+ self,
+ filter=ListOf(id=Nonnegative)
+ ):
+ return ListOf(
+ activation_sent=Timepoint,
+ affiliation=Name,
+ auth_token=Name,
+ auth_token_created=Timepoint,
+ auth_token_expires=Timepoint,
+ date_joined=Timepoint,
+ date_signed_terms=Timepoint,
+ email=Email,
+ email_verified=Boolean,
+ first_name=Name,
+ has_credits=Boolean,
+ has_signed_terms=Boolean,
+ id=Nonnegative,
+ invitations=Nonnegative,
+ invitations_sent=ListOf(
+ code=Name,
+ consumed=Boolean,
+ created=Timepoint,
+ id=Nonnegative,
+ realname=Name,
+ username=Email
+ ),
+ is_active=Boolean,
+ is_staff=Boolean,
+ is_superuser=Boolean,
+ is_verified=Boolean,
+ last_login=Timepoint,
+ last_name=Name,
+ level=Nonnegative,
+ password=Name,
+ provider=Name,
+ third_party_identifier=Name,
+ updated=Timepoint,
+ user_permissions=ListOf(
+ codename=Name,
+ id=Nonnegative,
+ name=Name
+ ),
+ username=Name,
+ astakos_groups=ListOf(
+ approval_date=Timepoint,
+ creation_date=Timepoint,
+ desc=Text(),
+ max_participants=Nonnegative,
+ expiration_date=Timepoint,
+ group_ptr=Url,
+ homepage=Url,
+ id=Nonnegative,
+ issue_date=Timepoint,
+ kind=Name,
+ moderation_enabled=Boolean,
+ name=Name,
+ #permissions=ListOf(),
+ policy=ListOf(id=Nonnegative, name=Name)
+ )
+ )
+
+ def get_user_status(
+ self,
+ user_id=Nonnegative
+ ):
+ return ListOf(
+ name=Name,
+ description=Text(),
+ unit=Name,
+ maxValue=Integer(),
+ currValue=Integer()
+ )
+
+ def list_resources(self, filter=ListOf(id=Nonnegative)):
+ return ListOf(
+ desc=Text(),
+ group=Name,
+ id=Nonnegative,
+ meta=ListOf(key=Name, value=Name),
+ name=Name,
+ service=Name,
+ unit=Name
+ )
+
+ def add_services(
+ self,
+ services=ListOf(
+ name=Name,
+ url=Url,
+ icon=Filepath,
+ resources=ListOf(
+ name=Name,
+ desc=Text(),
+ unit=Name,
+ group=Name
+ )
+ )
+ ):
+ rejected = ListOf(service=Name)
+ return rejected
+
+ def update_services(
+ self,
+ services=ListOf(id=Nonnegative, url=Url, icon=Filepath)
+ ):
+ rejected = ListOf(service=Name)
+ return rejected
+
+ def remove_services(self, ids=ListOf(Nonnegative)):
+ rejected = ListOf(service=Name)
+ return rejected
+
+ def add_resources(
+ self,
+ service_id=Nonnegative,
+ update=Boolean,
+ resources=ListOf(
+ name=Name,
+ resources=ListOf(
+ name=Name,
+ desc=Text(),
+ unit=Name,
+ group=Name)
+ )
+ ):
+ rejected = ListOf(service=Name)
+ return rejected
+
+ def remove_resources(
+ self,
+ service_id=Nonnegative,
+ ids=ListOf(Nonnegative)
+ ):
+ rejected = ListOf(Name)
+ return rejected
+
+ def create_groups(
+ self,
+ groups=ListOf(
+ name=Name,
+ kind=Name,
+ homepage=Url,
+ desc=Text(),
+ policies=ListOf(resource=Name, upimit=Nonnegative),
+ issue_date=Timepoint,
+ expiration_date=Timepoint,
+ moderation_enabled=Boolean,
+ participants=Nonnegative,
+ permissions=ListOf(permission=Name),
+ members=ListOf(user=Email, is_approved=Boolean),
+ owners=ListOf(user=Email)
+ )
+ ):
+ rejected = ListOf(group=Name)
+ return rejected
+
+ def enable_groups(self, data=ListOf(group=Name)):
+ rejected = ListOf(group=Name)
+ return rejected
+
+ def search_groups(self, key=Name):
+ return ListOf(
+ group=Name,
+ kind=Nonnegative,
+ homepage=Url,
+ desc=Text(),
+ creation_date=Timepoint,
+ issue_date=Timepoint,
+ expiration_date=Timepoint,
+ moderation_enabled=Boolean,
+ participants=Nonnegative,
+ owner=ListOf(user=Email),
+ policies=ListOf(resource=Name, upimit=Nonnegative),
+ members=ListOf(user=Email, is_approved=Boolean)
+ )
+
+ def list_groups(self):
+ return ListOf(
+ group=Name,
+ kind=Nonnegative,
+ homepage=Url,
+ desc=Text(),
+ creation_date=Timepoint,
+ issue_date=Timepoint,
+ expiration_date=Timepoint,
+ moderation_enabled=Boolean,
+ participants=Nonnegative,
+ owners=ListOf(user=Email),
+ policies=ListOf(resource=Name, upimit=Nonnegative),
+ members=ListOf(user=Email, is_approved=Boolean)
+ )
+
+ def add_owners(
+ self,
+ data=ListOf(group=Name, owners=ListOf(user=Email))
+ ):
+ rejected = ListOf(user=Email)
+ return rejected
+
+ def remove_owners(
+ self,
+ data=ListOf(group=Name, owners=ListOf(user=Email))
+ ):
+ rejected = ListOf(user=Email)
+ return rejected
+
+ def add_members(
+ self,
+ data=ListOf(group=Name, members=ListOf(user=Email))
+ ):
+ rejected = ListOf(user=Email)
+ return rejected
+
+ def remove_members(
+ self,
+ data=ListOf(group=Name, members=ListOf(user=Email))
+ ):
+ rejected = ListOf(user=Email)
+ return rejected
+
+ def add_policies(
+ self,
+ data=ListOf(group=Name, resource=Name, upimit=Nonnegative)
+ ):
+ rejected = ListOf(group=Name, resource=Name)
+ return rejected
+
+ def remove_group_policies(
+ self,
+ data=ListOf(group=Name, resource=Name, upimit=Nonnegative)
+ ):
+ rejected = ListOf(group=Name, resource=Name)
+ return rejected
+
+ def update_group_policies(
+ self, data=ListOf(group=Name, resource=Name, upimit=Nonnegative)
+ ):
+ rejected = ListOf(group=Name, resource=Name)
+ return rejected
+
+ def approve_members(
+ self,
+ data=ListOf(group=Name, members=ListOf(user=Email))
+ ):
+ rejected = ListOf(user=Email)
+ return rejected
+
+ def disapprove_members(
+ self,
+ data=ListOf(group=Name, members=ListOf(user=Email))
+ ):
+ rejected = ListOf(user=Email)
+ return rejected
+
+ def add_group_permissions(
+ self,
+ data=ListOf(group=Name, permission=Name)
+ ):
+ rejected = ListOf(group=Name, permission=Name)
+ return rejected
+
+ def delete_group_permissions(
+ self,
+ data=ListOf(group=Name, permission=Name)
+ ):
+ rejected = ListOf(group=Name, permission=Name)
+ return rejected
+
+ def list_resource_units(self):
+ return ListOf(Name)
+
- def get_approval_terms(term=Nonnegative):
++ def get_approval_terms(self, term=Nonnegative):
+ return Text()
+
- def add_approval_terms(location=Filepath):
++ def add_approval_terms(self, location=Filepath):
+ return Nonnegative
+
+# def change_emails():
+# pass
from django.core.validators import email_re
from astakos.im.models import AstakosUser
+ from astakos.im.settings import LOGGING_LEVEL
+
+ import logging
+
+ logger = logging.getLogger(__name__)
+
class TokenBackend(ModelBackend):
"""
AuthenticationBackend used to authenticate using token instead
return None
if user.check_password(password):
return user
+ else:
+ msg = 'Invalid password during authentication for %s' % username
+ logger._log(LOGGING_LEVEL, msg, [])
-
-
++
+
def get_user(self, user_id):
try:
return AstakosUser.objects.get(pk=user_id)
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
--from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL, \
- LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES, \
- COOKIE_NAME, LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES, \
- GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
-from astakos.im.api.admin import get_menu
++from astakos.im.settings import (
++ IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL,
++ LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES,
+ GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
++)
+from astakos.im.api import get_menu
from astakos.im.util import get_query
+from astakos.im.models import GroupKind
-from django.conf import settings
-from django.core.urlresolvers import reverse
from django.utils import simplejson as json
+
def im_modules(request):
return {'im_modules': IM_MODULES}
--- /dev/null
+ # 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.
+
+ import logging
+
+ from urllib import quote, unquote
+
+ from django.contrib.auth.models import AnonymousUser
+ from django.http import HttpRequest
+ from django.utils.translation import ugettext as _
+
+ from astakos.im.settings import (
+ COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, LOGGING_LEVEL
+ )
+
++import astakos.im.messages as astakos_messages
++
+ logger = logging.getLogger(__name__)
+
+ class Cookie():
+ def __init__(self, request, response=None):
+ cookies = getattr(request, 'COOKIES', {})
+ cookie = unquote(cookies.get(COOKIE_NAME, ''))
+ self.email, sep, self.auth_token = cookie.partition('|')
+ self.request = request
+ self.response = response
+
+ @property
+ def email(self):
+ return getattr(self, 'email', '')
+
+ @property
+ def auth_token(self):
+ return getattr(self, 'auth_token', '')
+
+ @property
+ def is_set(self):
- no_token = not self.auth_token
++ no_token = not self.auth_token
+ return not no_token
+
+ @property
+ def is_valid(self):
+ return self.email == getattr(self.user, 'email', '') and \
+ self.auth_token == getattr(self.user, 'auth_token', '')
+
+ @property
+ def user(self):
+ return getattr(self.request, 'user', AnonymousUser())
+
+ def __set(self):
+ if not self.response:
- raise ValueError(_('There is no response.'))
++ raise ValueError(_(astakos_messages.NO_RESPONSE))
+ user = self.user
+ expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
+ cookie_value = quote(user.email + '|' + user.auth_token)
+ self.response.set_cookie(
+ COOKIE_NAME, value=cookie_value, expires=expire_fmt, path='/',
+ domain=COOKIE_DOMAIN, secure=COOKIE_SECURE
+ )
+ msg = 'Cookie [expiring %(auth_token_expires)s] set for %(email)s' % user.__dict__
+ logger._log(LOGGING_LEVEL, msg, [])
+
+ def __delete(self):
+ if not self.response:
- raise ValueError(_('There is no response.'))
++ raise ValueError(_(astakos_messages.NO_RESPONSE))
+ self.response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
+ msg = 'Cookie deleted for %(email)s' % self.__dict__
+ logger._log(LOGGING_LEVEL, msg, [])
+
+ def fix(self, response=None):
+ self.response = response or self.response
+ if self.user.is_authenticated():
+ if not self.is_set or not self.is_valid:
+ self.__set()
+ else:
+ if self.is_set:
+ self.__delete()
--- /dev/null
+# 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.
+
+import logging
+
+from functools import wraps
+from urlparse import urlparse
+
+from astakos.im.settings import QUEUE_CONNECTION
+
+if QUEUE_CONNECTION:
+ from synnefo.lib.queue import (exchange_connect, exchange_send,
+ exchange_close, UserEvent, Receipt
+ )
+
+QUEUE_CLIENT_ID = '3' # Astakos.
+INSTANCE_ID = '1'
+RESOURCE = 'addcredits'
+DEFAULT_CREDITS = 1000
+
+logging.basicConfig(format='%(asctime)s [%(levelname)s] %(name)s %(message)s',
+ datefmt='%Y-%m-%d %H:%M:%S'
+ )
+logger = logging.getLogger('endpoint.aquarium')
+
+
- def wrapper(func):
++def call(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if not QUEUE_CONNECTION:
+ return
+
+ try:
+ body, key = func(*args, **kwargs)
+ conn = exchange_connect(QUEUE_CONNECTION)
+ parts = urlparse(QUEUE_CONNECTION)
+ exchange = parts.path[1:]
+ routing_key = key % exchange
+ exchange_send(conn, routing_key, body)
+ exchange_close(conn)
+ except BaseException, e:
+ logger.exception(e)
+ return wrapper
+
+
- @wrapper
++@call
+def report_user_event(user, create=False):
+ eventType = 'create' if not create else 'modify'
+ body = UserEvent(QUEUE_CLIENT_ID, user.email, user.is_active, eventType, {}
+ ).format()
+ routing_key = '%s.user'
+ return body, routing_key
+
+
- @wrapper
++@call
+def report_user_credits_event(user):
+ body = Receipt(QUEUE_CLIENT_ID, user.email, INSTANCE_ID, RESOURCE,
+ DEFAULT_CREDITS, details={}
+ ).format()
+ routing_key = '%s.resource'
+ return body, routing_key
+
+
+def report_credits_event():
+ # better approach?
+ from astakos.im.models import AstakosUser
+ map(report_user_credits_event, AstakosUser.objects.all())
--- /dev/null
+# 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.
+
- import socket
+import logging
+import itertools
+
+from functools import wraps
- from itertools import tee
+
+from django.utils.translation import ugettext as _
+
+from astakos.im.settings import QUOTA_HOLDER_URL, LOGGING_LEVEL
+
+if QUOTA_HOLDER_URL:
+ from kamaki.clients.quotaholder import QuotaholderClient
+
+ENTITY_KEY = '1'
+
+inf = float('inf')
+
+logger = logging.getLogger(__name__)
+
+inf = float('inf')
+
+def call(func_name):
+ """Decorator function for Quotaholder client calls."""
+ def decorator(payload_func):
+ @wraps(payload_func)
+ def wrapper(entities=(), client=None, **kwargs):
+ if not entities:
+ return client, ()
+
+ if not QUOTA_HOLDER_URL:
+ return client, ()
+
+ c = client or QuotaholderClient(QUOTA_HOLDER_URL)
+ func = c.__dict__.get(func_name)
+ if not func:
+ return c, ()
+
+ data = payload_func(entities, client, **kwargs)
+ if not data:
+ return c, data
+
+ funcname = func.__name__
+ kwargs = {'context': {}, funcname: data}
+ rejected = func(**kwargs)
+ msg = _('%s: %s - Rejected: %s' % (funcname, data, rejected,))
+ logger.log(LOGGING_LEVEL, msg)
+ return c, rejected
+ return wrapper
+ return decorator
+
+
+@call('set_quota')
+def send_quota(users, client=None):
+ data = []
+ append = data.append
+ for user in users:
+ for resource, uplimit in user.quota.iteritems():
+ key = ENTITY_KEY
+ quantity = None
+ capacity = uplimit if uplimit != inf else None
+ import_limit = None
+ export_limit = None
+ flags = 0
+ args = (
+ user.email, resource, key, quantity, capacity, import_limit,
+ export_limit, flags)
+ append(args)
+ return data
+
+
+@call('set_quota')
+def send_resource_quantities(resources, client=None):
+ data = []
+ append = data.append
+ for resource in resources:
+ key = ENTITY_KEY
+ quantity = resource.meta.filter(key='quantity') or None
+ capacity = None
+ import_limit = None
+ export_limit = None
+ flags = 0
+ args = (resource.service, resource, key, quantity, capacity,
+ import_limit, export_limit, flags)
+ append(args)
+ return data
+
+
+@call('get_quota')
+def get_quota(users, client=None):
+ data = []
+ append = data.append
+ for user in users:
+ try:
+ entity = user.email
+ except AttributeError:
+ continue
+ else:
+ for r in user.quota.keys():
+ args = entity, r, ENTITY_KEY
+ append(args)
+ return data
+
+
+@call('create_entity')
+def create_entities(entities, client=None, field=''):
+ data = []
+ append = data.append
+ for entity in entities:
+ try:
+ entity = entity.__getattribute__(field)
+ except AttributeError:
+ continue
+ owner = 'system'
+ key = ENTITY_KEY
+ ownerkey = ''
+ args = entity, owner, key, ownerkey
+ append(args)
+ return data
+
+
+def register_users(users, client=None):
+ users, copy = itertools.tee(users)
+ client, rejected = create_entities(entities=users,
+ client=client,
+ field='email')
+ created = (e for e in copy if unicode(e) not in rejected)
+ return send_quota(created, client)
+
+
+def register_resources(resources, client=None):
+ resources, copy = itertools.tee(resources)
+ client, rejected = create_entities(entities=resources,
+ client=client,
+ field='service')
+ created = (e for e in copy if unicode(e) not in rejected)
+ return send_resource_quantities(created, client)
+
+
+from datetime import datetime
+
+strptime = datetime.strptime
+timefmt = '%Y-%m-%dT%H:%M:%S.%f'
+
+SECOND_RESOLUTION = 1
+
+
+def total_seconds(timedelta_object):
+ return timedelta_object.seconds + timedelta_object.days * 86400
+
+
+def iter_timeline(timeline, before):
+ if not timeline:
+ return
+
+ for t in timeline:
+ yield t
+
+ t = dict(t)
+ t['issue_time'] = before
+ yield t
+
+
+def _usage_units(timeline, after, before, details=0):
+
+ t_total = 0
+ uu_total = 0
+ t_after = strptime(after, timefmt)
+ t_before = strptime(before, timefmt)
+ t0 = t_after
+ u0 = 0
+
+ for point in iter_timeline(timeline, before):
+ issue_time = point['issue_time']
+
+ if issue_time <= after:
+ u0 = point['target_allocated_through']
+ continue
+
+ t = strptime(issue_time, timefmt) if issue_time <= before else t_before
+ t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
+ t_total += t_diff
+ uu_cost = u0 * t_diff
+ uu_total += uu_cost
+ t0 = t
+ u0 = point['target_allocated_through']
+
+ target = point['target']
+ if details:
+ yield (target,
+ point['resource'],
+ point['name'],
+ issue_time,
+ uu_cost,
+ uu_total)
+
+ if not t_total:
+ return
+
+ yield (target,
+ 'total',
+ point['resource'],
+ issue_time,
+ uu_total / t_total,
+ uu_total)
+
+
+def usage_units(timeline, after, before, details=0):
+ return list(_usage_units(timeline, after, before, details=details))
+
+
+def traffic_units(timeline, after, before, details=0):
+ tu_total = 0
+ target = None
+ issue_time = None
+
+ for point in timeline:
+ issue_time = point['issue_time']
+ if issue_time <= after:
+ continue
+ if issue_time > before:
+ break
+
+ target = point['target']
+ tu = point['target_allocated_through']
+ tu_total += tu
+
+ if details:
+ yield (target,
+ point['resource'],
+ point['name'],
+ issue_time,
+ tu,
+ tu_total)
+
+ if not tu_total:
+ return
+
+ yield (target,
+ 'total',
+ point['resource'],
+ issue_time,
+ tu_total // len(timeline),
+ tu_total)
+
+
+def timeline_charge(entity, resource, after, before, details, charge_type):
+ key = '1'
+ if charge_type == 'charge_usage':
+ charge_units = usage_units
+ elif charge_type == 'charge_traffic':
+ charge_units = traffic_units
+ else:
+ m = 'charge type %s not supported' % charge_type
+ raise ValueError(m)
+
+ quotaholder = QuotaholderClient(QUOTA_HOLDER_URL)
+ timeline = quotaholder.get_timeline(
+ context={},
+ after=after,
+ before=before,
+ get_timeline=[[entity, resource, key]])
+ cu = charge_units(timeline, after, before, details=details)
+ return cu
from django.template import Context, loader
from django.utils.http import int_to_base36
from django.core.urlresolvers import reverse
-from django.utils.functional import lazy
from django.utils.safestring import mark_safe
-from django.contrib import messages
from django.utils.encoding import smart_str
- from django.forms.extras.widgets import SelectDateWidget
-from django.forms.models import fields_for_model
+from django.conf import settings
- from astakos.im.models import (AstakosUser, EmailChange, AstakosGroup,
- Invitation, Membership, GroupKind, Resource,
- get_latest_terms, RESOURCE_SEPARATOR)
- from astakos.im.settings import (INVITATIONS_PER_LEVEL, BASEURL, SITENAME,
- RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED,
- DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
- PASSWORD_RESET_EMAIL_SUBJECT,
- NEWPASSWD_INVALIDATE_TOKEN)
+ from astakos.im.models import (
- AstakosUser, Invitation, get_latest_terms,
- EmailChange, PendingThirdPartyUser
++ AstakosUser, EmailChange, AstakosGroup, Invitation, GroupKind,
++ Resource, PendingThirdPartyUser, get_latest_terms, RESOURCE_SEPARATOR
+ )
-from astakos.im.settings import (INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL,
- BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL,
- RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT,
- NEWPASSWD_INVALIDATE_TOKEN
++from astakos.im.settings import (
++ INVITATIONS_PER_LEVEL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY,
++ RECAPTCHA_ENABLED, DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
++ PASSWORD_RESET_EMAIL_SUBJECT, NEWPASSWD_INVALIDATE_TOKEN
+ )
from astakos.im.widgets import DummyWidget, RecaptchaWidget
from astakos.im.functions import send_change_email
save behavior is complete.
"""
user = super(LocalUserCreationForm, self).save(commit=False)
- user.renew_token()
if commit:
user.save()
- logger._log(LOGGING_LEVEL, 'Created user %s' % user.email, [])
+ logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
return user
+
class InvitedLocalUserCreationForm(LocalUserCreationForm):
"""
Extends the LocalUserCreationForm: email is readonly.
user.save()
return user
+
class ThirdPartyUserCreationForm(forms.ModelForm):
+ id = forms.CharField(
+ widget=forms.HiddenInput(),
+ label='',
+ required=False
+ )
+ third_party_identifier = forms.CharField(
+ widget=forms.HiddenInput(),
+ label=''
+ )
class Meta:
model = AstakosUser
- fields = ("email", "first_name", "last_name",
- "third_party_identifier", "has_signed_terms")
+ fields = ['id', 'email', 'third_party_identifier', 'first_name', 'last_name']
def __init__(self, *args, **kwargs):
"""
# Overriding field label since we need to apply a link
# to the terms within the label
terms_link_html = '<a href="%s" target="_blank">%s</a>' \
- % (reverse('latest_terms'), _("the terms"))
+ % (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:
# copy email value to additional_mail in case user will change it
name = 'email'
field = self.fields[name]
- self.initial['additional_email'] = self.initial.get(name,
- field.initial)
-
+ self.initial['additional_email'] = self.initial.get(name, field.initial)
+ self.initial['email'] = None
+
def clean_email(self):
email = self.cleaned_data['email']
+ if self.instance:
+ if self.instance.email == email:
+ raise forms.ValidationError(_("This is your current email."))
for user in AstakosUser.objects.filter(email=email):
if user.provider == 'shibboleth':
- raise forms.ValidationError(_(astakos_messages.SHIBBOLETH_EMAIL_USED))
- elif not user.is_active:
- raise forms.ValidationError(_(astakos_messages.SHIBBOLETH_INACTIVE_ACC))
+ raise forms.ValidationError(_(
+ "This email is already associated with another \
+ shibboleth account."
+ )
+ )
+ else:
+ raise forms.ValidationError(_("This email is already used"))
super(ShibbolethUserCreationForm, self).clean_email()
return email
+
+ def save(self, commit=True):
+ user = super(ShibbolethUserCreationForm, self).save(commit=False)
+ try:
+ p = PendingThirdPartyUser.objects.get(
+ provider=user.provider,
+ third_party_identifier=user.third_party_identifier
+ )
+ except:
+ pass
+ else:
+ p.delete()
+ return user
-class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm, InvitedThirdPartyUserCreationForm):
+
+class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm,
+ InvitedThirdPartyUserCreationForm):
pass
+
class LoginForm(AuthenticationForm):
username = forms.EmailField(label=_("Email"))
recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
rrf = self.cleaned_data['recaptcha_response_field']
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
if not check.is_valid:
- raise forms.ValidationError(_('You have not entered the correct words'))
+ raise forms.ValidationError(_(astakos_messages.CAPTCHA_VALIDATION_ERR))
-
+
def clean(self):
- super(LoginForm, self).clean()
- if self.user_cache and self.user_cache.provider not in ('local', ''):
- raise forms.ValidationError(_(astakos_messages.SUSPENDED_LOCAL_ACC))
+ """
+ Override default behavior in order to check user's activation later
+ """
+ try:
+ super(LoginForm, self).clean()
+ except forms.ValidationError, e:
+ if self.user_cache is None:
+ raise
+ if self.request:
+ if not self.request.session.test_cookie_worked():
+ raise
return self.cleaned_data
+
class ProfileForm(forms.ModelForm):
"""
Subclass of ``ModelForm`` for permiting user to edit his/her profile.
class Meta:
model = AstakosUser
- fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires')
+ fields = ('email', 'first_name', 'last_name', 'auth_token',
+ 'auth_token_expires')
def __init__(self, *args, **kwargs):
+ self.session_key = kwargs.pop('session_key', None)
super(ProfileForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
ro_fields = ('email', 'auth_token', 'auth_token_expires')
super(ExtendedPasswordChangeForm, self).__init__(user, *args, **kwargs)
def save(self, commit=True):
- if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
- self.user.renew_token()
+ try:
+ if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
+ self.user.renew_token()
+ self.user.flush_sessions(current_key=self.session_key)
+ except AttributeError:
+ # if user model does has not such methods
+ pass
return super(ExtendedPasswordChangeForm, self).save(commit=commit)
+
+class AstakosGroupCreationForm(forms.ModelForm):
+ kind = forms.ModelChoiceField(
+ queryset=GroupKind.objects.all(),
+ label="",
+ widget=forms.HiddenInput()
+ )
+ name = forms.URLField(widget=forms.TextInput(attrs={'placeholder': 'eg. foo.ece.ntua.gr'}), help_text="Name should be in the form of dns",)
+ moderation_enabled = forms.BooleanField(
+ help_text="Check if you want to approve members participation manually",
+ required=False,
+ initial=True
+ )
+ max_participants = forms.IntegerField(
+ required=True, min_value=1
+ )
+
+ class Meta:
+ model = AstakosGroup
+
+ def __init__(self, *args, **kwargs):
+ #update QueryDict
+ args = list(args)
+ qd = args.pop(0).copy()
+ members_unlimited = qd.pop('members_unlimited', False)
+ members_uplimit = qd.pop('members_uplimit', None)
+# max_participants = None if members_unlimited else members_uplimit
+# qd['max_participants']= max_participants.pop(0) if max_participants else None
+
+ #substitue QueryDict
+ args.insert(0, qd)
+
+ super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
+ self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
+ 'issue_date', 'expiration_date',
+ 'moderation_enabled', 'max_participants']
+ def add_fields((k, v)):
+ k = k.partition('_proxy')[0]
+ self.fields[k] = forms.IntegerField(
+ required=False,
+ widget=forms.HiddenInput(),
+ min_value=1
+ )
+ map(add_fields,
+ ((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
+ )
+
+ def add_fields((k, v)):
+ self.fields[k] = forms.BooleanField(
+ required=False,
+ #widget=forms.HiddenInput()
+ )
+ map(add_fields,
+ ((k, v) for k,v in qd.iteritems() if k.startswith('is_selected_'))
+ )
+
+ def policies(self):
+ self.clean()
+ policies = []
+ append = policies.append
+ for name, uplimit in self.cleaned_data.iteritems():
+
+ subs = name.split('_uplimit')
+ if len(subs) == 2:
+ prefix, suffix = subs
+ s, sep, r = prefix.partition(RESOURCE_SEPARATOR)
+ resource = Resource.objects.get(service__name=s, name=r)
+
+ # keep only resource limits for selected resource groups
- print '###', resource.group, s, r, uplimit, self.cleaned_data
+ if self.cleaned_data.get(
+ 'is_selected_%s' % resource.group, False
+ ):
+ append(dict(service=s, resource=r, uplimit=uplimit))
+ return policies
+
+class AstakosGroupCreationSummaryForm(forms.ModelForm):
+ kind = forms.ModelChoiceField(
+ queryset=GroupKind.objects.all(),
+ label="",
+ widget=forms.HiddenInput()
+ )
+ name = forms.URLField()
+ moderation_enabled = forms.BooleanField(
+ help_text="Check if you want to approve members participation manually",
+ required=False,
+ initial=True
+ )
+ max_participants = forms.IntegerField(
+ required=False, min_value=1
+ )
+
+ class Meta:
+ model = AstakosGroup
+
+ def __init__(self, *args, **kwargs):
+ #update QueryDict
+ args = list(args)
+ qd = args.pop(0).copy()
+ members_unlimited = qd.pop('members_unlimited', False)
+ members_uplimit = qd.pop('members_uplimit', None)
+# max_participants = None if members_unlimited else members_uplimit
+# qd['max_participants']= max_participants.pop(0) if max_participants else None
+
+ #substitue QueryDict
+ args.insert(0, qd)
+
+ super(AstakosGroupCreationSummaryForm, self).__init__(*args, **kwargs)
+ self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
+ 'issue_date', 'expiration_date',
+ 'moderation_enabled', 'max_participants']
+ def add_fields((k, v)):
+ self.fields[k] = forms.IntegerField(
+ required=False,
+ widget=forms.TextInput(),
+ min_value=1
+ )
+ map(add_fields,
+ ((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
+ )
+
+ def add_fields((k, v)):
+ self.fields[k] = forms.BooleanField(
+ required=False,
+ widget=forms.HiddenInput()
+ )
+ map(add_fields,
+ ((k, v) for k,v in qd.iteritems() if k.startswith('is_selected_'))
+ )
+ for f in self.fields.values():
+ f.widget = forms.HiddenInput()
+
+ def clean(self):
+ super(AstakosGroupCreationSummaryForm, self).clean()
+ self.cleaned_data['policies'] = []
+ append = self.cleaned_data['policies'].append
- print '#', self.cleaned_data
+ #tbd = [f for f in self.fields if (f.startswith('is_selected_') and (not f.endswith('_proxy')))]
+ tbd = [f for f in self.fields if f.startswith('is_selected_')]
+ for name, uplimit in self.cleaned_data.iteritems():
- print '####', name, uplimit
+ subs = name.split('_uplimit')
+ if len(subs) == 2:
+ tbd.append(name)
+ prefix, suffix = subs
+ s, sep, r = prefix.partition(RESOURCE_SEPARATOR)
+ resource = Resource.objects.get(service__name=s, name=r)
+
- print '#### ####', resource
+ # keep only resource limits for selected resource groups
+ if self.cleaned_data.get(
+ 'is_selected_%s' % resource.group, False
+ ):
+ append(dict(service=s, resource=r, uplimit=uplimit))
+ for name in tbd:
+ self.cleaned_data.pop(name, None)
+ return self.cleaned_data
+
+class AstakosGroupUpdateForm(forms.ModelForm):
+ class Meta:
+ model = AstakosGroup
+ fields = ('homepage', 'desc')
+
+
+class AddGroupMembersForm(forms.Form):
+ q = forms.CharField(
+ max_length=800, widget=forms.Textarea, label=_('Add members'),
+ help_text=_(astakos_messages.ADD_GROUP_MEMBERS_Q_HELP),
+ required=True)
+
+ def clean(self):
+ q = self.cleaned_data.get('q') or ''
+ users = q.split(',')
+ users = list(u.strip() for u in users if u)
+ db_entries = AstakosUser.objects.filter(email__in=users)
+ unknown = list(set(users) - set(u.email for u in db_entries))
+ if unknown:
+ raise forms.ValidationError(_(astakos_messages.UNKNOWN_USERS) % ','.join(unknown))
+ self.valid_users = db_entries
+ return self.cleaned_data
+
+ def get_valid_users(self):
+ """Should be called after form cleaning"""
+ try:
+ return self.valid_users
+ except:
+ return ()
+
+
+class AstakosGroupSearchForm(forms.Form):
+ q = forms.CharField(max_length=200, label='Search group')
+
+
+class TimelineForm(forms.Form):
- # entity = forms.CharField(
- # widget=forms.HiddenInput(), label='')
+ entity = forms.ModelChoiceField(
+ queryset=AstakosUser.objects.filter(is_active=True)
+ )
+ resource = forms.ModelChoiceField(
+ queryset=Resource.objects.all()
+ )
+ start_date = forms.DateTimeField()
+ end_date = forms.DateTimeField()
+ details = forms.BooleanField(required=False, label="Detailed Listing")
+ operation = forms.ChoiceField(
+ label='Charge Method',
+ choices=(('', '-------------'),
+ ('charge_usage', 'Charge Usage'),
+ ('charge_traffic', 'Charge Traffic'), )
+ )
+
+ def clean(self):
+ super(TimelineForm, self).clean()
+ d = self.cleaned_data
+ if 'resource' in d:
+ d['resource'] = str(d['resource'])
+ if 'start_date' in d:
+ d['start_date'] = d['start_date'].strftime(
+ "%Y-%m-%dT%H:%M:%S.%f")[:24]
+ if 'end_date' in d:
+ d['end_date'] = d['end_date'].strftime("%Y-%m-%dT%H:%M:%S.%f")[:24]
+ if 'entity' in d:
+ d['entity'] = d['entity'].email
+ return d
+
+
+class AstakosGroupSortForm(forms.Form):
+ sort_by = forms.ChoiceField(label='Sort by',
+ choices=(('groupname', 'Name'),
+ ('kindname', 'Type'),
+ ('issue_date', 'Issue Date'),
+ ('expiration_date',
+ 'Expiration Date'),
+ ('approved_members_num',
+ 'Participants'),
+ ('is_enabled', 'Status'),
+ ('moderation_enabled', 'Moderation'),
+ ('membership_status',
+ 'Enrollment Status')
+ ),
+ required=False)
+
+
+class MembersSortForm(forms.Form):
+ sort_by = forms.ChoiceField(label='Sort by',
+ choices=(('person__email', 'User Id'),
+ ('person__first_name', 'Name'),
+ ('date_joined', 'Status')
+ ),
+ required=False)
+
+
+class PickResourceForm(forms.Form):
+ resource = forms.ModelChoiceField(
+ queryset=Resource.objects.select_related().all()
+ )
+ resource.widget.attrs["onchange"] = "this.form.submit()"
+
+
class ExtendedSetPasswordForm(SetPasswordForm):
"""
Extends SetPasswordForm by enabling user
to optionally renew also the token.
"""
if not NEWPASSWD_INVALIDATE_TOKEN:
- renew = forms.BooleanField(label='Renew token', required=False,
- initial=True,
- help_text='Unsetting this may result in security risk.')
-
+ renew = forms.BooleanField(
+ label='Renew token',
+ required=False,
+ initial=True,
+ help_text='Unsetting this may result in security risk.'
+ )
+
def __init__(self, user, *args, **kwargs):
super(ExtendedSetPasswordForm, self).__init__(user, *args, **kwargs)
-
+
def save(self, commit=True):
- if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
- if isinstance(self.user, AstakosUser):
+ try:
+ 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()
+ except BaseException, e:
+ logger.exception(e)
- pass
return super(ExtendedSetPasswordForm, self).save(commit=commit)
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
--from django.core.exceptions import ValidationError
from django.template import Context, loader
- from django.contrib.auth import login as auth_login, logout as auth_logout
- from django.http import HttpRequest
+ from django.contrib.auth import (
+ login as auth_login,
- logout as auth_logout,
- SESSION_KEY
++ logout as auth_logout
+ )
-from django.http import HttpRequest
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
from urllib import quote
from urlparse import urljoin
from datetime import datetime
from functools import wraps
- from astakos.im.settings import (DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL,
- LOGGING_LEVEL, VERIFICATION_EMAIL_SUBJECT,
- ACCOUNT_CREATION_SUBJECT,
- GROUP_CREATION_SUBJECT,
- HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
- INVITATION_EMAIL_SUBJECT,
- GREETING_EMAIL_SUBJECT,
- FEEDBACK_EMAIL_SUBJECT,
- EMAIL_CHANGE_EMAIL_SUBJECT)
- import astakos.im.models
+ from astakos.im.settings import (
- DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL,
- SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL, LOGGING_LEVEL,
- VERIFICATION_EMAIL_SUBJECT, ADMIN_NOTIFICATION_EMAIL_SUBJECT,
- HELPDESK_NOTIFICATION_EMAIL_SUBJECT, INVITATION_EMAIL_SUBJECT,
- GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT, EMAIL_CHANGE_EMAIL_SUBJECT
++ DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
++ VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
++ GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
++ INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
++ EMAIL_CHANGE_EMAIL_SUBJECT
+ )
-from astakos.im.models import Invitation, AstakosUser, SessionCatalog
+import astakos.im.messages as astakos_messages
++import astakos.im.models
logger = logging.getLogger(__name__)
return r
return with_logging
- login = logged(auth_login, '%s logged in.')
+
+ def login(request, user):
+ auth_login(request, user)
- SessionCatalog(session_key=request.session.session_key, user=user).save()
++ astakos.im.models.SessionCatalog(
++ session_key=request.session.session_key,
++ user=user).save()
+
+ login = logged(login, '%s logged in.')
logout = logged(auth_logout, '%s logged out.')
+
def send_verification(user, template_name='im/activation_email.txt'):
"""
Send email to user to verify his/her email and activate his/her account.
raise SendInvitationError()
else:
msg = 'Sent invitation %s' % invitation
- logger._log(LOGGING_LEVEL, msg, [])
+ logger.log(LOGGING_LEVEL, msg)
- invitation.inviter.invitations = max(0, self.invitations - 1)
++ invitation.inviter.invitations = max(0, invitation.inviter.invitations - 1)
+ invitation.inviter.save()
+
def send_greeting(user, email_template_name='im/welcome_email.txt'):
"""
from optparse import make_option
--from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group
++from django.core.management.base import NoArgsCommand
-from astakos.im.models import AstakosUser
+from astakos.im.models import AstakosGroup
from ._common import format_bool
--class Command(BaseCommand):
++class Command(NoArgsCommand):
help = "List groups"
-
- option_list = BaseCommand.option_list + (
+
- option_list = BaseCommand.option_list + (
++ option_list = NoArgsCommand.option_list + (
make_option('-c',
- action='store_true',
- dest='csv',
- default=False,
- help="Use pipes to separate values"),
+ action='store_true',
+ dest='csv',
+ default=False,
+ help="Use pipes to separate values"),
+ make_option('-p',
+ action='store_true',
+ dest='pending',
+ default=False,
+ help="List only groups pending enable"),
)
-
- def handle(self, *args, **options):
- if args:
- raise CommandError("Command doesn't accept any arguments")
-
- groups = Group.objects.all().order_by('id')
-
- labels = ('id', 'name', 'permissions')
- columns = (3, 12, 50)
-
- if not options['csv']:
+
- def handle(self, *args, **options):
++ def handle_noargs(self, **options):
+ groups = AstakosGroup.objects.all()
+
+ if options.get('pending'):
+ groups = filter(lambda g: g.is_disabled, groups)
+
+ labels = ('id', 'name', 'enabled', 'moderation', 'permissions')
+ columns = (3, 25, 12, 12, 50)
+
+ if not options.get('csv'):
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
self.stdout.write(line + '\n')
sep = '-' * len(line)
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
--from optparse import make_option
--
from django.core.management.base import BaseCommand, CommandError
--from django.contrib.auth.models import Group, Permission
++from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
--from django.core.exceptions import ValidationError
--from astakos.im.models import AstakosUser
from ._common import add_group_permission
+
class Command(BaseCommand):
args = "<groupname> <permission> [<permissions> ...]"
help = "Add group permissions"
def handle(self, *args, **options):
if args:
raise CommandError("Command doesn't accept any arguments")
-
- invitations = Invitation.objects.all()
-
+
+ invitations = Invitation.objects.all().order_by('id')
+
labels = ('id', 'inviter', 'email', 'real name', 'code', 'consumed')
columns = (3, 24, 24, 24, 20, 4, 8)
-
+
if not options['csv']:
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
self.stdout.write(line + '\n')
--- /dev/null
+# 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
- from django.db.utils import IntegrityError
+
+from astakos.im.models import AstakosUser, Resource
+from astakos.im.endpoints.qh import register_users, register_resources
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ help = "Send user information and resource quota in the Quotaholder"
+
+ def handle(self, *args, **options):
+ try:
+ register_resources(Resource.objects.all())
+ register_users(AstakosUser.objects.all())
+ except BaseException, e:
+ logger.exception(e)
+ raise CommandError("Syncing failed.")
--- /dev/null
+# 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 BaseCommand, CommandError
++from django.core.management.base import BaseCommand
+
+from astakos.im.models import Resource
+
+
+class Command(BaseCommand):
+ help = "List resources"
+
+ option_list = BaseCommand.option_list + (
+ make_option('-c',
+ action='store_true',
+ dest='csv',
+ default=False,
+ help="Use pipes to separate values"),
+ )
+
+ def handle(self, *args, **options):
+ resources = Resource.objects.select_related().all()
+
+ labels = ('id', 'service', 'name')
+ columns = (3, 40, 40)
+
+ 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 r in resources:
+ fields = (str(r.id), r.service.name, r.name)
+
+ if options['csv']:
+ line = '|'.join(fields)
+ else:
+ line = ' '.join(f.rjust(w) for f, w in zip(fields, columns))
+
+ self.stdout.write(line.encode('utf8') + '\n')
--- /dev/null
+# 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
- from django.db.utils import IntegrityError
+
+from astakos.im.models import Resource
+
+
+class Command(BaseCommand):
+ args = "<resource>"
+ help = "Add a resource"
+
+ def handle(self, *args, **options):
+ if len(args) < 1:
+ raise CommandError("Invalid number of arguments")
+
+ kwargs = {}
+ if args[0].isdigit():
+ kwargs['id'] = args[0]
+ else:
+ kwargs['name'] = args[0]
+
+ try:
+ r = Resource.objects.get(**kwargs)
+ except Resource.DoesNotExist, e:
+ raise CommandError("Invalid resource")
+ r.delete()
from django.core.management.base import BaseCommand, CommandError
--from astakos.im.models import Service
+from astakos.im.api.callpoint import AstakosCallpoint
class Command(BaseCommand):
args = "<name> <url> [<icon>]"
from optparse import make_option
--from django.core.management.base import BaseCommand, CommandError
++from django.core.management.base import NoArgsCommand
from astakos.im.models import Service
-class Command(BaseCommand):
+
- class Command(BaseCommand):
++class Command(NoArgsCommand):
help = "List services"
-- option_list = BaseCommand.option_list + (
++ option_list = NoArgsCommand.option_list + (
make_option('-c',
- action='store_true',
- dest='csv',
- default=False,
- help="Use pipes to separate values"),
+ action='store_true',
+ dest='csv',
+ default=False,
+ help="Use pipes to separate values"),
)
-- def handle(self, *args, **options):
- services = Service.objects.all()
- if args:
- raise CommandError("Command doesn't accept any arguments")
-
++ def handle_noargs(self, **options):
+ services = Service.objects.all().order_by('id')
labels = ('id', 'name', 'url', 'auth_token', 'icon')
columns = (3, 12, 40, 20, 20)
import socket
from optparse import make_option
- from uuid import uuid4
- from datetime import datetime
-from random import choice
-from string import digits, lowercase, uppercase
-from uuid import uuid4
from django.core.management.base import BaseCommand, CommandError
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
-from django.contrib.auth.models import Group
from astakos.im.models import AstakosUser
-from astakos.im.util import reserved_email
+from astakos.im.api.callpoint import AstakosCallpoint
+
- from ._common import add_user_permission
-
-
+def filter_custom_options(options):
+ base_dests = list(
+ getattr(o, 'dest', None) for o in BaseCommand.option_list)
+ return dict((k, v) for k, v in options.iteritems() if k not in base_dests)
-from ._common import add_user_permission
class Command(BaseCommand):
- args = "<email> <first name> <last name> <affiliation>"
+ args = "<email>"
help = "Create a user"
-
+
option_list = BaseCommand.option_list + (
+ make_option('--first-name',
+ dest='first_name',
+ metavar='NAME',
+ help="Set user's first name"),
+ make_option('--last-name',
+ dest='last_name',
+ metavar='NAME',
+ help="Set user's last name"),
+ make_option('--affiliation',
+ dest='affiliation',
+ metavar='AFFILIATION',
+ help="Set user's affiliation"),
+ make_option('--password',
+ dest='password',
+ metavar='PASSWORD',
+ help="Set user's password"),
make_option('--active',
- action='store_true',
- dest='active',
- default=False,
- help="Activate user"),
+ action='store_true',
+ dest='is_active',
+ default=False,
+ help="Activate user"),
make_option('--admin',
- action='store_true',
- dest='admin',
- default=False,
- help="Give user admin rights"),
- make_option('--password',
- dest='password',
- metavar='PASSWORD',
- help="Set user's password"),
- make_option('--add-group',
- dest='add-group',
- help="Add user group"),
- make_option('--add-permission',
- dest='add-permission',
- help="Add user permission")
- )
-
+ action='store_true',
+ dest='is_superuser',
+ default=False,
+ help="Give user admin rights"),
+ make_option('-g',
+ action='append',
+ dest='groups',
+ help="Add user group (may be used multiple times)"),
+ make_option('-p',
+ action='append',
+ dest='permissions',
+ help="Add user permission (may be used multiple times)")
+ )
+
def handle(self, *args, **options):
- if len(args) != 4:
+ if len(args) != 1:
raise CommandError("Invalid number of arguments")
-
- args = [a.decode('utf8') for a in args]
- email, first, last, affiliation = args
-
+
+ email = args[0].decode('utf8')
+
try:
- validate_email( email )
+ validate_email(email)
except ValidationError:
raise CommandError("Invalid email")
-
- username = uuid4().hex[:30]
- password = options.get('password')
- if password is None:
- password = AstakosUser.objects.make_random_password()
-
- if reserved_email(email):
- raise CommandError("A user with this email already exists")
-
- user = AstakosUser(username=username, first_name=first, last_name=last,
- email=email, affiliation=affiliation,
- provider='local')
- user.set_password(password)
-
- if options['active']:
- user.is_active = True
- if options['admin']:
- user.is_admin = True
-
+
+ u = {'email': email}
+ u.update(filter_custom_options(options))
+ if not u.get('password'):
+ u['password'] = AstakosUser.objects.make_random_password()
+
try:
- user.save()
+ c = AstakosCallpoint()
+ r = c.create_users((u,))
except socket.error, e:
raise CommandError(e)
except ValidationError, e:
raise CommandError(e)
else:
- msg = "Created user id %d" % (user.id,)
- if options['password'] is None:
- msg += " with password '%s'" % (password,)
- self.stdout.write(msg + '\n')
-
- groupname = options.get('add-group')
- if groupname is not None:
- try:
- group = Group.objects.get(name=groupname)
- user.groups.add(group)
- self.stdout.write('Group: %s added successfully\n' % groupname)
- except Group.DoesNotExist, e:
- self.stdout.write('Group named %s does not exist\n' % groupname)
-
- pname = options.get('add-permission')
- if pname is not None:
- try:
- r, created = add_user_permission(user, pname)
- if created:
- self.stdout.write('Permission: %s created successfully\n' % pname)
- if r > 0:
- self.stdout.write('Permission: %s added successfully\n' % pname)
- elif r==0:
- self.stdout.write('User has already permission: %s\n' % pname)
- except Exception, e:
- raise CommandError(e)
+ failed = (res for res in r if not res.is_success)
+ for r in failed:
+ if not r.is_success:
+ raise CommandError(r.reason)
+ if not failed:
+ self.stdout.write('User created successfully')
+ if not u.get('password'):
- self.stdout.write('with password: %s' % u['password'])
++ self.stdout.write('with password: %s' % u['password'])
from django.db.utils import IntegrityError
from django.db import transaction
-from astakos.im.functions import invite, SendMailError
-from astakos.im.models import Invitation
+from astakos.im.functions import SendMailError
- from astakos.im.models import Invitation
from ._common import get_user
from optparse import make_option
--from django.core.management.base import BaseCommand, CommandError
++from django.core.management.base import NoArgsCommand
from astakos.im.models import AstakosUser
from ._common import format_bool
--class Command(BaseCommand):
++class Command(NoArgsCommand):
help = "List users"
-
- option_list = BaseCommand.option_list + (
+
- option_list = BaseCommand.option_list + (
++ option_list = NoArgsCommand.option_list + (
make_option('-c',
- action='store_true',
- dest='csv',
- default=False,
- help="Use pipes to separate values"),
+ action='store_true',
+ dest='csv',
+ default=False,
+ help="Use pipes to separate values"),
make_option('-p',
- action='store_true',
- dest='pending',
- default=False,
- help="List only users pending activation"),
+ action='store_true',
+ dest='pending',
+ default=False,
+ help="List only users pending activation"),
make_option('-n',
- action='store_true',
- dest='pending_send_mail',
- default=False,
- help="List only users who have not received activation"),
- )
-
- def handle(self, *args, **options):
- if args:
- raise CommandError("Command doesn't accept any arguments")
-
+ action='store_true',
+ dest='pending_send_mail',
+ default=False,
+ help="List only users who have not received activation"),
+ )
+
- def handle(self, *args, **options):
- if args:
- raise CommandError("Command doesn't accept any arguments")
-
- users = AstakosUser.objects.all()
++ def handle_noargs(self, **options):
+ users = AstakosUser.objects.all().order_by('id')
if options['pending']:
users = users.filter(is_active=False)
elif options['pending_send_mail']:
id = str(user.id)
active = format_bool(user.is_active)
admin = format_bool(user.is_superuser)
- fields = (id, user.email, user.realname, active, admin, user.provider,
- ','.join([g.name for g in user.groups.all()]))
-
+ fields = (
- id, user.email, user.realname, active, admin, user.provider)
++ id, user.email, user.realname, active, admin, user.provider
++ )
+
if options['csv']:
line = '|'.join(fields)
else:
from optparse import make_option
from django.core.management.base import BaseCommand, CommandError
--from django.contrib.auth.models import Group, Permission
--from django.contrib.contenttypes.models import ContentType
++from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from astakos.im.models import AstakosUser
--- /dev/null
+# 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.
+
+ACCOUNT_AUTHENTICATION_FAILED = 'Cannot authenticate account.'
- ACCOUNT_INACTIVE = 'Inactive account.'
+ACCOUNT_ALREADY_ACTIVE = 'Account is already active.'
++ACCOUNT_PENDING_ACTIVATION = 'Your request is pending activation.'
++ACCOUNT_RESEND_ACTIVATION = 'You have not followed the activation link. <a href="%(send_activation_url)s">Resend activation email?</a>'
++INACTIVE_ACCOUNT_CHANGE_EMAIL = ''.join([ACCOUNT_RESEND_ACTIVATION, ' or <a href="%(signup_url)s">Provide new email?</a>'])
++
++ACCOUNT_UNKNOWN = 'There is no such account.'
+TOKEN_UNKNOWN = 'There is no user matching this token.'
+
- INVITATION_SENT = 'Invitation sent to %(email)s.'
+PROFILE_UPDATED = 'Profile has been updated successfully.'
+FEEDBACK_SENT = 'Feedback successfully sent.'
+EMAIL_CHANGED = 'Account email has been changed successfully.'
+EMAIL_CHANGE_REGISTERED = 'Change email request has been registered succefully. \
+ You are going to receive a verification email in the new address.'
+
+OBJECT_CREATED = 'The %(verbose_name)s was created successfully.'
+MEMBER_JOINED_GROUP = '%(realname)s has been successfully joined the group.'
+MEMBER_REMOVED = '%(realname)s has been successfully removed from the group.'
+BILLING_ERROR = 'Service response status: %(status)d'
+LOGOUT_SUCCESS = 'You have successfully logged out.'
+
+GENERIC_ERROR = 'Something wrong has happened. \
+ Please contact the administrators for more details.'
+
+MAX_INVITATION_NUMBER_REACHED = 'There are no invitations left.'
+GROUP_MAX_PARTICIPANT_NUMBER_REACHED = 'Group maximum participant number has been reached.'
+NO_APPROVAL_TERMS = 'There are no approval terms.'
+PENDING_EMAIL_CHANGE_REQUEST = 'There is already a pending change email request.'
+OBJECT_CREATED_FAILED = 'The %(verbose_name)s creation failed: %(reason)s.'
+GROUP_JOIN_FAILURE = 'Failed to join group.'
+GROUPKIND_UNKNOWN = 'There is no such a group kind'
+NOT_MEMBER = 'User is not member of the group.'
+NOT_OWNER = 'User is not a group owner.'
+OWNER_CANNOT_LEAVE_GROUP = 'Owner cannot leave the group.'
+
+# Field validation fields
+REQUIRED_FIELD = 'This field is required.'
+EMAIL_USED = 'This email address is already in use. Please supply a different email address.'
+SHIBBOLETH_EMAIL_USED = 'This email is already associated with another shibboleth account.'
+SHIBBOLETH_INACTIVE_ACC = 'This email is already associated with an inactive account. \
+ You need to wait to be activated before being able to switch to a shibboleth account.'
++SHIBBOLETH_MISSING_EPPN = 'Missing unique token in request.'
++SHIBBOLETH_MISSING_NAME = 'Missing user name in request.'
+
+SIGN_TERMS = 'You have to agree with the terms.'
+CAPTCHA_VALIDATION_ERR = 'You have not entered the correct words.'
+SUSPENDED_LOCAL_ACC = 'Local login is not the current authentication method for this account.'
+UNUSABLE_PASSWORD = 'This account has not a usable password.'
+EMAIL_UNKNOWN = 'That e-mail address doesn\'t have an associated user account. \
+ Are you sure you\'ve registered?'
+INVITATION_EMAIL_EXISTS = 'There is already invitation for this email.'
+INVITATION_CONSUMED_ERR = 'Invitation is used.'
+UNKNOWN_USERS = 'Unknown users: %s'
+UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR = 'Another account with the same email & is_active combination found.'
+INVALID_ACTIVATION_KEY = 'Invalid activation key.'
+NEW_EMAIL_ADDR_RESERVED = 'The new email address is reserved.'
+EMAIL_RESERVED = 'Email: %(email)s is reserved'
++NO_LOCAL_AUTH = 'Local login is not the current authentication method for this account.'
++SWITCH_ACCOUNT_FAILURE = 'Account failed to switch. Invalid parameters.'
++SWITCH_ACCOUNT_SUCCESS_WITH_PROVIDER = 'Account failed to switch to %(provider)s.'
++SWITCH_ACCOUNT_SUCCESS = 'Account successfully switched to %(provider)s.'
+
+# Field help text
+ADD_GROUP_MEMBERS_Q_HELP = 'Add comma separated user emails, eg. user1@user.com, user2@user.com'
+ASTAKOSUSER_GROUPS_HELP = 'In addition to the permissions manually assigned, \
+ this user will also get all permissions granted to each group he/she is in.'
+EMAIL_CHANGE_NEW_ADDR_HELP = 'Your old email address will be used until you verify your new one.'
+
+EMAIL_SEND_ERR = 'Failed to send %s.'
+ADMIN_NOTIFICATION_SEND_ERR = EMAIL_SEND_ERR % 'admin notification'
+VERIFICATION_SEND_ERR = EMAIL_SEND_ERR % 'verification'
+INVITATION_SEND_ERR = EMAIL_SEND_ERR % 'invitation'
+GREETING_SEND_ERR = EMAIL_SEND_ERR % 'greeting'
+FEEDBACK_SEND_ERR = EMAIL_SEND_ERR % 'feedback'
+CHANGE_EMAIL_SEND_ERR = EMAIL_SEND_ERR % 'feedback'
+NOTIFICATION_SEND_ERR = EMAIL_SEND_ERR % 'notification'
+
-
+MISSING_NEXT_PARAMETER = 'No next parameter'
+
++INVITATION_SENT = 'Invitation sent to %(email)s.'
+VERIFICATION_SENT = 'Verification sent.'
-
+SWITCH_ACCOUNT_LINK_SENT = 'This email is already associated with another local account. \
+ To change this account to a shibboleth one follow the link in the verification email sent to %(email)s. \
+ Otherwise just ignore it.'
- NOTIFACATION_SENT = 'Your request for an account was successfully received and is now pending approval. \
++NOTIFICATION_SENT = 'Your request for an account was successfully received and is now pending approval. \
+ You will be notified by email in the next few days. \
+ Thanks for your interest in ~okeanos! The GRNET team.'
- REGISTRATION_COMPLETED = 'Registration completed. You can now login.'
++ACTIVATION_SENT = 'Activation sent.'
++
++REGISTRATION_COMPLETED = 'Registration completed. You can now login.'
++
++NO_RESPONSE = 'There is no response.'
++NOT_ALLOWED_NEXT_PARAM = 'Not allowed next parameter.'
++MISSING_KEY_PARAMETER = 'Missing key parameter.'
++INVALID_KEY_PARAMETER = 'Invalid key.'
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
--from urllib import unquote
- from django.contrib.auth import authenticate
+ from urlparse import urlunsplit, urlsplit
- from astakos.im.settings import COOKIE_NAME
- from astakos.im.functions import login
+ from django.http import HttpResponse
+ from django.utils.http import urlencode
+
+ from astakos.im.cookie import Cookie
-from astakos.im.settings import COOKIE_NAME
+ from astakos.im.util import get_query
+
class CookieAuthenticationMiddleware(object):
def process_request(self, request):
- assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
- if request.user.is_authenticated():
- return None
-
- cookie = unquote(request.COOKIES.get(COOKIE_NAME, ''))
- email, sep, auth_token = cookie.partition('|')
- if not sep:
- return None
-
- try:
- user = authenticate(email=email, auth_token=auth_token)
- if user:
- request.user = user
- login(request, user)
- except:
- pass
- return None
+ cookie = Cookie(request)
+ if cookie.is_valid:
+ return
+
+ response = HttpResponse(status=302)
+
+ parts = list(urlsplit(request.path))
+ params = get_query(request)
+ parts[3] = urlencode(params)
+ url = urlunsplit(parts)
+
+ response['Location'] = url
+ cookie.fix(response)
+ return response
+
+ def process_response(self, request, response):
+ Cookie(request, response).fix()
+ return response
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.v2 import DataMigration
+from django.db.models import Count
+
+
+class Migration(DataMigration):
+ def forwards(self, orm):
+
+ def _create_groupkind(name):
+ try:
+ orm.GroupKind(name=name).save()
+ except:
+ pass
+
+ t = ('default', 'course', 'project', 'laboratory', 'organization')
+ map(_create_groupkind, t)
+
+ default = orm.GroupKind.objects.get(name='default')
+
+ groups = {}
+
+ def _create_astakogroup(name):
+ try:
- groups[name] = orm.AstakosGroup.objects.get(name=name)
++ groups[name] = orm['im.AstakosGroup'].objects.get(name=name)
+ except orm.AstakosGroup.DoesNotExist:
+ try:
+ g = orm['auth.Group'].objects.get(name=name)
+ groups[
+ name] = extended = orm.AstakosGroup(group_ptr_id=g.pk)
+ extended.__dict__.update(g.__dict__)
+ extended.kind = default
+ extended.approval_date = datetime.datetime.now()
+ extended.issue_date = datetime.datetime.now()
+ extended.moderation_enabled = False
+ extended.save()
+ map(lambda u: orm.Membership(group=extended,
+ person=orm.AstakosUser.objects.get(id=u.id),
+ date_joined=datetime.datetime.now()
+ ).save(),
+ g.user_set.all())
+ except orm['auth.Group'].DoesNotExist:
+ groups[name] = orm.AstakosGroup(name=name,
+ kind=default,
+ approval_date=datetime.datetime.now(),
+ issue_date=datetime.datetime.now(),
+ moderation_enabled=False
+ )
+ groups[name].save()
+
+ # catch integrate
+ t = ('default', 'shibboleth', 'helpdesk', 'faculty',
+ 'ugrad', 'grad', 'researcher', 'associate')
+ map(_create_astakogroup, t)
+
+ orphans = orm.AstakosUser.objects.annotate(
+ num_groups=Count('astakos_groups')).filter(num_groups=0)
+ map(lambda u: orm.Membership(group=groups['default'],
+ person=u, date_joined=datetime.datetime.now()).save(), orphans)
+
+ def backwards(self, orm):
+ def _delete_groupkind(name):
+ try:
+ orm.GroupKind.objects.get(name=name).delete()
+ except orm.GroupKind.DoesNotExist:
+ pass
+
+ def _delete_astakosgroup(name):
+ try:
+ orm.AstakosGroup.objects.get(name=name).delete()
+ except orm.AstakosGroup.DoesNotExist:
+ pass
+
+ t = ('default', 'shibboleth', 'helpdesk', 'faculty',
+ 'ugrad', 'grad', 'researcher', 'associate')
+ map(_delete_astakosgroup, t)
+
+ t = ('default', 'course', 'project', 'laboratory', 'organization')
+ map(_delete_groupkind, t)
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'im.additionalmail': {
+ 'Meta': {'object_name': 'AdditionalMail'},
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+ },
+ 'im.approvalterms': {
+ 'Meta': {'object_name': 'ApprovalTerms'},
+ 'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 8, 12, 40, 8, 181485)', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'im.astakosgroup': {
+ 'Meta': {'object_name': 'AstakosGroup', '_ormbases': ['auth.Group']},
+ 'approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 8, 12, 40, 8, 175548)'}),
+ 'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+ 'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
+ 'issue_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.GroupKind']"}),
+ 'moderation_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'policy': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosGroupQuota']", 'blank': 'True'})
+ },
+ 'im.astakosgroupquota': {
+ 'Meta': {'unique_together': "(('resource', 'group'),)", 'object_name': 'AstakosGroupQuota'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+ },
+ 'im.astakosuser': {
+ 'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
+ 'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'astakos_groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosGroup']", 'symmetrical': 'False', 'through': "orm['im.Membership']", 'blank': 'True'}),
+ 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'invitations': ('django.db.models.fields.IntegerField', [], {'default': '100'}),
+ 'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'owner': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'owner'", 'null': 'True', 'to': "orm['im.AstakosGroup']"}),
+ 'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
+ 'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
+ 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
+ },
+ 'im.astakosuserquota': {
+ 'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+ },
+ 'im.emailchange': {
+ 'Meta': {'object_name': 'EmailChange'},
+ 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+ 'requested_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 8, 12, 40, 8, 183025)'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchange_user'", 'unique': 'True', 'to': "orm['im.AstakosUser']"})
+ },
+ 'im.groupkind': {
+ 'Meta': {'object_name': 'GroupKind'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'im.invitation': {
+ 'Meta': {'object_name': 'Invitation'},
+ 'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+ 'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'im.membership': {
+ 'Meta': {'unique_together': "(('person', 'group'),)", 'object_name': 'Membership'},
+ 'date_joined': ('django.db.models.fields.DateField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'date_requested': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 8, 8, 12, 40, 8, 179349)', 'blank': 'True'}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+ },
+ 'im.resource': {
+ 'Meta': {'object_name': 'Resource'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'meta': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.ResourceMetadata']", 'symmetrical': 'False'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Service']"})
+ },
+ 'im.resourcemetadata': {
+ 'Meta': {'object_name': 'ResourceMetadata'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'im.service': {
+ 'Meta': {'object_name': 'Service'},
+ 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+ 'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['im']
from time import asctime
from datetime import datetime, timedelta
from base64 import b64encode
-from urlparse import urlparse
from random import randint
+from collections import defaultdict
from django.db import models, IntegrityError
-from django.contrib.auth.models import User, UserManager, Group
+from django.contrib.auth.models import User, UserManager, Group, Permission
from django.utils.translation import ugettext as _
-from django.core.exceptions import ValidationError
-from django.template.loader import render_to_string
-from django.core.mail import send_mail
from django.db import transaction
-from django.db.models.signals import post_save, pre_save, post_syncdb
+from django.core.exceptions import ValidationError
- from django.db import transaction
- from django.db.models.signals import (pre_save, post_save, post_syncdb,
- post_delete)
++from django.db.models.signals import (
++ pre_save, post_save, post_syncdb, post_delete
++)
+from django.contrib.contenttypes.models import ContentType
+
+from django.dispatch import Signal
from django.db.models import Q
+ from django.conf import settings
+ from django.utils.importlib import import_module
-from astakos.im.settings import (
- DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
- AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION, SITENAME,
- EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL
+from astakos.im.settings import (DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
+ AUTH_TOKEN_DURATION, BILLING_FIELDS,
+ EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL)
- from astakos.im.endpoints.qh import (register_users, send_quota,
- register_resources)
++from astakos.im.endpoints.qh import (
++ register_users, send_quota, register_resources
+ )
+from astakos.im.endpoints.aquarium.producer import report_user_event
+from astakos.im.functions import send_invitation
+from astakos.im.tasks import propagate_groupmembers_quota
- from astakos.im.functions import send_invitation
-QUEUE_CLIENT_ID = 3 # Astakos.
+import astakos.im.messages as astakos_messages
logger = logging.getLogger(__name__)
+DEFAULT_CONTENT_TYPE = None
+try:
+ content_type = ContentType.objects.get(app_label='im', model='astakosuser')
+except:
+ content_type = DEFAULT_CONTENT_TYPE
+
+RESOURCE_SEPARATOR = '.'
+
+inf = float('inf')
+
+class Service(models.Model):
+ name = models.CharField('Name', max_length=255, unique=True, db_index=True)
+ url = models.FilePathField()
+ icon = models.FilePathField(blank=True)
+ auth_token = models.CharField('Authentication Token', max_length=32,
+ null=True, blank=True)
+ auth_token_created = models.DateTimeField('Token creation date', null=True)
+ auth_token_expires = models.DateTimeField(
+ 'Token expiration date', null=True)
+
- def save(self, **kwargs):
- if not self.id:
- self.renew_token()
- super(Service, self).save(**kwargs)
-
+ def renew_token(self):
+ md5 = hashlib.md5()
+ md5.update(self.name.encode('ascii', 'ignore'))
+ md5.update(self.url.encode('ascii', 'ignore'))
+ md5.update(asctime())
+
+ self.auth_token = b64encode(md5.digest())
+ self.auth_token_created = datetime.now()
+ self.auth_token_expires = self.auth_token_created + \
+ timedelta(hours=AUTH_TOKEN_DURATION)
+
+ def __str__(self):
+ return self.name
+
+ @property
+ def resources(self):
+ return self.resource_set.all()
+
+ @resources.setter
+ def resources(self, resources):
+ for s in resources:
+ self.resource_set.create(**s)
+
+ def add_resource(self, service, resource, uplimit, update=True):
+ """Raises ObjectDoesNotExist, IntegrityError"""
+ resource = Resource.objects.get(service__name=service, name=resource)
+ if update:
+ AstakosUserQuota.objects.update_or_create(user=self,
+ resource=resource,
+ defaults={'uplimit': uplimit})
+ else:
+ q = self.astakosuserquota_set
+ q.create(resource=resource, uplimit=uplimit)
+
+
+class ResourceMetadata(models.Model):
+ key = models.CharField('Name', max_length=255, unique=True, db_index=True)
+ value = models.CharField('Value', max_length=255)
+
+
+class Resource(models.Model):
+ name = models.CharField('Name', max_length=255, unique=True, db_index=True)
+ meta = models.ManyToManyField(ResourceMetadata)
+ service = models.ForeignKey(Service)
+ desc = models.TextField('Description', null=True)
+ unit = models.CharField('Name', null=True, max_length=255)
+ group = models.CharField('Group', null=True, max_length=255)
+
+ def __str__(self):
+ return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
+
+
+class GroupKind(models.Model):
+ name = models.CharField('Name', max_length=255, unique=True, db_index=True)
+
+ def __str__(self):
+ return self.name
+
+
+class AstakosGroup(Group):
+ kind = models.ForeignKey(GroupKind)
+ homepage = models.URLField(
+ 'Homepage Url', max_length=255, null=True, blank=True)
+ desc = models.TextField('Description', null=True)
+ policy = models.ManyToManyField(
+ Resource,
+ null=True,
+ blank=True,
+ through='AstakosGroupQuota'
+ )
+ creation_date = models.DateTimeField(
+ 'Creation date',
+ default=datetime.now()
+ )
+ issue_date = models.DateTimeField('Issue date', null=True)
+ expiration_date = models.DateTimeField(
+ 'Expiration date',
+ null=True
+ )
+ moderation_enabled = models.BooleanField(
+ 'Moderated membership?',
+ default=True
+ )
+ approval_date = models.DateTimeField(
+ 'Activation date',
+ null=True,
+ blank=True
+ )
+ estimated_participants = models.PositiveIntegerField(
+ 'Estimated #members',
+ null=True,
+ blank=True,
+ )
+ max_participants = models.PositiveIntegerField(
+ 'Maximum numder of participants',
+ null=True,
+ blank=True
+ )
+
+ @property
+ def is_disabled(self):
+ if not self.approval_date:
+ return True
+ return False
+
+ @property
+ def is_enabled(self):
+ if self.is_disabled:
+ return False
+ if not self.issue_date:
+ return False
+ if not self.expiration_date:
+ return True
+ now = datetime.now()
+ if self.issue_date > now:
+ return False
+ if now >= self.expiration_date:
+ return False
+ return True
+
+ def enable(self):
+ if self.is_enabled:
+ return
+ self.approval_date = datetime.now()
+ self.save()
+ quota_disturbed.send(sender=self, users=self.approved_members)
+ propagate_groupmembers_quota.apply_async(
+ args=[self], eta=self.issue_date)
+ propagate_groupmembers_quota.apply_async(
+ args=[self], eta=self.expiration_date)
+
+ def disable(self):
+ if self.is_disabled:
+ return
+ self.approval_date = None
+ self.save()
+ quota_disturbed.send(sender=self, users=self.approved_members)
+
+ @transaction.commit_manually
+ def approve_member(self, person):
+ m, created = self.membership_set.get_or_create(person=person)
- # update date_joined in any case
+ try:
- m.approve()
++ m.approve()
+ except:
+ transaction.rollback()
+ raise
+ else:
+ transaction.commit()
+
+# def disapprove_member(self, person):
+# self.membership_set.remove(person=person)
+
+ @property
+ def members(self):
+ q = self.membership_set.select_related().all()
+ return [m.person for m in q]
+
+ @property
+ def approved_members(self):
+ q = self.membership_set.select_related().all()
+ return [m.person for m in q if m.is_approved]
+
+ @property
+ def quota(self):
+ d = defaultdict(int)
+ for q in self.astakosgroupquota_set.select_related().all():
+ d[q.resource] += q.uplimit or inf
+ return d
+
+ def add_policy(self, service, resource, uplimit, update=True):
+ """Raises ObjectDoesNotExist, IntegrityError"""
- print '#', locals()
+ resource = Resource.objects.get(service__name=service, name=resource)
+ if update:
+ AstakosGroupQuota.objects.update_or_create(
+ group=self,
+ resource=resource,
+ defaults={'uplimit': uplimit}
+ )
+ else:
+ q = self.astakosgroupquota_set
+ q.create(resource=resource, uplimit=uplimit)
+
+ @property
+ def policies(self):
+ return self.astakosgroupquota_set.select_related().all()
+
+ @policies.setter
+ def policies(self, policies):
+ for p in policies:
+ service = p.get('service', None)
+ resource = p.get('resource', None)
+ uplimit = p.get('uplimit', 0)
+ update = p.get('update', True)
+ self.add_policy(service, resource, uplimit, update)
+
+ @property
+ def owners(self):
+ return self.owner.all()
+
+ @property
+ def owner_details(self):
+ return self.owner.select_related().all()
+
+ @owners.setter
+ def owners(self, l):
+ self.owner = l
+ map(self.approve_member, l)
+
+
class AstakosUser(User):
"""
Extends ``django.contrib.auth.models.User`` by defining additional fields.
if self.is_active and self.activation_sent:
# reset the activation sent
self.activation_sent = None
+
super(AstakosUser, self).save(**kwargs)
-
- def renew_token(self):
-
- # set default group if does not exist
- groupname = 'default'
- if groupname not in self.__groupnames:
- try:
- group = Group.objects.get(name = groupname)
- self.groups.add(group)
- except Group.DoesNotExist, e:
- logger.exception(e)
+
+ def renew_token(self, flush_sessions=False, current_key=None):
md5 = hashlib.md5()
+ md5.update(settings.SECRET_KEY)
md5.update(self.username)
md5.update(self.realname.encode('ascii', 'ignore'))
md5.update(asctime())
self.auth_token = b64encode(md5.digest())
self.auth_token_created = datetime.now()
self.auth_token_expires = self.auth_token_created + \
- timedelta(hours=AUTH_TOKEN_DURATION)
+ timedelta(hours=AUTH_TOKEN_DURATION)
+ if flush_sessions:
+ self.flush_sessions(current_key)
msg = 'Token renewed for %s' % self.email
- logger._log(LOGGING_LEVEL, msg, [])
+ logger.log(LOGGING_LEVEL, msg)
+ def flush_sessions(self, current_key=None):
+ q = self.sessions
+ if current_key:
+ q = q.exclude(session_key=current_key)
+
+ keys = q.values_list('session_key', flat=True)
+ if keys:
+ msg = 'Flushing sessions: %s' % ','.join(keys)
- logger._log(LOGGING_LEVEL, msg, [])
++ logger.log(LOGGING_LEVEL, msg, [])
+ engine = import_module(settings.SESSION_ENGINE)
+ for k in keys:
+ s = engine.SessionStore(k)
+ s.flush()
+
def __unicode__(self):
- return self.username
-
+ return '%s (%s)' % (self.realname, self.email)
+
def conflicting_email(self):
- q = AstakosUser.objects.exclude(username = self.username)
- q = q.filter(email = self.email)
+ q = AstakosUser.objects.exclude(username=self.username)
+ q = q.filter(email=self.email)
if q.count() != 0:
return True
return False
"""
Implements a unique_together constraint for email and is_active fields.
"""
- q = AstakosUser.objects.exclude(username=self.username)
- q = q.filter(email=self.email)
- q = q.filter(is_active=self.is_active)
+ q = AstakosUser.objects.all()
+ q = q.filter(email = self.email)
+ q = q.filter(is_active = self.is_active)
+ if self.id:
+ q = q.filter(~Q(id = self.id))
if q.count() != 0:
- raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
-
+ raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
+
+ @property
def signed_terms(self):
term = get_latest_terms()
if not term:
return False
return True
+ def store_disturbed_quota(self, set=True):
- self.disturbed_qutoa = set
++ self.disturbed_quota = set
+ self.save()
+
+
+class Membership(models.Model):
+ person = models.ForeignKey(AstakosUser)
+ group = models.ForeignKey(AstakosGroup)
+ date_requested = models.DateField(default=datetime.now(), blank=True)
+ date_joined = models.DateField(null=True, db_index=True, blank=True)
+
+ class Meta:
+ unique_together = ("person", "group")
+
+ def save(self, *args, **kwargs):
+ if not self.id:
+ if not self.group.moderation_enabled:
+ self.date_joined = datetime.now()
+ super(Membership, self).save(*args, **kwargs)
+
+ @property
+ def is_approved(self):
+ if self.date_joined:
+ return True
+ return False
+
+ def approve(self):
- if self.is_approved:
- return
++ if self.is_approved:
++ return
+ if self.group.max_participants:
+ assert len(self.group.approved_members) + 1 <= self.group.max_participants, \
- 'Maximum participant number has been reached.'
++ 'Maximum participant number has been reached.'
+ self.date_joined = datetime.now()
+ self.save()
+ quota_disturbed.send(sender=self, users=(self.person,))
+
+ def disapprove(self):
+ self.delete()
+ quota_disturbed.send(sender=self, users=(self.person,))
+
+class AstakosQuotaManager(models.Manager):
+ def _update_or_create(self, **kwargs):
+ assert kwargs, \
+ 'update_or_create() must be passed at least one keyword argument'
+ obj, created = self.get_or_create(**kwargs)
+ defaults = kwargs.pop('defaults', {})
+ if created:
+ return obj, True, False
+ else:
+ try:
+ params = dict(
+ [(k, v) for k, v in kwargs.items() if '__' not in k])
+ params.update(defaults)
+ for attr, val in params.items():
+ if hasattr(obj, attr):
+ setattr(obj, attr, val)
+ sid = transaction.savepoint()
+ obj.save(force_update=True)
+ transaction.savepoint_commit(sid)
+ return obj, False, True
+ except IntegrityError, e:
+ transaction.savepoint_rollback(sid)
+ try:
+ return self.get(**kwargs), False, False
+ except self.model.DoesNotExist:
+ raise e
+
+ update_or_create = _update_or_create
+
+class AstakosGroupQuota(models.Model):
+ objects = AstakosQuotaManager()
+ limit = models.PositiveIntegerField('Limit', null=True) # obsolete field
+ uplimit = models.BigIntegerField('Up limit', null=True)
+ resource = models.ForeignKey(Resource)
+ group = models.ForeignKey(AstakosGroup, blank=True)
+
+ class Meta:
+ unique_together = ("resource", "group")
+
+class AstakosUserQuota(models.Model):
+ objects = AstakosQuotaManager()
+ limit = models.PositiveIntegerField('Limit', null=True) # obsolete field
+ uplimit = models.BigIntegerField('Up limit', null=True)
+ resource = models.ForeignKey(Resource)
+ user = models.ForeignKey(AstakosUser)
+
+ class Meta:
+ unique_together = ("resource", "user")
+
+
class ApprovalTerms(models.Model):
"""
Model for approval terms
owner = models.ForeignKey(AstakosUser)
email = models.EmailField()
+
+def _generate_invitation_code():
+ while True:
+ code = randint(1, 2L ** 63 - 1)
+ try:
+ Invitation.objects.get(code=code)
+ # An invitation with this code already exists, try again
+ except Invitation.DoesNotExist:
+ return code
+
+
+def get_latest_terms():
+ try:
+ term = ApprovalTerms.objects.order_by('-id')[0]
+ return term
+ except IndexError:
+ pass
+ return None
+
+ class PendingThirdPartyUser(models.Model):
+ """
+ Model for registring successful third party user authentications
+ """
+ third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
+ provider = models.CharField('Provider', max_length=255, blank=True)
+ email = models.EmailField(_('e-mail address'), blank=True, null=True)
+ first_name = models.CharField(_('first name'), max_length=30, blank=True)
+ last_name = models.CharField(_('last name'), max_length=30, blank=True)
+ affiliation = models.CharField('Affiliation', max_length=255, blank=True)
+ username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
+
+ class Meta:
+ unique_together = ("provider", "third_party_identifier")
+
+ @property
+ def realname(self):
+ return '%s %s' %(self.first_name, self.last_name)
+
+ @realname.setter
+ def realname(self, value):
+ parts = value.split(' ')
+ if len(parts) == 2:
+ self.first_name = parts[0]
+ self.last_name = parts[1]
+ else:
+ self.last_name = parts[0]
+
+ def save(self, **kwargs):
+ if not self.id:
+ # set username
+ while not self.username:
+ username = uuid.uuid4().hex[:30]
+ try:
+ AstakosUser.objects.get(username = username)
+ except AstakosUser.DoesNotExist, e:
+ self.username = username
+ super(PendingThirdPartyUser, self).save(**kwargs)
+
+ class SessionCatalog(models.Model):
+ session_key = models.CharField(_('session key'), max_length=40)
+ user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
+
+
def create_astakos_user(u):
try:
AstakosUser.objects.get(user_ptr=u.pk)
except AstakosUser.DoesNotExist:
extended_user = AstakosUser(user_ptr_id=u.pk)
extended_user.__dict__.update(u.__dict__)
- # extended_user.renew_token()
extended_user.save()
- except:
- pass
+ except BaseException, e:
+ logger.exception(e)
- pass
+
-def superuser_post_syncdb(sender, **kwargs):
- # if there was created a superuser
- # associate it with an AstakosUser
+def fix_superusers(sender, **kwargs):
+ # Associate superusers with AstakosUser
admins = User.objects.filter(is_superuser=True)
for u in admins:
create_astakos_user(u)
-post_syncdb.connect(superuser_post_syncdb)
-def superuser_post_save(sender, instance, **kwargs):
- if instance.is_superuser:
- create_astakos_user(instance)
+def user_post_save(sender, instance, created, **kwargs):
+ if not created:
+ return
+ create_astakos_user(instance)
-def astakosuser_post_save(sender, instance, **kwargs):
- pass
-post_save.connect(superuser_post_save, sender=User)
+def set_default_group(user):
+ try:
+ default = AstakosGroup.objects.get(name='default')
+ Membership(
+ group=default, person=user, date_joined=datetime.now()).save()
+ except AstakosGroup.DoesNotExist, e:
+ logger.exception(e)
+
+
+def astakosuser_pre_save(sender, instance, **kwargs):
+ instance.aquarium_report = False
+ instance.new = False
+ try:
+ db_instance = AstakosUser.objects.get(id=instance.id)
+ except AstakosUser.DoesNotExist:
+ # create event
+ instance.aquarium_report = True
+ instance.new = True
+ else:
+ get = AstakosUser.__getattribute__
+ l = filter(lambda f: get(db_instance, f) != get(instance, f),
+ BILLING_FIELDS)
+ instance.aquarium_report = True if l else False
+
+
+def astakosuser_post_save(sender, instance, created, **kwargs):
+ if instance.aquarium_report:
+ report_user_event(instance, create=instance.new)
+ if not created:
+ return
+ set_default_group(instance)
+ # TODO handle socket.error & IOError
+ register_users((instance,))
+ instance.renew_token()
+
+
+def resource_post_save(sender, instance, created, **kwargs):
+ if not created:
+ return
+ register_resources((instance,))
+
+
+def send_quota_disturbed(sender, instance, **kwargs):
+ users = []
+ extend = users.extend
+ if sender == Membership:
+ if not instance.group.is_enabled:
+ return
+ extend([instance.person])
+ elif sender == AstakosUserQuota:
+ extend([instance.user])
+ elif sender == AstakosGroupQuota:
+ if not instance.group.is_enabled:
+ return
+ extend(instance.group.astakosuser_set.all())
+ elif sender == AstakosGroup:
+ if not instance.is_enabled:
+ return
+ quota_disturbed.send(sender=sender, users=users)
+
+
+def on_quota_disturbed(sender, users, **kwargs):
- print '>>>', locals()
++# print '>>>', locals()
+ if not users:
+ return
+ send_quota(users)
+ def renew_token(sender, instance, **kwargs):
+ if not instance.id:
+ instance.renew_token()
+
+post_syncdb.connect(fix_superusers)
+post_save.connect(user_post_save, sender=User)
+pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
+post_save.connect(astakosuser_post_save, sender=AstakosUser)
+post_save.connect(resource_post_save, sender=Resource)
+
+quota_disturbed = Signal(providing_args=["users"])
+quota_disturbed.connect(on_quota_disturbed)
+
+post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
+post_delete.connect(send_quota_disturbed, sender=Membership)
+post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
+post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
+post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
+post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
++
+ pre_save.connect(renew_token, sender=AstakosUser)
+ pre_save.connect(renew_token, sender=Service)
LOGGING_LEVEL = getattr(settings, 'ASTAKOS_LOGGING_LEVEL', INFO)
# Configurable email subjects
-INVITATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_INVITATION_EMAIL_SUBJECT',
- 'Invitation to %s alpha2 testing' % SITENAME)
+INVITATION_EMAIL_SUBJECT = getattr(
+ settings, 'ASTAKOS_INVITATION_EMAIL_SUBJECT',
+ 'Invitation to %s alpha2 testing' % SITENAME)
GREETING_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_GREETING_EMAIL_SUBJECT',
- 'Welcome to %s alpha2 testing' % SITENAME)
+ 'Welcome to %s alpha2 testing' % SITENAME)
FEEDBACK_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_FEEDBACK_EMAIL_SUBJECT',
- 'Feedback from %s alpha2 testing' % SITENAME)
-VERIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_VERIFICATION_EMAIL_SUBJECT',
- '%s alpha2 testing account activation is needed' % SITENAME)
-ADMIN_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJECT',
- '%s alpha2 testing account created (%%(user)s)' % SITENAME)
-HELPDESK_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT',
- '%s alpha2 testing account activated (%%(user)s)' % SITENAME)
-EMAIL_CHANGE_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT',
- 'Email change on %s alpha2 testing' % SITENAME)
-PASSWORD_RESET_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
- 'Password reset on %s alpha2 testing' % SITENAME)
+ 'Feedback from %s alpha2 testing' % SITENAME)
+VERIFICATION_EMAIL_SUBJECT = getattr(
+ settings, 'ASTAKOS_VERIFICATION_EMAIL_SUBJECT',
+ '%s alpha2 testing account activation is needed' % SITENAME)
+ACCOUNT_CREATION_SUBJECT = getattr(
+ settings, 'ASTAKOS_ACCOUNT_CREATION_SUBJECT',
+ '%s alpha2 testing account created (%%(user)s)' % SITENAME)
+GROUP_CREATION_SUBJECT = getattr(settings, 'ASTAKOS_GROUP_CREATION_SUBJECT',
+ '%s alpha2 testing group created (%%(group)s)' % SITENAME)
+HELPDESK_NOTIFICATION_EMAIL_SUBJECT = getattr(
+ settings, 'ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT',
+ '%s alpha2 testing account activated (%%(user)s)' % SITENAME)
+EMAIL_CHANGE_EMAIL_SUBJECT = getattr(
+ settings, 'ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT',
+ 'Email change on %s alpha2 testing' % SITENAME)
+PASSWORD_RESET_EMAIL_SUBJECT = getattr(
+ settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
+ 'Password reset on %s alpha2 testing' % SITENAME)
+
+# Set the quota holder component URI
+QUOTA_HOLDER_URL = getattr(settings, 'ASTAKOS_QUOTA_HOLDER_URL', '')
+
+# Set the cloud service properties
+SERVICES = getattr(settings, 'ASTAKOS_SERVICES', {
+ 'cyclades': {
+ 'url': 'https://node1.example.com/ui/',
+ 'resources': [{
+ 'name':'vm',
+ 'group':'compute',
+ 'uplimit':2,
+ 'desc': 'Number of virtual machines'
+ },{
+ 'name':'disk',
+ 'group':'compute',
+ 'uplimit':30*1024*1024*1024,
+ 'unit':'bytes',
+ 'desc': 'Virtual machine disk size'
+ },{
+ 'name':'cpu',
+ 'group':'compute',
+ 'uplimit':6,
+ 'desc': 'Number of virtual machine processors'
+ },{
+ 'name':'ram',
+ 'group':'compute',
+ 'uplimit':6*1024*1024*1024,
+ 'unit':'bytes',
+ 'desc': 'Virtual machines'
+ },{
+ 'name':'network.private',
+ 'group':'network',
+ 'uplimit':1,
+ 'desc': 'Private networks'
+ }
+ ]
+ },
+ 'pithos+': {
+ 'url': 'https://node2.example.com/ui/',
+ 'resources':[{
+ 'name':'diskspace',
+ 'group':'storage',
+ 'uplimit':5 * 1024 * 1024 * 1024,
+ 'unit':'bytes',
+ 'desc': 'Pithos account diskspace'
+ }]
+ }
+})
+
+# Set the billing URI
+AQUARIUM_URL = getattr(settings, 'ASTAKOS_AQUARIUM_URL', '')
+
+# Set how many objects should be displayed per page
+PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 8)
# Enforce token renewal on password change/reset
-NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
+NEWPASSWD_INVALIDATE_TOKEN = getattr(
+ settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
+
+
+RESOURCES_PRESENTATION_DATA = getattr(
+ settings, 'ASTAKOS_RESOURCES_PRESENTATION_DATA', {
+ 'groups': {
+ 'compute': {
+ 'help_text':'group compute help text',
+ 'is_abbreviation':False,
+ 'report_desc':'',
+ 'verbose_name':'compute',
+ },
+ 'storage': {
+ 'help_text':'group storage help text',
+ 'is_abbreviation':False,
+ 'report_desc':'',
+ 'verbose_name':'storage',
+ },
+ },
+ 'resources': {
+ 'pithos+.diskspace': {
+ 'help_text':'resource pithos+.diskspace help text',
+ 'is_abbreviation':False,
+ 'report_desc':'Pithos+ Diskspace',
+ 'placeholder':'eg. 10GB',
+ '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',
+ },
+ 'cyclades.disk': {
+ 'help_text':'resource cyclades.disk help text',
+ 'is_abbreviation':False,
+ 'report_desc':'Disk',
+ 'placeholder':'eg. 5GB, 2GB etc',
+ 'verbose_name':'disk'
+ },
+ 'cyclades.ram': {
+ 'help_text':'resource cyclades.ram help text',
+ 'is_abbreviation':True,
+ 'report_desc':'RAM',
+ 'placeholder':'eg. 4GB',
+ 'verbose_name':'ram'
+ },
+ 'cyclades.cpu': {
+ 'help_text':'resource cyclades.cpu help text',
+ 'is_abbreviation':True,
+ 'report_desc':'CPUs',
+ 'placeholder':'eg. 1',
+ 'verbose_name':'cpu'
+ },
+ 'cyclades.network.private': {
+ 'help_text':'resource cyclades.network.private help text',
+ 'is_abbreviation':False,
+ 'report_desc':'Network',
+ 'placeholder':'eg. 1',
+ 'verbose_name':'private network'
+ }
+
+ }
+
+ })
+
+ # Permit local account migration
+ ENABLE_LOCAL_ACCOUNT_MIGRATION = getattr(settings, 'ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION', True)
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
-from django.http import HttpResponseBadRequest, HttpResponseRedirect
++from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
-from django.contrib.auth import authenticate
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
+ from django.core.urlresolvers import reverse
+ from django.contrib.auth.decorators import login_required
from astakos.im.util import prepare_response, get_query
- from astakos.im.views import requires_anonymous
- from astakos.im.forms import LoginForm
+ from astakos.im.views import requires_anonymous, signed_terms_required
-from astakos.im.models import AstakosUser, PendingThirdPartyUser
++from astakos.im.models import PendingThirdPartyUser
+ from astakos.im.forms import LoginForm, ExtendedPasswordChangeForm
from astakos.im.settings import RATELIMIT_RETRIES_ALLOWED
+ from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION
+import astakos.im.messages as astakos_messages
+
from ratelimit.decorators import ratelimit
-retries = RATELIMIT_RETRIES_ALLOWED-1
-rate = str(retries)+'/m'
+retries = RATELIMIT_RETRIES_ALLOWED - 1
+rate = str(retries) + '/m'
+
@require_http_methods(["GET", "POST"])
@csrf_exempt
on_failure: the template name to render on login failure
"""
was_limited = getattr(request, 'limited', False)
- form = LoginForm(data=request.POST, was_limited=was_limited, request=request)
+ form = LoginForm(data=request.POST,
+ was_limited=was_limited,
+ request=request)
next = get_query(request).get('next', '')
+ username = get_query(request).get('key')
+
if not form.is_valid():
- return render_to_response(on_failure,
- {'login_form': form,
- 'next': next},
- context_instance=RequestContext(request))
+ return render_to_response(
+ on_failure,
+ {'login_form':form,
+ 'next':next,
+ 'key':username},
+ context_instance=RequestContext(request)
+ )
# get the user from the cash
user = form.user_cache
-
+
message = None
if not user:
- message = _('Cannot authenticate account')
+ message = _(astakos_messages.ACCOUNT_AUTHENTICATION_FAILED)
elif not user.is_active:
- message = _(astakos_messages.ACCOUNT_INACTIVE)
+ if not user.activation_sent:
- message = _('Your request is pending activation')
++ message = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION)
+ else:
- url = reverse('send_activation', kwargs={'user_id':user.id})
- message = _('You have not followed the activation link. \
- <a href="%s">Resend activation email?</a>' % url)
++ send_activation_url = reverse('send_activation', kwargs={'user_id':user.id})
++ message = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION) % locals()
+ elif user.provider not in ('local', ''):
- message = _(
- 'Local login is not the current authentication method for this account.'
- )
++ message = _(astakos_messages.NO_LOCAL_AUTH)
+
if message:
messages.error(request, message)
return render_to_response(on_failure,
- {'form': form},
+ {'login_form':form},
context_instance=RequestContext(request))
-
+
+ # hook for switching account to use third party authentication
+ if ENABLE_LOCAL_ACCOUNT_MIGRATION and username:
+ try:
+ new = PendingThirdPartyUser.objects.get(
+ username=username)
+ except:
+ messages.error(
+ request,
- _('Account failed to switch to %(provider)s' % locals())
++ _(astakos_messages.SWITCH_ACCOUNT_FAILURE)
+ )
+ return render_to_response(
+ on_failure,
+ {'login_form':form,
+ 'next':next},
+ context_instance=RequestContext(request)
+ )
+ else:
+ user.provider = new.provider
+ user.third_party_identifier = new.third_party_identifier
+ user.save()
+ new.delete()
+ messages.success(
+ request,
- _('Account successfully switched to %(provider)s' % user.__dict__)
++ _(astakos_messages.SWITCH_ACCOUNT_SUCCESS_WITH_PROVIDER) % user.__dict__
+ )
return prepare_response(request, user, next)
+
+ @require_http_methods(["GET", "POST"])
+ @signed_terms_required
+ @login_required
+ def password_change(request, template_name='registration/password_change_form.html',
+ post_change_redirect=None, password_change_form=ExtendedPasswordChangeForm):
+ if post_change_redirect is None:
+ post_change_redirect = reverse('django.contrib.auth.views.password_change_done')
+ if request.method == "POST":
+ form = password_change_form(
+ user=request.user,
+ data=request.POST,
+ session_key=request.session.session_key
+ )
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect(post_change_redirect)
+ else:
+ form = password_change_form(user=request.user)
+ return render_to_response(template_name, {
+ 'form': form,
- }, context_instance=RequestContext(request))
++ }, context_instance=RequestContext(request))
# or implied, of GRNET S.A.
from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
from django.utils.translation import ugettext as _
-from django.contrib import messages
from django.utils.http import urlencode
from django.contrib.auth import authenticate
- from django.http import HttpResponse, HttpResponseBadRequest
+ from django.http import (
+ HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
+ )
from django.core.exceptions import ValidationError
from django.views.decorators.http import require_http_methods
-from urllib import quote
-from urlparse import urlunsplit, urlsplit, urlparse, parse_qsl
+from urlparse import urlunsplit, urlsplit, parse_qsl
- from astakos.im.settings import COOKIE_NAME, COOKIE_DOMAIN
- from astakos.im.util import set_cookie
+ from astakos.im.settings import COOKIE_DOMAIN
+ from astakos.im.util import restrict_next
from astakos.im.functions import login as auth_login, logout
+import astakos.im.messages as astakos_messages
+
import logging
logger = logging.getLogger(__name__)
"""
next = request.GET.get('next')
if not next:
- return HttpResponseBadRequest(_('No next parameter'))
+ return HttpResponseBadRequest(_(astakos_messages.MISSING_NEXT_PARAMETER))
+ if not restrict_next(
+ next, domain=COOKIE_DOMAIN, allowed_schemes=('pithos',)
+ ):
- return HttpResponseForbidden(_('Not allowed next parameter'))
++ return HttpResponseForbidden(_(astakos_messages.NOT_ALLOWED_NEXT_PARAM))
force = request.GET.get('force', None)
response = HttpResponse()
if force == '':
except ValidationError, e:
return HttpResponseBadRequest(e)
# authenticate before login
- user = authenticate(email=request.user.email, auth_token=request.user.auth_token)
+ user = authenticate(email=request.user.email,
+ auth_token=request.user.auth_token
+ )
auth_login(request, user)
- set_cookie(response, user)
logger.info('Token reset for %s' % request.user.email)
parts = list(urlsplit(next))
- parts[3] = urlencode({'user': request.user.email, 'token': request.user.auth_token})
+ parts[3] = urlencode({'user': request.user.email,
+ 'token': request.user.auth_token
+ }
+ )
url = urlunsplit(parts)
response['Location'] = url
response.status_code = 302
from django.contrib import messages
from django.template import RequestContext
from django.views.decorators.http import require_http_methods
-from django.db.models import Q
-from django.core.exceptions import ValidationError
+ from django.http import HttpResponseRedirect
+ from django.core.urlresolvers import reverse
-from urlparse import urlunsplit, urlsplit
-from django.utils.http import urlencode
++from django.core.exceptions import ImproperlyConfigured
-from astakos.im.util import prepare_response, get_context, get_invitation
+from astakos.im.util import prepare_response, get_context
from astakos.im.views import requires_anonymous, render_response
- from astakos.im.models import AstakosUser
-from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
-
++from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION
+ 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.activation_backends import get_backend
+
+import astakos.im.messages as astakos_messages
+ import logging
+
+ logger = logging.getLogger(__name__)
+
class Tokens:
# these are mapped by the Shibboleth SP software
- SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName
+ SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName
SHIB_NAME = "HTTP_SHIB_INETORGPERSON_GIVENNAME"
SHIB_SURNAME = "HTTP_SHIB_PERSON_SURNAME"
SHIB_CN = "HTTP_SHIB_PERSON_COMMONNAME"
SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
SHIB_MAIL = "HTTP_SHIB_MAIL"
+
@require_http_methods(["GET", "POST"])
@requires_anonymous
- def login(request, backend=None, on_login_template='im/login.html',
- on_creation_template='im/third_party_registration.html',
- extra_context=None):
+ def login(
+ request,
+ login_template='im/login.html',
+ signup_template='im/third_party_check_local.html',
+ extra_context=None
+ ):
+ extra_context = extra_context or {}
+
tokens = request.META
-
+
try:
- eppn = tokens[Tokens.SHIB_EPPN]
- except KeyError:
- return HttpResponseBadRequest("Missing unique token in request")
-
- if Tokens.SHIB_DISPLAYNAME in tokens:
- realname = tokens[Tokens.SHIB_DISPLAYNAME]
- elif Tokens.SHIB_CN in tokens:
- realname = tokens[Tokens.SHIB_CN]
- elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
- realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
- else:
- return HttpResponseBadRequest("Missing user name in request")
-
+ eppn = tokens.get(Tokens.SHIB_EPPN)
+ if not eppn:
- raise KeyError(_('Missing unique token in request'))
++ raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_EPPN))
+ if Tokens.SHIB_DISPLAYNAME in tokens:
+ realname = tokens[Tokens.SHIB_DISPLAYNAME]
+ elif Tokens.SHIB_CN in tokens:
+ realname = tokens[Tokens.SHIB_CN]
+ elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
+ realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
+ else:
- raise KeyError(_('Missing user name in request'))
++ raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_NAME))
+ except KeyError, e:
+ extra_context['login_form'] = LoginForm(request=request)
+ messages.error(request, e)
+ return render_response(
+ login_template,
+ context_instance=get_context(request, extra_context)
+ )
+
affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
- email = tokens.get(Tokens.SHIB_MAIL, None)
-
+ email = tokens.get(Tokens.SHIB_MAIL, '')
+
try:
- user = AstakosUser.objects.get(provider='shibboleth',
- third_party_identifier=eppn)
+ user = AstakosUser.objects.get(
+ provider='shibboleth',
+ third_party_identifier=eppn
+ )
if user.is_active:
return prepare_response(request,
user,
request.GET.get('next'),
'renew' in request.GET)
+ elif not user.activation_sent:
- message = _('Your request is pending activation')
++ message = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION)
+ messages.error(request, message)
else:
- message = _(astakos_messages.ACCOUNT_INACTIVE)
+ urls = {}
- urls['send_activation'] = reverse(
++ urls['send_activation_url'] = reverse(
+ 'send_activation',
+ kwargs={'user_id':user.id}
+ )
- urls['signup'] = reverse(
++ urls['signup_url'] = reverse(
+ 'shibboleth_signup',
+ args= [user.username]
+ )
- message = _(
- 'You have not followed the activation link. \
- <a href="%(send_activation)s">Resend activation email?</a> or \
- <a href="%(signup)s">Provide new email?</a>' % urls
- )
++ message = _(astakos_messages.INACTIVE_ACCOUNT_CHANGE_EMAIL) % urls
messages.error(request, message)
- return render_response(on_login_template,
- login_form=LoginForm(request=request),
- context_instance=RequestContext(request))
+ return render_response(login_template,
+ login_form = LoginForm(request=request),
+ context_instance=RequestContext(request))
except AstakosUser.DoesNotExist, e:
- user = AstakosUser(third_party_identifier=eppn, realname=realname,
- affiliation=affiliation, provider='shibboleth',
- email=email)
+ # First time
try:
- if not backend:
- backend = get_backend(request)
- form = backend.get_signup_form(
- provider='shibboleth', instance=user)
- except Exception, e:
- form = SimpleBackend(request).get_signup_form(
+ user, created = PendingThirdPartyUser.objects.get_or_create(
+ third_party_identifier=eppn,
provider='shibboleth',
- instance=user)
- messages.error(request, e)
- return render_response(on_creation_template,
- signup_form=form,
- provider='shibboleth',
- context_instance=get_context(request,
- extra_context))
+ defaults=dict(
+ realname=realname,
+ affiliation=affiliation,
+ email=email
+ )
+ )
+ user.save()
+ except BaseException, e:
+ logger.exception(e)
+ template = login_template
+ extra_context['login_form'] = LoginForm(request=request)
- messages.error(request, _('Something went wrong.'))
++ messages.error(request, _(astakos_messages.GENERIC_ERROR))
+ else:
+ if not ENABLE_LOCAL_ACCOUNT_MIGRATION:
+ url = reverse(
+ 'shibboleth_signup',
+ args= [user.username]
+ )
+ return HttpResponseRedirect(url)
+ else:
+ template = signup_template
+ extra_context['username'] = user.username
+
+ extra_context['provider']='shibboleth'
+ return render_response(
+ template,
+ context_instance=get_context(request, extra_context)
+ )
+
+ @require_http_methods(["GET"])
+ @requires_anonymous
+ def signup(
+ request,
+ username,
+ backend=None,
+ on_creation_template='im/third_party_registration.html',
+ extra_context=None
+ ):
+ extra_context = extra_context or {}
+ if not username:
- return HttpResponseBadRequest(_('Missing key parameter.'))
++ return HttpResponseBadRequest(_(astakos_messages.MISSING_KEY_PARAMETER))
+ try:
+ pending = PendingThirdPartyUser.objects.get(username=username)
+ except PendingThirdPartyUser.DoesNotExist:
+ try:
+ user = AstakosUser.objects.get(username=username)
+ except AstakosUser.DoesNotExist:
- return HttpResponseBadRequest(_('Invalid key.'))
++ return HttpResponseBadRequest(_(astakos_messages.INVALID_KEY_PARAMETER))
+ else:
+ d = pending.__dict__
+ d.pop('_state', None)
+ d.pop('id', None)
+ user = AstakosUser(**d)
+ try:
+ backend = backend or get_backend(request)
+ except ImproperlyConfigured, e:
+ messages.error(request, e)
+ else:
+ extra_context['form'] = backend.get_signup_form(
+ provider='shibboleth',
+ instance=user
+ )
+ extra_context['provider']='shibboleth'
+ return render_response(
+ on_creation_template,
+ context_instance=get_context(request, extra_context)
- )
++ )
{% block body.right %}
{% if "local" in im_modules %}
<div class="form-stacked">
- <form action="{% url astakos.im.views.signup %}" method="post"
+ <form action="{% url signup %}" method="post"
class="innerlabels signup">{% csrf_token %}
- <h2><span>Provide an email address to complete the registration:</span></h2>
+ <h2><span>SIGN UP</span></h2>
<input type="hidden" name="next" value="{{ next }}">
<input type="hidden" name="code" value="{{ code }}">
<input type="hidden" name="provider" value={{ provider|default:"local" }}>
from django import template
--from django.core.urlresolvers import reverse, resolve
++from django.core.urlresolvers import resolve
from django.conf import settings
register = template.Library()
register = template.Library()
+DELIM = ','
+
+
+@register.filter
+def monthssince(joined_date):
+ now = datetime.datetime.now()
+ date = datetime.datetime(
+ year=joined_date.year, month=joined_date.month, day=1)
+ months = []
+
+ month = date.month
+ year = date.year
+ timestamp = calendar.timegm(date.utctimetuple())
+
+ while date < now:
+ months.append((year, month, timestamp))
+
+ if date.month < 12:
+ month = date.month + 1
+ year = date.year
+ else:
+ month = 1
+ year = date.year + 1
+
+ date = datetime.datetime(year=year, month=month, day=1)
+ timestamp = calendar.timegm(date.utctimetuple())
+
+ return months
+
+
@register.filter
def lookup(d, key):
- return d[key]
+ print d, key
+ return d.get(key)
+
+@register.filter
+def lookup_uni(d, key):
+ return d.get(unicode(key))
+
+
+@register.filter
+def dkeys(d):
+ return d.keys()
+
+
+@register.filter
+def month_name(month_number):
+ return calendar.month_name[month_number]
+
+
+@register.filter
+def todate(value, arg=''):
+ secs = int(value) / 1000
+ return datetime.datetime.fromtimestamp(secs)
+
+
+@register.filter
+def rcut(value, chars='/'):
+ return value.rstrip(chars)
+
+
+@register.filter
+def paginate(l, args):
- l = l or []
- page, delim, sorting = args.partition(DELIM)
- if sorting:
- if isinstance(l, QuerySet):
- l = l.order_by(sorting)
- elif isinstance(l, list):
- default = ''
- if sorting.endswith('_date'):
- default = datetime.datetime.utcfromtimestamp(0)
- l.sort(key=lambda i: getattr(i, sorting)
- if getattr(i, sorting) else default)
- paginator = Paginator(l, PAGINATE_BY)
- try:
- paginator.len
- except AttributeError:
- paginator._count = len(list(l))
-
- try:
- page_number = int(page)
- except ValueError:
- if page == 'last':
- page_number = paginator.num_pages
- else:
- page_number = 1
- try:
- page = paginator.page(page_number)
- except EmptyPage:
- page = paginator.page(1)
- return page
++ l = l or []
++ page, delim, sorting = args.partition(DELIM)
++ if sorting:
++ if isinstance(l, QuerySet):
++ l = l.order_by(sorting)
++ elif isinstance(l, list):
++ default = ''
++ if sorting.endswith('_date'):
++ default = datetime.datetime.utcfromtimestamp(0)
++ l.sort(key=lambda i: getattr(i, sorting)
++ if getattr(i, sorting) else default)
++ paginator = Paginator(l, PAGINATE_BY)
++ try:
++ paginator.len
++ except AttributeError:
++ paginator._count = len(list(l))
++
++ try:
++ page_number = int(page)
++ except ValueError:
++ if page == 'last':
++ page_number = paginator.num_pages
++ else:
++ page_number = 1
++ try:
++ page = paginator.page(page_number)
++ except EmptyPage:
++ page = paginator.page(1)
++ return page
+
+
+@register.filter
+def concat(str1, str2):
+ if not str2:
+ return str(str1)
+ return '%s%s%s' % (str1, DELIM, str2)
+
+
+@register.filter
+def items(d):
+ if isinstance(d, defaultdict):
+ return d.iteritems()
+ return d
+
+
+@register.filter
+def get_value_after_dot(value):
+ return value.split(".")[1]
+
+@register.filter
+def strip_http(value):
+ return value.replace('http://','')[:-1]
+
+
+from math import log
+unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 0, 0, 0, 0])
+
+@register.filter
+def sizeof_fmt(num):
+
+ """Human friendly file size"""
+ if math.isinf(num):
+ return 'Unlimited'
+ if num > 1:
+ exponent = min(int(log(num, 1024)), len(unit_list) - 1)
+ quotient = float(num) / 1024**exponent
+ unit, num_decimals = unit_list[exponent]
+ format_string = '{0:.%sf} {1}' % (num_decimals)
+ return format_string.format(quotient, unit)
+ if num == 0:
+ return '0 bytes'
+ if num == 1:
+ return '1 byte'
+ else:
+ return '';
+
+@register.filter
+def isinf(v):
+ if math.isinf(v):
+ return 'Unlimited'
+ else:
+ return v
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, EMAILCHANGE_ENABLED
urlpatterns = patterns('astakos.im.views',
- url(r'^$', 'index', {}, name='index'),
- url(r'^login/?$', 'index', {}, name='login'),
- url(r'^profile/?$',
- 'edit_profile', {}, name='edit_profile'),
- url(r'^feedback/?$', 'feedback', {}, name='feedback'),
- url(r'^signup/?$', 'signup',
- {'on_success': 'im/login.html',
- 'extra_context': {'login_form': LoginForm()}},
- name='signup'),
- url(r'^logout/?$', 'logout',
- {'template': 'im/login.html',
- 'extra_context': {'login_form': LoginForm()}},
- name='logout'),
- url(r'^activate/?$', 'activate', {}, name='activate'),
- url(r'^approval_terms/?$',
- 'approval_terms', {}, name='latest_terms'),
- url(r'^approval_terms/(?P<term_id>\d+)/?$',
- 'approval_terms'),
- url(r'^password/?$',
- 'change_password', {}, name='password_change'),
- url(r'^resources/?$',
- 'resource_list', {}, name='resource_list'),
- url(r'^billing/?$', 'billing', {}, name='billing'),
- url(r'^timeline/?$', 'timeline', {}, name='timeline'),
- url(r'^group/add/complete/?$', 'group_add_complete', {},
- name='group_add_complete'),
- url(r'^group/add/(?P<kind_name>\w+)?$',
- 'group_add', {}, name='group_add'),
- url(r'^group/list/?$',
- 'group_list', {}, name='group_list'),
- url(r'^group/(?P<group_id>\d+)/?$', 'group_detail',
- {}, name='group_detail'),
- url(r'^group/search/?$',
- 'group_search', {}, name='group_search'),
- url(r'^group/all/?$',
- 'group_all', {}, name='group_all'),
- url(r'^group/(?P<group_id>\d+)/join/?$', 'group_join',
- {}, name='group_join'),
- url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave',
- {}, name='group_leave'),
- url(
- r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/approve/?$',
- 'approve_member', {}, name='approve_member'),
- url(
- r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/disapprove/?$',
- 'disapprove_member', {}, name='disapprove_member'),
- url(r'^group/create/?$', 'group_create_list', {},
- name='group_create_list'),
- )
+ url(r'^$', 'index', {}, name='index'),
+ url(r'^login/?$', 'index', {}, name='login'),
- url(r'^profile/?$', 'edit_profile'),
- url(r'^feedback/?$', 'feedback'),
- url(r'^signup/?$', 'signup', {'on_success':'im/login.html', 'extra_context':{'login_form':LoginForm()}}),
- url(r'^logout/?$', 'logout', {'template':'im/login.html', 'extra_context':{'login_form':LoginForm()}}),
- url(r'^activate/?$', 'activate'),
++ url(r'^profile/?$','edit_profile', {}, name='edit_profile'),
++ url(r'^feedback/?$', 'feedback', {}, name='feedback'),
++ url(r'^signup/?$', 'signup', {'on_success': 'im/login.html', 'extra_context': {'login_form': LoginForm()}}, name='signup'),
++ url(r'^logout/?$', 'logout', {'template': 'im/login.html', 'extra_context': {'login_form': LoginForm()}}, name='logout'),
++ url(r'^activate/?$', 'activate', {}, name='activate'),
+ url(r'^approval_terms/?$', 'approval_terms', {}, name='latest_terms'),
+ url(r'^approval_terms/(?P<term_id>\d+)/?$', 'approval_terms'),
- url(r'^send/activation/(?P<user_id>\d+)/?$', 'send_activation', {}, name='send_activation')
++ url(r'^send/activation/(?P<user_id>\d+)/?$', 'send_activation', {}, name='send_activation'),
++ url(r'^resources/?$', 'resource_list', {}, name='resource_list'),
++ url(r'^billing/?$', 'billing', {}, name='billing'),
++ url(r'^timeline/?$', 'timeline', {}, name='timeline'),
++ url(r'^group/add/complete/?$', 'group_add_complete', {}, name='group_add_complete'),
++ url(r'^group/add/(?P<kind_name>\w+)?$', 'group_add', {}, name='group_add'),
++ url(r'^group/list/?$', 'group_list', {}, name='group_list'),
++ url(r'^group/(?P<group_id>\d+)/?$', 'group_detail', {}, name='group_detail'),
++ url(r'^group/search/?$', 'group_search', {}, name='group_search'),
++ url(r'^group/all/?$', 'group_all', {}, name='group_all'),
++ url(r'^group/(?P<group_id>\d+)/join/?$', 'group_join', {}, name='group_join'),
++ url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave', {}, name='group_leave'),
++ url(r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/approve/?$', 'approve_member', {}, name='approve_member'),
++ url(r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/disapprove/?$', 'disapprove_member', {}, name='disapprove_member'),
++ url(r'^group/create/?$', 'group_create_list', {}, name='group_create_list')
+ )
+
+
if EMAILCHANGE_ENABLED:
urlpatterns += patterns('astakos.im.views',
- url(r'^email_change/?$',
- 'change_email', {}, name='email_change'),
- url(
- r'^email_change/confirm/(?P<activation_key>\w+)/', 'change_email', {},
- name='email_change_confirm')
- )
-
+ url(r'^email_change/?$', 'change_email', {}, name='email_change'),
+ url(r'^email_change/confirm/(?P<activation_key>\w+)/?$', 'change_email', {},
+ name='email_change_confirm')
+ )
+
urlpatterns += patterns('astakos.im.target',
- url(r'^login/redirect/?$', 'redirect.login')
-)
+ url(r'^login/redirect/?$', 'redirect.login')
+ )
if 'local' in IM_MODULES:
urlpatterns += patterns('astakos.im.target',
import datetime
import time
--from urllib import quote
-
-from urlparse import urlsplit, urlunsplit, urlparse
-
++from urlparse import urlparse
from datetime import tzinfo, timedelta
++
from django.http import HttpResponse, HttpResponseBadRequest, urlencode
from django.template import RequestContext
--from django.utils.translation import ugettext as _
from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
-from django.core.exceptions import ValidationError
-from django.contrib.sessions.backends.base import SessionBase
+from django.core.exceptions import ValidationError, ObjectDoesNotExist
- from django.db.models.fields import Field
+from django.utils.translation import ugettext as _
-from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
+from astakos.im.models import AstakosUser, Invitation
- from astakos.im.settings import COOKIE_NAME, \
- COOKIE_DOMAIN, COOKIE_SECURE, FORCE_PROFILE_UPDATE, LOGGING_LEVEL
+ from astakos.im.settings import (
- INVITATIONS_PER_LEVEL, COOKIE_DOMAIN, FORCE_PROFILE_UPDATE, LOGGING_LEVEL
++ COOKIE_DOMAIN, FORCE_PROFILE_UPDATE
+ )
from astakos.im.functions import login
+import astakos.im.messages as astakos_messages
+
logger = logging.getLogger(__name__)
+
class UTC(tzinfo):
- def utcoffset(self, dt):
- return timedelta(0)
+ def utcoffset(self, dt):
+ return timedelta(0)
+
+ def tzname(self, dt):
+ return 'UTC'
- def tzname(self, dt):
- return 'UTC'
+ def dst(self, dt):
+ return timedelta(0)
- def dst(self, dt):
- return timedelta(0)
def isoformat(d):
- """Return an ISO8601 date string that includes a timezone."""
+ """Return an ISO8601 date string that includes a timezone."""
+
+ return d.replace(tzinfo=UTC()).isoformat()
- return d.replace(tzinfo=UTC()).isoformat()
def epoch(datetime):
- return int(time.mktime(datetime.timetuple())*1000)
+ return int(time.mktime(datetime.timetuple()) * 1000)
+
-def get_context(request, extra_context={}, **kwargs):
- if not extra_context:
- extra_context = {}
+def get_context(request, extra_context=None, **kwargs):
+ extra_context = extra_context or {}
extra_context.update(kwargs)
return RequestContext(request, extra_context)
code = request.POST.get('code')
if not code:
return
- invitation = Invitation.objects.get(code = code)
+ invitation = Invitation.objects.get(code=code)
if invitation.is_consumed:
- raise ValueError(_('Invitation is used'))
+ raise ValueError(_(astakos_messages.INVITATION_CONSUMED_ERR))
if reserved_email(invitation.username):
- raise ValueError(_('Email: %s is reserved' % invitation.username))
+ email = invitation.username
- raise ValueError(_(astakos_messages.EMAIL_RESRVED) % locals())
++ raise ValueError(_(astakos_messages.EMAIL_RESERVED) % locals())
return invitation
+ def restrict_next(url, domain=None, allowed_schemes=()):
+ """
+ Return url if having the supplied ``domain`` (if present) or one of the ``allowed_schemes``.
+ Otherwise return None.
+
+ >>> print restrict_next('/im/feedback', '.okeanos.grnet.gr')
+ /im/feedback
+ >>> print restrict_next('pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
+ //pithos.okeanos.grnet.gr/im/feedback
+ >>> print restrict_next('https://pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
+ https://pithos.okeanos.grnet.gr/im/feedback
+ >>> print restrict_next('pithos://127.0.0,1', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('pithos://127.0.0,1', '.okeanos.grnet.gr', allowed_schemes=('pithos'))
+ pithos://127.0.0,1
+ >>> print restrict_next('node1.example.com', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('//node1.example.com', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('https://node1.example.com', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('https://node1.example.com')
+ https://node1.example.com
+ >>> print restrict_next('//node1.example.com')
+ //node1.example.com
+ >>> print restrict_next('node1.example.com')
+ //node1.example.com
+ """
+ if not url:
+ return
+ parts = urlparse(url, scheme='http')
+ if not parts.netloc and not parts.path.startswith('/'):
+ # fix url if does not conforms RFC 1808
+ url = '//%s' % url
+ parts = urlparse(url, scheme='http')
+ # TODO more scientific checks?
+ if not parts.netloc: # internal url
+ return url
+ elif not domain:
+ return url
+ elif parts.netloc.endswith(domain):
+ return url
+ elif parts.scheme in allowed_schemes:
+ return url
def prepare_response(request, user, next='', renew=False):
"""Return the unique username and the token
or user has not a valid token.
"""
renew = renew or (not user.auth_token)
- renew = renew or (user.auth_token_expires and user.auth_token_expires < datetime.datetime.now())
+ renew = renew or (user.auth_token_expires < datetime.datetime.now())
if renew:
- user.renew_token()
+ user.renew_token(
+ flush_sessions=True,
+ current_key=request.session.session_key
+ )
try:
user.save()
except ValidationError, e:
# authenticate before login
user = authenticate(email=user.email, auth_token=user.auth_token)
login(request, user)
- set_cookie(response, user)
request.session.set_expiry(user.auth_token_expires)
-
+
if not next:
- next = reverse('index')
-
+ next = reverse('astakos.im.views.index')
+
response['Location'] = next
response.status_code = 302
return response
-
- def set_cookie(response, user):
- expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
- cookie_value = quote(user.email + '|' + user.auth_token)
- response.set_cookie(COOKIE_NAME, value=cookie_value,
- expires=expire_fmt, path='/',
- domain=COOKIE_DOMAIN, secure=COOKIE_SECURE)
- msg = 'Cookie [expiring %s] set for %s' % (
- user.auth_token_expires,
- user.email
- )
- logger.log(LOGGING_LEVEL, msg)
-
-
class lazy_string(object):
def __init__(self, function, *args, **kwargs):
- self.function=function
- self.args=args
- self.kwargs=kwargs
-
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+
def __str__(self):
if not hasattr(self, 'str'):
- self.str=self.function(*self.args, **self.kwargs)
+ self.str = self.function(*self.args, **self.kwargs)
return self.str
+
def reverse_lazy(*args, **kwargs):
return lazy_string(reverse, *args, **kwargs)
# or implied, of GRNET S.A.
import logging
-import socket
+import calendar
+import inflect
+
+engine = inflect.engine()
-from smtplib import SMTPException
from urllib import quote
from functools import wraps
- from datetime import datetime, timedelta
- from collections import defaultdict
++from datetime import datetime
-from django.core.mail import send_mail
-from django.http import (
- HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
-)
-from django.shortcuts import redirect
-from django.template.loader import render_to_string
-from django.utils.translation import ugettext as _
-from django.core.urlresolvers import reverse
-from django.contrib.auth.decorators import login_required
from django.contrib import messages
+from django.contrib.auth.decorators import login_required
- from django.contrib.auth.views import password_change
+from django.core.urlresolvers import reverse
from django.db import transaction
- from django.db.models import Q
-from django.utils.http import urlencode
from django.db.utils import IntegrityError
- from django.forms.fields import URLField
-from django.contrib.auth.views import password_change
-from django.core.exceptions import ValidationError
-from django.views.decorators.http import require_http_methods
+from django.http import (HttpResponse, HttpResponseBadRequest,
+ HttpResponseForbidden, HttpResponseRedirect,
+ HttpResponseBadRequest, Http404)
+from django.shortcuts import redirect
+from django.template import RequestContext, loader as template_loader
+from django.utils.http import urlencode
+from django.utils.translation import ugettext as _
- from django.views.generic.create_update import (create_object, delete_object,
++from django.views.generic.create_update import (delete_object,
+ get_model_and_form_class)
- from django.views.generic.list_detail import object_list, object_detail
- from django.http import HttpResponseBadRequest
++from django.views.generic.list_detail import object_list
+from django.core.xheaders import populate_xheaders
- from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
- Resource, EmailChange, GroupKind, Membership,
- AstakosGroupQuota, RESOURCE_SEPARATOR)
-from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
++from django.template.loader import render_to_string
+from django.views.decorators.http import require_http_methods
- from django.db.models.query import QuerySet
-
from astakos.im.activation_backends import get_backend, SimpleBackend
- from astakos.im.util import get_context, prepare_response, set_cookie, get_query
-from astakos.im.util import (
- get_context, prepare_response, get_query, restrict_next
-)
-from astakos.im.forms import *
-from astakos.im.functions import (send_greeting, send_feedback, SendMailError,
- invite as invite_func, logout as auth_logout, activate as activate_func,
- send_activation as send_activation_func
-)
-from astakos.im.settings import (
- DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_DOMAIN, IM_MODULES,
- SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
-)
++
++from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
++ EmailChange, GroupKind, Membership,
++ RESOURCE_SEPARATOR)
++from astakos.im.util import get_context, prepare_response, get_query, restrict_next
+from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
+ FeedbackForm, SignApprovalTermsForm,
- ExtendedPasswordChangeForm, EmailChangeForm,
++ EmailChangeForm,
+ AstakosGroupCreationForm, AstakosGroupSearchForm,
+ AstakosGroupUpdateForm, AddGroupMembersForm,
- AstakosGroupSortForm, MembersSortForm,
++ MembersSortForm,
+ TimelineForm, PickResourceForm,
+ AstakosGroupCreationSummaryForm)
+from astakos.im.functions import (send_feedback, SendMailError,
+ logout as auth_logout,
+ activate as activate_func,
- switch_account_to_shibboleth,
++ send_activation as send_activation_func,
+ send_group_creation_notification,
+ SendNotificationError)
+from astakos.im.endpoints.qh import timeline_charge
- from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, LOGOUT_NEXT,
++from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT,
+ LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA)
+from astakos.im.tasks import request_billing
+from astakos.im.api.callpoint import AstakosCallpoint
+
+import astakos.im.messages as astakos_messages
logger = logging.getLogger(__name__)
-
+DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
+ 'https://', '')"""
+
+callpoint = AstakosCallpoint()
+
- def render_response(template, tab=None, status=200, reset_cookie=False,
- context_instance=None, **kwargs):
+ 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
if tab is None:
tab = template.partition('_')[0].partition('.html')[0]
kwargs.setdefault('tab', tab)
- html = render_to_string(template, kwargs, context_instance=context_instance)
+ html = template_loader.render_to_string(
+ template, kwargs, context_instance=context_instance)
response = HttpResponse(html, status=status)
- if reset_cookie:
- set_cookie(response, context_instance['request'].user)
return response
return func(request, *args, **kwargs)
return wrapper
+
@require_http_methods(["GET", "POST"])
@signed_terms_required
- def index(request, login_template_name='im/login.html', extra_context=None):
+ def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
"""
If there is logged on user renders the profile page otherwise renders login page.
im/profile.html or im/login.html or ``template_name`` keyword argument.
"""
+ extra_context = extra_context or {}
template_name = login_template_name
if request.user.is_authenticated():
- return HttpResponseRedirect(reverse('edit_profile'))
- return render_response(template_name,
- login_form=LoginForm(request=request),
- context_instance=get_context(request, extra_context))
+ return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
+
+ return render_response(
+ template_name,
+ login_form = LoginForm(request=request),
+ context_instance = get_context(request, extra_context)
+ )
+
@require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
The view expectes the following settings are defined:
* LOGIN_URL: login uri
-- * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
- * ASTAKOS_DEFAULT_FROM_EMAIL: from email
"""
+ extra_context = extra_context or {}
status = None
message = None
form = InvitationForm()
try:
prev_token = request.user.auth_token
user = form.save()
- reset_cookie = user.auth_token != prev_token
- form = ProfileForm(instance=user)
- next = request.POST.get('next')
+ form = ProfileForm(
+ instance=user,
+ session_key=request.session.session_key
+ )
+ next = restrict_next(
+ request.POST.get('next'),
+ domain=COOKIE_DOMAIN
+ )
if next:
return redirect(next)
- msg = _('<p>Profile has been updated successfully</p>')
- messages.add_message(request, messages.SUCCESS, msg)
+ msg = _(astakos_messages.PROFILE_UPDATED)
+ messages.success(request, msg)
except ValueError, ve:
- messages.add_message(request, messages.ERROR, ve)
+ messages.success(request, ve)
elif request.method == "GET":
- request.user.is_verified = True
- request.user.save()
+ if not request.user.is_verified:
+ request.user.is_verified = True
+ request.user.save()
return render_response(template_name,
- reset_cookie=reset_cookie,
- profile_form=form,
- context_instance=get_context(request,
- extra_context))
+ profile_form = form,
+ context_instance = get_context(request,
+ extra_context))
+
+@transaction.commit_manually
@require_http_methods(["GET", "POST"])
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
"""
An dictionary of variables to add to the template context.
**Template:**
-
+
im/signup.html or ``template_name`` keyword argument.
- im/signup_complete.html or ``on_success`` keyword argument.
+ im/signup_complete.html or ``on_success`` keyword argument.
"""
+ extra_context = extra_context or {}
if request.user.is_authenticated():
- return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
-
+ return HttpResponseRedirect(reverse('edit_profile'))
+
provider = get_query(request).get('provider', 'local')
+ id = get_query(request).get('id')
+ try:
+ instance = AstakosUser.objects.get(id=id) if id else None
+ except AstakosUser.DoesNotExist:
+ instance = None
+
try:
if not backend:
backend = get_backend(request)
- form = backend.get_signup_form(provider)
+ form = backend.get_signup_form(provider, instance)
except Exception, e:
form = SimpleBackend(request).get_signup_form(provider)
- messages.add_message(request, messages.ERROR, e)
+ messages.error(request, e)
if request.method == 'POST':
if form.is_valid():
user = form.save(commit=False)
if additional_email != user.email:
user.additionalmail_set.create(email=additional_email)
msg = 'Additional email: %s saved for user %s.' % (
- additional_email, user.email)
- logger.log(LOGGING_LEVEL, msg)
+ additional_email,
+ user.email
+ )
+ logger._log(LOGGING_LEVEL, msg, [])
if user and user.is_active:
next = request.POST.get('next', '')
- return prepare_response(request, user, next=next)
+ response = prepare_response(request, user, next=next)
+ transaction.commit()
+ return response
messages.add_message(request, status, message)
- transaction.commit()
- return render_response(on_success,
- context_instance=get_context(request, extra_context))
+ return render_response(
+ on_success,
+ context_instance=get_context(
+ request,
+ extra_context
+ )
+ )
except SendMailError, e:
+ logger.exception(e)
+ status = messages.ERROR
message = e.message
- messages.add_message(request, status, message)
+ messages.error(request, message)
+ transaction.rollback()
except BaseException, e:
+ logger.exception(e)
- status = messages.ERROR
- message = _('Something went wrong.')
- messages.add_message(request, status, message)
+ message = _(astakos_messages.GENERIC_ERROR)
+ messages.error(request, message)
logger.exception(e)
+ transaction.rollback()
return render_response(template_name,
- signup_form = form,
- provider = provider,
+ signup_form=form,
+ provider=provider,
context_instance=get_context(request, extra_context))
+
@require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
**Settings:**
* LOGIN_URL: login uri
-- * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
"""
+ extra_context = extra_context or {}
if request.method == 'GET':
form = FeedbackForm()
if request.method == 'POST':
try:
send_feedback(msg, data, request.user, email_template_name)
except SendMailError, e:
- message = e.message
- status = messages.ERROR
+ messages.error(request, message)
else:
- message = _('Feedback successfully sent')
- status = messages.SUCCESS
- messages.add_message(request, status, message)
+ message = _(astakos_messages.FEEDBACK_SENT)
+ messages.success(request, message)
return render_response(template_name,
- feedback_form = form,
- context_instance = get_context(request, extra_context))
+ feedback_form=form,
+ context_instance=get_context(request, extra_context))
+
- @require_http_methods(["GET", "POST"])
+ @require_http_methods(["GET"])
+@signed_terms_required
def logout(request, template='registration/logged_out.html', extra_context=None):
"""
- Wraps `django.contrib.auth.logout` and delete the cookie.
+ Wraps `django.contrib.auth.logout`.
"""
+ extra_context = extra_context or {}
response = HttpResponse()
if request.user.is_authenticated():
email = request.user.email
elif LOGOUT_NEXT:
response['Location'] = LOGOUT_NEXT
response.status_code = 301
- return response
- messages.success(request, _(astakos_messages.LOGOUT_SUCCESS))
- context = get_context(request, extra_context)
- response.write(
- template_loader.render_to_string(template, context_instance=context))
+ else:
- messages.add_message(request, messages.SUCCESS, _('<p>You have successfully logged out.</p>'))
++ messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
+ context = get_context(request, extra_context)
+ response.write(render_to_string(template, context_instance=context))
return response
+
@require_http_methods(["GET", "POST"])
@transaction.commit_manually
-def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
+def activate(request, greeting_email_template_name='im/welcome_email.txt',
+ helpdesk_email_template_name='im/helpdesk_notification.txt'):
"""
Activates the user identified by the ``auth`` request parameter, sends a welcome email
and renews the user token.
try:
user = AstakosUser.objects.get(auth_token=token)
except AstakosUser.DoesNotExist:
- return HttpResponseBadRequest(_('No such user'))
-
+ return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
+
if user.is_active:
- message = _('Account already active.')
- messages.add_message(request, messages.ERROR, message)
+ message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
+ messages.error(request, message)
return index(request)
-
+
try:
- local_user = AstakosUser.objects.get(
- ~Q(id=user.id),
- email=user.email,
- is_active=True
- )
- except AstakosUser.DoesNotExist:
- try:
- activate_func(
- user,
- greeting_email_template_name,
- helpdesk_email_template_name,
- verify_email=True
- )
- response = prepare_response(request, user, next, renew=True)
- transaction.commit()
- return response
- except SendMailError, e:
- message = e.message
- messages.error(request, message)
- transaction.rollback()
- return index(request)
- except BaseException, e:
- message = _(astakos_messages.GENERIC_ERROR)
- messages.error(request, message)
- logger.exception(e)
- transaction.rollback()
- return index(request)
- else:
- try:
- user = switch_account_to_shibboleth(
- user,
- local_user,
- greeting_email_template_name
- )
- response = prepare_response(request, user, next, renew=True)
- transaction.commit()
- return response
- except SendMailError, e:
- message = e.message
- messages.error(request, message)
- transaction.rollback()
- return index(request)
- except BaseException, e:
- message = _(astakos_messages.GENERIC_ERROR)
- messages.error(request, message)
- logger.exception(e)
- transaction.rollback()
- return index(request)
+ activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
+ response = prepare_response(request, user, next, renew=True)
+ transaction.commit()
+ return response
+ except SendMailError, e:
+ message = e.message
+ messages.add_message(request, messages.ERROR, message)
+ transaction.rollback()
+ return index(request)
+ except BaseException, e:
+ status = messages.ERROR
- message = _('Something went wrong.')
++ message = _(astakos_messages.GENERIC_ERROR)
+ messages.add_message(request, messages.ERROR, message)
+ logger.exception(e)
+ transaction.rollback()
+ return index(request)
+
@require_http_methods(["GET", "POST"])
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
+ extra_context = extra_context or {}
term = None
terms = None
if not term_id:
terms = f.read()
if request.method == 'POST':
- next = request.POST.get('next')
+ next = restrict_next(
+ request.POST.get('next'),
+ domain=COOKIE_DOMAIN
+ )
if not next:
- next = reverse('astakos.im.views.index')
+ next = reverse('index')
form = SignApprovalTermsForm(request.POST, instance=request.user)
if not form.is_valid():
return render_response(template_name,
return HttpResponseRedirect(next)
else:
form = None
- if request.user.is_authenticated() and not request.user.signed_terms():
+ if request.user.is_authenticated() and not request.user.signed_terms:
form = SignApprovalTermsForm(instance=request.user)
return render_response(template_name,
- terms = terms,
- approval_terms_form = form,
- context_instance = get_context(request, extra_context))
+ terms=terms,
+ approval_terms_form=form,
+ context_instance=get_context(request, extra_context))
+
@require_http_methods(["GET", "POST"])
- @signed_terms_required
- def change_password(request):
- return password_change(request,
- post_change_redirect=reverse('edit_profile'),
- password_change_form=ExtendedPasswordChangeForm)
-
-
- @require_http_methods(["GET", "POST"])
- @signed_terms_required
@login_required
+ @signed_terms_required
@transaction.commit_manually
def change_email(request, activation_key=None,
email_template_name='registration/email_change_email.txt',
try:
ec = form.save(email_template_name, request)
except SendMailError, e:
- status = messages.ERROR
msg = e
+ messages.error(request, msg)
transaction.rollback()
except IntegrityError, e:
- status = messages.ERROR
- msg = _('There is already a pending change email request.')
+ msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
+ messages.error(request, msg)
else:
- status = messages.SUCCESS
- msg = _('Change email request has been registered succefully.\
- You are going to receive a verification email in the new address.')
+ msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
+ messages.success(request, msg)
transaction.commit()
- return render_response(form_template_name,
- form=form,
- context_instance=get_context(request,
- extra_context))
- messages.add_message(request, status, msg)
- return render_response(form_template_name,
- form = form,)
++ return render_response(
++ form_template_name,
++ form=form,
++ context_instance=get_context(request, extra_context)
++ )
+
+
+ def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
+ extra_context = extra_context or {}
+ try:
+ u = AstakosUser.objects.get(id=user_id)
+ except AstakosUser.DoesNotExist:
- messages.error(request, _('Invalid user id'))
++ messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
+ else:
+ try:
+ send_activation_func(u)
- msg = _('Activation sent.')
++ msg = _(astakos_messages.ACTIVATION_SENT)
+ messages.success(request, msg)
+ except SendMailError, e:
+ messages.error(request, e)
+ return render_response(
+ template_name,
- login_form = LoginForm(request=request),
++ login_form = LoginForm(request=request),
+ context_instance = get_context(
+ request,
+ extra_context
+ )
+ )
+
+class ResourcePresentation():
+
+ def __init__(self, data):
+ self.data = data
+
+ def update_from_result(self, result):
+ if result.is_success:
+ for r in result.data:
+ rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
+ if not rname in self.data['resources']:
+ self.data['resources'][rname] = {}
+
+ self.data['resources'][rname].update(r)
+ self.data['resources'][rname]['id'] = rname
+ group = r.get('group')
+ if not group in self.data['groups']:
+ self.data['groups'][group] = {}
+
+ self.data['groups'][r.get('group')].update({'name': r.get('group')})
+
+ def test(self, quota_dict):
+ for k, v in quota_dict.iteritems():
+ rname = k
+ value = v
+ if not rname in self.data['resources']:
- self.data['resources'][rname] = {}
++ self.data['resources'][rname] = {}
+
+
+ self.data['resources'][rname]['value'] = value
+
+
+ def update_from_result_report(self, result):
+ if result.is_success:
+ for r in result.data:
+ rname = r.get('name')
+ if not rname in self.data['resources']:
+ self.data['resources'][rname] = {}
+
+ self.data['resources'][rname].update(r)
+ self.data['resources'][rname]['id'] = rname
+ group = r.get('group')
+ if not group in self.data['groups']:
+ self.data['groups'][group] = {}
+
+ self.data['groups'][r.get('group')].update({'name': r.get('group')})
+
+ def get_group_resources(self, group):
+ return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
+
+ def get_groups_resources(self):
+ for g in self.data['groups']:
+ yield g, self.get_group_resources(g)
+
+ def get_quota(self, group_quotas):
- print '!!!!!', group_quotas
+ for r, v in group_quotas.iteritems():
+ rname = str(r)
+ quota = self.data['resources'].get(rname)
+ quota['value'] = v
+ yield quota
+
+
+ def get_policies(self, policies_data):
+ for policy in policies_data:
+ rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
+ policy.update(self.data['resources'].get(rname))
+ yield policy
+
+ def __repr__(self):
+ return self.data.__repr__()
+
+ def __iter__(self, *args, **kwargs):
+ return self.data.__iter__(*args, **kwargs)
+
+ def __getitem__(self, *args, **kwargs):
+ return self.data.__getitem__(*args, **kwargs)
+
+ def get(self, *args, **kwargs):
+ return self.data.get(*args, **kwargs)
+
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_add(request, kind_name='default'):
+
+ result = callpoint.list_resources()
+ resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
+ resource_catalog.update_from_result(result)
+
+ if not result.is_success:
+ messages.error(
+ request,
+ 'Unable to retrieve system resources: %s' % result.reason
+ )
+
+ try:
+ kind = GroupKind.objects.get(name=kind_name)
+ except:
+ return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
+
+
+ post_save_redirect = '/im/group/%(id)s/'
+ context_processors = None
+ model, form_class = get_model_and_form_class(
+ model=None,
+ form_class=AstakosGroupCreationForm
+ )
+
+ if request.method == 'POST':
+ form = form_class(request.POST, request.FILES)
+ if form.is_valid():
+ return render_response(
+ template='im/astakosgroup_form_summary.html',
+ context_instance=get_context(request),
+ form = AstakosGroupCreationSummaryForm(form.cleaned_data),
+ policies = resource_catalog.get_policies(form.policies()),
+ resource_catalog= resource_catalog,
+ )
+
+ else:
+ now = datetime.now()
+ data = {
+ 'kind': kind,
+ }
+ for group, resources in resource_catalog.get_groups_resources():
+ data['is_selected_%s' % group] = False
+ for resource in resources:
+ data['%s_uplimit' % resource] = ''
+
+ form = form_class(data)
+
+ # Create the template, context, response
+ 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,
+ 'kind': kind,
+ 'resource_catalog':resource_catalog,
+ }, context_processors)
+ return HttpResponse(t.render(c))
+
+
+#@require_http_methods(["POST"])
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_add_complete(request):
+ model = AstakosGroup
+ form = AstakosGroupCreationSummaryForm(request.POST)
+ if form.is_valid():
+ d = form.cleaned_data
+ d['owners'] = [request.user]
+ result = callpoint.create_groups((d,)).next()
+ if result.is_success:
+ new_object = result.data[0]
+ msg = _(astakos_messages.OBJECT_CREATED) %\
+ {"verbose_name": model._meta.verbose_name}
+ messages.success(request, msg, fail_silently=True)
+
+ # send notification
+ try:
+ send_group_creation_notification(
+ template_name='im/group_creation_notification.txt',
+ dictionary={
+ 'group': new_object,
+ 'owner': request.user,
+ 'policies': d.get('policies', [])
+ }
+ )
+ except SendNotificationError, e:
+ messages.error(request, e, fail_silently=True)
+ post_save_redirect = '/im/group/%(id)s/'
+ return HttpResponseRedirect(post_save_redirect % new_object)
+ else:
+ d = {"verbose_name": model._meta.verbose_name,
+ "reason":result.reason}
+ msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
+ messages.error(request, msg, fail_silently=True)
+ return render_response(
+ template='im/astakosgroup_form_summary.html',
+ context_instance=get_context(request),
+ form=form)
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_list(request):
+ none = request.user.astakos_groups.none()
+ sorting = request.GET.get('sorting')
+ query = """
+ SELECT auth_group.id,
+ %s AS groupname,
+ im_groupkind.name AS kindname,
+ im_astakosgroup.*,
+ owner.email AS groupowner,
+ (SELECT COUNT(*) FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND date_joined IS NOT NULL) AS approved_members_num,
+ (SELECT CASE WHEN(
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s) IS NULL
+ THEN 0 ELSE 1 END) AS membership_status
+ FROM im_astakosgroup
+ INNER JOIN im_membership ON (
+ im_astakosgroup.group_ptr_id = im_membership.group_id)
+ INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
+ INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
+ LEFT JOIN im_astakosuser_owner ON (
+ im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
+ LEFT JOIN auth_user as owner ON (
+ im_astakosuser_owner.astakosuser_id = owner.id)
+ WHERE im_membership.person_id = %s
+ """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id)
+
+ if sorting:
+ query = query+" ORDER BY %s ASC" %sorting
+ else:
+ query = query+" ORDER BY groupname ASC"
+ q = AstakosGroup.objects.raw(query)
+
+
+
+ # Create the template, context, response
+ template_name = "%s/%s_list.html" % (
+ q.model._meta.app_label,
+ q.model._meta.object_name.lower()
+ )
+ extra_context = dict(
+ is_search=False,
+ q=q,
+ sorting=request.GET.get('sorting'),
+ page=request.GET.get('page', 1)
+ )
+ return render_response(template_name,
+ context_instance=get_context(request, extra_context)
+ )
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_detail(request, group_id):
+ q = AstakosGroup.objects.select_related().filter(pk=group_id)
+ q = q.extra(select={
+ 'is_member': """SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'is_owner': """SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_astakosuser_owner
+ WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+ AND astakosuser_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'kindname': """SELECT name FROM im_groupkind
+ WHERE id = im_astakosgroup.kind_id"""})
+
+ model = q.model
+ context_processors = None
+ mimetype = None
+ try:
+ obj = q.get()
+ except AstakosGroup.DoesNotExist:
+ raise Http404("No %s found matching the query" % (
+ model._meta.verbose_name))
+
+ update_form = AstakosGroupUpdateForm(instance=obj)
+ addmembers_form = AddGroupMembersForm()
+ if request.method == 'POST':
+ update_data = {}
+ addmembers_data = {}
+ for k, v in request.POST.iteritems():
+ if k in update_form.fields:
+ update_data[k] = v
+ if k in addmembers_form.fields:
+ addmembers_data[k] = v
+ update_data = update_data or None
+ addmembers_data = addmembers_data or None
+ update_form = AstakosGroupUpdateForm(update_data, instance=obj)
+ addmembers_form = AddGroupMembersForm(addmembers_data)
+ if update_form.is_valid():
+ update_form.save()
+ if addmembers_form.is_valid():
+ try:
+ map(obj.approve_member, addmembers_form.valid_users)
+ except AssertionError:
+ msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
+ messages.error(request, msg)
+ addmembers_form = AddGroupMembersForm()
+
+ template_name = "%s/%s_detail.html" % (
+ model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ 'object': obj,
+ }, context_processors)
+
+ # validate sorting
+ sorting = request.GET.get('sorting')
+ if sorting:
+ form = MembersSortForm({'sort_by': sorting})
+ if form.is_valid():
+ sorting = form.cleaned_data.get('sort_by')
+
+
+
+ result = callpoint.list_resources()
+ resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
+ resource_catalog.update_from_result(result)
+
+
+ if not result.is_success:
+ messages.error(
+ request,
+ 'Unable to retrieve system resources: %s' % result.reason
+ )
+
- print '######', obj.quota
-
+ extra_context = {'update_form': update_form,
+ 'addmembers_form': addmembers_form,
+ 'page': request.GET.get('page', 1),
+ 'sorting': sorting,
+ 'resource_catalog':resource_catalog,
+ 'quota':resource_catalog.get_quota(obj.quota)}
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ response = HttpResponse(t.render(c), mimetype=mimetype)
+ populate_xheaders(
+ request, response, model, getattr(obj, obj._meta.pk.name))
+ return response
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_search(request, extra_context=None, **kwargs):
+ q = request.GET.get('q')
+ sorting = request.GET.get('sorting')
+ if request.method == 'GET':
+ form = AstakosGroupSearchForm({'q': q} if q else None)
+ else:
+ form = AstakosGroupSearchForm(get_query(request))
+ if form.is_valid():
+ q = form.cleaned_data['q'].strip()
+ if q:
+ queryset = AstakosGroup.objects.select_related()
+ queryset = queryset.filter(name__contains=q)
+ queryset = queryset.filter(approval_date__isnull=False)
+ queryset = queryset.extra(select={
+ 'groupname': DB_REPLACE_GROUP_SCHEME,
+ 'kindname': "im_groupkind.name",
+ 'approved_members_num': """
+ SELECT COUNT(*) FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND date_joined IS NOT NULL""",
+ 'membership_approval_date': """
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s""" % request.user.id,
+ 'is_member': """
+ SELECT CASE WHEN EXISTS(
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'is_owner': """
+ SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_astakosuser_owner
+ WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+ AND astakosuser_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'is_owner': """SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_astakosuser_owner
+ WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+ AND astakosuser_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ })
+ if sorting:
+ # TODO check sorting value
+ queryset = queryset.order_by(sorting)
+ else:
+ queryset = queryset.order_by("groupname")
+
+ else:
+ queryset = AstakosGroup.objects.none()
+ return object_list(
+ request,
+ queryset,
+ paginate_by=PAGINATE_BY,
+ page=request.GET.get('page') or 1,
+ template_name='im/astakosgroup_list.html',
+ extra_context=dict(form=form,
+ is_search=True,
+ q=q,
+ sorting=sorting))
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_all(request, extra_context=None, **kwargs):
+ q = AstakosGroup.objects.select_related()
+ q = q.filter(approval_date__isnull=False)
+ q = q.extra(select={
+ 'groupname': DB_REPLACE_GROUP_SCHEME,
+ 'kindname': "im_groupkind.name",
+ 'approved_members_num': """
+ SELECT COUNT(*) FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND date_joined IS NOT NULL""",
+ 'membership_approval_date': """
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s""" % request.user.id,
+ 'is_member': """
+ SELECT CASE WHEN EXISTS(
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'is_owner': """SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_astakosuser_owner
+ WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+ AND astakosuser_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id, })
+ sorting = request.GET.get('sorting')
+ if sorting:
+ # TODO check sorting value
+ q = q.order_by(sorting)
+ else:
+ q = q.order_by("groupname")
+
+ return object_list(
+ request,
+ q,
+ paginate_by=PAGINATE_BY,
+ page=request.GET.get('page') or 1,
+ template_name='im/astakosgroup_list.html',
+ extra_context=dict(form=AstakosGroupSearchForm(),
+ is_search=True,
+ sorting=sorting))
+
+
+#@require_http_methods(["POST"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+def group_join(request, group_id):
+ m = Membership(group_id=group_id,
+ person=request.user,
+ date_requested=datetime.now())
+ try:
+ m.save()
+ post_save_redirect = reverse(
+ 'group_detail',
+ kwargs=dict(group_id=group_id))
+ return HttpResponseRedirect(post_save_redirect)
+ except IntegrityError, e:
+ logger.exception(e)
+ msg = _(astakos_messages.GROUP_JOIN_FAILURE)
+ messages.error(request, msg)
+ return group_search(request)
+
+
+@require_http_methods(["POST"])
+@signed_terms_required
+@login_required
+def group_leave(request, group_id):
+ try:
+ m = Membership.objects.select_related().get(
+ group__id=group_id,
+ person=request.user)
+ except Membership.DoesNotExist:
- return HttpResponseBadRequest(_(astakos_messages.NOT_A_MEMBER))
++ return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
+ if request.user in m.group.owner.all():
+ return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
+ return delete_object(
+ request,
+ model=Membership,
+ object_id=m.id,
+ template_name='im/astakosgroup_list.html',
+ post_delete_redirect=reverse(
+ 'group_detail',
+ kwargs=dict(group_id=group_id)))
+
+
+def handle_membership(func):
+ @wraps(func)
+ def wrapper(request, group_id, user_id):
+ try:
+ m = Membership.objects.select_related().get(
+ group__id=group_id,
+ person__id=user_id)
+ except Membership.DoesNotExist:
+ return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
+ else:
+ if request.user not in m.group.owner.all():
+ return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
+ func(request, m)
+ return group_detail(request, group_id)
+ return wrapper
+
+
+#@require_http_methods(["POST"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+@handle_membership
+def approve_member(request, membership):
+ try:
+ membership.approve()
+ realname = membership.person.realname
+ msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
+ messages.success(request, msg)
+ except AssertionError:
+ msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
+ messages.error(request, msg)
+ except BaseException, e:
+ logger.exception(e)
+ realname = membership.person.realname
+ msg = _(astakos_messages.GENERIC_ERROR)
+ messages.error(request, msg)
+
+
+@signed_terms_required
+@login_required
+@handle_membership
+def disapprove_member(request, membership):
+ try:
+ membership.disapprove()
+ realname = membership.person.realname
- msg = MEMBER_REMOVED % realname
++ msg = astakos_messages.MEMBER_REMOVED % realname
+ messages.success(request, msg)
+ except BaseException, e:
+ logger.exception(e)
+ msg = _(astakos_messages.GENERIC_ERROR)
+ messages.error(request, msg)
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+def resource_list(request):
- # if request.method == 'POST':
- # form = PickResourceForm(request.POST)
- # if form.is_valid():
- # r = form.cleaned_data.get('resource')
- # if r:
- # groups = request.user.membership_set.only('group').filter(
- # date_joined__isnull=False)
- # groups = [g.group_id for g in groups]
- # q = AstakosGroupQuota.objects.select_related().filter(
- # resource=r, group__in=groups)
- # else:
- # form = PickResourceForm()
- # q = AstakosGroupQuota.objects.none()
- #
- # return object_list(request, q,
- # template_name='im/astakosuserquota_list.html',
- # extra_context={'form': form, 'data':data})
-
+ def with_class(entry):
+ entry['load_class'] = 'red'
+ max_value = float(entry['maxValue'])
+ curr_value = float(entry['currValue'])
+ if max_value > 0 :
- entry['ratio'] = (curr_value / max_value) * 100
- else:
- entry['ratio'] = 0
++ entry['ratio'] = (curr_value / max_value) * 100
++ else:
++ entry['ratio'] = 0
+ if entry['ratio'] < 66:
+ entry['load_class'] = 'yellow'
+ if entry['ratio'] < 33:
+ entry['load_class'] = 'green'
+ return entry
+
+ def pluralize(entry):
+ entry['plural'] = engine.plural(entry.get('name'))
+ return entry
+
+ result = callpoint.get_user_status(request.user.id)
+ if result.is_success:
+ backenddata = map(with_class, result.data)
+ data = map(pluralize, result.data)
+ else:
+ data = None
+ messages.error(request, result.reason)
+ resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
+ resource_catalog.update_from_result_report(result)
+
+
+
+ return render_response('im/resource_list.html',
+ data=data,
+ context_instance=get_context(request),
- resource_catalog=resource_catalog,
++ resource_catalog=resource_catalog,
+ result=result)
+
+
+def group_create_list(request):
+ form = PickResourceForm()
+ return render_response(
+ template='im/astakosgroup_create_list.html',
+ context_instance=get_context(request),)
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+def billing(request):
+
+ today = datetime.today()
+ month_last_day = calendar.monthrange(today.year, today.month)[1]
- data['resources'] = map(with_class, data['resources'])
+ start = request.POST.get('datefrom', None)
+ if start:
+ today = datetime.fromtimestamp(int(start))
+ month_last_day = calendar.monthrange(today.year, today.month)[1]
+
+ start = datetime(today.year, today.month, 1).strftime("%s")
+ end = datetime(today.year, today.month, month_last_day).strftime("%s")
+ r = request_billing.apply(args=('pgerakios@grnet.gr',
+ int(start) * 1000,
+ int(end) * 1000))
+ data = {}
+
+ try:
+ status, data = r.result
+ data = _clear_billing_data(data)
+ if status != 200:
+ messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
+ except:
+ messages.error(request, r.result)
+
- print type(start)
-
+ return render_response(
+ template='im/billing.html',
+ context_instance=get_context(request),
+ data=data,
+ zerodate=datetime(month=1, year=1970, day=1),
+ today=today,
+ start=int(start),
+ month_last_day=month_last_day)
+
+
+def _clear_billing_data(data):
+
+ # remove addcredits entries
+ def isnotcredit(e):
+ return e['serviceName'] != "addcredits"
+
+ # separate services
+ def servicefilter(service_name):
+ service = service_name
+
+ def fltr(e):
+ return e['serviceName'] == service
+ return fltr
+
+ data['bill_nocredits'] = filter(isnotcredit, data['bill'])
+ data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
+ data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
+ data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
+
+ return data
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+def timeline(request):
+# data = {'entity':request.user.email}
+ timeline_body = ()
+ timeline_header = ()
+# form = TimelineForm(data)
+ form = TimelineForm()
+ if request.method == 'POST':
+ data = request.POST
+ form = TimelineForm(data)
+ if form.is_valid():
+ data = form.cleaned_data
+ timeline_header = ('entity', 'resource',
+ 'event name', 'event date',
+ 'incremental cost', 'total cost')
+ timeline_body = timeline_charge(
+ data['entity'], data['resource'],
+ data['start_date'], data['end_date'],
+ data['details'], data['operation'])
+
+ return render_response(template='im/timeline.html',
+ context_instance=get_context(request),
+ form=form,
+ timeline_header=timeline_header,
+ timeline_body=timeline_body)
- return data
++ return data
#ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT = 'Email change on %s alpha2 testing' % SITENAME
#ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT = 'Password reset on %s alpha2 testing' % SITENAME
+# Set the quota holder component URI
+#ASTAKOS_QUOTA_HOLDER_URL = ''
+
+# Set the cloud service properties
+# SERVICES = getattr(settings, 'ASTAKOS_SERVICES',
+# {'cyclades': {'url':'https://node1.example.com/ui/',
+# 'quota': {'vm': 2}},
+# 'pithos+': {'url':'https://node2.example.com/ui/',
+# 'quota': {'diskspace': 50 * 1024 * 1024 * 1024}}})
+
+# Set the billing URI
+#ASTAKOS_AQUARIUM_URL = ''
+
+# Set how many objects should be displayed per page
+#PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 10)
+
# Enforce token renewal on password change/reset
- NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
+ # NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
+
+ # Permit local account migration
+ # ENABLE_LOCAL_ACCOUNT_MIGRATION = getattr(settings, 'ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION', True)