Commit 03ef9b95 authored by Christos Stavrakakis's avatar Christos Stavrakakis

Merge branch 'devel-0.13' into develop

Conflicts:
	snf-cyclades-app/synnefo/api/servers.py
	snf-cyclades-app/synnefo/logic/backend.py
parents 296f2893 9c421726
......@@ -31,3 +31,6 @@ pithos/data
!*/synnefo/versions/__init__.py
snf-astakos-app/astakos/version.py
snf-tools/synnefo_tools/version.py
snf-quotaholder-app/quotaholder_django/version.py
*.iml
*.graffle
......@@ -97,9 +97,9 @@ the dictionary as the account string to identify the user accessible resources.
Registration Flow
-----------------
Responsible for handling the account registration and activation requests is the ``signup`` view. This view checks whether it is a request for a local account. If this is not the case, the user is navigated to the third-party provider to authenticate against it and upon success is redirected back in the ``signup`` view. If the supplied information is valid and an inactive account is created. Then the appropriate ``ActivationBackend`` handles the account activation: the ``InvitationsBackend`` if the invitation mechanism is enabled and the ``SimpleBackend`` otherwise.
Responsible for handling the account registration and activation requests is the ``signup`` view. This view checks whether it is a request for a local account. If this is not the case, the user is navigated to the third-party provider to authenticate against it and upon success is redirected back in the ``signup`` view. If the supplied information is valid and an inactive account is created. Then the appropriate ``ActivationBackend`` handles the account activation: the ``InvitationsBackend`` if the invitation mechanism is enabled and the ``SimpleBackend`` otherwise.
In the first case, if the request is accompanied with a valid invitation code the user is automatically activated and since it's email address (where received the invitation) is verified, acquires a valid token and is logged in the system. If there is no invitation associated with the request, the system check whether the email matches any of the ASTAKOS_RE_USER_EMAIL_PATTERNS and if it does it sends an email to the user to verify the email address, otherwise the system sends a notification email to the administrators and informs the user that the account activation will be moderated by them.
In the first case, if the request is accompanied with a valid invitation code the user is automatically activated and since its email address (where received the invitation) is verified, acquires a valid token and is logged in the system. If there is no invitation associated with the request, the system check whether the email matches any of the ASTAKOS_RE_USER_EMAIL_PATTERNS and if it does it sends an email to the user to verify the email address, otherwise the system sends a notification email to the administrators and informs the user that the account activation will be moderated by them.
If the invitation mechanism is not enabled, the ``SimpleBackend`` checks if the email address matches any of the ASTAKOS_RE_USER_EMAIL_PATTERNS or the moderation is not enabled and it sends a verification email, otherwise informs the user that the account is pending approval and sends a notification email to the admins.
......@@ -108,7 +108,7 @@ The verification email contains a link that navigates the user to ``activate`` v
If FORCE_PROFILE_UPDATE is set, after the first successful login the user is navigated first to the ``profile`` view, before been redirected to the ``next`` parameter value.
.. image:: images/astakos-signup.jpg
:scale: 100%
:scale: 80%
Login Flow
----------
......@@ -120,7 +120,7 @@ If ASTAKOS_RECAPTCHA_ENABLED is set and the user fails several times (ASTAKOS_RA
Upon success, the system renews the token (if it has been expired), logins the user and sets the cookie, before redirecting the user to the ``next`` parameter value.
.. image:: images/astakos-login.jpg
:scale: 100%
:scale: 80%
Approval Terms
--------------
......
# Copyright 2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
# Import general commission framework
from .exception import (CallError, CorruptedError, InvalidDataError,
ReturnButFail,
register_exception, register_exceptions)
from .callpoint import Callpoint, mkcallargs
from .specificator import (Specificator, SpecifyException,
Canonifier, CanonifyException,
Canonical,
Null, Nothing, Integer, Serial,
Text, Bytes, Tuple, ListOf, Dict, Args)
# Import standard implementations?
# 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 .specificator import CanonifyException
from .exception import CorruptedError, InvalidDataError, ReturnButFail
from .importing import imp_module
from re import compile as re_compile, sub as re_sub
class Callpoint(object):
api_spec = None
CorruptedError = CorruptedError
InvalidDataError = InvalidDataError
original_calls = None
def __init__(self, connection=None):
from json import loads, dumps
self.json_loads = loads
self.json_dumps = dumps
self.init_connection(connection)
original_calls = {}
self.original_calls = original_calls
canonifier = self.api_spec
if canonifier is None:
m = "No api spec given to '%s'" % (type(self).__name__,)
raise NotImplementedError(m)
for call_name, call_doc in canonifier.call_docs():
if hasattr(self, call_name):
# don't crash: wrap the function instead
#m = ( "Method '%s' defined both in natively "
# "in callpoint '%s' and in api spec '%s'" %
# (call_name,
# type(self).__name__,
# type(canonifier).__name__) )
#raise ValueError(m)
call_func = getattr(self, call_name)
if not callable(call_func):
m = ( "api spec '%s', method '%s' is not a "
"callable attribute in callpoint '%s'" %
( type(canonifier).__name__,
call_name,
type(self).__name ) )
raise ValueError(m)
original_calls[call_name] = call_func
def mk_call_func():
local_call_name = call_name
def call_func(**data):
return self.make_call(local_call_name, data)
call_func.__name__ = call_name
call_func.__doc__ = call_doc
return call_func
setattr(self, call_name, mk_call_func())
def init_connection(self, connection):
pass
def commit(self):
pass
def rollback(self):
pass
def do_make_call(self, call_name, data):
raise NotImplementedError
def validate_call(self, call_name):
return hasattr(self, call_name)
def make_call_from_json_description(self, json_description):
try:
description = self.json_loads(json_description)
except ValueError, e:
m = "Cannot load json description"
raise self.InvalidDataError(m)
data = self.make_call_from_description(description)
json_data = self.json_dumps(data) if data is not None else None
return json_data
def make_call_from_description(self, description):
try:
call_name = description['call_name']
call_data = description['call_data']
except (TypeError, KeyError), e:
m = "Invalid description"
raise self.InvalidDataError(m, e)
return self.make_call(call_name, call_data)
def make_call_from_json(self, call_name, json_data):
if json_data:
try:
data = self.json_loads(json_data)
except ValueError, e:
m = "Cannot load json data"
raise self.InvalidDataError(m, e)
else:
data = None
data = self.make_call(call_name, data)
json_data = self.json_dumps(data)
return json_data
def make_call(self, call_name, data):
if call_name.startswith('_'):
m = "Invalid call '%s'" % (call_name,)
raise self.InvalidDataError(m)
canonifier = self.api_spec
try:
data = canonifier.canonify_input(call_name, data)
except CanonifyException, e:
m = "Invalid input to call '%s'" % (call_name,)
raise self.InvalidDataError(m, e)
if not self.validate_call(call_name):
m = "Cannot find specified call '%s'" % (call_name,)
raise self.CorruptedError(m)
call_func = self.original_calls.get(call_name, None)
try:
if call_func is None:
data = self.do_make_call(call_name, data)
else:
data = call_func(**data)
self.commit()
except ReturnButFail, e:
self.rollback()
data = e.data
except Exception, e:
self.rollback()
raise
try:
data = canonifier.canonify_output(call_name, data)
except CanonifyException, e:
m = "Invalid output from call '%s'" % (call_name,)
raise self.CorruptedError(m, e)
return data
def mkcallargs(**kw):
return kw
versiontag_pattern = re_compile('[^a-zA-Z0-9_-]')
def mk_versiontag(version):
if not version or version == 'v':
return ''
return '_' + re_sub(versiontag_pattern, '_', version)
def get_callpoint(pointname, version=None, automake=None, **kw):
versiontag = mk_versiontag(version)
components = pointname.split('.')
appname = components[0]
if len(components) < 2:
raise ValueError("invalid pointname '%s'" % (pointname,))
category = components[1]
if not category or category not in ['clients', 'servers']:
raise ValueError("invalid pointname '%s'" % (pointname,))
modname = ('%s.callpoint.API_Callpoint%s'
% (pointname, versiontag))
try:
API_Callpoint = imp_module(modname)
return API_Callpoint
except ImportError:
if not automake:
raise
if category != 'clients':
m = ("Can only auto-make callpoint in 'clients' not '%s'" % (category,))
raise ValueError(m)
components = components[1:]
if not components:
raise ValueError("invalid pointname '%s'" % (pointname))
pointname = '.'.join(components)
if pointname == 'quotaholder':
apiname = 'quotaholder.api.QuotaholderAPI'
else:
apiname = '%s.api.API_Spec%s' % (pointname, versiontag)
API_Spec = imp_module(apiname)
basename = 'commissioning.clients.%s.API_Callpoint' % (automake,)
BaseCallpoint = imp_module(basename)
stupidpython = (appname,
version if version is not None else 'v',
pointname,
automake)
class AutoCallpoint(BaseCallpoint):
appname, version, pointname, automake = stupidpython
api_spec = API_Spec()
return AutoCallpoint
# 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.
class CallError(Exception):
exceptions = {}
def __new__(cls, *args, **kw):
call_error = kw.get('call_error', None)
if call_error is None:
call_error = cls.__name__
else:
call_error = str(call_error)
cls = CallError.exceptions.get(call_error, cls)
self = Exception.__new__(cls)
return self
def __init__(self, *args, **kw):
self.call_error = kw.get('call_error', self.__class__.__name__)
self.args = args
def __str__(self):
return '\n--------\n'.join(str(x) for x in self.args)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__,
','.join(str(x) for x in self.args))
@classmethod
def from_exception(cls, exc):
args = None
try:
args = tuple(exc.args)
except (TypeError, AttributeError), e:
pass
if args is None:
args = (str(exc),)
self = cls(*args, call_error=exc.__class__.__name__)
return self
def to_dict(self):
return {'call_error': self.call_error,
'error_args': self.args}
@classmethod
def from_dict(cls, dictobj):
args = None
try:
if 'error_args' in dictobj and 'call_error' in dictobj:
args = dictobj['error_args']
call_error = dictobj['call_error']
except TypeError, e:
pass
if args is None:
args = (str(dictobj),)
call_error = 'UnknownError'
self = cls(*args, call_error=call_error)
return self
def register_exceptions(*exceptions):
for exception in exceptions:
if not issubclass(exception, CallError):
m = "Registering '%s': is not a CallError subclass" % (exception,)
raise ValueError(m)
CallError.exceptions[exception.__name__] = exception
def register_exception(exc):
register_exceptions(exc)
return exc
@register_exception
class CorruptedError(CallError):
pass
@register_exception
class InvalidDataError(CallError):
pass
class ReturnButFail(Exception):
def __init__(self, retval=None):
self.data = retval
# 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 imp import find_module, load_module
_modules = {}
def imp_module(fullname):
if fullname in _modules:
return _modules[fullname]
components = fullname.split('.')
if not components:
raise ValueError('invalid module name')
module = None
modulepath = []
for name in components:
if not name:
raise ValueError("Relative paths not allowed")
modulepath.append(name)
modulename = '.'.join(modulepath)
if modulename in _modules:
module = _modules[modulename]
elif hasattr(module, name):
module = getattr(module, name)
elif not hasattr(module, '__path__'):
m = find_module(name)
module = load_module(modulename, *m)
else:
try:
m = find_module(name, module.__path__)
module = load_module(modulename, *m)
except ImportError:
m = "No module '%s' in '%s'" % (name, module.__path__)
raise ImportError(m)
_modules[modulename] = module
return module
def list_modules():
return sorted(_modules.keys())
This diff is collapsed.
# 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.
try:
from collections import OrderedDict
except ImportError:
from .ordereddict import OrderedDict
from itertools import chain
from cStringIO import StringIO