about summary refs log tree commit diff
diff options
context:
space:
mode:
authorzsloan2020-06-05 16:52:56 -0500
committerzsloan2020-06-05 16:52:56 -0500
commita302a2b0ac0e7c0f26a0d063c3f2b057f61d47f1 (patch)
treea9f5c88f7d2818bf4e0481b85872c7580a8d577c
parent218576a04f90cc0bc9e53685323e1caa8cffe986 (diff)
downloadgenenetwork2-a302a2b0ac0e7c0f26a0d063c3f2b057f61d47f1.tar.gz
Commiting other current group/resource management code, plus the new files
-rw-r--r--wqflask/base/trait.py2
-rw-r--r--wqflask/maintenance/set_resource_defaults.py155
-rw-r--r--wqflask/utility/authentication_tools.py46
-rw-r--r--wqflask/utility/redis_tools.py37
-rw-r--r--wqflask/wqflask/group_manager.py77
-rw-r--r--wqflask/wqflask/resource_manager.py72
-rw-r--r--wqflask/wqflask/static/new/javascript/group_manager.js38
-rw-r--r--wqflask/wqflask/templates/admin/create_group.html89
-rw-r--r--wqflask/wqflask/templates/admin/group_manager.html68
-rw-r--r--wqflask/wqflask/templates/admin/manage_resource.html92
-rw-r--r--wqflask/wqflask/templates/admin/search_for_groups.html64
-rw-r--r--wqflask/wqflask/templates/admin/select_group_to_add.html54
-rw-r--r--wqflask/wqflask/templates/new_security/not_authenticated.html11
-rw-r--r--wqflask/wqflask/templates/show_trait_details.html5
-rw-r--r--wqflask/wqflask/views.py3
15 files changed, 764 insertions, 49 deletions
diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py
index 405c4ebf..2a945588 100644
--- a/wqflask/base/trait.py
+++ b/wqflask/base/trait.py
@@ -391,6 +391,8 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False):
         if response.strip() == "no-access":
             trait.view = False
             return trait
+        else:
+            trait_info = json.loads(response)
     except:
         resource_info = get_resource_info(resource_id)
         default_permissions = resource_info['default_mask']['data']
diff --git a/wqflask/maintenance/set_resource_defaults.py b/wqflask/maintenance/set_resource_defaults.py
new file mode 100644
index 00000000..ba102d9c
--- /dev/null
+++ b/wqflask/maintenance/set_resource_defaults.py
@@ -0,0 +1,155 @@
+"""

+

+Script that sets default resource access masks for use with the DB proxy

+

+Defaults will be:

+Owner - omni_gn

+Mask  - Public/non-confidential: { data: "view",

+                                   metadata: "view",

+                                   admin: "not-admin" }

+        Private/confidentia:     { data: "no-access",

+                                   metadata: "no-access",

+                                   admin: "not-admin" }

+

+To run:

+./bin/genenetwork2 ~/my_settings.py -c ./wqflask/maintenance/gen_select_dataset.py

+

+"""

+

+from __future__ import print_function, division

+

+import sys

+import json

+

+# NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead

+sys.path.insert(0,'./')

+

+# NEW: import app to avoid a circular dependency on utility.tools

+from wqflask import app

+

+from utility.tools import SQL_URI

+from utility.redis_tools import get_redis_conn, get_user_id, add_resource, get_resources

+Redis = get_redis_conn()

+

+import MySQLdb

+

+import urlparse

+

+from utility.logger import getLogger

+logger = getLogger(__name__)

+

+def parse_db_uri():

+    """Converts a database URI to the db name, host name, user name, and password"""

+

+    parsed_uri = urlparse.urlparse(SQL_URI)

+

+    db_conn_info = dict(

+                        db = parsed_uri.path[1:],

+                        host = parsed_uri.hostname,

+                        user = parsed_uri.username,

+                        passwd = parsed_uri.password)

+

+    print(db_conn_info)

+    return db_conn_info

+

+def insert_probeset_resources(default_owner_id):

+    current_resources = Redis.hgetall("resources")

+    Cursor.execute("""  SELECT 

+                            ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.confidentiality, ProbeSetFreeze.public

+                        FROM 

+                            ProbeSetFreeze""")

+

+    resource_results = Cursor.fetchall()

+    for i, resource in enumerate(resource_results):

+        if i % 20 == 0:

+            print(i)

+        resource_ob = {}

+        resource_ob['name'] = resource[1]

+        resource_ob['owner_id'] = default_owner_id

+        resource_ob['data'] = { "dataset" : str(resource[0])}

+        resource_ob['type'] = "dataset-probeset"

+        if resource[2] < 1 and resource[3] > 0:

+            resource_ob['default_mask'] = { "data": ["no-access", "view"] }

+        else:

+            resource_ob['default_mask'] = { "data": ["no-access"] }

+        resource_ob['group_masks'] = {}

+

