Initial cleanup of the codebase

Change-Id: Idbc92ea3c2d7ee4d4807d1d83ceee9a299b9a9f7
diff --git a/service/providers/__init__.py b/service/providers/__init__.py
new file mode 100644
index 0000000..497a9b3
--- /dev/null
+++ b/service/providers/__init__.py
@@ -0,0 +1,32 @@
+import logging
+
+from APIFactory import URIBuilder
+
+from types import *
+from notifications import *
+from utils import *
+
+
+__author__ = 'hanl'
+
+# instance reference for authentication provider
+# must be set before usage!
+PROVIDER = None
+
+
+def init_app(config):
+    provider_name = config.get('AUTH_PROVIDER', 'providers.CustomProvider')
+    split = provider_name.split('.')
+    # get last element, so you have the class name
+    _class = split[len(split) - 1]
+
+    provider_name = provider_name.replace("." + _class, "")
+    module = __import__(provider_name)
+    obj = getattr(module, _class, None)
+    if obj is None:
+        raise KeyError("the provider class '%s.%s' is undefined or could not be found!" % (provider_name, _class))
+    instance = obj(config)
+    logging.info("successfully loaded provider '%s.%s'" % (provider_name, _class))
+    global PROVIDER
+    PROVIDER = instance
+
diff --git a/service/providers/notifications.py b/service/providers/notifications.py
new file mode 100644
index 0000000..925eaba
--- /dev/null
+++ b/service/providers/notifications.py
@@ -0,0 +1,91 @@
+import os
+
+__author__ = 'hanl'
+
+path = os.path.dirname(__file__)
+# modify this to change the Template Directory
+TEMPLATE_DIR = '/templates/'
+
+
+class Handler(object):
+    def register(self, key, value):
+        setattr(self, key, value)
+
+
+    def get(self, key):
+        if hasattr(self, key):
+            return getattr(self, key)
+
+
+
+# for possible email usage
+class MessageTemplate(object):
+    def __init__(self, template_name=None, html=True, values={}):
+        self.values = values
+        self.template_name = template_name
+        self.html = html
+
+    def render(self):
+        content = open(os.path.join(path, TEMPLATE_DIR, self.template_name)).read()
+        for k, v in self.values.iteritems():
+            content = content.replace('%%s%%' % k, v)
+        return content
+
+
+class HtmlBuilder(object):
+    def __init__(self):
+        self.title = None
+        self.head = None
+        self.body = None
+
+    def appendHead(self, head):
+        if not self.head:
+            self.head = ""
+        self.head.append("\n" + head)
+
+    def appendBody(self, body):
+        if not self.body:
+            self.body = ""
+        self.head.append("\n" + body)
+
+
+
+
+class NotificationHandler(object):
+
+    @staticmethod
+    def isError(response):
+        try:
+            raw_json = response.json()
+            if raw_json.get('errors') or raw_json.get('error') or raw_json.get('err'):
+                return True
+        except ValueError:
+            if response.status_code != 200:
+                return True
+        return False
+
+    @staticmethod
+    def getMessage(response):
+        pass
+
+    @staticmethod
+    def notify(json, notify_func, **kwargs):
+        if "category" not in kwargs.keys():
+            kwargs['category'] = "danger"
+
+        if json.get('errors'):
+            for error in json.get("errors"):
+                notify_func(error[1], "danger")
+        elif json.get('error'):
+            if json.get('error_description'):
+                notify_func(json.get('error_description'), **kwargs)
+            else:
+                notify_func(json.get('error'), **kwargs)
+        elif json.get('err'):
+            notify_func(json.get('errstr'), **kwargs)
+
+    @staticmethod
+    def getLocalized(code):
+        # return _("status_" + str(code))
+        return "this is a default message"
+
diff --git a/service/providers/requestUtils.py b/service/providers/requestUtils.py
new file mode 100644
index 0000000..9de2a8c
--- /dev/null
+++ b/service/providers/requestUtils.py
@@ -0,0 +1,26 @@
+from requests.auth import AuthBase
+
+__author__ = 'hanl'
+
+
+class Oauth2Auth(AuthBase):
+    '''
+    starts needs to match the provider name
+    '''
+    def __init__(self, token=None):
+        self.token = token
+
+    def __call__(self, r):
+        if self.token is not None:
+            r.headers['Authorization'] = "OAuth2 Bearer " + self.token
+        return r
+
+
+class CustomAuth(AuthBase):
+    def __init__(self, token=None):
+        self.token = token
+
+    def __call__(self, r):
+        if self.token is not None:
+            r.headers['Authorization'] = "api_token " + self.token
+        return r
diff --git a/service/providers/types.py b/service/providers/types.py
new file mode 100644
index 0000000..a56bd75
--- /dev/null
+++ b/service/providers/types.py
@@ -0,0 +1,244 @@
+from flask import flash
+from requests.auth import HTTPBasicAuth
+import logging
+import APIFactory
+from providers import utils
+from providers.utils import User
+from providers import requestUtils
+from providers.notifications import NotificationHandler
+
+
+__author__ = 'hanl'
+
+
+class Provider(object):
+    def __init__(self, auth_header, config):
+        self.error = False
+        self.auth_header = auth_header.__name__
+        self.type = None
+        self.secret = config.get('SECRET_KEY', None)
+        self.admins = config.get('ADMINS')[self.__class__.__name__]
+
+
+    def authenticate(self, username=None, password=None):
+        pass
+
+    def get_user(self, session=None, full=False):
+        if not session and not session['user_id']:
+            return ValueError("username and session must be provided!")
+
+    def login(self, session=None, user=None):
+        pass
+
+    # deprecated
+    def is_error(self):
+        if self.error:
+            self.error = False
+            return True
+        return self.error
+
+    # deprecated
+    def is_admin(self, reference):
+        '''
+        sub classes should override and call this function and give it the
+        respective reference, e.g token or a username or whatever reference should be
+        used to identify admins
+        :param reference:
+        :return:
+        '''
+        return False
+
+    def logout(self, session=None):
+        pass
+
+    def is_authenticated(self):
+        '''
+        check tokens for non expired status
+
+        :return:
+        '''
+        pass
+
+    def get_header(self, session):
+        token = session[self.type]
+        obj = getattr(__import__(requestUtils), self.auth_header, None)
+        print "the instance: %s" % str(obj)
+        return obj(token)
+
+
+class CustomProvider(Provider):
+    def __init__(self, config):
+        super(CustomProvider, self).__init__(requestUtils.CustomAuth, config)
+        self.type = 'api_token'
+
+    def authenticate(self, username=None, password=None):
+        pass
+
+    def get_user(self, session=None, full=False):
+        """
+        Returns the user model instance associated with the given request session.
+        If no user is retrieved no user instance is returned
+        """
+
+        if not session or not session['user_id']:
+            return None
+        if full:
+            id_token = session[self.type]
+            code = utils.decrypt_openid(secret=self.secret, token=id_token)
+            if "email" in code and "firstName" in code and "lastName" in code:
+                user = User(username=session['user_id'], email=code['email'], firstName=code['firstName'],
+                            lastName=code['lastName'], address=code['address'], institution=code['institution'],
+                            phone=code['phone'])
+                return user
+            else:
+                NotificationHandler.notify({"error": "Data could not be loaded!"}, flash)
+        return User(username=session['user_id'])
+
+
+    def get_token(self, session, full=False):
+        if full:
+            id_token = session[self.type]
+            code = utils.decrypt_openid(secret=self.secret, token=id_token)
+            if "email" in code and "firstName" in code and "lastName" in code:
+                user = User(username=session['user_id'], email=code['email'], firstName=code['firstName'],
+                            lastName=code['lastName'], address=code['address'], institution=code['institution'],
+                            phone=code['phone'])
+                return user
+            else:
+                NotificationHandler.notify({"error": "Data could not be loaded!"}, flash)
+
+    def login(self, session=None, user=None):
+        '''
+        :param login_func: client specific login function
+        :param user: user object to register user for
+        :return: boolean if login successful
+        '''
+        super(CustomProvider, self).login(session, user)
+
+        response = APIFactory.get("auth/requestToken", auth=HTTPBasicAuth(username=user.username,
+                                                                          password=user.password))
+        user.password = None
+
+        if response is None:
+            return False
+        elif NotificationHandler.isError(response):
+            NotificationHandler.notify(response.json(), flash)
+            return False
+        print "the response %i:%s" % (response.status_code, str(response.content))
+        if 'token_type' in response.content:
+            json = response.json()
+            session[self.type] = json['id_token']
+        else:
+	    token = response.content.replace(self.type, '')
+            session[self.type] = token.replace(' ', '')
+	logging.info("the resulting authentication token %s" % session[self.type])
+        return True
+
+
+    def logout(self, session=None):
+        if self.type not in session:
+            return False
+        session.pop(self.type, None)
+        return True
+
+    def is_authenticated(self):
+        '''
+        check that oauth id_token and access_token are not expired!
+        :return:
+        '''
+        pass
+
+
+class OAuth2Provider(Provider):
+    def __init__(self, config):
+        super(OAuth2Provider, self).__init__(requestUtils.Oauth2Auth)
+        self.type = 'access_token'
+        self.client = config.get("OAUTH2_CLIENT_ID", "")
+        self.secret = config.get("OAUTH2_CLIENT_SECRET", "")
+        self.scopes = config.get("OPENID_CONNECT_SCOPES", "")
+
+    def authenticate(self, username=None, password=None):
+        pass
+
+    def get_user(self, session=None, full=False):
+        """
+        Returns the user model instance associated with the given request session.
+        If no user is retrieved None is returned
+        """
+        if not session or not session['user_id']:
+            return None
+
+        if full and "openid" in self.scopes and "profile" in self.scopes:
+            id_token = session['id_token']
+            code = utils.decrypt_openid(secret=self.secret, token=id_token)
+            user = User(username=session['user_id'], email=code['email'], firstName=code['firstName'],
+                        lastName=code['lastName'], address=code['address'], institution=code['institution'],
+                        phone=code['phone'])
+            return user
+        elif full:
+            response = APIFactory.get("user/info", auth=self.get_header(session[self.type]))
+            if response is None:
+                return None
+            elif NotificationHandler.isError(response):
+                NotificationHandler.notify(response.json(), flash)
+                return None
+            else:
+                code = response.json()
+                user = User(username=session['user_id'], email=code['email'], firstName=code['firstName'],
+                            lastName=code['lastName'], address=code['address'], institution=code['institution'],
+                            phone=code['phone'])
+                return user
+        else:
+            # for the most tasks its only about to have a user object, not the actual data!
+            return User(username=session['user_id'])
+
+    def login(self, session=None, user=None):
+        '''
+        :param login_func: client specific login function
+        :param user: user object to register user for
+        :return: boolean if login successful
+        '''
+        super(OAuth2Provider, self).login(session, user)
+
+        params = {"username": user.username, "password": user.password,
+                  "grant_type": "password", "client_id": self.client,
+                  "client_secret": self.secret,
+                  "scope": self.scopes}
+        response = APIFactory.post(path='oauth2/token', params=params)
+        user.password = None
+
+        if response is None:
+            return False
+        elif NotificationHandler.isError(response):
+            NotificationHandler.notify(response.json(), flash)
+            return False
+        print "the response %i:%s" % (response.status_code, str(response.content))
+
+        session[self.type] = response.json()[self.type]
+        if "openid" in self.scopes:
+            session['id_token'] = response.json()['id_token']
+        else:
+            session['id_token'] = ""
+        return True
+
+    def is_admin(self, session):
+        if session['access_token'] in self.admins:
+            return True
+
+    def logout(self, session=None):
+        if self.type not in session:
+            return False
+
+        session.pop(self.type, None)
+        if 'id_token' in session:
+            session.pop('id_token', None)
+        return True
+
+
+    def is_authenticated(self):
+        '''
+        check that oauth id_token and access_token are not expired!
+        add function to auth decorator
+        :return:
+        '''
+        pass
diff --git a/service/providers/utils.py b/service/providers/utils.py
new file mode 100644
index 0000000..41fb8bc
--- /dev/null
+++ b/service/providers/utils.py
@@ -0,0 +1,63 @@
+from flask_login import UserMixin
+import jwt
+
+
+__author__ = 'hanl'
+
+
+def decrypt_openid(secret, token=None):
+    values = {}
+    if token:
+        values = jwt.decode(jwt=token, key=secret)
+        print "decoded values %s" % str(values)
+    return values
+
+
+def encrypt_string(secret, *args):
+    jwt_string = jwt.encode(args, key=secret)
+    print "encoded string %s" % str(jwt_string)
+    return jwt_string
+
+
+
+class User(UserMixin):
+    '''
+    the user object
+    '''
+
+    def __init__(self, username, password=None, email=None, firstName=None, lastName=None, address=None,
+                 institution=None,
+                 phone=None, country=None):
+        if not username:
+            raise ValueError("the username must be set")
+        self.username = username
+        self.password = password
+        self.email = email
+        self.firstName = firstName
+        self.lastName = lastName
+        self.address = address
+        self.institution = institution
+        self.phone = phone
+        self.country = country
+
+    def has_details(self):
+        if self.firstName and self.lastName and self.email:
+            return True
+        return False
+
+    def is_admin(self):
+        '''
+        to be used for admin purposes
+        '''
+        return False
+
+    def get_id(self):
+        '''
+        :return: id reference to retrieve user object from middleware
+        '''
+        return unicode(self.username)
+
+    def get_full_name(self):
+        if self.firstName and self.lastName:
+            return u' '.join([self.firstName, self.lastName])
+        return None