Merge pull request #30 from dod-ccpo/basic-auth

Basic authentication
This commit is contained in:
Jason Garber - Ctr 2018-06-14 13:18:08 -04:00 committed by GitHub
commit 6cf7a7bffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 186 additions and 62 deletions

View File

@ -7,7 +7,6 @@ name = "pypi"
tornado = "==5.0.2"
webassets = "==0.12.1"
Unipath = "==1.1"
requests = "*"
[dev-packages]
pytest = "==3.6.0"

38
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "68a0d5093979093899f0f86faa82eb55f90f9a67a16b11a5701ea85096e72ee8"
"sha256": "391e254ddb902877afca9c07aa2306710ce6d1e207b029c1a8b5dc0115ee99a5"
},
"pipfile-spec": 6,
"requires": {
@ -16,35 +16,6 @@
]
},
"default": {
"certifi": {
"hashes": [
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
],
"version": "==2018.4.16"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
],
"version": "==2.6"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
],
"index": "pypi",
"version": "==2.18.4"
},
"tornado": {
"hashes": [
"sha256:1b83d5c10550f2653380b4c77331d6f8850f287c4f67d7ce1e1c639d9222fbc7",
@ -64,13 +35,6 @@
"index": "pypi",
"version": "==1.1"
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
],
"version": "==1.22"
},
"webassets": {
"hashes": [
"sha256:e7d9c8887343123fd5b32309b33167428cb1318cdda97ece12d0907fd69d38db"

View File

@ -33,7 +33,7 @@ class ApiClient(object):
kwargs['body'] = dumps(kwargs['json'])
del kwargs['json']
headers = kwargs.get('headers', {})
headers['Content-Type'] = 'application-json'
headers['Content-Type'] = 'application/json'
kwargs['headers'] = headers
response = yield self.client.fetch(url, method=method, **kwargs)

View File

@ -4,34 +4,62 @@ import tornado.web
from tornado.web import url
from atst.handlers.main import MainHandler
from atst.handlers.home import Home
from atst.handlers.login import Login
from atst.handlers.workspace import Workspace
from atst.handlers.request import Request
from atst.handlers.request_new import RequestNew
from atst.handlers.dev import Dev
from atst.home import home
from atst.api_client import ApiClient
ENV = os.getenv("TORNADO_ENV", "dev")
def make_app(config):
authz_client = ApiClient(config['default']['AUTHZ_BASE_URL'])
authz_client = ApiClient(config["default"]["AUTHZ_BASE_URL"])
authnid_client = ApiClient(config["default"]["AUTHNID_BASE_URL"])
routes = [
url(r"/", Home, {"page": "login"}, name="main"),
url(
r"/login",
Login,
{"authnid_client": authnid_client},
name="login",
),
url(r"/home", MainHandler, {"page": "home"}, name="home"),
url( r"/workspaces/blank", MainHandler, {'page': 'workspaces_blank'}, name='workspaces_blank' ),
url(
r"/workspaces",
Workspace,
{"page": "workspaces", "authz_client": authz_client},
name="workspaces",
),
url(r"/requests", Request, {"page": "requests"}, name="requests"),
url(r"/requests/new", RequestNew, {"page": "requests_new"}, name="request_new"),
url(
r"/requests/new/([0-9])",
RequestNew,
{"page": "requests_new"},
name="request_form",
),
url(r"/users", MainHandler, {"page": "users"}, name="users"),
url(r"/reports", MainHandler, {"page": "reports"}, name="reports"),
url(r"/calculator", MainHandler, {"page": "calculator"}, name="calculator"),
]
if not ENV == "production":
routes += [url(r"/login-dev", Dev, {"action": "login"}, name="dev-login")]
app = tornado.web.Application(
routes,
login_url="/",
app = tornado.web.Application([
url( r"/", MainHandler, {'page': 'login'}, name='login' ),
url( r"/home", MainHandler, {'page': 'home'}, name='home' ),
url( r"/workspaces/blank", MainHandler, {'page': 'workspaces_blank'}, name='workspaces_blank' ),
url( r"/workspaces",
Workspace,
{'page': 'workspaces', 'authz_client': authz_client},
name='workspaces'),
url( r"/requests", Request, {'page': 'requests'}, name='requests' ),
url( r"/requests/new", RequestNew, {'page': 'requests_new'}, name='request_new' ),
url( r"/requests/new/([0-9])", RequestNew, {'page': 'requests_new'}, name='request_form' ),
url( r"/users", MainHandler, {'page': 'users'}, name='users' ),
url( r"/reports", MainHandler, {'page': 'reports'}, name='reports' ),
url( r"/calculator", MainHandler, {'page': 'calculator'}, name='calculator' ),
],
template_path = home.child('templates'),
static_path = home.child('static'),
cookie_secret=config["default"]["COOKIE_SECRET"],
debug=config['default'].getboolean('DEBUG')
)
return app
@ -40,12 +68,12 @@ def make_app(config):
def make_config():
BASE_CONFIG_FILENAME = os.path.join(
os.path.dirname(__file__),
'../config/base.ini',
"../config/base.ini"
)
ENV_CONFIG_FILENAME = os.path.join(
os.path.dirname(__file__),
'../config/',
'{}.ini'.format(os.getenv('TORNADO_ENV', 'dev').lower())
"../config/",
"{}.ini".format(ENV.lower())
)
config = ConfigParser()

View File

@ -25,3 +25,13 @@ class BaseHandler(tornado.web.RequestHandler):
ns = super(BaseHandler, self).get_template_namespace()
ns.update(helpers)
return ns
def get_current_user(self):
if self.get_secure_cookie('atst'):
return True
else:
False
# this is a temporary implementation until we have real sessions
def _start_session(self):
self.set_secure_cookie('atst', 'valid-user-session')

13
atst/handlers/dev.py Normal file
View File

@ -0,0 +1,13 @@
from atst.handler import BaseHandler
class Dev(BaseHandler):
def initialize(self, action):
self.action = action
def get(self):
if self.action == 'login':
self._login()
def _login(self):
self._start_session()
self.redirect("/home")

10
atst/handlers/home.py Normal file
View File

@ -0,0 +1,10 @@
import tornado
from atst.handler import BaseHandler
class Home(BaseHandler):
def initialize(self, page):
self.page = page
def get(self):
self.render( '%s.html.to' % self.page, page = self.page )

37
atst/handlers/login.py Normal file
View File

@ -0,0 +1,37 @@
import tornado
from atst.handler import BaseHandler
class Login(BaseHandler):
def initialize(self, authnid_client):
self.authnid_client = authnid_client
@tornado.gen.coroutine
def get(self):
token = self.get_query_argument("bearer-token")
if token:
valid = yield self._validate_login_token(token)
if valid:
self._start_session()
self.redirect("/home")
return
url = self.get_login_url()
self.redirect(url)
return
@tornado.gen.coroutine
def _validate_login_token(self, token):
try:
response = yield self.authnid_client.post(
"/api/v1/validate", json={"token": token}
)
return response.code == 200
except tornado.httpclient.HTTPError as error:
if error.response.code == 401:
return False
else:
raise error

View File

@ -1,3 +1,5 @@
import atst
import tornado
from atst.handler import BaseHandler
class MainHandler(BaseHandler):
@ -5,5 +7,6 @@ class MainHandler(BaseHandler):
def initialize(self, page):
self.page = page
@tornado.web.authenticated
def get(self):
self.render( '%s.html.to' % self.page, page = self.page )

View File

@ -1,3 +1,4 @@
import tornado
from atst.handler import BaseHandler
mock_requests = [
@ -31,5 +32,6 @@ class Request(BaseHandler):
def initialize(self, page):
self.page = page
@tornado.web.authenticated
def get(self):
self.render('requests.html.to', page = self.page, requests = mock_requests )

View File

@ -1,3 +1,4 @@
import tornado
from atst.handler import BaseHandler
class RequestNew(BaseHandler):
@ -22,6 +23,7 @@ class RequestNew(BaseHandler):
def initialize(self, page):
self.page = page
@tornado.web.authenticated
def get(self, screen = 1):
self.render( 'requests/screen-%d.html.to' % int(screen),
page = self.page,

View File

@ -1,5 +1,5 @@
from atst.handler import BaseHandler
import requests
import tornado
import tornado.gen
mock_workspaces = [
@ -13,8 +13,6 @@ mock_workspaces = [
}
]
session = requests.Session()
class Workspace(BaseHandler):
def initialize(self, page, authz_client):
@ -22,5 +20,6 @@ class Workspace(BaseHandler):
self.authz_client = authz_client
@tornado.gen.coroutine
@tornado.web.authenticated
def get(self):
self.render( 'workspaces.html.to', page = self.page, workspaces = mock_workspaces )

View File

@ -1,5 +1,7 @@
[default]
PORT=8000
ENVIRONMENT = dev
DEBUG = true
AUTHZ_BASE_URL = http://localhost
PORT = 8000
AUTHNID_BASE_URL= http://localhost
COOKIE_SECRET = some-secret-please-replace

55
tests/test_auth.py Normal file
View File

@ -0,0 +1,55 @@
import re
import pytest
import tornado.web
import tornado.gen
@pytest.mark.gen_test
def test_redirects_when_not_logged_in(http_client, base_url):
response = yield http_client.fetch(
base_url + "/home", raise_error=False, follow_redirects=False
)
location = response.headers['Location']
assert response.code == 302
assert response.error
assert re.match('/\??', location)
@pytest.mark.gen_test
def test_login_with_valid_bearer_token(app, monkeypatch, http_client, base_url):
@tornado.gen.coroutine
def _validate_login_token(c, t):
return True
monkeypatch.setattr(
"atst.handlers.login.Login._validate_login_token",
_validate_login_token
)
response = yield http_client.fetch(
base_url + "/login?bearer-token=abc-123",
follow_redirects=False,
raise_error=False
)
assert response.headers["Set-Cookie"].startswith("atst")
assert response.headers['Location'] == '/home'
assert response.code == 302
@pytest.mark.gen_test
def test_login_via_dev_endpoint(app, http_client, base_url):
response = yield http_client.fetch(
base_url + "/login-dev", raise_error=False, follow_redirects=False
)
assert response.headers["Set-Cookie"].startswith("atst")
assert response.code == 302
assert response.headers["Location"] == "/home"
@pytest.mark.gen_test
@pytest.mark.skip(reason="need to work out auth error user paths")
def test_login_with_invalid_bearer_token(http_client, base_url):
response = yield http_client.fetch(
base_url + "/home",
raise_error=False,
headers={"Cookie": "bearer-token=anything"},
)