+        add_resource(resource_ob)

+

+def insert_publish_resources(default_owner_id):

+    current_resources = Redis.hgetall("resources")

+    Cursor.execute("""  SELECT 

+                            PublishXRef.Id, PublishFreeze.Id, InbredSet.InbredSetCode

+                        FROM

+                            PublishXRef, PublishFreeze, InbredSet, Publication

+                        WHERE

+                            PublishFreeze.InbredSetId = PublishXRef.InbredSetId AND

+                            InbredSet.Id = PublishXRef.InbredSetId AND

+                            Publication.Id = PublishXRef.PublicationId""")

+

+    resource_results = Cursor.fetchall()

+    for resource in resource_results:

+        if resource[2]:

+            resource_ob = {}

+            if resource[2]:

+                resource_ob['name'] = resource[2] + "_" + str(resource[0])

+            else:

+                resource_ob['name'] = str(resource[0])

+            resource_ob['owner_id'] = default_owner_id

+            resource_ob['data'] = { "dataset" : str(resource[1]) ,

+                                    "trait"   : str(resource[0])}

+            resource_ob['type'] = "dataset-publish"

+            resource_ob['default_mask'] = { "data": "view" }

+

+            resource_ob['group_masks'] = {}

+

+            add_resource(resource_ob)

+        else:

+            continue

+

+def insert_geno_resources(default_owner_id):

+    current_resources = Redis.hgetall("resources")

+    Cursor.execute("""  SELECT 

+                            GenoFreeze.Id, GenoFreeze.ShortName, GenoFreeze.confidentiality

+                        FROM 

+                            GenoFreeze""")

+

+    resource_results = Cursor.fetchall()

+    for i, resource in enumerate(resource_results):

+        if i % 20 == 0:

+            print(i)

+        resource_ob = {}

+        resource_ob['name'] = resource[1]

+        resource_ob['owner_id'] = default_owner_id

+        resource_ob['data'] = { "dataset" : str(resource[0]) }

+        resource_ob['type'] = "dataset-geno"

+        if resource[2] < 1:

+            resource_ob['default_mask'] = { "data": "view" }

+        else:

+            resource_ob['default_mask'] = { "data": "no-access" }

+        resource_ob['group_masks'] = {}

+

+        add_resource(resource_ob)

+

+def insert_resources(default_owner_id):

+    current_resources = get_resources()

+    print("START")

+    insert_publish_resources(default_owner_id)

+    print("AFTER PUBLISH")

+    insert_geno_resources(default_owner_id)

+    print("AFTER GENO")

+    insert_probeset_resources(default_owner_id)

+    print("AFTER PROBESET")

+

+def main():

+    """Generates and outputs (as json file) the data for the main dropdown menus on the home page"""

+

+    Redis.delete("resources")

+

+    owner_id = get_user_id("email_address", "zachary.a.sloan@gmail.com")

+    insert_resources(owner_id)

+

+if __name__ == '__main__':

+    Conn = MySQLdb.Connect(**parse_db_uri())

+    Cursor = Conn.cursor()

+    main()
\ No newline at end of file
diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py
new file mode 100644
index 00000000..537881a5
--- /dev/null
+++ b/wqflask/utility/authentication_tools.py
@@ -0,0 +1,46 @@
+from __future__ import absolute_import, print_function, division

+

+import json

+import requests

+

+from base import data_set

+

+from utility import hmac

+from utility.redis_tools import get_redis_conn, get_resource_info, get_resource_id

+

+from flask import Flask, g, redirect, url_for

+

+import logging

+logger = logging.getLogger(__name__ )

+

+def check_resource_availability(dataset, trait_id=None):

+    resource_id = get_resource_id(dataset, trait_id)

+

+    if resource_id:

+        the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id)

+        try:

+            response = json.loads(requests.get(the_url).content)['data']

+        except:

+            resource_info = get_resource_info(resource_id)

+            response = resource_info['default_mask']['data']

+

+        if 'view' in response:

+            return True

+        else:

+            return redirect(url_for("no_access_page"))

+

+    return True

+

+def check_owner(dataset=None, trait_id=None, resource_id=None):

+    if resource_id:

+        resource_info = get_resource_info(resource_id)

+        if g.user_session.user_id == resource_info['owner_id']:

+            return resource_id

+    else:

+        resource_id = get_resource_id(dataset, trait_id)

+        if resource_id:

+            resource_info = get_resource_info(resource_id)

+            if g.user_session.user_id == resource_info['owner_id']:

+                return resource_id

+

+    return False
\ No newline at end of file
diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py
index 0ad96879..bc30a0af 100644
--- a/wqflask/utility/redis_tools.py
+++ b/wqflask/utility/redis_tools.py
@@ -95,14 +95,17 @@ def get_user_groups(user_id):
     user_group_ids = []   #ZS: Group IDs where user is a regular user
     groups_list = Redis.hgetall("groups")
     for key in groups_list:
