Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add (optional) basic auth feature #52

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
8 changes: 8 additions & 0 deletions ndscheduler/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
STATIC_DIR_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static')
TEMPLATE_DIR_PATH = STATIC_DIR_PATH
APP_INDEX_PAGE = 'index.html'
WEBSITE_TITLE = 'Scheduler'

#
# Server setup
Expand Down Expand Up @@ -100,3 +101,10 @@

# Packages that contains job classes, e.g., simple_scheduler.jobs
JOB_CLASS_PACKAGES = []

# Basic Auth
#
# If needs basic auth, modify the dict below
# e.g. BASIC_AUTH_CREDENTIALS = {'username': 'password'}
#
BASIC_AUTH_CREDENTIALS = {}
29 changes: 29 additions & 0 deletions ndscheduler/server/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import json
import base64

from concurrent import futures

Expand All @@ -17,8 +18,36 @@ class BaseHandler(tornado.web.RequestHandler):

executor = futures.ThreadPoolExecutor(max_workers=settings.TORNADO_MAX_WORKERS)

basic_auth_credentials = settings.BASIC_AUTH_CREDENTIALS
basic_auth_realm = 'Scheduler'

def send_challenge(self):
"""Send challenge response."""
header = 'Basic realm="{}"'.format(self.basic_auth_realm)
self.set_status(401)
self.set_header('WWW-Authenticate', header)
self.finish()

def get_basic_auth_result(self):
"""Get HTTP basic access authentication result."""
auth_header = self.request.headers.get('Authorization', '')
if not auth_header.startswith('Basic '):
self.send_challenge()
return

auth_data = auth_header.split(' ')[-1]
auth_data = base64.b64decode(auth_data).decode('utf-8')
username, password = auth_data.split(':')

challenge = self.basic_auth_credentials.get(username)
if challenge != password:
self.send_challenge()

def prepare(self):
"""Preprocess requests."""
if len(self.basic_auth_credentials) > 0:
self.get_basic_auth_result()

try:
if self.request.headers['Content-Type'].startswith('application/json'):
self.json_args = json.loads(self.request.body.decode())
Expand Down
9 changes: 8 additions & 1 deletion ndscheduler/server/handlers/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,12 @@ class Handler(base.BaseHandler):
def get(self):
"""Serve up the single page app for scheduler dashboard."""

website_info = {
'title': settings.WEBSITE_TITLE,
}

meta_info = utils.get_all_available_jobs()
self.render(settings.APP_INDEX_PAGE, jobs_meta_info=json.dumps(meta_info))
self.render(settings.APP_INDEX_PAGE,
jobs_meta_info=json.dumps(meta_info),
website_info=website_info,
)
4 changes: 2 additions & 2 deletions ndscheduler/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="description" content="">
<meta name="author" content="">

<title>Scheduler</title>
<title>{{ website_info['title'] }}</title>
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link href="static/css/vendor/bootstrap.css" rel="stylesheet" type="text/css">
<link href="static/css/vendor/bootstrap-switch.css" rel="stylesheet" type="text/css">
Expand All @@ -33,7 +33,7 @@
<i class="icon-bar"></i>
<i class="icon-bar"></i>
</button>
<span class="navbar-brand">Nextdoor Scheduler</span>
<span class="navbar-brand">{{ website_info['title'] }}</span>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
Expand Down
30 changes: 30 additions & 0 deletions simple_scheduler/jobs/shell_output_job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""A job to run executable programs and get output."""

from subprocess import check_output

from ndscheduler import job


class ShellJob(job.JobBase):

@classmethod
def meta_info(cls):
return {
'job_class_string': '%s.%s' % (cls.__module__, cls.__name__),
'notes': ('This will run an executable program. You can specify as many '
'arguments as you want. This job will pass these arguments to the '
'program in order.'),
'arguments': [
{'type': 'string', 'description': 'Executable path'}
],
'example_arguments': '["/usr/local/my_program", "--file", "/tmp/abc", "--mode", "safe"]'
}

def run(self, *args, **kwargs):
return {'result': check_output(args).decode('utf-8')}


if __name__ == "__main__":
# You can easily test this job here
job = ShellJob.create_test_instance()
job.run('ls', '-l')