Browse Source

Add an option to allow CORS requests

Raphael Khaiat 8 years ago
parent
commit
cf100309a3

+ 24 - 1
Docs/settings.md

@@ -194,6 +194,7 @@ rest_api:
   password_protected: True
   login: admin
   password: secret
+  allowed_cors_origin: "*"
 ```
 
 #### active
@@ -211,10 +212,32 @@ Login used by the basic HTTP authentication. Must be provided if `password_prote
 #### Password
 Password used by the basic HTTP authentication. Must be provided if `password_protected` is `True`
 
+#### Cors request
+If you want to allow request from external application, you'll need to enable the CORS requests settings by defining authorized origins.
+To do so, just indicated the origins that are allowed to leverage the API. The authorize values are:
+
+False to forbid CORS request.
+```
+allowed_cors_origin: False
+```
+
+or either a string or a list:
+```
+allowed_cors_origin: "*"
+```
+(in case of "*", all origins are accepted).
+or
+```
+allowed_cors_origin:
+  - 'http://mydomain.com/*'
+  - 'http://localhost:4200/*'
+```
+
+Remember that an origin is composed of the scheme (http(s)), the port (eg: 80, 4200,…) and the domain (mydomain.com, localhost).
 
 ## Default synapse
 
-Run a default [synapse](brain.md) when Kalliope can't find the order in any synapse. 
+Run a default [synapse](brain.md) when Kalliope can't find the order in any synapse.
 
 ```
 default_synapse: "synapse-name"

+ 1 - 1
Tests/settings/settings_test.yml

@@ -71,7 +71,7 @@ rest_api:
   password_protected: True
   login: admin
   password: secret
-
+  allowed_cors_origin: False
 
 # ---------------------------
 # Default Synapse

+ 1 - 0
Tests/test_rest_api.py

@@ -28,6 +28,7 @@ class TestRestAPI(LiveServerTestCase):
         sl.settings.rest_api.password_protected = False
         sl.settings.active = True
         sl.settings.port = 5000
+        sl.settings.allowed_cors_origin = "*"
 
         # prepare a test brain
         brain_to_test = full_path_brain_to_test

+ 6 - 3
Tests/test_settings_loader.py

@@ -30,7 +30,8 @@ class TestSettingLoader(unittest.TestCase):
                  'login': 'admin',
                  'password': 'secret',
                  'password_protected': True,
-                 'port': 5000},
+                 'port': 5000,
+                 'allowed_cors_origin': False},
             'default_trigger': 'snowboy',
             'triggers': [{'snowboy': {'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'}}],
             'speech_to_text': [{'google': {'language': 'fr-FR'}}],
@@ -91,7 +92,8 @@ class TestSettingLoader(unittest.TestCase):
                            parameters={'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'})
         settings_object.triggers = [trigger1]
         settings_object.rest_api = RestAPI(password_protected=True, active=True,
-                                           login="admin", password="secret", port=5000)
+                                           login="admin", password="secret", port=5000,
+                                           allowed_cors_origin=False)
         settings_object.cache_path = '/tmp/kalliope_tts_cache'
         settings_object.default_synapse = 'Default-synapse'
         resources = Resources(neuron_folder="/tmp/kalliope/tests/kalliope_resources_dir/neurons",
@@ -146,7 +148,8 @@ class TestSettingLoader(unittest.TestCase):
 
     def test_get_rest_api(self):
         expected_rest_api = RestAPI(password_protected=True, active=True,
-                                    login="admin", password="secret", port=5000)
+                                    login="admin", password="secret", port=5000,
+                                    allowed_cors_origin=False)
 
         sl = SettingLoader(file_path=self.settings_file_to_test)
         self.assertEqual(expected_rest_api, sl._get_rest_api(self.settings_dict))

+ 1 - 0
install/files/python_requirements.txt

@@ -9,6 +9,7 @@ cffi==1.9.1
 ipaddress==1.0.17
 flask==0.11.1
 Flask-Restful==0.3.5
+flask_cors==3.0.2
 requests==2.12.4
 httpretty==0.8.14
 mock==2.0.0

+ 6 - 1
kalliope/core/ConfigurationManager/SettingLoader.py

@@ -440,13 +440,18 @@ class SettingLoader(object):
                 if not 1024 <= port <= 65535:
                     raise SettingInvalidException("port must be in range 1024-65535")
 
+                # check the CORS request settings
+                allowed_cors_origin = False
+                if "allowed_cors_origin" in rest_api:
+                     allowed_cors_origin = rest_api["allowed_cors_origin"]
+
             except KeyError, e:
                 # print e
                 raise SettingNotFound("%s settings not found" % e)
 
             # config ok, we can return the rest api object
             rest_api_obj = RestAPI(password_protected=password_protected, login=login, password=password,
-                                   active=active, port=port)
+                                   active=active, port=port, allowed_cors_origin=allowed_cors_origin)
             return rest_api_obj
         else:
             raise NullSettingException("rest_api settings cannot be null")

+ 2 - 1
kalliope/core/MainController.py

@@ -31,7 +31,8 @@ class MainController:
         if self.settings.rest_api.active:
             Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port)
             app = Flask(__name__)
-            flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain=self.brain)
+            flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain=self.brain,
+                                 allowed_cors_origin=self.settings.rest_api.allowed_cors_origin)
             flask_api.daemon = True
             flask_api.start()
 

+ 6 - 4
kalliope/core/Models/RestAPI.py

@@ -3,28 +3,30 @@ class RestAPI(object):
     This Class is representing the rest API with all its configuration.
     """
 
-    def __init__(self, password_protected=None, login=None, password=None, active=None, port=None):
+    def __init__(self, password_protected=None, login=None, password=None, active=None, port=None, allowed_cors_origin=None):
         """
-
         :param password_protected: If true, the rest api will ask for an authentication
         :param login: login used if auth is activated
         :param password: password used if auth is activated
         :param active: specify if the rest api is loaded on start with Kalliope
+        :param allowed_cors_origin: specify allowed origins
         """
         self.password_protected = password_protected
         self.login = login
         self.password = password
         self.active = active
         self.port = port
+        self.allowed_cors_origin = allowed_cors_origin
 
     def __str__(self):
         return "%s: RestAPI: password_protected: %s, login: %s, " \
-               "password: %s, active: %s, port: %s" % (self.__class__.__name__,
+              "password: %s, active: %s, port: %s, allow_cors_request: %s" % (self.__class__.__name__,
                                                        self.password_protected,
                                                        self.login,
                                                        self.password,
                                                        self.active,
-                                                       self.port)
+                                                       self.port,
+                                                       self.allowed_cors_origin)
 
     def __eq__(self, other):
         """

+ 9 - 1
kalliope/core/RestAPI/FlaskAPI.py

@@ -1,16 +1,20 @@
+import logging
 import threading
 
 from flask import jsonify
 from flask import request
 from flask_restful import abort
+from flask_cors import CORS, cross_origin
 
 from kalliope.core import OrderAnalyser
 from kalliope.core.RestAPI.utils import requires_auth
 from kalliope.core.SynapseLauncher import SynapseLauncher
 
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
 
 class FlaskAPI(threading.Thread):
-    def __init__(self, app, port=5000, brain=None):
+    def __init__(self, app, port=5000, brain=None, allowed_cors_origin=False):
         """
 
         :param app: Flask API
@@ -22,11 +26,15 @@ class FlaskAPI(threading.Thread):
         self.app = app
         self.port = port
         self.brain = brain
+        self.allowed_cors_origin = allowed_cors_origin
 
         # Flask configuration remove default Flask behaviour to encode to ASCII
         self.app.url_map.strict_slashes = False
         self.app.config['JSON_AS_ASCII'] = False
 
+        if self.allowed_cors_origin is not False:
+            cors = CORS(app, resources={r"/*": {"origins": allowed_cors_origin}}, supports_credentials=True)
+
         # Add routing rules
         self.app.add_url_rule('/synapses', view_func=self.get_synapses, methods=['GET'])
         self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.get_synapse, methods=['GET'])

+ 1 - 0
kalliope/settings.yml

@@ -107,6 +107,7 @@ rest_api:
   password_protected: True
   login: admin
   password: secret
+  allowed_cors_origin: False
 
 # ---------------------------
 # Default Synapse

+ 1 - 0
setup.py

@@ -68,6 +68,7 @@ setup(
         'ipaddress==1.0.17',
         'flask==0.11.1',
         'Flask-Restful==0.3.5',
+        'flask_cors==3.0.2',
         'requests==2.12.4',
         'httpretty==0.8.14',
         'mock==2.0.0',