-        group_ob = json.loads(groups_list[key])
-        group_admins = set(group_ob['admins'])
-        group_members = set(group_ob['members'])
-        if user_id in group_admins:
-            admin_group_ids.append(group_ob['id'])
-        elif user_id in group_members:
-            user_group_ids.append(group_ob['id'])
-        else:
+        try:
+            group_ob = json.loads(groups_list[key])
+            group_admins = set(group_ob['admins'])
+            group_members = set(group_ob['members'])
+            if user_id in group_admins:
+                admin_group_ids.append(group_ob['id'])
+            elif user_id in group_members:
+                user_group_ids.append(group_ob['id'])
+            else:
+                continue
+        except:
             continue
 
     admin_groups = []
@@ -122,6 +125,24 @@ def get_group_info(group_id):
 
     return group_info
 
+def get_group_by_unique_column(column_name, column_value):
+    """ Get group by column; not sure if there's a faster way to do this """
+
+    matched_groups = []
+
+    all_group_list = Redis.hgetall("groups")
+    for key in all_group_list:
+        group_info = json.loads(all_group_list[key])
+        if column_name == "admins" or column_name == "members": #ZS: Since these fields are lists, search in the list
+            if column_value in group_info[column_name]:
+                matched_groups.append(group_info)
+        else:
+            if group_info[column_name] == column_value:
+                matched_groups.append(group_info)
+
+    return matched_groups
+
+
 def create_group(admin_user_ids, member_user_ids = [], group_name = "Default Group Name"):
     group_id = str(uuid.uuid4())
     new_group = {
diff --git a/wqflask/wqflask/group_manager.py b/wqflask/wqflask/group_manager.py
new file mode 100644
index 00000000..f41ae56d
--- /dev/null
+++ b/wqflask/wqflask/group_manager.py
@@ -0,0 +1,77 @@
+

+from __future__ import print_function, division, absolute_import

+

+from flask import (Flask, g, render_template, url_for, request, make_response,

+                   redirect, flash)

+

+from wqflask import app

+from wqflask.user_login import send_verification_email

+

+from utility.redis_tools import get_user_groups, get_group_info, create_group, delete_group, add_users_to_group, remove_users_from_group, \

+                                change_group_name, save_verification_code, check_verification_code, get_user_by_unique_column

+

+from utility.logger import getLogger

+logger = getLogger(__name__)

+

+@app.route("/groups/manage", methods=('GET', 'POST'))

+def manage_groups():

+   params = request.form if request.form else request.args

+   if "add_new_group" in params:

+      return redirect(url_for('add_group'))

+   else:

+      admin_groups, user_groups = get_user_groups(g.user_session.user_id)

+      return render_template("admin/group_manager.html", admin_groups=admin_groups, user_groups=user_groups)

+

+@app.route("/groups/remove", methods=('POST',))

+def remove_groups():

+   group_ids_to_remove = request.form['selected_group_ids']

+   for group_id in group_ids_to_remove.split(":"):

+      delete_group(g.user_session.user_id, group_id)

+

+   return redirect(url_for('manage_groups'))

+

+@app.route("/groups/create", methods=('GET', 'POST'))

+def add_group():

+   params = request.form if request.form else request.args

+   if "group_name" in params:

+      member_user_ids = set()

+      admin_user_ids = set()

+      admin_user_ids.add(g.user_session.user_id) #ZS: Always add the user creating the group as an admin

+      if "admin_emails" in params:

+         admin_emails = params['admin_emails_to_add'].split(",")

+         for email in admin_emails:

+            user_details = get_user_by_unique_column("email_address", email)

+            if user_details:

+               admin_user_ids.add(user_details['user_id'])

+         #send_group_invites(params['group_id'], user_email_list = admin_emails, user_type="admins")

+      if "user_emails" in params:

+         member_emails = params['member_emails_to_add'].split(",")

+         for email in member_emails:

+            user_details = get_user_by_unique_column("email_address", email)

+            if user_details:

+               member_user_ids.add(user_details['user_id'])

+         #send_group_invites(params['group_id'], user_email_list = user_emails, user_type="members")

+

+      create_group(list(admin_user_ids), list(member_user_ids), params['group_name'])

+      return redirect(url_for('manage_groups'))

+   else:

+      return render_template("admin/create_group.html")

+

+#ZS: Will integrate this later, for now just letting users be added directly

+def send_group_invites(group_id, user_email_list = [], user_type="members"):

+   for user_email in user_email_list:

+      user_details = get_user_by_unique_column("email_address", user_email)

+      if user_details:

+         group_info = get_group_info(group_id)

+         #ZS: Probably not necessary since the group should normally always exist if group_id is being passed here,

+         #    but it's technically possible to hit it if Redis is cleared out before submitting the new users or something

+         if group_info:

+            #ZS: Don't add user if they're already an admin or if they're being added a regular user and are already a regular user,

+            #    but do add them if they're a regular user and are added as an admin

+            if (user_details['user_id'] in group_info['admins']) or \

+               ((user_type == "members") and (user_details['user_id'] in group_info['members'])):

+               continue

+            else:

+               send_verification_email(user_details, template_name = "email/group_verification.txt", key_prefix = "verification_code", subject = "You've been invited to join a GeneNetwork user group")

+

+#@app.route()
\ No newline at end of file
diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py
new file mode 100644
index 00000000..7d88b8ed
--- /dev/null
+++ b/wqflask/wqflask/resource_manager.py
@@ -0,0 +1,72 @@
+from __future__ import print_function, division, absolute_import

+

+from flask import (Flask, g, render_template, url_for, request, make_response,

+                   redirect, flash)

+

+from wqflask import app

+

+from utility.authentication_tools import check_owner

+from utility.redis_tools import get_resource_info, get_group_info, get_group_by_unique_column, get_user_id

+

+from utility.logger import getLogger

+logger = getLogger(__name__)

+

+@app.route("/resources/manage", methods=('GET', 'POST'))

+def view_resource():

+    params = request.form if request.form else request.args

+    if 'resource_id' in request.args:

+        resource_id = request.args['resource_id']

+        if check_owner(resource_id=resource_id):

+            resource_info = get_resource_info(resource_id)

+            group_masks = resource_info['group_masks']

+            group_masks_with_names = get_group_names(group_masks)

+            default_mask = resource_info['default_mask']['data']

+            return render_template("admin/manage_resource.html", resource_id = resource_id, resource_info=resource_info, default_mask=default_mask, group_masks=group_masks_with_names)

+        else:

+            return redirect(url_for("no_access_page"))

+        

+@app.route("/resources/add_group", methods=('POST',))

+def add_group_to_resource():

+    resource_id = request.form['resource_id']

+    if check_owner(resource_id=resource_id):

+        if all(key in request.form for key in ('group_id', 'group_name', 'user_name', 'user_email')):

+            group_list = []

+            if request.form['group_id'] != "":

+                the_group = get_group_info(request.form['group_id'])

+                if the_group:

+                    group_list.append(the_group)

+            if request.form['group_name'] != "":

+                matched_groups = get_group_by_unique_column("name", request.form['group_name'])

+                for group in matched_groups:

+                    group_list.append(group)

+            if request.form['user_name'] != "":

+                user_id = get_user_id("user_name", request.form['user_name'])

+                if user_id:

+                    matched_groups = get_group_by_unique_column("admins", user_id)

+                    matched_groups += get_group_by_unique_column("members", user_id)

+                    for group in matched_groups:

+                        group_list.append(group)

+            if request.form['user_email'] != "":

+                user_id = get_user_id("email_address", request.form['user_email'])

+                if user_id:

+                    matched_groups = get_group_by_unique_column("admins", user_id)

+                    matched_groups += get_group_by_unique_column("members", user_id)

+                    for group in matched_groups:

+                        group_list.append(group)

+            return render_template("admin/select_group_to_add.html", group_list=group_list, resource_id = resource_id)

+        elif 'selected_group' in request.form:

+            group_id = request.form['selected_group']

+            return render_template("admin/set_group_privileges.html", resource_id = resource_id, group_id = group_id)

+        else:

+            return render_template("admin/search_for_groups.html", resource_id = resource_id)

+    else:

+        return redirect(url_for("no_access_page"))

+

+def get_group_names(group_masks):

+    group_masks_with_names = {}

+    for group_id, group_mask in group_masks.iteritems():

+        this_mask = group_mask

+        group_name = get_group_info(group_id)['name']

+        this_mask['name'] = group_name

+    

+    return group_masks_with_names
\ No newline at end of file
diff --git a/wqflask/wqflask/static/new/javascript/group_manager.js b/wqflask/wqflask/static/new/javascript/group_manager.js
new file mode 100644
index 00000000..5e82d104
--- /dev/null
+++ b/wqflask/wqflask/static/new/javascript/group_manager.js
@@ -0,0 +1,38 @@
+$('#add_to_admins').click(function() {

+    add_emails('admin')

+})

+

+$('#add_to_members').click(function() {

+    add_emails('member')

+})

+

+$('#clear_admins').click(function(){

+    clear_emails('admin')

+})

+

+$('#clear_members').click(function(){

+    clear_emails('member')

+})

+

+

+function add_emails(user_type){

+    var email_address = $('input[name=user_email]').val();

+    var email_list_string = $('input[name=' + user_type + '_emails_to_add]').val()

+    console.log(email_list_string)

+    if (email_list_string == ""){

+        var email_set = new Set();

+    } else {

+        var email_set = new Set(email_list_string.split(","))

+    }

+    email_set.add(email_address)

+

+    $('input[name=' + user_type + '_emails_to_add]').val(Array.from(email_set).join(','))

+

+    var emails_display_string = Array.from(email_set).join('\n')

+    $('.added_' + user_type + 's').val(emails_display_string)

+}

+

+function clear_emails(user_type){

+    $('input[name=' + user_type + '_emails_to_add]').val("")

+    $('.added_' + user_type + 's').val("")

+}
\ No newline at end of file
diff --git a/wqflask/wqflask/templates/admin/create_group.html b/wqflask/wqflask/templates/admin/create_group.html
new file mode 100644
index 00000000..55c3fa0b
--- /dev/null
+++ b/wqflask/wqflask/templates/admin/create_group.html
@@ -0,0 +1,89 @@
+{% extends "base.html" %}

+{% block title %}Group Manager{% endblock %}

+{% block content %}

+<!-- Start of body -->

+    <div class="container">

+        <div class="page-header">

+            <h1>Create Group</h1>

+        </div>

+        <form action="/groups/create" method="POST">

+            <input type="hidden" name="admin_emails_to_add" value="">

+            <input type="hidden" name="member_emails_to_add" value="">

+            <fieldset>

+                <div class="form-horizontal" style="width: 900px; margin-bottom: 50px;">

+                    <div class="form-group" style="padding-left: 20px;">

+                        <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Group Name:</label>

+                        <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                            <div class="col-xs-12">

+                                <input name="group_name" type="text" style="width:100%;"></input>

+                            </div>

+                        </div>

+                    </div>

+                    <div class="form-group" style="padding-left: 20px;">

+                        <label for="user_email" class="col-xs-3" style="float: left; font-size: 18px;">Add User Email:</label>

+                        <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                            <div class="col-xs-12">

+                                <input name="user_email" type="text" style="width:100%;"></input>

+                            </div>

+                        </div>

+                    </div>

+                    <div class="form-group" style="padding-left: 20px;">

+                        <label class="col-xs-3"></label>

+                        <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                            <div class="col-xs-6">

+                                <button type="button" id="add_to_admins" class="btn btn-default">Add to Admins</button>

+                            </div>

+                            <div class="col-xs-6">

+                                <button type="button" id="add_to_members" class="btn btn-default">Add to Members</button>

+                            </div>

+                        </div>

+                    </div>

+                    <div class="form-group" style="padding-left: 20px;">

+                        <label class="col-xs-3" style="font-size: 18px;">Members to be added:</label>

+                        <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                            <div class="col-xs-6">

+                                <textarea rows="8" cols="60" readonly placeholder="No users added" style="overflow-y: scroll; resize: none; width: 200px;" class="added_admins"></textarea>

+                            </div>

+                            <div class="col-xs-6">

+                                <textarea rows="8" cols="60" readonly placeholder="No users added" style="overflow-y: scroll; resize: none; width: 200px;" class="added_members"></textarea>

+                            </div>

+                        </div>

+                    </div>

+                    <div class="form-group" style="padding-left: 20px;">

+                        <label class="col-xs-3"></label>

+                        <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                            <div class="col-xs-6">

+                                <button type="button" id="clear_admins" class="btn btn-default">Clear</button>

+                            </div>

+                            <div class="col-xs-6">

+                                <button type="button" id="clear_members" class="btn btn-default">Clear</button>

+                            </div>

+                        </div>

+                    </div>

+                    <div class="form-group" style="padding-left: 20px;">

+                        <label for="create_group" class="col-xs-3" style="float: left; font-size: 18px;"></label>

+                        <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                            <div class="col-xs-6">

+                                <button type="button" id="create_group" class="btn btn-primary">Create Group</button>

+                            </div>

+                        </div>

+                    </div>

+                </div>

+            </fieldset>

+        </form>

+    </div>

+

+

+

+<!-- End of body -->

+

+{% endblock %}

+

+{% block js %}

+    <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.js"></script>

+    <script language="javascript" type="text/javascript" src="/static/new/javascript/group_manager.js"></script>

+    <script language="javascript" type="text/javascript" src="/static/packages/underscore/underscore-min.js"></script>

+

+    <script type="text/javascript" charset="utf-8">

+    </script>

+{% endblock %}

diff --git a/wqflask/wqflask/templates/admin/group_manager.html b/wqflask/wqflask/templates/admin/group_manager.html
index b7df1aad..23d8205a 100644
--- a/wqflask/wqflask/templates/admin/group_manager.html
+++ b/wqflask/wqflask/templates/admin/group_manager.html
@@ -1,15 +1,23 @@
 {% extends "base.html" %}
 {% block title %}Group Manager{% endblock %}
+{% block css %}
+    <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" />
+    <link rel="stylesheet" type="text/css" href="/static/new/packages/DataTables/extensions/buttons.dataTables.css">
+    <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" />
+{% endblock %}
 {% block content %}
 <!-- Start of body -->
     <div class="container">
         <div class="page-header">
             <h1>Manage Groups</h1>
-            <button type="button" id="remove_groups" class="btn btn-primary" data-url="/groups/remove">Remove Selected Groups</button>
+            <div style="display: inline;">
+                <button type="button" id="create_group" class="btn btn-primary" data-url="/groups/create">Create Group</button>
+                <button type="button" id="remove_groups" class="btn btn-primary" data-url="/groups/remove">Remove Selected Groups</button>
+            </div>
         </div>
         <form id="groups_form" action="/groups/manage" method="POST">
             <input type="hidden" name="selected_group_ids" value="">
-            <div class="container" style="margin-bottom: 30px;">
+            <div style="min-width: 800px; max-width: 1000px;">
                 {% if admin_groups|length == 0 and user_groups|length == 0 %}
                 <h4>You currently aren't a member or admin of any groups.</h4>
                 <br>
@@ -20,7 +28,7 @@
                 {% if admin_groups|length == 0 %}
                 <h4>You currently aren't the administrator of any groups.</h4>
                 {% else %}
-                <table id="admin_groups" class="table table-hover" style="min-width: 800px; max-width: 1000px;">
+                <table id="admin_groups" class="table-hover table-striped cell-border" style="float: left;">
                     <thead>
                         <tr>
                             <th></th>
@@ -29,17 +37,19 @@
                             <th># Members</th>
                             <th>Created</th>
                             <th>Last Changed</th>
+                            <th>Group ID</th>
                         </tr>
                     </thead>
                     <tbody>
                         {% for group in admin_groups %}
                         <tr>
                             <td><input type="checkbox" name="group_id" value="{{ group.id }}"></td>
-                            <td>{{ loop.index }}</td>
+                            <td align="right">{{ loop.index }}</td>
                             <td>{{ group.name }}</td>
-                            <td>{{ group.admins|length + group.users|length }}</td>
+                            <td align="right">{{ group.admins|length + group.users|length }}</td>
                             <td>{{ group.created_timestamp }}</td>
                             <td>{{ group.changed_timestamp }}</td>
+                            <td>{{ group.id }}</td>
                         </tr>
                         {% endfor %}
                     </tbody>
@@ -47,13 +57,13 @@
                 {% endif %}
             </div>
             <hr>
-            <div class="container">
+            <div style="min-width: 800px; max-width: 1000px;">
                 <div><h3>User Groups</h3></div>
                 <hr>
                 {% if user_groups|length == 0 %}
                 <h4>You currently aren't a member of any groups.</h4>
                 {% else %}
-                <table id="user_groups" class="table table-hover" style="min-width: 800px; max-width: 1000px;">
+                <table id="user_groups" class="table-hover table-striped cell-border" style="float: left;">
                     <thead>
                         <tr>
                             <th></th>
@@ -88,48 +98,26 @@
 {% endblock %}
 
 {% block js %}
-    <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.js"></script>
     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script>
 
-    <script language="javascript" type="text/javascript" src="/static/packages/underscore/underscore-min.js"></script>
-
     <script type="text/javascript" charset="utf-8">
         $(document).ready( function () {
-            $('#admin_groups, #user_groups').dataTable( {
-                "drawCallback": function( settings ) {
-                     $('#admin_groups tr').click(function(event) {
-                         if (event.target.type !== 'checkbox') {
-                             $(':checkbox', this).trigger('click');
-                         }
-                     });
-                },
-                "columns": [
-                    { "type": "natural" },
-                    { "type": "natural" },
-                    { "type": "natural" },
-                    { "type": "natural" },
-                    { "type": "natural" },
-                    { "type": "natural" }
-                ],
-                "columnDefs": [ {
-                    "targets": 0,
-                    "orderable": false
-                } ],
-                "order": [[1, "asc" ]],
-                "sDom": "Ztr",
-                "iDisplayLength": -1,
-                "autoWidth": true,
-                "bDeferRender": true,
-                "bSortClasses": false,
-                "paging": false,
-                "orderClasses": true
-            } );
-
+            {% if admin_groups|length != 0 %}
+            $('#admin_groups').dataTable();
+            {% endif %}
+            {% if user_groups|length != 0 %}
+            $('#user_groups').dataTable();
+            {% endif %}
             submit_special = function(url) {
                 $("#groups_form").attr("action", url);
                 return $("#groups_form").submit();
             };
 
+            $("#create_group").on("click", function() {
+                url = $(this).data("url")
+                return submit_special(url)
+            });
+
             $("#remove_groups").on("click", function() {
                 url = $(this).data("url")
                 groups = []
diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html
new file mode 100644
index 00000000..a47f47ad
--- /dev/null
+++ b/wqflask/wqflask/templates/admin/manage_resource.html
@@ -0,0 +1,92 @@
+{% extends "base.html" %}

+{% block title %}Resource Manager{% endblock %}

+{% block content %}

+<!-- Start of body -->

+    <div class="container">

+        <div class="page-header">

+            <h1>Resource Manager</h1>

+        </div>

+        <form id="manage_resource" action="/resources/manage" method="POST">

+            <input type="hidden" name="resource_id" value="{{ resource_id }}">

+            <div class="col-xs-6" style="min-width: 600px; max-width: 800px;">

+                <fieldset>

+                    <div class="form-horizontal" style="width: 900px; margin-bottom: 50px;">

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Resource Name:</label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                {{ resource_info.name }}

+                            </div>

+                        </div>

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label for="user_email" class="col-xs-3" style="float: left; font-size: 18px;">Open to Public:</label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                <label class="radio-inline">

+                                    <input type="radio" name="default_mask" value="True" checked="">

+                                    Yes

+                                </label>

+                                <label class="radio-inline">

+                                    <input type="radio" name="default_mask" value="False">

+                                    No

+                            </label>

+                            </div>

+                        </div>

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label class="col-xs-3" style="float: left; font-size: 18px;"></label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                <button id="save_changes" class="btn btn-primary">Save Changes</button>

+                            </div>

+                        </div>

+                    </div>

+                </fieldset>

+            </div>

+            <div class="col-xs-6" style="min-width: 600px; max-width: 800px;">

+                <button id="add_group_to_resource" class="btn btn-primary" style="margin-bottom: 30px;" data-url="/resources/add_group">Add Group</button>

+                <br>

+                {% if group_masks|length > 0 %}

+                <h3>Current Group Permissions</h3>

+                <table>

+                    <thead>

+                        <tr>

+                            <th>Name</th>

+                            <th>Data</th>

+                            <th>Metadata</th>

+                            <th>Admin</th>

+                        </tr>

+                    </thead>

+                    <tbody>

+                        {% for key, value in group_masks.iteritems() %}

+                        <tr>

+                            <td>{{ value.name }}</td>

+                            <td>{{ value.data }}</td>

+                            <td>{{ value.metadata }}</td>

+                            <td>{{ value.admin }}</td>

+                        </tr>

+                        {% endfor %}

+                    </tbody>

+                </table>

+                {% else %}

+                <h3>No groups are currently added to this resource.</h3>

+                {% endif %}

+            </div>

+        </form>

+    </div>

+

+

+

+<!-- End of body -->

+

+{% endblock %}

+

+{% block js %}

+    <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.js"></script>

+    <script language="javascript" type="text/javascript" src="/static/new/javascript/group_manager.js"></script>

+    <script language="javascript" type="text/javascript" src="/static/packages/underscore/underscore-min.js"></script>

+

+    <script type="text/javascript" charset="utf-8">

+        $('#add_group_to_resource').click(function(){

+            url = $(this).data("url");

+            $('#manage_resource').attr("action", url)

+            $('#manage_resource').submit()

+        })

+    </script>

+{% endblock %}

diff --git a/wqflask/wqflask/templates/admin/search_for_groups.html b/wqflask/wqflask/templates/admin/search_for_groups.html
new file mode 100644
index 00000000..89eb11dd
--- /dev/null
+++ b/wqflask/wqflask/templates/admin/search_for_groups.html
@@ -0,0 +1,64 @@
+{% extends "base.html" %}

+{% block title %}Resource Manager{% endblock %}

+{% block content %}

+<!-- Start of body -->

+    <div class="container">

+        <div class="page-header">

+            <h1>Find Groups</h1>

+        </div>

+        <form id="find_groups" action="/resources/add_group" method="POST">

+            <input type="hidden" name="resource_id" value="{{ resource_id }}">

+            <div style="min-width: 600px; max-width: 800px;">

+                <fieldset>

+                    <div class="form-horizontal" style="width: 900px;">

+                        <div style="margin-bottom: 30px;">

+                            <h2>Search by:</h2>

+                        </div>

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Group ID:</label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                <input name="group_id" type="text" value="">

+                            </div>

+                        </div>

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Group Name:</label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                <input name="group_name" type="text" value="">

+                            </div>

+                        </div>

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label for="user_name" class="col-xs-3" style="float: left; font-size: 18px;">User Name:</label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                <input name="user_name" type="text" value="">

+                            </div>

+                        </div>

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label for="user_email" class="col-xs-3" style="float: left; font-size: 18px;">User E-mail:</label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                <input name="user_email" type="text" value="">

+                            </div>

+                        </div>

+                        <div class="form-group" style="padding-left: 20px;">

+                            <label class="col-xs-3" style="float: left; font-size: 18px;"></label>

+                            <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;">

+                                <button type="submit" id="find_groups" class="btn btn-primary">Search</button>

+                            </div>

+                        </div>

+                    </div>

+                </fieldset>

+            </div>

+        </form>

+    </div>

+

+<!-- End of body -->

+

+{% endblock %}

+

+{% block js %}

+    <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.js"></script>

+    <script language="javascript" type="text/javascript" src="/static/new/javascript/group_manager.js"></script>

+    <script language="javascript" type="text/javascript" src="/static/packages/underscore/underscore-min.js"></script>

+

+    <script type="text/javascript" charset="utf-8">

+    </script>

+{% endblock %}

diff --git a/wqflask/wqflask/templates/admin/select_group_to_add.html b/wqflask/wqflask/templates/admin/select_group_to_add.html
new file mode 100644
index 00000000..df70fb2f
--- /dev/null
+++ b/wqflask/wqflask/templates/admin/select_group_to_add.html
@@ -0,0 +1,54 @@
+{% extends "base.html" %}

+{% block title %}Matched Groups{% endblock %}

+{% block css %}

+    <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" />

+    <link rel="stylesheet" type="text/css" href="/static/new/packages/DataTables/extensions/buttons.dataTables.css">

+    <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" />

+{% endblock %}

+{% block content %}

+<!-- Start of body -->

+    <div class="container">

+        <h1>The following groups were found:</h1>

+        <br>

+        <form id="add_groups">

+            <input type="hidden" name="resource_id" value="{{ resource_id }}">

+            <div id="groups_list" style="min-width: 600px; max-width: 800px;">

+                {% if group_list|length > 0 %}

+                <button type="submit" class="btn btn-primary" style="margin-bottom: 40px;">Add Selected Group</button>

+                <table id="groups_table" class="table-hover table-striped cell-border" style="float: left;">

+                    <thead>

+                        <tr>

+                            <th></th>

+                            <th>Name</th>

+                            <th>Created</th>

+                            <th>Last Changed</th>

+                        </tr>

+                    </thead>

+                    <tbody>

+                    {% for group in group_list %}

+                        <tr>

+                            <td align="center" style="padding: 0px;"><input type="radio" name="selected_group" VALUE="{{ group.id }}"></td>

+                            <td>{% if 'name' in group %}{{ group.name }}{% else %}N/A{% endif %}</td>

+                            <td>{% if 'created_timestamp' in group %}{{ group.created_timestamp }}{% else %}N/A{% endif %}</td>

+                            <td>{% if 'changed_timestamp' in group %}{{ group.changed_timestamp }}{% else %}N/A{% endif %}</td>

+                        </tr>

+                    {% endfor %}

+                    </tbody>

+                </table>

+                {% else %}

+                <h2>No matching groups were found.</h2>

+                {% endif %}

+            </div>

+        </form>

+    </div>

+

+<!-- End of body -->

+

+{% endblock %}

+

+{% block js %}

+    <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script>

+    <script>

+        $('#groups_table').dataTable();

+    </script>

+{% endblock %}

diff --git a/wqflask/wqflask/templates/new_security/not_authenticated.html b/wqflask/wqflask/templates/new_security/not_authenticated.html
new file mode 100644
index 00000000..7d0d3060
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/not_authenticated.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}

+{% block title %}Authentication Needed{% endblock %}

+{% block content %}

+    <div class="container">

+        <div class="page-header">

+            <h3>You lack the permissions to view this data.</h3>

+        </div>

+        <p>Please contact the data's owner or GN administrators if you believe you should have access to this data.</p>

+    </div>

+

+{% endblock %}
\ No newline at end of file
diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html
index 878b6ced..5c315878 100644
--- a/wqflask/wqflask/templates/show_trait_details.html
+++ b/wqflask/wqflask/templates/show_trait_details.html
@@ -248,6 +248,11 @@
         <a target="_blank" href="http://gn1.genenetwork.org/webqtl/main.py?cmd=show&db={{ this_trait.dataset.name }}&probeset={{ this_trait.name }}">
         <button type="button" id="view_in_gn1" class="btn btn-primary" title="View Trait in GN1">View in GN1</button>
         </a>
+        {% if resource_id %}
+        <a target="_blank" href="./resources/manage?resource_id={{ resource_id }}">
+            <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource">Edit</button>
+        </a>
+        {% endif %}
     </div>
 </div>
 
diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py
index 24a4dcee..ee827ba3 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -30,6 +30,7 @@ import sqlalchemy
 from wqflask import app
 from flask import g, Response, request, make_response, render_template, send_from_directory, jsonify, redirect, url_for
 from wqflask import group_manager
+from wqflask import resource_manager
 from wqflask import search_results
 from wqflask import export_traits
 from wqflask import gsearch
@@ -89,13 +90,13 @@ def connect_db():
 @app.before_request
 def check_access_permissions():
     logger.debug("@app.before_request check_access_permissions")
+    available = True
     if "temp_trait" in request.args:
         if request.args['temp_trait'] == "True":
             pass
     else:
         if 'dataset' in request.args:
             dataset = create_dataset(request.args['dataset'])
-            logger.debug("USER:", Redis.hget("users"))
             if 'trait_id' in request.args:
                 available = check_resource_availability(dataset, request.args['trait_id'])
             else: