diff --git a/.travis.yml b/.travis.yml index bd06366..c82285f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,6 @@ # @unittest.skip("Unconditional skipping") # @unittest.skipIf(os.getenv('CONTINUOUS_INTEGRATION', '') > '', reason='Travis VM') -# The redidropper/startup/initializer.py does not need the env variable anymore -#env: REDIDROPPER_CONFIG=~/build/ctsit/redi-dropper-client/app/deploy/application.conf - # http://docs.travis-ci.com/user/migrating-from-legacy/ sudo: false language: python @@ -48,8 +45,8 @@ script: - pushd app - echo $TRAVIS_BUILD_DIR - ls -al deploy/ - # put config file in the place expected by config.py - - cp deploy/sample.settings.conf deploy/settings.conf + # link the settings file to make it visible in config.py + - cp deploy/sample.vagrant.settings.conf deploy/settings.conf - python setup.py nosetests - popd diff --git a/README.md b/README.md index 1f2251a..5e00d83 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# RediDropper +# REDI-Dropper + +[![DOI](https://zenodo.org/badge/doi/10.5281/zenodo.32500.svg)](http://dx.doi.org/10.5281/zenodo.32500) | Branch | [Travis-CI](https://travis-ci.org/ctsit/redi-dropper-client/builds) | [Coveralls](https://coveralls.io/github/ctsit/redi-dropper-client) | | :----- | :---------------------------: | :-------: | diff --git a/app/db/002/data.sql b/app/db/002/data.sql index 5e211c6..1bbee61 100644 --- a/app/db/002/data.sql +++ b/app/db/002/data.sql @@ -5,6 +5,7 @@ SET @end = CONCAT(CURDATE() + interval 6 month, ' 23:59:59'); SET @end2 = CONCAT(CURDATE() - interval 1 month, ' 23:59:59'); +-- Note: there is no value inserted in the usrPasswordHash INSERT INTO User (usrEmail, usrFirst, usrLast, usrAddedAt, usrAccessExpiresAt, usrIsActive, usrEmailConfirmedAt) VALUES ('asura@ufl.edu', 'Andrei', 'Şérenfaü', NOW(), @end, 1, NOW()), @@ -12,9 +13,7 @@ VALUES ('cpb@ufl.edu', 'Christopher', 'Barnes', NOW(), @end2, 1, NOW()), ('pbc@ufl.edu', 'Philip', 'Chase', NOW(), @end, 0, NOW()), ('keyes@ufl.edu', 'Roy', 'Keyes', NOW(), @end, 0, NOW()), - ('taeber@ufl.edu', 'Taeber', 'Rapczak', NOW(), @end, 0, NOW()), - ('atloiaco@ufl.edu', 'Alex', 'Loiacono', NOW(), @end, 0, NOW()), - ('cavedivr@ufl.edu', 'Erik', 'Schmidt', NOW(), @end, 0, NOW()) + ('taeber@ufl.edu', 'Taeber', 'Rapczak', NOW(), @end, 0, NOW()) ; INSERT INTO Role (rolName, rolDescription) @@ -35,27 +34,8 @@ UNION SELECT usrID, rolID, NOW() FROM User, Role WHERE usrEmail = 'cpb@ufl.edu' UNION SELECT usrID, rolID, NOW() FROM User, Role WHERE usrEmail = 'pbc@ufl.edu' AND rolName = 'technician' UNION SELECT usrID, rolID, NOW() FROM User, Role WHERE usrEmail = 'keyes@ufl.edu' AND rolName = 'technician' UNION SELECT usrID, rolID, NOW() FROM User, Role WHERE usrEmail = 'taeber@ufl.edu' AND rolName = 'technician' -UNION SELECT usrID, rolID, NOW() FROM User, Role WHERE usrEmail = 'atloiaco@ufl.edu' AND rolName = 'technician' -UNION SELECT usrID, rolID, NOW() FROM User, Role WHERE usrEmail = 'cavedivr@ufl.edu' AND rolName = 'technician' ; --- REDCap Subjects (retrieve from server) --- INSERT INTO Subject (sbjRedcapID, sbjAddedAt) --- VALUES --- ('001', NOW()), --- ('002', NOW()), --- ('003', NOW()), --- ('004', NOW()), --- ('005', NOW()) --- ; - - --- REDCap event (retrieve from server) --- INSERT INTO Event (evtRedcapArm, evtRedcapEvent, evtAddedAt) --- SELECT 'Arm 1', 'Event 01', NOW() --- UNION SELECT 'Arm 1', 'Event 02', NOW() --- ; - -- Subject Files -- INSERT INTO SubjectFile (sbjID, evtID, sfFileName, sfFileCheckSum, sfFileSize, sfUploadedAt, usrID) diff --git a/app/deploy/fabfile.py b/app/deploy/fabfile.py index bdcc449..0e8d35a 100644 --- a/app/deploy/fabfile.py +++ b/app/deploy/fabfile.py @@ -136,16 +136,10 @@ def _is_prod(): return env.environment == 'production' -def _motd(): - """Print the message of the day""" - print(MOTD_PROD if _is_prod() else MOTD_STAG) - - def bootstrap(tag='master'): """Bootstrap the deployment using the specified branch""" require('environment', provided_by=[production, staging]) - _motd() - + print(MOTD_PROD if _is_prod() else MOTD_STAG) msg = colors.red('\n%(project_path)s exists. ' 'Do you want to continue anyway?' % env) @@ -351,11 +345,16 @@ def mysql_reset_tables(): def _toggle_apache_site(state): - """Switch site's status to enabled or disabled""" + """Switch site's status to enabled or disabled + Note: the `project_name` is used for referencing the config files + """ action = "Enabling" if state else "Disabling" print('\n%s site...' % action) env.apache_command = 'a2ensite' if state else 'a2dissite' sudo('%(apache_command)s %(project_name)s' % env) + + # We have to have the ssl config too because we use the NetScaler + sudo('%(apache_command)s %(project_name)s-ssl' % env) sudo('service apache2 reload') @@ -433,6 +432,13 @@ def update_config(tag='master'): 'group': 'root' }, 2: { + 'local': os.path.abspath('%(environment)s/virtualhost-ssl.conf' + % env), + 'remote': env.vhost_ssl_file, + 'mode': '644', + 'group': 'root' + }, + 3: { 'local': local_settings_file, 'remote': env.settings_file, 'mode': '640' diff --git a/app/deploy/sample.fabric.py b/app/deploy/sample.fabric.py index 996ed74..1014231 100644 --- a/app/deploy/sample.fabric.py +++ b/app/deploy/sample.fabric.py @@ -81,6 +81,8 @@ def get_settings(overrides={}): SETTINGS['server_group'] = 'app-runner' SETTINGS['vhost_file'] = ('/etc/apache2/sites-available/%(project_name)s' % SETTINGS) + SETTINGS['vhost_ssl_file'] = ('/etc/apache2/sites-available/%(project_name)s-ssl' % + SETTINGS) SETTINGS['wsgi_file'] = ('%(project_path)s/dropper.wsgi' % SETTINGS) SETTINGS['settings_file'] = ('%(project_path)s/settings.conf' % diff --git a/app/deploy/sample.settings.conf b/app/deploy/sample.vagrant.settings.conf similarity index 100% rename from app/deploy/sample.settings.conf rename to app/deploy/sample.vagrant.settings.conf diff --git a/app/deploy/sample.virtualhost-ssl.conf b/app/deploy/sample.virtualhost-ssl.conf index 677035c..75a1372 100644 --- a/app/deploy/sample.virtualhost-ssl.conf +++ b/app/deploy/sample.virtualhost-ssl.conf @@ -1,84 +1,34 @@ - -ServerSignature off -ServerTokens Prod +# +# Apache SSL config +# @see NetScaler: https://www.citrix.com/products/netscaler-application-delivery-controller/overview/what-is-an-adc.html +# + +## +## SSL Global Context +## +## All SSL configuration in this context applies both to +## the main server and all SSL-enabled virtual hosts. +## + + + ServerName %(project_url)s:443 + UseCanonicalName On - - - - - ServerName %(project_url)s - Header set Strict-Transport-Security "max-age=31536000; includeSubDomains" - - SSLEngine on - SSLProtocol All -SSLv2 -SSLv3 - SSLCipherSuite AES256+EECDH:AES256+EDH:!aNULL:!eNULL - SSLCompression off - SSLCertificateFile /etc/apache2/ssl/%(project_url)s.crt - SSLCertificateKeyFile /etc/apache2/ssl/%(project_url)s.key - - # Possible values: debug, info, notice, warn, error, crit, alert, emerg. LogLevel warn - ServerAdmin webmaster@localhost - ErrorLog %(project_path)s/logs/error.log - CustomLog %(project_path)s/logs/access.log combined - - ############################################################################ - # @see https://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIDaemonProcess - ########################################################################### -#ALZ_INSTANCE_BEGIN - WSGIDaemonProcess %(project_name)s processes=2 threads=3 stack-size=1048576 maximum-requests=500 inactivity-timeout=300 display-name=%%{GROUP} python-path=%(env_path)s/lib/%(python)s/site-packages - WSGIProcessGroup %(project_name)s - WSGIScriptAlias /alz %(wsgi_file)s - - - WSGIScriptReloading On - WSGIProcessGroup %(project_name)s - WSGIApplicationGroup %%{GLOBAL} - - Order deny,allow - Allow from all - - - Alias /alz/static %(project_repo_path)s/app/redidropper/static - - Order allow,deny - Allow from all - -#ALZ_INSTANCE_END - -#ONEFL_INSTANCE_BEGIN - WSGIDaemonProcess dropper_onefl user=www-data group=www-data processes=2 threads=3 stack-size=1048576 maximum-requests=500 inactivity-timeout=300 display-name=%%{GROUP} python-path=/srv/apps/dropper-onefl/env/lib/python2.7/site-packages - WSGIProcessGroup dropper_onefl - WSGIScriptAlias /onefl /srv/apps/dropper-onefl/dropper.wsgi - - - WSGIScriptReloading On - WSGIProcessGroup dropper_onefl - WSGIApplicationGroup %%{GLOBAL} + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW - Order deny,allow - Allow from all - + SSLCertificateFile /etc/apache2/ssl/%(project_url)s.crt + SSLCertificateKeyFile /etc/apache2/ssl/%(project_url)s.key - Alias /onefl/static /srv/apps/dropper-onefl/src/current/app/redidropper/static - - Order allow,deny - Allow from all - -#ONEFL_INSTANCE_END + + SSLOptions +StdEnvVars + - ########################################################################### - - AuthType shibboleth - ShibRequireSession Off - ShibUseHeaders On - # require valid-user - require shibboleth - + SetEnvIf User-Agent ".*MSIE.*" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 - Options -Indexes - SetOutputFilter DEFLATE - AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/x-javascript - diff --git a/app/deploy/sample.virtualhost.conf b/app/deploy/sample.virtualhost.conf index 60cc76a..3227123 100644 --- a/app/deploy/sample.virtualhost.conf +++ b/app/deploy/sample.virtualhost.conf @@ -2,7 +2,7 @@ ServerSignature off ServerTokens Prod - ServerName https:// %(project_url)s:443 + ServerName https://%(project_url)s:443 Header set Strict-Transport-Security "max-age=31536000; includeSubDomains" # Possible values: debug, info, notice, warn, error, crit, alert, emerg. diff --git a/app/redidropper/routes/api.py b/app/redidropper/routes/api.py index 480d0f1..a08ce59 100644 --- a/app/redidropper/routes/api.py +++ b/app/redidropper/routes/api.py @@ -183,7 +183,9 @@ def download_file(): @app.route('/api/save_user', methods=['POST']) @login_required def api_save_user(): - """ Save a new user to the database """ + """ Save a new user to the database + TODO: Add support for reading a password field + """ email = request.form['email'] first = request.form['first'] last = request.form['last'] @@ -201,11 +203,14 @@ def api_save_user(): return utils.jsonify_error( {'message': 'Sorry. This email is already taken.'}) - # @TODO: fix hardcoded values - # salt, hashed_pass = generate_auth(app.config['SECRET_KEY'], password) + # @TODO: use a non-gatorlink password here + password = email + salt, password_hash = utils.generate_auth(app.config['SECRET_KEY'], + password) added_date = datetime.today() access_end_date = utils.get_expiration_date(180) + # Note: we store the salt as a prefix user = UserEntity.create(email=email, first=first, last=last, @@ -213,7 +218,7 @@ def api_save_user(): added_at=added_date, modified_at=added_date, access_expires_at=access_end_date, - password_hash="") + password_hash="{}:{}".format(salt, password_hash)) user_roles = [] try: diff --git a/app/redidropper/routes/pages.py b/app/redidropper/routes/pages.py index 5cbd46f..a109102 100644 --- a/app/redidropper/routes/pages.py +++ b/app/redidropper/routes/pages.py @@ -167,9 +167,14 @@ def render_login_local(): LogEntity.login(uuid, "No such email: {}".format(email)) return redirect(url_for('index')) - # if utils.is_valid_auth(app.config['SECRET_KEY'], auth.uathSalt, - # password, auth.uathPassword): - if '' == user.password_hash: + password_hash = user.password_hash + + # @TODO: enforce the `local password` policy + if '' == password_hash or \ + utils.is_valid_auth(app.config['SECRET_KEY'], + password_hash[0:16], + password, + password_hash[17:]): app.logger.info('Log login event for: {}'.format(user)) LogEntity.login(uuid, 'Successful login via email/password') login_user(user, remember=False, force=False) @@ -181,6 +186,7 @@ def render_login_local(): else: app.logger.info('Incorrect pass for: {}'.format(user)) LogEntity.login_error(uuid, 'Incorrect pass for: {}'.format(user)) + utils.flash_error("Incorrect username/password.") # When sending a GET request render the login form return render_template('index.html', form=form, diff --git a/app/redidropper/routes/users.py b/app/redidropper/routes/users.py index c8935ef..948811e 100644 --- a/app/redidropper/routes/users.py +++ b/app/redidropper/routes/users.py @@ -88,11 +88,11 @@ def get_user_links(): 'logout': ('logout', 'Logout'), } role = get_highest_role() + # print "highest role: {}".format(role) + if role is None: return [] - print "highest role: {}".format(role) - if ROLE_ADMIN == role: links = [pages['admin'], pages['upload_files'], diff --git a/app/redidropper/utils.py b/app/redidropper/utils.py index 3b7d310..a877afa 100644 --- a/app/redidropper/utils.py +++ b/app/redidropper/utils.py @@ -86,8 +86,8 @@ def generate_auth(pepper, password): Note: requires a request context. """ salt = _create_salt() - hashed_pass = _generate_sha512_hmac(pepper, salt, password) - return (salt, hashed_pass) + password_hash = _generate_sha512_hmac(pepper, salt, password) + return (salt, password_hash) def is_valid_auth(pepper, salt, candidate_password, correct_hash): diff --git a/docs/README.md b/docs/README.md index 4fd67f5..dc43253 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,16 +26,17 @@ This folder stores the code for RediDropper web application. Optional step - create a self-signed certificate: -
-cd redi-dropper-client/app/ssl
-./gen_cert.sh
-
+ $ cd redi-dropper-client/app/ssl + $ ./gen_cert.sh The above command will produce two files used in debug mode: - server.crt - server.key +Note: if you get errors related to mising "Guest Additions" please try: + + vagrant plugin install vagrant-vbguest ## Developer's Workflow - Without Vagrant @@ -54,8 +55,6 @@ manually using Python's embedded webserver. The manual process requires the following commands for setup: -``` - brew install mysql mysql --version (mysql Ver 14.14 Distrib 5.6.24, for osx10.9 (x86_64) using EditLine wrapper) @@ -74,8 +73,8 @@ The manual process requires the following commands for setup: fab prep_develop fab init_db - # create and edit the settings file - cp deploy/sample.settings.conf deploy/settings.conf + # create and edit the settings file to make it visible in config.py + cp deploy/sample.vagrant.settings.conf deploy/settings.conf # run the application fab run @@ -84,7 +83,6 @@ The manual process requires the following commands for setup: Finally you can open your browser at https://localhost:5000/ and login as admin@example.com with any password -``` # Initial Deployment @@ -101,15 +99,13 @@ aginst the "staging" or "production" server specified as an argument. After you clone the repository: -- create three files in your local `deploy` folder: - -``` +- create the required files in your local `deploy` folder: $ cd redi-dropper-client/app/deploy $ cp sample.fabric.py staging/fabric.py $ cp sample.deploy.settings.conf staging/settings.conf $ cp sample.virtualhost.conf staging/virtualhost.conf -``` + $ cp sample.virtualhost-ssl.conf staging/virtualhost-ssl.conf - edit the files in the staging (or production) folder to reflect the proper username/passwords/hosts/paths @@ -118,32 +114,23 @@ After you clone the repository: - execute the initial deploy' command for staging (or production): -``` - $ cd redi-dropper-client/app/deploy $ git fetch --tags upstream $ ./deploy.sh -i -t tag_number -r ~/git staging OR $ ./deploy/deploy.sh -i -t tag_number -r ~/git production -``` Once you have the fabric tool installed you can create the database tables in staging or production databases: -``` - $ fab staging mysql_conf $ fab staging mysql_list_tables $ fab staging mysql_create_tables -``` If tables already exist in the database and you wish to re-create them please run: -``` - $ fab staging mysql_reset_tables -``` Note: Reseting tables does not create a backup of the tables so please make sure the existing data can be discarded. @@ -159,12 +146,9 @@ Assumptions: Re-upload configuration and code changes by executing one of the following: -``` - $ deploy/deploy.sh -t tag_number -r ~/git staging OR $ deploy/deploy.sh -t tag_number -r ~/git production -``` Note: You might need to refresh the list of tags from the upstream diff --git a/vagrant/bootstrap_functions.sh b/vagrant/bootstrap_functions.sh index 5739752..59a45e8 100644 --- a/vagrant/bootstrap_functions.sh +++ b/vagrant/bootstrap_functions.sh @@ -20,25 +20,18 @@ function install_utils() { cp $SHARED_FOLDER/dot_files/sqliterc /home/vagrant/.sqliterc cp $SHARED_FOLDER/dot_files/sqliterc /root/.sqliterc - apt-get install -y vim ack-grep -} - -function install_redis() { - -} - -function install_openvas() { - + apt-get install -y vim ack-grep nmap } function install_apache_for_python() { - # https://www.digitalocean.com/community/tutorials/how-to-deploy-a-flask-application-on-an-ubuntu-vps - apt-get install -y \ - apache2 libapache2-mod-wsgi \ - python-dev python-pip \ - mysql-server libmysqlclient-dev \ - libffi-dev \ - libsqlite3-dev + # https://www.digitalocean.com/community/tutorials/how-to-deploy-a-flask-application-on-an-ubuntu-vps + apt-get install -y \ + libssl-dev \ + apache2 libapache2-mod-wsgi \ + python-dev python-pip \ + mysql-server libmysqlclient-dev \ + libffi-dev \ + libsqlite3-dev } function install_dropper() { @@ -51,10 +44,10 @@ function install_dropper() { pushd /var/www/dropper # Setting up a virtual environment will keep the application and its # dependencies isolated from the main system. - log "Install via pip: virtualenv..." pip install virtualenv log "Creating virtual environment: /var/www/app/venv" + virtualenv venv . venv/bin/activate log "Installing required python packages..." @@ -63,8 +56,8 @@ function install_dropper() { popd pushd /var/www/dropper/app/deploy - log "Link app config file" - ln -sfv sample.settings.conf settings.conf + log "Link app config file to make it visible in config.py... " + ln -sfv sample.vagrant.settings.conf settings.conf popd pushd /var/www/dropper/app