Merge pull request #133 from dod-ccpo/cleanup-tornado

Remove the last remnants of tornado
This commit is contained in:
richard-dds 2018-08-07 10:14:14 -04:00 committed by GitHub
commit ae201fde84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 64 additions and 903 deletions

View File

@ -4,10 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
tornado = "*"
webassets = "*"
Unipath = "*"
wtforms-tornado = "*"
pendulum = "*"
redis = "*"
sqlalchemy = "*"

38
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "647d98b5384d1942bbe6bfe7930b1cd249886da2f47645802cd6f93369f44538"
"sha256": "2b149e0d8c23814a2c701b53f5c75b36714a2ccd4e2a2769924ef6e2a3f09e97"
},
"pipfile-spec": 6,
"requires": {
@ -271,7 +271,6 @@
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
"sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5"
],
"markers": "python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*'",
"version": "==2018.5"
},
"redis": {
@ -304,19 +303,6 @@
"index": "pypi",
"version": "==1.2.10"
},
"tornado": {
"hashes": [
"sha256:1c0816fc32b7d31b98781bd8ebc7a9726d7dce67407dc353a2e66e697e138448",
"sha256:4f66a2172cb947387193ca4c2c3e19131f1c70fa8be470ddbbd9317fd0801582",
"sha256:5327ba1a6c694e0149e7d9126426b3704b1d9d520852a3e4aa9fc8fe989e4046",
"sha256:6a7e8657618268bb007646b9eae7661d0b57f13efc94faa33cd2588eae5912c9",
"sha256:a9b14804783a1d77c0bd6c66f7a9b1196cbddfbdf8bceb64683c5ae60bd1ec6f",
"sha256:c58757e37c4a3172949c99099d4d5106e4d7b63aa0617f9bb24bfbff712c7866",
"sha256:d8984742ce86c0855cccecd5c6f54a9f7532c983947cff06f3a0e2115b47f85c"
],
"index": "pypi",
"version": "==5.1"
},
"unipath": {
"hashes": [
"sha256:09839adcc72e8a24d4f76d63656f30b5a1f721fc40c9bcd79d8c67bdd8b47dae",
@ -353,13 +339,6 @@
"sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1"
],
"version": "==2.2.1"
},
"wtforms-tornado": {
"hashes": [
"sha256:dadb5e504d01f14bf75900f592888bb402ada6b8f8235fe583359f562d351a3a"
],
"index": "pypi",
"version": "==0.0.2"
}
},
"develop": {
@ -522,7 +501,6 @@
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
],
"markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'",
"version": "==4.3.4"
},
"itsdangerous": {
@ -640,7 +618,6 @@
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
],
"markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'",
"version": "==0.7.1"
},
"prompt-toolkit": {
@ -663,7 +640,6 @@
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
],
"markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'",
"version": "==1.5.4"
},
"pygments": {
@ -675,11 +651,11 @@
},
"pylint": {
"hashes": [
"sha256:0edfec21270725c5aa8e8d8d06ef5666f766e0e748ed2f1ab23624727303b935",
"sha256:4cadcaa4f1fb19123d4baa758d9fbe6286c5b3aa513af6ea42a2d51d405db205"
"sha256:1d6d3622c94b4887115fe5204982eee66fdd8a951cf98635ee5caee6ec98c3ec",
"sha256:31142f764d2a7cd41df5196f9933b12b7ee55e73ef12204b648ad7e556c119fb"
],
"index": "pypi",
"version": "==2.1.0"
"version": "==2.1.1"
},
"pytest": {
"hashes": [
@ -713,11 +689,15 @@
},
"pyyaml": {
"hashes": [
"sha256:1cbc199009e78f92d9edf554be4fe40fb7b0bef71ba688602a00e97a51909110",
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
"sha256:6f89b5c95e93945b597776163403d47af72d243f366bf4622ff08bdfd1c950b7",
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
"sha256:be622cc81696e24d0836ba71f6272a2b5767669b0d79fdcf0295d51ac2e156c8",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b",
"sha256:f39411e380e2182ad33be039e8ee5770a5d9efe01a2bfb7ae58d9ba31c4a2a9d"
],
"version": "==4.2b4"
},

View File

@ -4,18 +4,19 @@
## Description
This is the main user-facing web application for the ATAT stack. All end-user
requests are handled by ATST, with it making backend calls to various
microservices when appropriate.
This is the user-facing web application for ATAT.
## Installation
### Requirements
See the [scriptz](https://github.com/dod-ccpo/scriptz) repository for the shared
requirements and guidelines for all ATAT applications.
Additionally, ATST requires a redis instance for session management. Have redis
installed and running. By default, ATST will try to connect to a redis instance
running on localhost on its default port, 6379.
ATST requires a postgres instance (>= 9.6) for persistence. Have postgres installed
and running on the default port of 5432.
ATST also requires a redis instance for session management. Have redis installed and
running on the default port of 6379.
### Cloning
This project contains git submodules. Here is an example clone command that will
@ -96,14 +97,21 @@ To re-run tests each time a file is changed:
## Notes
tornado templates are like mustache templates -- add the
Jinja templates are like mustache templates -- add the
following to `~/.vim/filetype.vim` for syntax highlighting:
:au BufRead *.html.to set filetype=mustache
## Icons
To render an icon use `{% module Icon('name') %}` in a template, where `name` is the filename of an svg file in `static/icons`.
To render an icon, use
```jinja
{% import "components/icon.html" %}
{{ Icon("icon-name", classes="css-classes") }}
```
where `icon-name` is the filename of an svg in `static/icons`.
All icons used should be from the Noun Project, specifically [this collection](https://thenounproject.com/monstercritic/collection/tinicons-a-set-of-tiny-icons-perfect-for-ui-elemen/) if possible.
@ -113,7 +121,7 @@ SVG markup should be cleaned an minified, [Svgsus](http://www.svgs.us/) works we
The `/login-dev` endpoint is protected by HTTP basic auth when deployed. This can be configured for NGINX following the instructions [here](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/). The following config should added within the main server block for the site:
```
```nginx
location /login-dev {
auth_basic "Developer Access";
auth_basic_user_file /etc/apache2/.htpasswd;

View File

@ -1,60 +0,0 @@
import tornado.gen
from tornado.httpclient import AsyncHTTPClient
from json import dumps, loads, decoder
class ApiClient(object):
def __init__(self, base_url, api_version=None, validate_cert=True):
self.base_url = base_url
if api_version:
self.base_url = f"{base_url}/api/{api_version}"
self.client = AsyncHTTPClient()
self.validate_cert = validate_cert
@tornado.gen.coroutine
def get(self, path, **kwargs):
return (yield self.make_request("GET", self.base_url + path, **kwargs))
@tornado.gen.coroutine
def put(self, path, **kwargs):
return (yield self.make_request("PUT", self.base_url + path, **kwargs))
@tornado.gen.coroutine
def post(self, path, **kwargs):
return (yield self.make_request("POST", self.base_url + path, **kwargs))
@tornado.gen.coroutine
def patch(self, path, **kwargs):
return (yield self.make_request("PATCH", self.base_url + path, **kwargs))
@tornado.gen.coroutine
def delete(self, path, **kwargs):
return (yield self.make_request("DELETE", self.base_url + path, **kwargs))
@tornado.gen.coroutine
def make_request(self, method, url, **kwargs):
# If 'json' kwarg is specified, serialize it to 'body' and update
# the Content-Type.
if "json" in kwargs:
kwargs["body"] = dumps(kwargs["json"])
del kwargs["json"]
headers = kwargs.get("headers", {})
headers["Content-Type"] = "application/json"
kwargs["headers"] = headers
if not "validate_cert" in kwargs:
kwargs["validate_cert"] = self.validate_cert
response = yield self.client.fetch(url, method=method, **kwargs)
return self.adapt_response(response)
def adapt_response(self, response):
if "application/json" in response.headers["Content-Type"]:
try:
json = loads(response.body)
setattr(response, "json", json)
except decoder.JSONDecodeError:
setattr(response, "json", {})
else:
setattr(response, "json", {})
setattr(response, "ok", 200 <= response.code < 300)
return response

View File

@ -6,6 +6,7 @@ from flask import Flask, request, g
from flask_session import Session
import redis
from unipath import Path
from flask_wtf.csrf import CSRFProtect
from atst.database import db
from atst.assets import environment as assets_environment
@ -31,6 +32,7 @@ def make_app(config):
static_folder=parent_dir.child("static").absolute(),
)
redis = make_redis(config)
csrf = CSRFProtect()
app.config.update(config)
app.config.update({"SESSION_REDIS": redis})
@ -39,6 +41,7 @@ def make_app(config):
make_crl_validator(app)
db.init_app(app)
csrf.init_app(app)
Session(app)
assets_environment.init_app(app)
@ -61,7 +64,7 @@ def make_flask_callbacks(app):
if re.match("\/workspaces\/[A-Za-z0-9]*", request.url)
else "global"
)
g.dev = os.getenv("TORNADO_ENV", "dev") == "dev"
g.dev = os.getenv("FLASK_ENV", "dev") == "dev"
g.matchesPath = lambda href: re.match("^" + href, request.path)
g.modalOpen = request.args.get("modal", False)
g.current_user = {
@ -80,13 +83,14 @@ def make_flask_callbacks(app):
def map_config(config):
return {
**config["default"],
"ENV": config["default"]["ENVIRONMENT"],
"DEBUG": config["default"]["DEBUG"],
"PORT": int(config["default"]["PORT"]),
"SQLALCHEMY_DATABASE_URI": config["default"]["DATABASE_URI"],
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
**config["default"],
"PERMANENT_SESSION_LIFETIME": int(config["default"]["PERMANENT_SESSION_LIFETIME"]),
"WTF_CSRF_ENABLED": config.getboolean("default", "WTF_CSRF_ENABLED"),
"PERMANENT_SESSION_LIFETIME": config.getint("default", "PERMANENT_SESSION_LIFETIME"),
}

View File

@ -1,5 +1,3 @@
import tornado
from tornado.gen import Return
from flask_wtf import FlaskForm

View File

@ -1,42 +0,0 @@
import tornado.web
from atst.sessions import SessionNotFoundError
from atst.domain.users import Users
helpers = {"assets": None}
class BaseHandler(tornado.web.RequestHandler):
def get_template_namespace(self):
ns = super(BaseHandler, self).get_template_namespace()
helpers["config"] = self.application.config
ns.update(helpers)
return ns
@tornado.gen.coroutine
def login(self, user):
db_user = yield self._get_user_permissions(user["id"])
user["atat_permissions"] = db_user.atat_permissions
user["atat_role"] = db_user.atat_role.name
session_id = self.sessions.start_session(user)
self.set_secure_cookie("atat", session_id)
return self.redirect("/home")
@tornado.gen.coroutine
def _get_user_permissions(self, user_id):
user_repo = Users(self.db_session)
user = user_repo.get_or_create(user_id, atat_role_name="developer")
return user
def get_current_user(self):
cookie = self.get_secure_cookie("atat")
if cookie:
try:
session = self.application.sessions.get_session(cookie)
except SessionNotFoundError:
self.clear_cookie("atat")
return None
else:
return None
return session["user"]

View File

@ -1,62 +0,0 @@
import tornado.gen
from atst.handler import BaseHandler
from atst.domain.users import Users
_DEV_USERS = {
"sam": {
"id": "164497f6-c1ea-4f42-a5ef-101da278c012",
"first_name": "Sam",
"last_name": "Seeceepio",
"atat_role": "ccpo",
},
"amanda": {
"id": "cce17030-4109-4719-b958-ed109dbb87c8",
"first_name": "Amanda",
"last_name": "Adamson",
"atat_role": "default",
},
"brandon": {
"id": "66ebf7b8-cbf0-4ed8-a102-5f105330df75",
"first_name": "Brandon",
"last_name": "Buchannan",
"atat_role": "default",
},
"christina": {
"id": "7707b9f2-5945-49ae-967a-be65baa88baf",
"first_name": "Christina",
"last_name": "Collins",
"atat_role": "default",
},
"dominick": {
"id": "6978ac0c-442a-46aa-a0c3-ff17b5ec2a8c",
"first_name": "Dominick",
"last_name": "Domingo",
"atat_role": "default",
},
"erica": {
"id": "596fd001-bb1d-4adf-87d8-fa2312e882de",
"first_name": "Erica",
"last_name": "Eichner",
"atat_role": "default",
},
}
class Dev(BaseHandler):
def initialize(self, action, sessions, db_session):
self.db_session = db_session
self.action = action
self.sessions = sessions
self.users_repo = Users(db_session)
@tornado.gen.coroutine
def get(self):
role = self.get_argument("username", "amanda")
user = _DEV_USERS[role]
yield self._set_user_permissions(user["id"], user["atat_role"])
yield self.login(user)
@tornado.gen.coroutine
def _set_user_permissions(self, user_id, role):
return self.users_repo.get_or_create(user_id, atat_role_name=role)

View File

@ -1,38 +0,0 @@
import tornado
from atst.handler import BaseHandler
class LoginRedirect(BaseHandler):
def initialize(self, authnid_client, sessions, db_session):
self.db_session = db_session
self.authnid_client = authnid_client
self.sessions = sessions
@tornado.gen.coroutine
def get(self):
token = self.get_query_argument("bearer-token")
if token:
user = yield self._fetch_user_info(token)
if user:
yield self.login(user)
else:
self.write_error(401)
url = self.get_login_url()
self.redirect(url)
@tornado.gen.coroutine
def _fetch_user_info(self, token):
try:
response = yield self.authnid_client.post(
"/validate", json={"token": token}
)
if response.code == 200:
return response.json["user"]
except tornado.httpclient.HTTPError as error:
if error.response.code == 401:
return None
else:
raise error

View File

@ -1,11 +0,0 @@
import tornado
from atst.handler import BaseHandler
class Main(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,44 +0,0 @@
import tornado
import pendulum
from atst.handler import BaseHandler
from atst.domain.requests import Requests
def map_request(user, request):
time_created = pendulum.instance(request.time_created)
is_new = time_created.add(days=1) > pendulum.now()
return {
"order_id": request.id,
"is_new": is_new,
"status": request.status,
"app_count": 1,
"date": time_created.format("M/DD/YYYY"),
"full_name": "{} {}".format(user["first_name"], user["last_name"]),
}
class Request(BaseHandler):
def initialize(self, page, db_session):
self.page = page
self.db_session = db_session
self.requests = Requests(db_session)
@tornado.web.authenticated
@tornado.gen.coroutine
def get(self):
user = self.get_current_user()
requests = yield self.fetch_requests(user)
mapped_requests = [map_request(user, request) for request in requests]
self.render("requests.html.to", page=self.page, requests=mapped_requests)
@tornado.gen.coroutine
def fetch_requests(self, user):
requests = []
if "review_and_approve_jedi_workspace_request" in user["atat_permissions"]:
requests = self.requests.get_many()
else:
requests = self.requests.get_many(creator_id=user["id"])
return requests

View File

@ -1,64 +0,0 @@
import tornado
from atst.handler import BaseHandler
from atst.forms.financial import FinancialForm
from atst.domain.requests import Requests
from atst.domain.pe_numbers import PENumbers
class RequestFinancialVerification(BaseHandler):
def initialize(self, page, db_session):
self.page = page
self.requests_repo = Requests(db_session)
self.pe_numbers_repo = PENumbers(db_session)
def get_existing_request(self, request_id):
return self.requests_repo.get(request_id)
@tornado.web.authenticated
@tornado.gen.coroutine
def get(self, request_id=None):
existing_request = self.get_existing_request(request_id)
form = FinancialForm(data=existing_request.body.get("financial_verification"))
self.render(
"requests/financial_verification.html.to",
page=self.page,
f=form,
request_id=request_id,
)
@tornado.gen.coroutine
def update_request(self, request_id, form_data):
request_data = {
"creator_id": self.current_user["id"],
"request": {"financial_verification": form_data},
}
return self.requests_repo.update(request_id, request_data)
@tornado.web.authenticated
@tornado.gen.coroutine
def post(self, request_id=None):
self.check_xsrf_cookie()
post_data = self.request.arguments
existing_request = self.get_existing_request(request_id)
form = FinancialForm(post_data)
rerender_args = dict(request_id=request_id, f=form)
if form.validate():
yield self.update_request(request_id, form.data)
# pylint: disable=E1121
valid = yield form.perform_extra_validation(
existing_request.body.get("financial_verification"),
self.pe_numbers_repo,
)
if valid:
self.redirect(
self.application.default_router.reverse_url(
"financial_verification_submitted"
)
)
else:
self.render("requests/financial_verification.html.to", **rerender_args)
else:
self.render("requests/financial_verification.html.to", **rerender_args)

View File

@ -1,228 +0,0 @@
import tornado
from collections import defaultdict
from atst.handler import BaseHandler
from atst.forms.request import RequestForm
from atst.forms.org import OrgForm
from atst.forms.poc import POCForm
from atst.forms.review import ReviewForm
from atst.domain.requests import Requests
from atst.domain.pe_numbers import PENumbers
class RequestNew(BaseHandler):
def initialize(self, page, db_session):
self.page = page
self.requests_repo = Requests(db_session)
self.pe_numbers_repo = PENumbers(db_session)
def get_existing_request(self, request_id):
if request_id is None:
return None
request = self.requests_repo.get(request_id)
return request
@tornado.web.authenticated
@tornado.gen.coroutine
def post(self, screen=1, request_id=None):
self.check_xsrf_cookie()
screen = int(screen)
post_data = self.request.arguments
current_user = self.get_current_user()
existing_request = self.get_existing_request(request_id)
jedi_flow = JEDIRequestFlow(
self.requests_repo,
self.pe_numbers_repo,
screen,
post_data=post_data,
request_id=request_id,
current_user=current_user,
existing_request=existing_request,
)
rerender_args = dict(
f=jedi_flow.form,
data=post_data,
page=self.page,
screens=jedi_flow.screens,
current=screen,
next_screen=jedi_flow.next_screen,
request_id=jedi_flow.request_id,
)
if jedi_flow.validate():
jedi_flow.create_or_update_request()
valid = yield jedi_flow.validate_warnings()
if valid:
if jedi_flow.next_screen > len(jedi_flow.screens):
where = "/requests"
else:
where = self.application.default_router.reverse_url(
"request_form_update",
jedi_flow.next_screen,
jedi_flow.request_id,
)
self.redirect(where)
else:
self.render("requests/screen-%d.html.to" % int(screen), **rerender_args)
else:
self.render("requests/screen-%d.html.to" % int(screen), **rerender_args)
@tornado.web.authenticated
@tornado.gen.coroutine
def get(self, screen=1, request_id=None):
screen = int(screen)
request = None
if request_id:
request = self.requests_repo.get(request_id)
jedi_flow = JEDIRequestFlow(
self.requests_repo,
self.pe_numbers_repo,
screen,
request,
request_id=request_id,
)
self.render(
"requests/screen-%d.html.to" % int(screen),
f=jedi_flow.form,
data=jedi_flow.current_step_data,
page=self.page,
screens=jedi_flow.screens,
current=screen,
next_screen=screen + 1,
request_id=request_id,
can_submit=jedi_flow.can_submit,
)
class JEDIRequestFlow(object):
def __init__(
self,
pe_numbers_repo,
current_step,
request=None,
post_data=None,
request_id=None,
current_user=None,
existing_request=None,
):
self.pe_numbers_repo = pe_numbers_repo
self.requests_repo = requests_repo
self.pe_numbers_repo = pe_numbers_repo
self.current_step = current_step
self.request = request
self.post_data = post_data
self.is_post = self.post_data is not None
self.request_id = request_id
self.form = self._form()
self.current_user = current_user
self.existing_request = existing_request
def _form(self):
if self.is_post:
return self.form_class()(self.post_data)
elif self.request:
return self.form_class()(data=self.current_step_data)
else:
return self.form_class()()
def validate(self):
return self.form.validate()
@tornado.gen.coroutine
def validate_warnings(self):
existing_request_data = (
self.existing_request and self.existing_request.body.get(self.form_section)
) or None
valid = yield self.form.perform_extra_validation(
existing_request_data, self.pe_numbers_repo
)
return valid
@property
def current_screen(self):
return self.screens[self.current_step - 1]
@property
def form_section(self):
return self.current_screen["section"]
def form_class(self):
return self.current_screen["form"]
@property
def current_step_data(self):
data = {}
if self.is_post:
data = self.post_data
if self.request:
if self.form_section == "review_submit":
data = self.request.body
else:
data = self.request.body.get(self.form_section, {})
return defaultdict(lambda: defaultdict(lambda: "Input required"), data)
@property
def can_submit(self):
return self.request and self.request.status != "incomplete"
@property
def next_screen(self):
return self.current_step + 1
@property
def screens(self):
return [
{
"title": "Details of Use",
"section": "details_of_use",
"form": RequestForm,
"subitems": [
{
"title": "Overall request details",
"id": "overall-request-details",
},
{"title": "Cloud Resources", "id": "cloud-resources"},
{"title": "Support Staff", "id": "support-staff"},
],
"show": True,
},
{
"title": "Information About You",
"section": "information_about_you",
"form": OrgForm,
"show": True,
},
{
"title": "Primary Point of Contact",
"section": "primary_poc",
"form": POCForm,
"show": True,
},
{
"title": "Review & Submit",
"section": "review_submit",
"form": ReviewForm,
"show": True,
},
]
def create_or_update_request(self):
request_data = {self.form_section: self.form.data}
if self.request_id:
self.requests_repo.update(self.request_id, request_data)
else:
request = self.requests_repo.create(self.current_user["id"], request_data)
self.request_id = request.id

View File

@ -1,20 +0,0 @@
import tornado
from atst.handler import BaseHandler
from atst.domain.requests import Requests
class RequestsSubmit(BaseHandler):
def initialize(self, db_session):
self.db_session = db_session
self.requests_repo = Requests(db_session)
@tornado.web.authenticated
@tornado.gen.coroutine
def post(self, request_id):
request = self.requests_repo.get(request_id)
request = yield self.requests_repo.submit(request)
if request.status == "approved":
self.redirect("/requests?modal=True")
else:
self.redirect("/requests")

View File

@ -1,9 +0,0 @@
from atst.handler import BaseHandler
class Root(BaseHandler):
def initialize(self, page):
self.page = page
def get(self):
self.render("%s.html.to" % self.page, page=self.page)

View File

@ -1,17 +0,0 @@
import tornado
from atst.handler import BaseHandler
from atst.domain.workspaces import Projects
class Workspace(BaseHandler):
def initialize(self):
self.projects_repo = Projects()
@tornado.web.authenticated
@tornado.gen.coroutine
def get(self, workspace_id):
projects = self.projects_repo.get_many(workspace_id)
self.render(
"workspace_projects.html.to", workspace_id=workspace_id, projects=projects
)

View File

@ -1,17 +0,0 @@
import tornado
from atst.handler import BaseHandler
from atst.domain.workspaces import Members
class WorkspaceMembers(BaseHandler):
def initialize(self):
self.members_repo = Members()
@tornado.web.authenticated
@tornado.gen.coroutine
def get(self, workspace_id):
members = self.members_repo.get_many(workspace_id)
self.render(
"workspace_members.html.to", workspace_id=workspace_id, members=members
)

View File

@ -1,22 +0,0 @@
from atst.handler import BaseHandler
import tornado
mock_workspaces = [
{
"name": "Unclassified IaaS and PaaS for Defense Digital Service (DDS)",
"id": "5966187a-eff9-44c3-aa15-4de7a65ac7ff",
"task_order": {"number": 123456},
"user_count": 23,
}
]
class Workspaces(BaseHandler):
def initialize(self, page, db_session):
self.page = page
self.db_session = db_session
@tornado.gen.coroutine
@tornado.web.authenticated
def get(self):
self.render("workspaces.html.to", page=self.page, workspaces=mock_workspaces)

View File

@ -1,3 +0,0 @@
from unipath import Path
home = Path(__file__).parent.parent

View File

@ -24,3 +24,7 @@ class User(Base):
@property
def atat_permissions(self):
return self.atat_role.permissions
@property
def full_name(self):
return "{} {}".format(self.first_name, self.last_name)

View File

@ -15,7 +15,7 @@ def map_request(user, request):
"status": request.status,
"app_count": 1,
"date": time_created.format("M/DD/YYYY"),
"full_name": "{} {}".format(user["first_name"], user["last_name"]),
"full_name": user.full_name
}

View File

@ -1,71 +0,0 @@
from uuid import uuid4
import json
from redis import exceptions
class SessionStorageError(Exception):
pass
class SessionNotFoundError(Exception):
pass
class Sessions(object):
def start_session(self, user):
raise NotImplementedError()
def get_session(self, session_id):
raise NotImplementedError()
def generate_session_id(self):
return str(uuid4())
def build_session_dict(self, user=None):
return {"user": user or {}}
class DictSessions(Sessions):
def __init__(self):
self.sessions = {}
def start_session(self, user):
session_id = self.generate_session_id()
self.sessions[session_id] = self.build_session_dict(user=user)
return session_id
def get_session(self, session_id):
try:
session = self.sessions[session_id]
except KeyError:
raise SessionNotFoundError
return session
class RedisSessions(Sessions):
def __init__(self, redis, ttl_seconds):
self.redis = redis
self.ttl_seconds = ttl_seconds
def start_session(self, user):
session_id = self.generate_session_id()
session_dict = self.build_session_dict(user=user)
session_serialized = json.dumps(session_dict)
try:
self.redis.setex(session_id, self.ttl_seconds, session_serialized)
except exceptions.ConnectionError:
raise SessionStorageError
return session_id
def get_session(self, session_id):
try:
session_serialized = self.redis.get(session_id)
except exceptions.ConnectionError:
raise
if session_serialized:
self.redis.expire(session_id, self.ttl_seconds)
return json.loads(session_serialized)
else:
raise SessionNotFoundError

View File

@ -1,27 +0,0 @@
import os
import re
def navigationContext(self):
return (
"workspace"
if re.match("\/workspaces\/[A-Za-z0-9]*", self.request.uri)
else "global"
)
def dev(self):
return os.getenv("FLASK_ENV", "dev") == "dev"
def matchesPath(self, href):
return re.match("^" + href, self.request.uri)
def modal(self, body):
return self.render_string("components/modal.html.to", body=body)
def modalOpen(self):
# For now, just check a dummy URL param
return self.get_argument("modal", False)

View File

@ -1,70 +0,0 @@
from tornado.web import UIModule
# from tornado.template import raw
import re
class Alert(UIModule):
def render(self, title, message=None, actions=None, level="info"):
return self.render_string(
"components/alert.html.to",
title=title,
message=message,
actions=actions,
level=level,
)
class TextInput(UIModule):
def render(self, field, placeholder=""):
return self.render_string(
"components/text_input.html.to",
field=field,
label=re.sub("<[^<]+?>", "", str(field.label)),
errors=field.errors,
placeholder=placeholder,
description=field.description,
)
class OptionsInput(UIModule):
def render(self, field, inline=False):
return self.render_string(
"components/options_input.html.to",
field=field,
label=re.sub("<[^<]+?>", "", str(field.label)),
errors=field.errors,
description=field.description,
inline=inline,
)
class Icon(UIModule):
def render(self, name, classes=""):
with open("static/icons/%s.svg" % name) as svg:
return self.render_string(
"components/icon.html.to", svg=svg.read(), name=name, classes=classes
)
class SidenavItem(UIModule):
def render(self, label, href, active=False, icon=None, subnav=None):
return self.render_string(
"navigation/_sidenav_item.html.to",
label=label,
href=href,
active=active,
icon=icon,
subnav=subnav,
)
class EmptyState(UIModule):
def render(self, message, actionLabel, actionHref, icon=None):
return self.render_string(
"components/empty_state.html.to",
message=message,
actionLabel=actionLabel,
actionHref=actionHref,
icon=icon,
)

View File

@ -2,7 +2,6 @@
PORT=8000
ENVIRONMENT = dev
DEBUG = true
AUTHNID_BASE_URL= https://localhost:8001
COOKIE_SECRET = some-secret-please-replace
SECRET = change_me_into_something_secret
SECRET_KEY = change_me_into_something_secret
@ -21,3 +20,4 @@ SESSION_USE_SIGNER = True
PERMANENT_SESSION_LIFETIME = 600
CRL_DIRECTORY = crl
CA_CHAIN = ssl/server-certs/ca-chain.pem
WTF_CSRF_ENABLED = true

View File

@ -3,3 +3,4 @@ PGHOST = postgreshost
PGDATABASE = atat_test
REDIS_URI = redis://redishost:6379
CRL_DIRECTORY = tests/fixtures/crl
WTF_CSRF_ENABLED = false

View File

@ -1,3 +1,4 @@
[default]
PGDATABASE = atat_test
CRL_DIRECTORY = tests/fixtures/crl
WTF_CSRF_ENABLED = false

View File

@ -10,7 +10,6 @@ ARG CIBUILD=true
ENV APP_DIR "${APP_DIR}"
ENV FLASK_ENV ci
ENV SKIP_PIPENV true
ENV TORNADO_ENV ci
# Use dumb-init for proper signal handling
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

9
ipython_setup.py Normal file
View File

@ -0,0 +1,9 @@
from atst.app import make_config, make_app
from atst.database import db
from atst.models import *
app = make_app(make_config())
ctx = app.app_context()
ctx.push()
print("\nWelcome to atst. This shell has all models in scope, and a SQLAlchemy session called db.")

9
script/console Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# If a command fails, exit the script
set -e
# Ensure we are in the app root directory (not the /script directory)
cd "$(dirname "${0}")/.."
pipenv run ipython -i ./ipython_setup.py

View File

@ -4,7 +4,7 @@
<div class='workspace-panel-container'>
<div class='col'>
{% include 'navigation/workspace_navigation.html.to' %}
{% include 'navigation/workspace_navigation.html' %}
</div>
<div class='col col--grow'>

View File

@ -18,7 +18,7 @@
icon="document",
active=g.matchesPath('/requests'),
subnav=[
{"label":"New Request", "href":"/requests/new/1", "icon": "plus", "active": g.matchesPath('/requests/new')},
{"label":"New Request", "href":url_for("requests.requests_form_new", screen=1), "icon": "plus", "active": g.matchesPath('/requests/new')},
]
) }}
{{ SidenavItem("Workspaces", href="/workspaces", icon="cloud", active=g.matchesPath('/workspaces')) }}

View File

@ -4,7 +4,7 @@
<ul>
{{ SidenavItem(
"Projects",
href='/workspace_projects/123456',
href=url_for("workspaces.workspace_projects", workspace_id=123456),
active=g.matchesPath('\/workspaces\/[A-Za-z0-9]*\/projects'),
subnav=[
{
@ -18,7 +18,7 @@
{{ SidenavItem(
"Members",
href='/workspace_members/123456',
href=url_for("workspaces.workspace_members", workspace_id=123456),
active=g.matchesPath('\/workspaces\/[A-Za-z0-9]*\/members'),
subnav=[
{

View File

@ -40,7 +40,7 @@
{{ EmptyState(
'There are currently no active requests for you to see.',
actionLabel='Create a new JEDI Cloud Request',
actionHref='/requests/new',
actionHref=url_for('requests.requests_form_new', screen=1),
icon='document'
) }}

View File

@ -1,7 +1,3 @@
import tornado.gen
from tornado.httpclient import HTTPRequest, HTTPResponse
from atst.api_client import ApiClient
from tests.factories import RequestFactory, UserFactory
@ -21,44 +17,4 @@ DOD_SDN_INFO = {
}
DOD_SDN = f"CN={DOD_SDN_INFO['last_name']}.{DOD_SDN_INFO['first_name']}.G.{DOD_SDN_INFO['dod_id']},OU=OTHER,OU=PKI,OU=DoD,O=U.S. Government,C=US"
class MockApiClient(ApiClient):
def __init__(self, service):
self.service = service
@tornado.gen.coroutine
def get(self, path, **kwargs):
return self._get_response("GET", path)
@tornado.gen.coroutine
def put(self, path, **kwargs):
return self._get_response("PUT", path)
@tornado.gen.coroutine
def patch(self, path, **kwargs):
return self._get_response("PATCH", path)
@tornado.gen.coroutine
def post(self, path, **kwargs):
return self._get_response("POST", path)
@tornado.gen.coroutine
def delete(self, path, **kwargs):
return self._get_response("DELETE", path)
def _get_response(self, verb, path, code=200, json=None):
response = HTTPResponse(
request=HTTPRequest(path, verb),
code=code,
headers={"Content-Type": "application/json"},
)
setattr(response, "ok", 200 <= code < 300)
if json:
setattr(response, "json", json)
return response
MOCK_VALID_PE_ID = "8675309U"

View File

@ -1,6 +1,5 @@
import re
import pytest
import tornado
import urllib
from tests.mocks import MOCK_REQUEST, MOCK_USER
from tests.factories import PENumberFactory