diff --git a/Pipfile b/Pipfile index d7cef862..1312960b 100644 --- a/Pipfile +++ b/Pipfile @@ -4,10 +4,8 @@ verify_ssl = true name = "pypi" [packages] -tornado = "*" webassets = "*" Unipath = "*" -wtforms-tornado = "*" pendulum = "*" redis = "*" sqlalchemy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 1ade9ff2..b9b8cab6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -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" }, diff --git a/README.md b/README.md index ccb33c69..e07d91ea 100644 --- a/README.md +++ b/README.md @@ -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; diff --git a/atst/api_client.py b/atst/api_client.py deleted file mode 100644 index c41e0682..00000000 --- a/atst/api_client.py +++ /dev/null @@ -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 diff --git a/atst/app.py b/atst/app.py index e1af87c8..4b7a0806 100644 --- a/atst/app.py +++ b/atst/app.py @@ -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"), } diff --git a/atst/forms/forms.py b/atst/forms/forms.py index a930fd92..2aaa4973 100644 --- a/atst/forms/forms.py +++ b/atst/forms/forms.py @@ -1,5 +1,3 @@ -import tornado -from tornado.gen import Return from flask_wtf import FlaskForm diff --git a/atst/handler.py b/atst/handler.py deleted file mode 100644 index 2e9d9462..00000000 --- a/atst/handler.py +++ /dev/null @@ -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"] diff --git a/atst/handlers/__init__.py b/atst/handlers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/atst/handlers/dev.py b/atst/handlers/dev.py deleted file mode 100644 index 8beeb983..00000000 --- a/atst/handlers/dev.py +++ /dev/null @@ -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) diff --git a/atst/handlers/login_redirect.py b/atst/handlers/login_redirect.py deleted file mode 100644 index 7746e934..00000000 --- a/atst/handlers/login_redirect.py +++ /dev/null @@ -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 diff --git a/atst/handlers/main.py b/atst/handlers/main.py deleted file mode 100644 index 737d3bd7..00000000 --- a/atst/handlers/main.py +++ /dev/null @@ -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) diff --git a/atst/handlers/request.py b/atst/handlers/request.py deleted file mode 100644 index 6918526e..00000000 --- a/atst/handlers/request.py +++ /dev/null @@ -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 diff --git a/atst/handlers/request_financial_verification.py b/atst/handlers/request_financial_verification.py deleted file mode 100644 index e6061c39..00000000 --- a/atst/handlers/request_financial_verification.py +++ /dev/null @@ -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) diff --git a/atst/handlers/request_new.py b/atst/handlers/request_new.py deleted file mode 100644 index f9f204f0..00000000 --- a/atst/handlers/request_new.py +++ /dev/null @@ -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 diff --git a/atst/handlers/request_submit.py b/atst/handlers/request_submit.py deleted file mode 100644 index 116e996a..00000000 --- a/atst/handlers/request_submit.py +++ /dev/null @@ -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") diff --git a/atst/handlers/root.py b/atst/handlers/root.py deleted file mode 100644 index ec68bfcc..00000000 --- a/atst/handlers/root.py +++ /dev/null @@ -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) diff --git a/atst/handlers/workspace.py b/atst/handlers/workspace.py deleted file mode 100644 index 2f1b5dd6..00000000 --- a/atst/handlers/workspace.py +++ /dev/null @@ -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 - ) diff --git a/atst/handlers/workspace_members.py b/atst/handlers/workspace_members.py deleted file mode 100644 index fcf8110c..00000000 --- a/atst/handlers/workspace_members.py +++ /dev/null @@ -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 - ) diff --git a/atst/handlers/workspaces.py b/atst/handlers/workspaces.py deleted file mode 100644 index b6fa1dbe..00000000 --- a/atst/handlers/workspaces.py +++ /dev/null @@ -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) diff --git a/atst/home.py b/atst/home.py deleted file mode 100644 index 883ec8dd..00000000 --- a/atst/home.py +++ /dev/null @@ -1,3 +0,0 @@ -from unipath import Path - -home = Path(__file__).parent.parent diff --git a/atst/models/user.py b/atst/models/user.py index 6a1b530d..643fd3f5 100644 --- a/atst/models/user.py +++ b/atst/models/user.py @@ -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) diff --git a/atst/routes/requests/index.py b/atst/routes/requests/index.py index 79090d67..7e098b47 100644 --- a/atst/routes/requests/index.py +++ b/atst/routes/requests/index.py @@ -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 } diff --git a/atst/sessions.py b/atst/sessions.py deleted file mode 100644 index f7f4dfd8..00000000 --- a/atst/sessions.py +++ /dev/null @@ -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 diff --git a/atst/ui_methods.py b/atst/ui_methods.py deleted file mode 100644 index a3ccbbba..00000000 --- a/atst/ui_methods.py +++ /dev/null @@ -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) diff --git a/atst/ui_modules.py b/atst/ui_modules.py deleted file mode 100644 index 68b40bdd..00000000 --- a/atst/ui_modules.py +++ /dev/null @@ -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, - ) diff --git a/config/base.ini b/config/base.ini index 2ca8f9f3..f256fa3a 100644 --- a/config/base.ini +++ b/config/base.ini @@ -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 diff --git a/config/ci.ini b/config/ci.ini index e7e5be1a..3ee7f480 100644 --- a/config/ci.ini +++ b/config/ci.ini @@ -3,3 +3,4 @@ PGHOST = postgreshost PGDATABASE = atat_test REDIS_URI = redis://redishost:6379 CRL_DIRECTORY = tests/fixtures/crl +WTF_CSRF_ENABLED = false diff --git a/config/test.ini b/config/test.ini index fe38c777..614cdb7b 100644 --- a/config/test.ini +++ b/config/test.ini @@ -1,3 +1,4 @@ [default] PGDATABASE = atat_test CRL_DIRECTORY = tests/fixtures/crl +WTF_CSRF_ENABLED = false diff --git a/deploy/docker/tester/Dockerfile b/deploy/docker/tester/Dockerfile index bfec5729..5486ffa1 100644 --- a/deploy/docker/tester/Dockerfile +++ b/deploy/docker/tester/Dockerfile @@ -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", "--"] diff --git a/ipython_setup.py b/ipython_setup.py new file mode 100644 index 00000000..125a3d2b --- /dev/null +++ b/ipython_setup.py @@ -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.") diff --git a/script/console b/script/console new file mode 100755 index 00000000..0561ac99 --- /dev/null +++ b/script/console @@ -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 diff --git a/templates/base_workspace.html b/templates/base_workspace.html index 0ccad5ba..fedc3278 100644 --- a/templates/base_workspace.html +++ b/templates/base_workspace.html @@ -4,7 +4,7 @@