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