Skip to content

Commit

Permalink
#21 Implement Security for deployed PixieApps
Browse files Browse the repository at this point in the history
  • Loading branch information
David Taieb committed Jan 18, 2018
1 parent 131384f commit ddcd81f
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 3 deletions.
7 changes: 7 additions & 0 deletions pixiegateway/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ def __init__(self, error_name, error_value, trace, code):
self.error_name, self.error_value, self.trace, self.code
)
super(CodeExecutionError, self).__init__(message)

class AppAccessError(Exception):
"""
Exception thrown when access to a PixieApp has not been authorized
"""
def __init__(self):
super(AppAccessError, self).__init__("Unauthorized Access")
11 changes: 10 additions & 1 deletion pixiegateway/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,17 @@
from .chartsManager import SingletonChartStorage
from .utils import sanitize_traceback
from .pixieGatewayApp import PixieGatewayApp
from .exceptions import CodeExecutionError
from .exceptions import CodeExecutionError, AppAccessError

class BaseHandler(tornado.web.RequestHandler):
"""Base class for all PixieGateway handler"""
def initialize(self):
self.output_json_error = False

def _handle_request_exception(self, exc):
if isinstance(exc, AppAccessError):
return self.send_error(401)

html_error = "<div>Unexpected error:</div><pre>{}</pre>".format(
str(exc) if isinstance(exc, CodeExecutionError) else traceback.format_exc()
)
Expand Down Expand Up @@ -157,6 +160,9 @@ def get(self, *args, **kwargs):

#check the notebooks first
pixieapp_def = NotebookMgr.instance().get_notebook_pixieapp(clazz)
#validate app security
if pixieapp_def is not None:
pixieapp_def.validate_security(self)
code = None
managed_client = yield self.session.get_managed_client(self, pixieapp_def, True)
if pixieapp_def is not None:
Expand Down Expand Up @@ -287,6 +293,9 @@ def post(self, name):
try:
notebook = nbformat.from_dict(json.loads(payload))
pixieapp_model = yield NotebookMgr.instance().publish(name, notebook)
if "url" in pixieapp_model:
server = self.request.protocol + "://" + self.request.host
pixieapp_model["url"] = server + pixieapp_model["url"]
self.set_status(200)
self.write(json.dumps(pixieapp_model))
self.finish()
Expand Down
27 changes: 26 additions & 1 deletion pixiegateway/notebookMgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import six
import nbformat
import astunparse
from uuid import uuid4
from traitlets.config.configurable import SingletonConfigurable
from traitlets import Unicode, default
from tornado import gen
Expand All @@ -27,6 +28,7 @@
from tornado.util import import_object
from .pixieGatewayApp import PixieGatewayApp
from .managedClient import ManagedClientPool
from .exceptions import AppAccessError
from IPython.core.getipython import get_ipython

def ast_parse(code):
Expand Down Expand Up @@ -78,8 +80,14 @@ def next_namespace(self):
def notebook_pixieapps(self):
return list(self.pixieapps.values())

def _process_security(self, notebook):
security = notebook.get("metadata", {}).get("pixiedust", {}).get("security", "token")
if security == "token":
notebook.get("metadata", {}).get("pixiedust", {})["security"] = "token:{}".format(uuid4().hex)

@gen.coroutine
def publish(self, name, notebook):
self._process_security(notebook)
full_path = os.path.join(self.notebook_dir, name)
pixieapp_def = self.read_pixieapp_def(notebook)
log_messages = ["Validating Notebook... Looking for a PixieApp"]
Expand All @@ -91,7 +99,10 @@ def publish(self, name, notebook):
nbformat.write(notebook, f, version=nbformat.NO_CONVERT)
log_messages.append("Successfully stored notebook file {}".format(name))
yield ManagedClientPool.instance().on_publish(pixieapp_def, log_messages)
pixieapp_model = {"log":log_messages}
pixieapp_model = {
"log":log_messages,
"url": pixieapp_def.url
}
pixieapp_model.update(pixieapp_def.to_dict())
raise gen.Return(pixieapp_model)
else:
Expand Down Expand Up @@ -170,6 +181,9 @@ def __init__(self, namespace, warmup_code, run_code, notebook):
self.title = pixiedust_meta.get("title",None)
self.deps = pixiedust_meta.get("imports", {})
self.pref_kernel = pixiedust_meta.get("kernel", None)
self.security = pixiedust_meta.get("security", None)
self.token = self.security.split(":") if self.security is not None else None
self.token = self.token[1] if self.token is not None and len(self.token) == 2 and self.token[0] == "token" else None

#validate and process the code
self.symbols = get_symbol_table(ast_parse(self.raw_warmup_code + "\n" + self.raw_run_code))
Expand Down Expand Up @@ -207,6 +221,17 @@ def run_code(self):
self._run_code = ""
return self._run_code

@property
def url(self):
url_token = "?token={}".format(self.token) if self.token is not None else ""
return "/pixieapp/{}{}".format(self.name, url_token)

def validate_security(self, request_handler):
if self.token is not None:
url_token = request_handler.get_argument('token', None)
if url_token != self.token:
raise AppAccessError()

def to_dict(self):
return {
"name": self.name,
Expand Down
2 changes: 1 addition & 1 deletion pixiegateway/template/admin/pixieappList.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<tbody>
{% for p in pixieapp_list%}
<tr>
<td><a target="_blank" href="/pixieapp/{{p.name}}">{{p.name}}</td>
<td><a target="_blank" href="{{p.url}}">{{p.name}}</td>
<td>{{p.description}}</td>
<td>{{p.location}}</td>
</tr>
Expand Down

0 comments on commit ddcd81f

Please sign in to comment.