diff --git a/Dockerfile b/Dockerfile index 320f278b..12992c44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,4 +7,9 @@ RUN cp -a /tmp/node_modules /dokomo/ ADD . /dokomo/ RUN pip install -r requirements.txt RUN nodejs node_modules/gulp/bin/gulp.js build +RUN mkdir -p /var/www/static/dist +RUN cp -r /dokomo/dokomoforms/static/dist /var/www/static +RUN cp /dokomo/dokomoforms/static/robots.txt /var/www/static/robots.txt +RUN cp /dokomo/dokomoforms/static/manifest.json /var/www/static/manifest.json +RUN cp /dokomo/dokomoforms/static/src/common/img/favicon.png /var/www/static/favicon.png EXPOSE 8888 diff --git a/README.md b/README.md index 30c1e12f..42a4d73d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Several solutions exist to handle offline mobile data collection. While this typ **Dokomo strives to simplify the process by integrating the elements of a data collection effort into a unified system, from creation of mobile-ready surveys to quick analysis and visualization of the collected data.** -The latest install script is here: https://raw.githubusercontent.com/SEL-Columbia/dokomoforms/v0.2.8/installer.sh +The latest install script is here: https://raw.githubusercontent.com/SEL-Columbia/dokomoforms/v0.2.9/installer.sh ## Features diff --git a/docker-compose.yml b/docker-compose.yml index f976fdb4..087bcc7b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,13 +9,16 @@ nginx: - ./nginx.conf:/etc/nginx/nginx.conf - /etc/letsencrypt:/etc/letsencrypt - /tmp:/tmp + volumes_from: + - webapp webapp: - image: "selcolumbia/dokomoforms:0.2.8" + image: "selcolumbia/dokomoforms:0.2.9" command: bash -c "./docker-wait-for-postgres.sh db && head -c 24 /dev/urandom > cookie_secret && python webapp.py" links: - "db:db" volumes: - ./local_config.py:/dokomo/local_config.py + - /var/www db: image: "mdillon/postgis:9.4" environment: diff --git a/dokomoforms/static/src/common/js/service-worker.js b/dokomoforms/static/src/common/js/service-worker.js index 3b5481af..b4064ee4 100644 --- a/dokomoforms/static/src/common/js/service-worker.js +++ b/dokomoforms/static/src/common/js/service-worker.js @@ -1 +1,2 @@ // Nothing to see here +// Eventually this should end up in /dist as well... diff --git a/dokomoforms/templates/admin_homepage.html b/dokomoforms/templates/admin_homepage.html index 8fed1b21..0bd40daf 100644 --- a/dokomoforms/templates/admin_homepage.html +++ b/dokomoforms/templates/admin_homepage.html @@ -82,7 +82,7 @@

Surveys

{% block extra_scripts %} - + {% end %} diff --git a/dokomoforms/templates/base.html b/dokomoforms/templates/base.html index ed7e7db8..8e8e6b0c 100644 --- a/dokomoforms/templates/base.html +++ b/dokomoforms/templates/base.html @@ -13,10 +13,10 @@ - + - - + + {{ options.organization }} Surveys -- Powered by DokomoData @@ -98,7 +98,7 @@ - + diff --git a/dokomoforms/templates/enumerate_homepage.html b/dokomoforms/templates/enumerate_homepage.html index 7ab1a1c4..78d19e6d 100644 --- a/dokomoforms/templates/enumerate_homepage.html +++ b/dokomoforms/templates/enumerate_homepage.html @@ -1 +1,51 @@ -Hi you are an enumerator. +{% from dokomoforms.options import options %} + + + + + {{ options.organization }} Surveys -- Powered by DokomoData + + + + + + + + + + + + + + + + +
+ Enumerator home page. +
+ + + + + + + + + diff --git a/dokomoforms/templates/index.html b/dokomoforms/templates/index.html index bcae590d..bb8ecca4 100644 --- a/dokomoforms/templates/index.html +++ b/dokomoforms/templates/index.html @@ -36,7 +36,7 @@

