Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 3 additions & 26 deletions app/models/log_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@
LOG_TYPE_LOGIN, \
LOG_TYPE_LOGOUT, \
LOG_TYPE_LOGIN_ERROR, \
LOG_TYPE_FILE_UPLOADED, \
LOG_TYPE_FILE_DOWNLOADED, \
LOG_TYPE_ACCOUNT_MODIFIED, \
LOG_TYPE_REDCAP_SUBJECTS_IMPORTED, \
LOG_TYPE_REDCAP_EVENTS_IMPORTED
LOG_TYPE_ACCOUNT_MODIFIED


class LogEntity(db.Model, CRUDMixin):
Expand Down Expand Up @@ -55,7 +51,8 @@ def get_logs(per_page=25, page_num=1):
def item_from_entity(entity):
return {
'id': entity.id,
'user_email': entity.web_session.user.email,
'user_email': entity.web_session.user.email
if entity.web_session.user is not None else '',
'type': entity.log_type.type,
'details': entity.details,
'web_session_ip': entity.web_session.ip,
Expand Down Expand Up @@ -106,31 +103,11 @@ def login_error(session_id, details=''):
""" Log failed login """
LogEntity._log(LOG_TYPE_LOGIN_ERROR, session_id, details)

@staticmethod
def file_uploaded(session_id, details=''):
""" Log file upload """
LogEntity._log(LOG_TYPE_FILE_UPLOADED, session_id, details)

@staticmethod
def file_downloaded(session_id, details=''):
""" Log file download """
LogEntity._log(LOG_TYPE_FILE_DOWNLOADED, session_id, details)

@staticmethod
def account_modified(session_id, details=''):
""" Log account changes """
LogEntity._log(LOG_TYPE_ACCOUNT_MODIFIED, session_id, details)

@staticmethod
def redcap_subjects_imported(session_id, details=''):
""" Log it """
LogEntity._log(LOG_TYPE_REDCAP_SUBJECTS_IMPORTED, session_id, details)

@staticmethod
def redcap_events_imported(session_id, details=''):
""" Log it """
LogEntity._log(LOG_TYPE_REDCAP_EVENTS_IMPORTED, session_id, details)

def __repr__(self):
""" Return a friendly object representation """
return "<LogEntity(logID: {0.id}, "\
Expand Down
7 changes: 1 addition & 6 deletions app/models/log_type_entity.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
ORM for app.LogType table
ORM for LogType table
"""
from app.crud_mixin import CRUDMixin
from app.main import db
Expand All @@ -8,12 +8,7 @@
LOG_TYPE_LOGIN = 'login'
LOG_TYPE_LOGOUT = 'logout'
LOG_TYPE_LOGIN_ERROR = 'login_error'
LOG_TYPE_FILE_UPLOADED = 'file_uploaded'
LOG_TYPE_FILE_DOWNLOADED = 'file_downloaded'
LOG_TYPE_ACCOUNT_MODIFIED = 'account_modified'
LOG_TYPE_REDCAP_SUBJECTS_IMPORTED = 'redcap_subjects_impported'
LOG_TYPE_REDCAP_EVENTS_IMPORTED = 'redcap_events_imported'
# LOG_TYPE_ = ''


class LogTypeEntity(db.Model, CRUDMixin):
Expand Down
34 changes: 27 additions & 7 deletions app/routes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@

# import math
from datetime import datetime
import collections

from flask import request
from flask import send_file
from flask import session
from flask import make_response
from flask_login import login_required

from app.main import app, db
Expand All @@ -26,7 +23,7 @@
from app.models.user_entity import UserEntity
from app.models.role_entity import RoleEntity

from app.routes.users import perm_admin, perm_admin_or_technician
from app.routes.users import perm_admin


@app.route('/api/save_user', methods=['POST'])
Expand Down Expand Up @@ -124,7 +121,6 @@ def api_list_logs():
dict(list_of_events=logs, total_pages=total_pages))



@app.route('/api/activate_account', methods=['POST'])
@login_required
@perm_admin.require()
Expand Down Expand Up @@ -163,16 +159,41 @@ def api_deactivate_account():
return utils.jsonify_success({"message": "User deactivated."})


def check_email_config():
"""
@return: False if there are missing configuration parameters for emailing
"""
passed = True
required = ['MAIL_USERNAME', 'MAIL_PASSWORD', 'MAIL_SERVER', 'MAIL_PORT']
errors = []

for req in required:
if req not in app.config:
errors.append("Emailing param {} was not configured.".format(req))

for error in errors:
app.logger.error(error)
passed = False

return passed, errors


@app.route('/api/send_verification_email', methods=['POST'])
@login_required
@perm_admin.require()
def api_send_verification_email():
"""
@TODO: Send Verification Email to user_id
Send Verification Email to the `user_id` specified in the request

:rtype: Response
:return the success or failed in json format
"""
passed, errors = check_email_config()
if not passed:
app.logger.warn(" ".join(errors))
return utils.jsonify_error(
{"message": "Unable to send email due to configuration errors."})

user_id = utils.get_safe_int(request.form.get('user_id'))
user = UserEntity.get_by_id(user_id)

Expand Down Expand Up @@ -276,4 +297,3 @@ def api_extend_account():
today_plus_180, user.email))
return utils.jsonify_success(
{"message": "Updated expiration date to {}".format(today_plus_180)})

32 changes: 14 additions & 18 deletions app/routes/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def load_user(user_id):
@login_manager.unauthorized_handler
def unauthorized():
""" Returns a message for the unauthorized users """
# return redirect('/')
return 'Please <a href="{}">login</a> first.'.format(url_for('index'))


Expand All @@ -68,13 +67,13 @@ def page_not_found(e):
class LoginForm(Form):
""" Declare the validation rules for the login form """
next = HiddenField(default='')

# email = TextField('Email', [validators.Length(min=4, max=25)])
email = TextField('Email')
password = PasswordField(
'Password', [
validators.Required(), validators.Length(
min=6, max=25)])
# email = TextField('Email')
email = TextField('Email',
[validators.Required(),
validators.Length(min=4, max=25)])
password = PasswordField('Password',
[validators.Required(),
validators.Length(min=6, max=25)])


def get_user_agent():
Expand All @@ -97,8 +96,6 @@ def get_user_agent():
browser=browser,
version=version,
language=language)

# app.logger.debug(user_agent)
return user_agent


Expand All @@ -108,7 +105,6 @@ def check_session_id():
Generate a UUID and store it in the session
as well as in the WebSession table.
"""
# TODO: Create UserAgentEntity and populate
user_agent = get_user_agent()

if 'uuid' not in session:
Expand Down Expand Up @@ -153,7 +149,7 @@ def render_login_local():

if request.method == 'POST' and form.validate():
email = form.email.data.strip(
) if form.email.data else "admin@example.com"
) if form.email.data else ""
password = form.password.data.strip() if form.password.data else ""
app.logger.debug("{} password: {}".format(email, password))

Expand Down Expand Up @@ -336,18 +332,18 @@ def logout():
https://shib.ncsu.edu/docs/logout.html
https://wiki.shibboleth.net/confluence/display/CONCEPT/SLOIssues
"""
# Log the logout
if 'uuid' in session:
LogEntity.logout(session['uuid'])

logout_user()

# Remove session keys set by Flask-Principal
for key in ('identity.name', 'identity.auth_type'):
# Remove session keys set by Flask-Principal, and `uuid` key set manually
for key in ('identity.name', 'identity.auth_type', 'uuid'):
session.pop(key, None)

# Tell Flask-Principal the user is anonymous
identity_changed.send(current_app._get_current_object(),
identity=AnonymousIdentity())

# Log the logout
LogEntity.logout(session['uuid'])
# Also pop the session id
session.pop('uuid')
return redirect('/')
34 changes: 0 additions & 34 deletions app/templates/api.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@
bindForm("#extend_account");
bindForm("#expire_account");

bindForm("#import_redcap_subjects");
bindForm("#import_redcap_events");
}

$(document).ready(function() {
Expand All @@ -73,38 +71,13 @@
<h4>List of API Functions</h4>

<ol class="text-left">
<li><form id="list_subject_events" method="post" action="/api/list_subject_events">
<input type="hidden" name="subject_id" value="1" />
<button>/api/list_subject_events </button> </form>
<li><form id="list_subject_event_files" method="post" action="/api/list_subject_event_files">
<input type="hidden" name="subject_id" value="1" />
<input type="hidden" name="event_id" value="1" />
<button>/api/list_subject_event_files </button> </form>
<li><form id="find_subject" method="post" action="/api/find_subject">
<input type="hidden" name="name" value="1" />
<button>/api/find_subject </button> </form>
<li><form id="list_events" method="post" action="/api/list_events">
<button>/api/list_events </button> </form>

<li><form id="upload" method="post" action="/api/upload">
<button>/api/upload </button> </form>
<li><form id="download_file" method="post" action="/api/download_file">
<input type="hidden" name="file_id" value="1" />
<button>/api/download_file </button> </form>

<li><form id="save_user" method="post" action="/api/save_user">
<input type="hidden" name="email" value="test@test.com" />
<button>/api/save_user </button> </form>
<li><form id="list_users" method="post" action="/api/list_users">
<button>/api/list_users </button> </form>
<li><form id="list_logs" method="post" action="/api/list_logs">
<button>/api/list_logs </button> </form>
<li><form id="list_local_subjects" method="post" action="/api/list_local_subjects">
<input type="hidden" name="per_page" value="5" />
<input type="hidden" name="page_num" value="1" />
<button>/api/list_local_subjects </button> </form>

<!-- ADMIN -->
<li><form id="activate_account" method="post" action="/api/activate_account">
<button>/api/activate_account </button> </form>
<li><form id="deactivate_account" method="post" action="/api/deactivate_account">
Expand All @@ -118,13 +91,6 @@ <h4>List of API Functions</h4>
<button>/api/extend_account </button> </form>
<li><form id="expire_account" method="post" action="/api/expire_account">
<button>/api/expire_account</button> </form>

<!-- All users -->
<li><form id="import_redcap_subjects" method="post" action="/api/import_redcap_subjects">
<button>/api/import_redcap_subjects </button> </form>
<li><form id="import_redcap_events" method="post" action="/api/import_redcap_events">
<button>/api/import_redcap_events </button> </form>
</ol>
</div>

<div class="col-md-4 img-rounded bordered text-left">
Expand Down
3 changes: 0 additions & 3 deletions app/templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@

<!-- Bootstrap core CSS -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/fileupload.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/sidebar.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/layout.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/start_upload.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/parallax.css') }}" rel="stylesheet">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">


<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!--
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
Expand Down
29 changes: 29 additions & 0 deletions tests/test_routes_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'''
Goal: test functions in routes/api.py
'''

from .base_test import BaseTestCase
from app.routes.api import check_email_config

class RoutesApiTests(BaseTestCase):

def test_check_email_config(self):
""" Fail when nothing set """
errors = [
'Emailing param MAIL_USERNAME was not configured.',
'Emailing param MAIL_PASSWORD was not configured.',
'Emailing param MAIL_SERVER was not configured.',
'Emailing param MAIL_PORT was not configured.']
passed, actual_errors = check_email_config()
self.assertFalse(passed)
self.assertEquals(errors, actual_errors)

def test_check_email_config_ok(self):
""" Pass when all set """
self.app.config['MAIL_USERNAME'] = 'xyz'
self.app.config['MAIL_PASSWORD'] = 'xyz'
self.app.config['MAIL_SERVER'] = 'xyz'
self.app.config['MAIL_PORT'] = 'xyz'
passed, actual_errors = check_email_config()
self.assertTrue(passed)
self.assertEquals([], actual_errors)