Merge pull request #133 from dod-ccpo/cleanup-tornado
Remove the last remnants of tornado
This commit is contained in:
commit
ae201fde84
2
Pipfile
2
Pipfile
@ -4,10 +4,8 @@ verify_ssl = true
|
|||||||
name = "pypi"
|
name = "pypi"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
tornado = "*"
|
|
||||||
webassets = "*"
|
webassets = "*"
|
||||||
Unipath = "*"
|
Unipath = "*"
|
||||||
wtforms-tornado = "*"
|
|
||||||
pendulum = "*"
|
pendulum = "*"
|
||||||
redis = "*"
|
redis = "*"
|
||||||
sqlalchemy = "*"
|
sqlalchemy = "*"
|
||||||
|
38
Pipfile.lock
generated
38
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "647d98b5384d1942bbe6bfe7930b1cd249886da2f47645802cd6f93369f44538"
|
"sha256": "2b149e0d8c23814a2c701b53f5c75b36714a2ccd4e2a2769924ef6e2a3f09e97"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -271,7 +271,6 @@
|
|||||||
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
|
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
|
||||||
"sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5"
|
"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"
|
"version": "==2018.5"
|
||||||
},
|
},
|
||||||
"redis": {
|
"redis": {
|
||||||
@ -304,19 +303,6 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.2.10"
|
"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": {
|
"unipath": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:09839adcc72e8a24d4f76d63656f30b5a1f721fc40c9bcd79d8c67bdd8b47dae",
|
"sha256:09839adcc72e8a24d4f76d63656f30b5a1f721fc40c9bcd79d8c67bdd8b47dae",
|
||||||
@ -353,13 +339,6 @@
|
|||||||
"sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1"
|
"sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1"
|
||||||
],
|
],
|
||||||
"version": "==2.2.1"
|
"version": "==2.2.1"
|
||||||
},
|
|
||||||
"wtforms-tornado": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:dadb5e504d01f14bf75900f592888bb402ada6b8f8235fe583359f562d351a3a"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.0.2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
@ -522,7 +501,6 @@
|
|||||||
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
|
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
|
||||||
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
|
"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"
|
"version": "==4.3.4"
|
||||||
},
|
},
|
||||||
"itsdangerous": {
|
"itsdangerous": {
|
||||||
@ -640,7 +618,6 @@
|
|||||||
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
||||||
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
|
"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"
|
"version": "==0.7.1"
|
||||||
},
|
},
|
||||||
"prompt-toolkit": {
|
"prompt-toolkit": {
|
||||||
@ -663,7 +640,6 @@
|
|||||||
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
||||||
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
|
"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"
|
"version": "==1.5.4"
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
@ -675,11 +651,11 @@
|
|||||||
},
|
},
|
||||||
"pylint": {
|
"pylint": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0edfec21270725c5aa8e8d8d06ef5666f766e0e748ed2f1ab23624727303b935",
|
"sha256:1d6d3622c94b4887115fe5204982eee66fdd8a951cf98635ee5caee6ec98c3ec",
|
||||||
"sha256:4cadcaa4f1fb19123d4baa758d9fbe6286c5b3aa513af6ea42a2d51d405db205"
|
"sha256:31142f764d2a7cd41df5196f9933b12b7ee55e73ef12204b648ad7e556c119fb"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.1.0"
|
"version": "==2.1.1"
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -713,11 +689,15 @@
|
|||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
"sha256:1cbc199009e78f92d9edf554be4fe40fb7b0bef71ba688602a00e97a51909110",
|
||||||
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
|
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
|
||||||
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
|
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
|
||||||
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
|
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
|
||||||
|
"sha256:6f89b5c95e93945b597776163403d47af72d243f366bf4622ff08bdfd1c950b7",
|
||||||
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
|
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
|
||||||
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
|
"sha256:be622cc81696e24d0836ba71f6272a2b5767669b0d79fdcf0295d51ac2e156c8",
|
||||||
|
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b",
|
||||||
|
"sha256:f39411e380e2182ad33be039e8ee5770a5d9efe01a2bfb7ae58d9ba31c4a2a9d"
|
||||||
],
|
],
|
||||||
"version": "==4.2b4"
|
"version": "==4.2b4"
|
||||||
},
|
},
|
||||||
|
26
README.md
26
README.md
@ -4,18 +4,19 @@
|
|||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
This is the main user-facing web application for the ATAT stack. All end-user
|
This is the user-facing web application for ATAT.
|
||||||
requests are handled by ATST, with it making backend calls to various
|
|
||||||
microservices when appropriate.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
See the [scriptz](https://github.com/dod-ccpo/scriptz) repository for the shared
|
See the [scriptz](https://github.com/dod-ccpo/scriptz) repository for the shared
|
||||||
requirements and guidelines for all ATAT applications.
|
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
|
ATST requires a postgres instance (>= 9.6) for persistence. Have postgres installed
|
||||||
running on localhost on its default port, 6379.
|
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
|
### Cloning
|
||||||
This project contains git submodules. Here is an example clone command that will
|
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
|
## 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:
|
following to `~/.vim/filetype.vim` for syntax highlighting:
|
||||||
|
|
||||||
:au BufRead *.html.to set filetype=mustache
|
:au BufRead *.html.to set filetype=mustache
|
||||||
|
|
||||||
|
|
||||||
## Icons
|
## 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.
|
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:
|
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 {
|
location /login-dev {
|
||||||
auth_basic "Developer Access";
|
auth_basic "Developer Access";
|
||||||
auth_basic_user_file /etc/apache2/.htpasswd;
|
auth_basic_user_file /etc/apache2/.htpasswd;
|
||||||
|
@ -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
|
|
10
atst/app.py
10
atst/app.py
@ -6,6 +6,7 @@ from flask import Flask, request, g
|
|||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
import redis
|
import redis
|
||||||
from unipath import Path
|
from unipath import Path
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
from atst.assets import environment as assets_environment
|
from atst.assets import environment as assets_environment
|
||||||
@ -31,6 +32,7 @@ def make_app(config):
|
|||||||
static_folder=parent_dir.child("static").absolute(),
|
static_folder=parent_dir.child("static").absolute(),
|
||||||
)
|
)
|
||||||
redis = make_redis(config)
|
redis = make_redis(config)
|
||||||
|
csrf = CSRFProtect()
|
||||||
|
|
||||||
app.config.update(config)
|
app.config.update(config)
|
||||||
app.config.update({"SESSION_REDIS": redis})
|
app.config.update({"SESSION_REDIS": redis})
|
||||||
@ -39,6 +41,7 @@ def make_app(config):
|
|||||||
make_crl_validator(app)
|
make_crl_validator(app)
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
csrf.init_app(app)
|
||||||
Session(app)
|
Session(app)
|
||||||
assets_environment.init_app(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)
|
if re.match("\/workspaces\/[A-Za-z0-9]*", request.url)
|
||||||
else "global"
|
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.matchesPath = lambda href: re.match("^" + href, request.path)
|
||||||
g.modalOpen = request.args.get("modal", False)
|
g.modalOpen = request.args.get("modal", False)
|
||||||
g.current_user = {
|
g.current_user = {
|
||||||
@ -80,13 +83,14 @@ def make_flask_callbacks(app):
|
|||||||
|
|
||||||
def map_config(config):
|
def map_config(config):
|
||||||
return {
|
return {
|
||||||
|
**config["default"],
|
||||||
"ENV": config["default"]["ENVIRONMENT"],
|
"ENV": config["default"]["ENVIRONMENT"],
|
||||||
"DEBUG": config["default"]["DEBUG"],
|
"DEBUG": config["default"]["DEBUG"],
|
||||||
"PORT": int(config["default"]["PORT"]),
|
"PORT": int(config["default"]["PORT"]),
|
||||||
"SQLALCHEMY_DATABASE_URI": config["default"]["DATABASE_URI"],
|
"SQLALCHEMY_DATABASE_URI": config["default"]["DATABASE_URI"],
|
||||||
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
|
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
|
||||||
**config["default"],
|
"WTF_CSRF_ENABLED": config.getboolean("default", "WTF_CSRF_ENABLED"),
|
||||||
"PERMANENT_SESSION_LIFETIME": int(config["default"]["PERMANENT_SESSION_LIFETIME"]),
|
"PERMANENT_SESSION_LIFETIME": config.getint("default", "PERMANENT_SESSION_LIFETIME"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import tornado
|
|
||||||
from tornado.gen import Return
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -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"]
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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")
|
|
@ -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)
|
|
@ -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
|
|
||||||
)
|
|
@ -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
|
|
||||||
)
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||||||
from unipath import Path
|
|
||||||
|
|
||||||
home = Path(__file__).parent.parent
|
|
@ -24,3 +24,7 @@ class User(Base):
|
|||||||
@property
|
@property
|
||||||
def atat_permissions(self):
|
def atat_permissions(self):
|
||||||
return self.atat_role.permissions
|
return self.atat_role.permissions
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
return "{} {}".format(self.first_name, self.last_name)
|
||||||
|
@ -15,7 +15,7 @@ def map_request(user, request):
|
|||||||
"status": request.status,
|
"status": request.status,
|
||||||
"app_count": 1,
|
"app_count": 1,
|
||||||
"date": time_created.format("M/DD/YYYY"),
|
"date": time_created.format("M/DD/YYYY"),
|
||||||
"full_name": "{} {}".format(user["first_name"], user["last_name"]),
|
"full_name": user.full_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
|
@ -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)
|
|
@ -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,
|
|
||||||
)
|
|
@ -2,7 +2,6 @@
|
|||||||
PORT=8000
|
PORT=8000
|
||||||
ENVIRONMENT = dev
|
ENVIRONMENT = dev
|
||||||
DEBUG = true
|
DEBUG = true
|
||||||
AUTHNID_BASE_URL= https://localhost:8001
|
|
||||||
COOKIE_SECRET = some-secret-please-replace
|
COOKIE_SECRET = some-secret-please-replace
|
||||||
SECRET = change_me_into_something_secret
|
SECRET = change_me_into_something_secret
|
||||||
SECRET_KEY = change_me_into_something_secret
|
SECRET_KEY = change_me_into_something_secret
|
||||||
@ -21,3 +20,4 @@ SESSION_USE_SIGNER = True
|
|||||||
PERMANENT_SESSION_LIFETIME = 600
|
PERMANENT_SESSION_LIFETIME = 600
|
||||||
CRL_DIRECTORY = crl
|
CRL_DIRECTORY = crl
|
||||||
CA_CHAIN = ssl/server-certs/ca-chain.pem
|
CA_CHAIN = ssl/server-certs/ca-chain.pem
|
||||||
|
WTF_CSRF_ENABLED = true
|
||||||
|
@ -3,3 +3,4 @@ PGHOST = postgreshost
|
|||||||
PGDATABASE = atat_test
|
PGDATABASE = atat_test
|
||||||
REDIS_URI = redis://redishost:6379
|
REDIS_URI = redis://redishost:6379
|
||||||
CRL_DIRECTORY = tests/fixtures/crl
|
CRL_DIRECTORY = tests/fixtures/crl
|
||||||
|
WTF_CSRF_ENABLED = false
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
[default]
|
[default]
|
||||||
PGDATABASE = atat_test
|
PGDATABASE = atat_test
|
||||||
CRL_DIRECTORY = tests/fixtures/crl
|
CRL_DIRECTORY = tests/fixtures/crl
|
||||||
|
WTF_CSRF_ENABLED = false
|
||||||
|
@ -10,7 +10,6 @@ ARG CIBUILD=true
|
|||||||
ENV APP_DIR "${APP_DIR}"
|
ENV APP_DIR "${APP_DIR}"
|
||||||
ENV FLASK_ENV ci
|
ENV FLASK_ENV ci
|
||||||
ENV SKIP_PIPENV true
|
ENV SKIP_PIPENV true
|
||||||
ENV TORNADO_ENV ci
|
|
||||||
|
|
||||||
# Use dumb-init for proper signal handling
|
# Use dumb-init for proper signal handling
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||||
|
9
ipython_setup.py
Normal file
9
ipython_setup.py
Normal 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
9
script/console
Executable 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
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<div class='workspace-panel-container'>
|
<div class='workspace-panel-container'>
|
||||||
<div class='col'>
|
<div class='col'>
|
||||||
{% include 'navigation/workspace_navigation.html.to' %}
|
{% include 'navigation/workspace_navigation.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='col col--grow'>
|
<div class='col col--grow'>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
icon="document",
|
icon="document",
|
||||||
active=g.matchesPath('/requests'),
|
active=g.matchesPath('/requests'),
|
||||||
subnav=[
|
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')) }}
|
{{ SidenavItem("Workspaces", href="/workspaces", icon="cloud", active=g.matchesPath('/workspaces')) }}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{{ SidenavItem(
|
{{ SidenavItem(
|
||||||
"Projects",
|
"Projects",
|
||||||
href='/workspace_projects/123456',
|
href=url_for("workspaces.workspace_projects", workspace_id=123456),
|
||||||
active=g.matchesPath('\/workspaces\/[A-Za-z0-9]*\/projects'),
|
active=g.matchesPath('\/workspaces\/[A-Za-z0-9]*\/projects'),
|
||||||
subnav=[
|
subnav=[
|
||||||
{
|
{
|
||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
{{ SidenavItem(
|
{{ SidenavItem(
|
||||||
"Members",
|
"Members",
|
||||||
href='/workspace_members/123456',
|
href=url_for("workspaces.workspace_members", workspace_id=123456),
|
||||||
active=g.matchesPath('\/workspaces\/[A-Za-z0-9]*\/members'),
|
active=g.matchesPath('\/workspaces\/[A-Za-z0-9]*\/members'),
|
||||||
subnav=[
|
subnav=[
|
||||||
{
|
{
|
@ -40,7 +40,7 @@
|
|||||||
{{ EmptyState(
|
{{ EmptyState(
|
||||||
'There are currently no active requests for you to see.',
|
'There are currently no active requests for you to see.',
|
||||||
actionLabel='Create a new JEDI Cloud Request',
|
actionLabel='Create a new JEDI Cloud Request',
|
||||||
actionHref='/requests/new',
|
actionHref=url_for('requests.requests_form_new', screen=1),
|
||||||
icon='document'
|
icon='document'
|
||||||
) }}
|
) }}
|
||||||
|
|
||||||
|
@ -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
|
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"
|
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"
|
MOCK_VALID_PE_ID = "8675309U"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
import pytest
|
import pytest
|
||||||
import tornado
|
|
||||||
import urllib
|
import urllib
|
||||||
from tests.mocks import MOCK_REQUEST, MOCK_USER
|
from tests.mocks import MOCK_REQUEST, MOCK_USER
|
||||||
from tests.factories import PENumberFactory
|
from tests.factories import PENumberFactory
|
||||||
|
Loading…
x
Reference in New Issue
Block a user