DokomoData

{% block extra_scripts %} - + {% if message %} + - + - + + + {% end %} diff --git a/installer.sh b/installer.sh index e5329e26..e10583a7 100755 --- a/installer.sh +++ b/installer.sh @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# Dokomo Forms installer for version 0.2.8 +# Dokomo Forms installer for version 0.2.9 set -e # Do you have docker installed? @@ -88,7 +88,10 @@ printf " Installing SSL certificate. Make sure \n" printf " you have set up the DNS records for \n" printf " your domain to point to this machine. \n" printf "========================================\n" -$SUDO docker run -it --rm -p 443:443 -p 80:80 --name letsencrypt \ +# for some reason these directories need to exist beforehand on Fedora... +$SUDO mkdir -p /etc/letsencrypt +$SUDO mkdir -p /var/lib/letsencrypt +$SUDO docker run -it --rm -p 443:443 -p 80:80 \ -v "/etc/letsencrypt:/etc/letsencrypt:Z" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt:Z" \ -v "/var/log:/var/log:Z" \ @@ -105,8 +108,8 @@ $SUDO openssl dhparam -out /etc/letsencrypt/live/$LETSENCRYPT_DIR/dhparam.pem 20 printf "========================================\n" printf " Downloading configuration files \n" printf "========================================\n" -$CURL -L https://raw.githubusercontent.com/SEL-Columbia/dokomoforms/v0.2.8/docker-compose.yml > docker-compose.yml -$CURL -L https://raw.githubusercontent.com/SEL-Columbia/dokomoforms/v0.2.8/nginx.conf > nginx.conf +$CURL -L https://raw.githubusercontent.com/SEL-Columbia/dokomoforms/v0.2.9/docker-compose.yml > docker-compose.yml +$CURL -L https://raw.githubusercontent.com/SEL-Columbia/dokomoforms/v0.2.9/nginx.conf > nginx.conf # Edit the configuration files printf "========================================\n" diff --git a/nginx.conf b/nginx.conf index d8b0cbc8..677cec67 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,6 +5,7 @@ http { upstream dokomo { server webapp:8888; } + include /etc/nginx/mime.types; server { listen 80; listen [::]:80; @@ -35,6 +36,31 @@ http { root /tmp/letsencrypt-auto; } + location ^~ /static/dist/ { + root /var/www; + if ($query_string) { + expires max; + } + } + + location = /favicon.png { + root /var/www/static; + if ($query_string) { + expires max; + } + } + + location = /robots.txt { + root /var/www/static; + if ($query_string) { + expires max; + } + } + + location = /static/manifest.json { + root /var/www; + } + location / { proxy_pass_header Server; proxy_set_header Host $http_host; diff --git a/package.json b/package.json index 98e85bed..f2a1d217 100644 --- a/package.json +++ b/package.json @@ -12,17 +12,17 @@ "bootstrap": "^3.3.6", "datatables": "https://github.com/DataTables/DataTables/archive/1.10.9.tar.gz", "es5-shim": "^4.3.1", - "highcharts-release": "^4.1.8", - "jquery": "^2.1.4", - "leaflet": "^0.7.5", + "highcharts-release": "^4.2.1", + "jquery": "^2.2.0", + "leaflet": "^0.7.7", "lodash-compat": "^3.10.1", "lz-string": "^1.4.4", - "moment": "^2.10.6", - "node-uuid": "^1.4.3", - "pouchdb": "^5.1.0", + "moment": "^2.11.1", + "node-uuid": "^1.4.7", + "pouchdb": "^5.2.0", "pouchdb-upsert": "^2.0.0", "ratchet": "https://github.com/twbs/ratchet/archive/v2.0.2.tar.gz", - "react": "^0.14.5", + "react": "^0.14.6", "react-dom": "~0.14.2", "screenfull": "^3.0.0" }, @@ -32,39 +32,39 @@ "babel-preset-es2015": "^6.1.18", "babel-preset-react": "^6.1.18", "babelify": "^7.2.0", - "browserify": "^12.0.1", - "browserify-shim": "^3.8.7", + "browserify": "^13.0.0", + "browserify-shim": "^3.8.12", "del": "^2.0.2", - "eslint-plugin-react": "^3.3.0", - "event-stream": "^3.3.1", - "gulp": "^3.8.11", - "gulp-concat": "^2.5.2", - "gulp-html-replace": "^1.4.5", + "eslint-plugin-react": "^3.15.0", + "event-stream": "^3.3.2", + "gulp": "^3.9.0", + "gulp-concat": "^2.6.0", + "gulp-html-replace": "^1.5.5", "gulp-if": "^2.0.0", - "gulp-less": "^3.0.3", - "gulp-livereload": "^3.8.0", - "gulp-minify-css": "^1.2.1", + "gulp-less": "^3.0.5", + "gulp-livereload": "^3.8.1", + "gulp-minify-css": "^1.2.3", "gulp-order": "^1.1.1", "gulp-react": "^3.0.1", "gulp-rename": "^1.2.2", "gulp-replace": "^0.5.4", - "gulp-sourcemaps": "^1.5.2", + "gulp-sourcemaps": "^1.6.0", "gulp-streamify": "^1.0.2", - "gulp-uglify": "1.4.2", - "istanbul": "^0.4.0", - "jest-cli": "^0.7.1", - "jsdom": "^7.0.2", - "jshint": "^2.7.0", + "gulp-uglify": "1.5.1", + "istanbul": "^0.4.2", + "jest-cli": "^0.8.2", + "jsdom": "^7.2.2", + "jshint": "^2.8.0", "mocha": "^2.2.4", "mocha-istanbul": "^0.2.0", "node-underscorify": "0.0.14", "react-addons-test-utils": "~0.14.2", "reactify": "^1.1.1", - "should": "^7.1.1", + "should": "^8.1.1", "supertest": "^1.1.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", - "watchify": "^3.2.1" + "watchify": "^3.7.0" }, "browserify": { "transform": [ diff --git a/tests/python/test_demo_mode.py b/tests/python/test_demo_mode.py index 78fee939..72f0fd6e 100644 --- a/tests/python/test_demo_mode.py +++ b/tests/python/test_demo_mode.py @@ -7,6 +7,7 @@ utils = (setUpModule, tearDownModule) +import dokomoforms.handlers from dokomoforms.models import Administrator from dokomoforms.options import options @@ -20,7 +21,26 @@ def get_app(self): self.app = Application(self.session, options=options) return self.app - def test_logging_in_creates_user(self): + def test_logging_in_creates_user_no_https(self): + dokomoforms.handlers.demo.options.https = False + no_user = ( + self.session + .query(count(Administrator.id)) + .filter_by(name='demo_user') + .scalar() + ) + self.assertEqual(no_user, 0) + self.fetch('/demo/login', _logged_in_user=None) + user = ( + self.session + .query(count(Administrator.id)) + .filter_by(name='demo_user') + .scalar() + ) + self.assertEqual(user, 1) + + def test_logging_in_creates_user_https(self): + dokomoforms.handlers.demo.options.https = True no_user = ( self.session .query(count(Administrator.id)) diff --git a/tests/python/test_handlers.py b/tests/python/test_handlers.py index cabdf43b..7894033c 100644 --- a/tests/python/test_handlers.py +++ b/tests/python/test_handlers.py @@ -243,6 +243,43 @@ def test_login_success(self): 1 ) + def test_login_as_admin_first_time(self): + admin_emails = ( + self.session + .query(models.Email) + .filter_by(address='admin@email.com') + .all() + ) + self.assertEqual(len(admin_emails), 0) + + dokomoforms.handlers.auth.options.https = False + dokomoforms.handlers.auth.options.admin_email = 'admin@email.com' + with patch.object(handlers.Login, '_async_post') as p: + dummy = lambda: None + dummy.body = json_encode( + {'status': 'okay', 'email': 'admin@email.com'} + ) + p.return_value = tornado.gen.Task( + lambda callback=None: callback(dummy) + ) + response = self.fetch( + '/user/login?assertion=woah', method='POST', body='', + _logged_in_user=None + ) + self.assertEqual(response.code, 200, msg=response.body) + self.assertEqual( + response.headers['Set-Cookie'].lower().count('secure'), + 1 + ) + + new_admin_emails = ( + self.session + .query(models.Email) + .filter_by(address='admin@email.com') + .all() + ) + self.assertEqual(len(new_admin_emails), 1) + def test_login_success_secure_cookie(self): dokomoforms.handlers.auth.options.https = True with patch.object(handlers.Login, '_async_post') as p: @@ -288,6 +325,20 @@ def test_login_fail(self): ) self.assertEqual(response.code, 400, msg=response.body) + def test_check_login_status_logged_out(self): + response = self.fetch( + '/user/authenticated', + method='POST', _logged_in_user=None, body='' + ) + self.assertEqual(response.code, 403) + + def test_check_login_status_logged_in(self): + response = self.fetch( + '/user/authenticated', + method='POST', body='' + ) + self.assertEqual(response.code, 200) + class TestBaseHandler(DokoHTTPTest): def test_clear_user_cookie_if_not_uuid(self): diff --git a/tests/python/test_selenium.py b/tests/python/test_selenium.py index fcae1ab7..40eb1572 100644 --- a/tests/python/test_selenium.py +++ b/tests/python/test_selenium.py @@ -520,7 +520,9 @@ class TestAdminOverview(AdminTest): @report_success_status def test_account_overview_renders_properly(self): self.get('/') + # Recent submissions table + self.sleep() self.assertEqual( len(self.drv.find_elements_by_class_name('submission-row')), 5 @@ -680,7 +682,7 @@ def test_update_settings(self): save_btn = self.drv.find_element_by_class_name('btn-save-user') self.sleep() save_btn.click() - self.sleep() + self.sleep(2) self.click(self.drv.find_element_by_id('UserDropdown')) try: @@ -1106,7 +1108,7 @@ def test_url_slug_collision(self): ) try: self.click(self.drv.find_element_by_class_name('save-survey-url')) - except (WebDriverException, ValueError): + except (WebDriverException, ValueError, TypeError): # Catch ValueError due to # https://github.com/SeleniumHQ/selenium/issues/1470 # @@ -1798,7 +1800,8 @@ def test_single_facility_question(self): self.wait_for_element( '.content > span:nth-child(2) > span:nth-child(1)' ' > div:nth-child(1) > button:nth-child(1)', - by=By.CSS_SELECTOR + by=By.CSS_SELECTOR, + timeout=10, ) self.click( self.drv @@ -4151,7 +4154,8 @@ def test_add_new_facility_revisit_cuts_out(self): self.sleep() self.wait_for_element( '.btn-add-facility', - by=By.CSS_SELECTOR + by=By.CSS_SELECTOR, + timeout=10, ) # click add button self.click( diff --git a/webapp.py b/webapp.py index f3ce0687..05b9a03e 100755 --- a/webapp.py +++ b/webapp.py @@ -379,7 +379,7 @@ def main(msg=None): # pragma: no cover logging.getLogger('sqlalchemy').setLevel(log_level) if options.kill: ensure_that_user_wants_to_drop_schema() - http_server = tornado.httpserver.HTTPServer(Application()) + http_server = tornado.httpserver.HTTPServer(Application(), xheaders=True) tornado.locale.load_gettext_translations( os.path.join(_pwd, 'locale'), 'dokomoforms' )