예제 #1
0
파일: API.py 프로젝트: i2a-org/bifrost
class API:
    """ The Endpoints class

    Initializes the API portion of Bifrost. Also, takes care of authorizations.

    Attributes
    ----------
    _endpoints : dict
        the endpoint with the associated classes
    _version : str
        current version that the API runs on
    _logger : Logger
        the logger object for keeping track of traffic
    _db : DB
        the DB object for DB interfaces
    """

    _endpoints = conf._endpoints
    _protected = conf._protected
    _workers = conf._workers

    _version = "v0"
    _logger = None
    _db = None
    _crypto = None
    _env = ""

    # --------------------------------------------------------------------------
    def __init__(self, env):
        """
        Parameters
        ----------
        env : str
            the environment that the current instance is running
        """
        print("[ENDPOINTS] Initializing...")
        # initialize libraries
        self._env = env
        self._db = DB(self._env, self._workers)
        self._logger = Logger(self._db, self._env)
        self._crypto = Crypto()
        # initialize Flask
        self._app = Flask(__name__)
        self._app.json_encoder = CustomJSONEncoder
        self._api = Api(self._app)
        self._app.before_request(self.detectAuthorization)
        self._app.after_request(self.finishRequest)
        for url in self._endpoints: self.addResource(self._endpoints[url], url)
        print("[ENDPOINTS] Done.")

    # --------------------------------------------------------------------------
    def getApp(self):
        """ Return Flask app

        AWS requires a Elastic Beanstalk app to be an executable app. As for now
        this works.

        Returns
        -------
        Flask
            the flask application for AWS
        """

        return self._app

    # --------------------------------------------------------------------------
    def logRequests(self, rtype, request):
        """ Prepare log messages

        Prepare the messages that we want and use Logger to save them to disk, or
        send a notification.

        Parameters
        ----------
        rtype : int
            response type
        request : Request
            request object that was generated by Flask
        """

        status = "NORMAL"
        if rtype == 404 or rtype == 500: status = "CRITICAL"
        elif rtype == 401: status = "NOAUTH"

        self._logger.log(
            "endpoints", json.dumps({
                "rtype": str(rtype),
                "path": request.path,
                "data": request.data.decode("utf8"),
                "args": request.args.to_dict(),
                "method": request.method,
                "remote_addr": request.remote_addr,
                "headers": request.headers.to_list()
            }), status=status
        )

    # --------------------------------------------------------------------------
    def sendAuthorizationError(self, message, token):
        """ Create the authorization error message

        Generates a error message that is used multiple times in the code.

        Parameters
        ----------
        message : str
            message to be sent
        token : str
            the token that was used in the request

        Returns
        -------
        str
            the returning JSON response as a string
        int
            HTTP response code
        """

        return (json.dumps({ "error":message }), 401)

    # --------------------------------------------------------------------------
    def isValid(self, session, patient_id):
        """ Check if token is valid

        Uses DB to check if the given token is existing and acive. The `active`
        flag in the DB can hence be used to quickly deactivate a token.

        Parameters
        ----------
        session : str
            the token that was used in the request
        patient_id : str
            the patient ID that was sent with the request

        Returns
        -------
        bool
            IF valid => True, ELSE False
        str
            the patient_id of the user associated with the session
        dict
            the full session dict containing all the information loaded from DB
        """

        # check if token set;
        try:
            if session == "" or patient_id == "":
                return False, "", {}

            session = self._db.getSession(session)
            if session["patient_id"] != patient_id: return False, "", {}
        except:
            return False, "", {}

        return True, session["patient_id"], session

    # --------------------------------------------------------------------------
    def detectAuthorization(self):
        """ Check if authorization is valid

        Uses the Flask request to check the header. The `Bearer` header must be
        present and name a valid session_id. Specifically, the function looks for
        the `Authorization: Bearer [SESSION]` header (note the exact format). Finally,
        the function adds `patient_id` and `session` to the request object, to make this
        information available to the system.
        """
        request_path = request.path[len(self._version)+1:]
        header = request.headers.get("Authorization")

        if request_path in self._protected and request.method in self._protected[request_path]:

            header = request.headers.get("Authorization")
            if not header:
                return self.sendAuthorizationError("Invalid header. Request registered.", "")
            # bearer or token not set;
            outs = header.split()
            if len(outs) != 2:
                return self.sendAuthorizationError("Invalid authentication. Request registered.", "")
            bearer, session = outs
            auth, patient_id, obj = self.isValid(session, request.headers.get("Patient"))
            if bearer != "Bearer" or not auth:
                return self.sendAuthorizationError("Invalid authentication. Request registered.", session)

            request.patient_id = patient_id
            request.session = session
            request.obj = obj

    # --------------------------------------------------------------------------
    def finishRequest(self, response):
        """ Hook for after response has been prepared

        This function logs the response.

        Parameters
        ----------
        response : Response
            Response object for Flask

        Returns
        -------
        response : Response
            Response object for Flask
        """

        response.headers["Access-Control-Allow-Origin"] = "*"
        response.headers["Access-Control-Allow-Headers"] = "Authorization,Patient"
        self.logRequests(response.status_code, request)
        return response

    # --------------------------------------------------------------------------
    def addResource(self, obj, url):
        """ Add resources to flask_restful

        Injects the API with the endpoints that are given in the `_endpoints`
        attribute.

        Parameters
        ----------
        obj : flask_restful.Resource
            class to inject
        url : str
            Flask formatted endpoint
        """

        print("[ADDED ROUTE]", "/"+self._version+url)
        self._api.add_resource(
            obj, 
            "/"+self._version+url, 
            resource_class_kwargs={
                "logger":self._logger,
                "db":self._db,
                "crypto": self._crypto,
                "workers": self._workers,
                "request_string": "/"+self._version+url
            }
        )