diff --git a/cms/djangoapps/auth/authz.py b/cms/djangoapps/auth/authz.py
index c4d1a9ddffb3d12a71257fa0bc5a66563ec6913d..1a1f138cb588a6bc2ae15e916ab5060edf79c27b 100644
--- a/cms/djangoapps/auth/authz.py
+++ b/cms/djangoapps/auth/authz.py
@@ -1,3 +1,6 @@
+"""
+Studio authorization functions primarily for course creators, instructors, and staff
+"""
 #=======================================================================================================================
 #
 # This code is somewhat duplicative of access.py in the LMS. We will unify the code as a separate story
@@ -11,7 +14,8 @@ from django.conf import settings
 from xmodule.modulestore import Location
 from xmodule.modulestore.locator import CourseLocator, Locator
 from xmodule.modulestore.django import loc_mapper
-from xmodule.modulestore.exceptions import InvalidLocationError
+from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError
+import itertools
 
 
 # define a couple of simple roles, we just need ADMIN and EDITOR now for our purposes
@@ -26,7 +30,11 @@ COURSE_CREATOR_GROUP_NAME = "course_creator_group"
 # of those two variables
 
 
-def get_course_groupname_for_role(location, role):
+def get_all_course_role_groupnames(location, role, use_filter=True):
+    '''
+    Get all of the possible groupnames for this role location pair. If use_filter==True,
+    only return the ones defined in the groups collection.
+    '''
     location = Locator.to_locator_or_location(location)
 
     # hack: check for existence of a group name in the legacy LMS format <role>_<course>
@@ -38,22 +46,46 @@ def get_course_groupname_for_role(location, role):
     except InvalidLocationError:  # will occur on old locations where location is not of category course
         pass
     if isinstance(location, Location):
+        # least preferred role_course format
         groupnames.append('{0}_{1}'.format(role, location.course))
+        try:
+            locator = loc_mapper().translate_location(location.course_id, location, False, False)
+            groupnames.append('{0}_{1}'.format(role, locator.course_id))
+        except (InvalidLocationError, ItemNotFoundError):
+            pass
     elif isinstance(location, CourseLocator):
         old_location = loc_mapper().translate_locator_to_location(location, get_course=True)
         if old_location:
+            # the slashified version of the course_id (myu/mycourse/myrun)
             groupnames.append('{0}_{1}'.format(role, old_location.course_id))
-
-    for groupname in groupnames:
-        if Group.objects.filter(name=groupname).exists():
-            return groupname
-    return groupnames[0]
+            # add the least desirable but sometimes occurring format.
+            groupnames.append('{0}_{1}'.format(role, old_location.course))
+    # filter to the ones which exist
+    default = groupnames[0]
+    if use_filter:
+        groupnames = [group for group in groupnames if Group.objects.filter(name=group).exists()]
+    return groupnames, default
 
 
-def get_users_in_course_group_by_role(location, role):
-    groupname = get_course_groupname_for_role(location, role)
-    (group, _created) = Group.objects.get_or_create(name=groupname)
-    return group.user_set.all()
+def get_course_groupname_for_role(location, role):
+    '''
+    Get the preferred used groupname for this role, location combo.
+    Preference order: 
+    * role_course_id (e.g., staff_myu.mycourse.myrun)
+    * role_old_course_id (e.g., staff_myu/mycourse/myrun)
+    * role_old_course (e.g., staff_mycourse)
+    '''
+    groupnames, default = get_all_course_role_groupnames(location, role)
+    return groupnames[0] if groupnames else default
+
+
+def get_course_role_users(course_locator, role):
+    '''
+    Get all of the users with the given role in the given course.
+    '''
+    groupnames, _ = get_all_course_role_groupnames(course_locator, role)
+    groups = [Group.objects.get(name=groupname) for groupname in groupnames]
+    return list(itertools.chain.from_iterable(group.user_set.all() for group in groups))
 
 
 def create_all_course_groups(creator, location):
@@ -65,11 +97,11 @@ def create_all_course_groups(creator, location):
 
 
 def create_new_course_group(creator, location, role):
-    groupname = get_course_groupname_for_role(location, role)
-    (group, created) = Group.objects.get_or_create(name=groupname)
-    if created:
-        group.save()
-
+    '''
+    Create the new course group always using the preferred name even if another form already exists.
+    '''
+    groupnames, __ = get_all_course_role_groupnames(location, role, use_filter=False)
+    group, __ = Group.objects.get_or_create(name=groupnames[0])
     creator.groups.add(group)
     creator.save()
 
@@ -82,15 +114,13 @@ def _delete_course_group(location):
     asserted permissions
     """
     # remove all memberships
-    instructors = Group.objects.get(name=get_course_groupname_for_role(location, INSTRUCTOR_ROLE_NAME))
-    for user in instructors.user_set.all():
-        user.groups.remove(instructors)
-        user.save()
-
-    staff = Group.objects.get(name=get_course_groupname_for_role(location, STAFF_ROLE_NAME))
-    for user in staff.user_set.all():
-        user.groups.remove(staff)
-        user.save()
+    for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
+        groupnames, _ = get_all_course_role_groupnames(location, role)
+        for groupname in groupnames:
+            group = Group.objects.get(name=groupname)
+            for user in group.user_set.all():
+                user.groups.remove(group)
+                user.save()
 
 
 def _copy_course_group(source, dest):
@@ -98,25 +128,25 @@ def _copy_course_group(source, dest):
     This is to be called only by either a command line code path or through an app which has already
     asserted permissions to do this action
     """
-    instructors = Group.objects.get(name=get_course_groupname_for_role(source, INSTRUCTOR_ROLE_NAME))
-    new_instructors_group = Group.objects.get(name=get_course_groupname_for_role(dest, INSTRUCTOR_ROLE_NAME))
-    for user in instructors.user_set.all():
-        user.groups.add(new_instructors_group)
-        user.save()
-
-    staff = Group.objects.get(name=get_course_groupname_for_role(source, STAFF_ROLE_NAME))
-    new_staff_group = Group.objects.get(name=get_course_groupname_for_role(dest, STAFF_ROLE_NAME))
-    for user in staff.user_set.all():
-        user.groups.add(new_staff_group)
-        user.save()
+    for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
+        groupnames, _ = get_all_course_role_groupnames(source, role)
+        for groupname in groupnames:
+            group = Group.objects.get(name=groupname)
+            new_group, _ = Group.objects.get_or_create(name=get_course_groupname_for_role(dest, INSTRUCTOR_ROLE_NAME))
+            for user in group.user_set.all():
+                user.groups.add(new_group)
+                user.save()
 
 
 def add_user_to_course_group(caller, user, location, role):
+    """
+    If caller is authorized, add the given user to the given course's role
+    """
     # only admins can add/remove other users
     if not is_user_in_course_group_role(caller, location, INSTRUCTOR_ROLE_NAME):
         raise PermissionDenied
 
-    group = Group.objects.get(name=get_course_groupname_for_role(location, role))
+    group, _ = Group.objects.get_or_create(name=get_course_groupname_for_role(location, role))
     return _add_user_to_group(user, group)
 
 
@@ -132,9 +162,7 @@ def add_user_to_creator_group(caller, user):
     if not caller.is_active or not caller.is_authenticated or not caller.is_staff:
         raise PermissionDenied
 
-    (group, created) = Group.objects.get_or_create(name=COURSE_CREATOR_GROUP_NAME)
-    if created:
-        group.save()
+    (group, _) = Group.objects.get_or_create(name=COURSE_CREATOR_GROUP_NAME)
     return _add_user_to_group(user, group)
 
 
@@ -152,6 +180,9 @@ def _add_user_to_group(user, group):
 
 
 def get_user_by_email(email):
+    """
+    Get the user whose email is the arg. Return None if no such user exists.
+    """
     user = None
     # try to look up user, return None if not found
     try:
@@ -163,13 +194,21 @@ def get_user_by_email(email):
 
 
 def remove_user_from_course_group(caller, user, location, role):
+    """
+    If caller is authorized, remove the given course x role authorization for user
+    """
     # only admins can add/remove other users
     if not is_user_in_course_group_role(caller, location, INSTRUCTOR_ROLE_NAME):
         raise PermissionDenied
 
     # see if the user is actually in that role, if not then we don't have to do anything
-    if is_user_in_course_group_role(user, location, role):
-        _remove_user_from_group(user, get_course_groupname_for_role(location, role))
+    groupnames, _ = get_all_course_role_groupnames(location, role)
+    for groupname in groupnames:
+        groups = user.groups.filter(name=groupname)
+        if groups:
+            # will only be one with that name
+            user.groups.remove(groups[0])
+            user.save()
 
 
 def remove_user_from_creator_group(caller, user):
@@ -195,11 +234,16 @@ def _remove_user_from_group(user, group_name):
 
 
 def is_user_in_course_group_role(user, location, role, check_staff=True):
+    """
+    Check whether the given user has the given role in this course. If check_staff
+    then give permission if the user is staff without doing a course-role query.
+    """
     if user.is_active and user.is_authenticated:
         # all "is_staff" flagged accounts belong to all groups
         if check_staff and user.is_staff:
             return True
-        return user.groups.filter(name=get_course_groupname_for_role(location, role)).exists()
+        groupnames, _ = get_all_course_role_groupnames(location, role)
+        return any(user.groups.filter(name=groupname).exists() for groupname in groupnames)
 
     return False
 
diff --git a/cms/djangoapps/contentstore/tests/test_permissions.py b/cms/djangoapps/contentstore/tests/test_permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..6dae9d883171190bb6e396706c05dc971298f729
--- /dev/null
+++ b/cms/djangoapps/contentstore/tests/test_permissions.py
@@ -0,0 +1,127 @@
+"""
+Test CRUD for authorization.
+"""
+from django.test.utils import override_settings
+from django.contrib.auth.models import User, Group
+
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from contentstore.tests.modulestore_config import TEST_MODULESTORE
+from contentstore.tests.utils import AjaxEnabledTestClient
+from xmodule.modulestore.django import loc_mapper
+from xmodule.modulestore import Location
+from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME
+from auth import authz
+import copy
+from contentstore.views.access import has_access
+
+
+@override_settings(MODULESTORE=TEST_MODULESTORE)
+class TestCourseAccess(ModuleStoreTestCase):
+    """
+    Course-based access (as opposed to access of a non-course xblock)
+    """
+    def setUp(self):
+        """
+        Create a staff user and log them in (creating the client).
+
+        Create a pool of users w/o granting them any permissions
+        """
+        super(TestCourseAccess, self).setUp()
+        uname = 'testuser'
+        email = 'test+courses@edx.org'
+        password = 'foo'
+
+        # Create the use so we can log them in.
+        self.user = User.objects.create_user(uname, email, password)
+
+        # Note that we do not actually need to do anything
+        # for registration if we directly mark them active.
+        self.user.is_active = True
+        # Staff has access to view all courses
+        self.user.is_staff = True
+        self.user.save()
+
+        self.client = AjaxEnabledTestClient()
+        self.client.login(username=uname, password=password)
+
+        # create a course via the view handler which has a different strategy for permissions than the factory
+        self.course_location = Location(['i4x', 'myu', 'mydept.mycourse', 'course', 'myrun'])
+        self.course_locator = loc_mapper().translate_location(
+            self.course_location.course_id, self.course_location, False, True
+        )
+        self.client.ajax_post(
+            self.course_locator.url_reverse('course'),
+            {
+                'org': self.course_location.org, 
+                'number': self.course_location.course,
+                'display_name': 'My favorite course',
+                'run': self.course_location.name,
+            }
+        )
+
+        self.users = self._create_users()
+
+    def _create_users(self):
+        """
+        Create 8 users and return them
+        """
+        users = []
+        for i in range(8):
+            username = "user{}".format(i)
+            email = "test+user{}@edx.org".format(i)
+            user = User.objects.create_user(username, email, 'foo')
+            user.is_active = True
+            user.save()
+            users.append(user)
+        return users
+
+    def tearDown(self):
+        """
+        Reverse the setup
+        """
+        self.client.logout()
+        ModuleStoreTestCase.tearDown(self)
+
+    def test_get_all_users(self):
+        """
+        Test getting all authors for a course where their permissions run the gamut of allowed group
+        types.
+        """
+        # first check the groupname for the course creator.
+        self.assertTrue(
+            self.user.groups.filter(
+                name="{}_{}".format(INSTRUCTOR_ROLE_NAME, self.course_locator.course_id)
+            ).exists(),
+            "Didn't add creator as instructor."
+        )
+        users = copy.copy(self.users)
+        user_by_role = {}
+        # add the misc users to the course in different groups
+        for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
+            user_by_role[role] = []
+            groupnames, _ = authz.get_all_course_role_groupnames(self.course_locator, role)
+            for groupname in groupnames:
+                group, _ = Group.objects.get_or_create(name=groupname)
+                user = users.pop()
+                user_by_role[role].append(user)
+                user.groups.add(group)
+                user.save()
+                self.assertTrue(has_access(user, self.course_locator), "{} does not have access".format(user))
+                self.assertTrue(has_access(user, self.course_location), "{} does not have access".format(user))
+
+        response = self.client.get_html(self.course_locator.url_reverse('course_team'))
+        for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
+            for user in user_by_role[role]:
+                self.assertContains(response, user.email)
+        
+        # test copying course permissions
+        copy_course_location = Location(['i4x', 'copyu', 'copydept.mycourse', 'course', 'myrun'])
+        copy_course_locator = loc_mapper().translate_location(
+            copy_course_location.course_id, copy_course_location, False, True
+        )
+        # pylint: disable=protected-access
+        authz._copy_course_group(self.course_locator, copy_course_locator)
+        for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
+            for user in user_by_role[role]:
+                self.assertTrue(has_access(user, copy_course_locator), "{} no copy access".format(user))
+                self.assertTrue(has_access(user, copy_course_location), "{} no copy access".format(user))
\ No newline at end of file
diff --git a/cms/djangoapps/contentstore/tests/test_users.py b/cms/djangoapps/contentstore/tests/test_users.py
index c10dec61bd61596d21a0c727bfbe233ee36e78fd..ec8b5570a5342568862af6603751f809ec2399c5 100644
--- a/cms/djangoapps/contentstore/tests/test_users.py
+++ b/cms/djangoapps/contentstore/tests/test_users.py
@@ -29,8 +29,8 @@ class UsersTestCase(CourseTestCase):
         self.detail_url = self.location.url_reverse('course_team', self.ext_user.email)
         self.inactive_detail_url = self.location.url_reverse('course_team', self.inactive_user.email)
         self.invalid_detail_url = self.location.url_reverse('course_team', "nonexistent@user.com")
-        self.staff_groupname = get_course_groupname_for_role(self.course.location, "staff")
-        self.inst_groupname = get_course_groupname_for_role(self.course.location, "instructor")
+        self.staff_groupname = get_course_groupname_for_role(self.course_locator, "staff")
+        self.inst_groupname = get_course_groupname_for_role(self.course_locator, "instructor")
 
     def test_index(self):
         resp = self.client.get(self.index_url, HTTP_ACCEPT='text/html')
@@ -145,18 +145,6 @@ class UsersTestCase(CourseTestCase):
         self.assertIn("error", result)
         self.assert_not_enrolled()
 
-    def test_detail_post_bad_json(self):
-        resp = self.client.post(
-            self.detail_url,
-            data="{foo}",
-            content_type="application/json",
-            HTTP_ACCEPT="application/json",
-        )
-        self.assertEqual(resp.status_code, 400)
-        result = json.loads(resp.content)
-        self.assertIn("error", result)
-        self.assert_not_enrolled()
-
     def test_detail_post_no_json(self):
         resp = self.client.post(
             self.detail_url,
diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py
index 044ef79473bf4ac1c1cec2fe410bf47ad6272e62..c7e379869b30ba78d3820ad2811d2f497d89d2df 100644
--- a/cms/djangoapps/contentstore/views/course.py
+++ b/cms/djangoapps/contentstore/views/course.py
@@ -292,7 +292,8 @@ def create_new_course(request):
 
     initialize_course_tabs(new_course)
 
-    create_all_course_groups(request.user, new_course.location)
+    new_location = loc_mapper().translate_location(new_course.location.course_id, new_course.location, False, True)
+    create_all_course_groups(request.user, new_location)
 
     # seed the forums
     seed_permissions_roles(new_course.location.course_id)
@@ -301,7 +302,6 @@ def create_new_course(request):
     # work.
     CourseEnrollment.enroll(request.user, new_course.location.course_id)
 
-    new_location = loc_mapper().translate_location(new_course.location.course_id, new_course.location, False, True)
     return JsonResponse({'url': new_location.url_reverse("course/", "")})
 
 
diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py
index 6f2a2fbdec9ff951c02d8076859ecb9a7abf1648..d68aaeff7b6b1cd11c2d4f9109e1313b844462c9 100644
--- a/cms/djangoapps/contentstore/views/user.py
+++ b/cms/djangoapps/contentstore/views/user.py
@@ -1,5 +1,4 @@
 import json
-from django.conf import settings
 from django.core.exceptions import PermissionDenied
 from django.contrib.auth.models import User, Group
 from django.contrib.auth.decorators import login_required
@@ -10,9 +9,11 @@ from django_future.csrf import ensure_csrf_cookie
 from mitxmako.shortcuts import render_to_response
 
 from xmodule.modulestore.django import modulestore, loc_mapper
-from util.json_request import JsonResponse
+from util.json_request import JsonResponse, expect_json
 from auth.authz import (
-    STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME, get_course_groupname_for_role)
+    STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME, get_course_groupname_for_role,
+    get_course_role_users
+)
 from course_creators.views import user_requested_access
 
 from .access import has_access
@@ -35,6 +36,7 @@ def request_course_creator(request):
     return JsonResponse({"Status": "OK"})
 
 
+# pylint: disable=unused-argument
 @login_required
 @ensure_csrf_cookie
 @require_http_methods(("GET", "POST", "PUT", "DELETE"))
@@ -62,38 +64,39 @@ def course_team_handler(request, tag=None, course_id=None, branch=None, version_
         return HttpResponseNotFound()
 
 
-def _manage_users(request, location):
+def _manage_users(request, locator):
     """
     This view will return all CMS users who are editors for the specified course
     """
-    old_location = loc_mapper().translate_locator_to_location(location)
+    old_location = loc_mapper().translate_locator_to_location(locator)
 
     # check that logged in user has permissions to this item
-    if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=STAFF_ROLE_NAME):
+    if not has_access(request.user, locator):
         raise PermissionDenied()
 
     course_module = modulestore().get_item(old_location)
-
-    staff_groupname = get_course_groupname_for_role(location, "staff")
-    staff_group, __ = Group.objects.get_or_create(name=staff_groupname)
-    inst_groupname = get_course_groupname_for_role(location, "instructor")
-    inst_group, __ = Group.objects.get_or_create(name=inst_groupname)
+    instructors = get_course_role_users(locator, INSTRUCTOR_ROLE_NAME)
+    # the page only lists staff and assumes they're a superset of instructors. Do a union to ensure.
+    staff = set(get_course_role_users(locator, STAFF_ROLE_NAME)).union(instructors)
 
     return render_to_response('manage_users.html', {
         'context_course': course_module,
-        'staff': staff_group.user_set.all(),
-        'instructors': inst_group.user_set.all(),
-        'allow_actions': has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME),
+        'staff': staff,
+        'instructors': instructors,
+        'allow_actions': has_access(request.user, locator, role=INSTRUCTOR_ROLE_NAME),
     })
 
 
-def _course_team_user(request, location, email):
-    old_location = loc_mapper().translate_locator_to_location(location)
+@expect_json
+def _course_team_user(request, locator, email):
+    """
+    Handle the add, remove, promote, demote requests ensuring the requester has authority
+    """
     # check that logged in user has permissions to this item
-    if has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
+    if has_access(request.user, locator, role=INSTRUCTOR_ROLE_NAME):
         # instructors have full permissions
         pass
-    elif has_access(request.user, location, role=STAFF_ROLE_NAME) and email == request.user.email:
+    elif has_access(request.user, locator, role=STAFF_ROLE_NAME) and email == request.user.email:
         # staff can only affect themselves
         pass
     else:
@@ -123,7 +126,7 @@ def _course_team_user(request, location, email):
         # what's the highest role that this user has?
         groupnames = set(g.name for g in user.groups.all())
         for role in roles:
-            role_groupname = get_course_groupname_for_role(old_location, role)
+            role_groupname = get_course_groupname_for_role(locator, role)
             if role_groupname in groupnames:
                 msg["role"] = role
                 break
@@ -139,7 +142,7 @@ def _course_team_user(request, location, email):
     # make sure that the role groups exist
     groups = {}
     for role in roles:
-        groupname = get_course_groupname_for_role(old_location, role)
+        groupname = get_course_groupname_for_role(locator, role)
         group, __ = Group.objects.get_or_create(name=groupname)
         groups[role] = group
 
@@ -162,22 +165,13 @@ def _course_team_user(request, location, email):
         return JsonResponse()
 
     # all other operations require the requesting user to specify a role
-    if request.META.get("CONTENT_TYPE", "").startswith("application/json") and request.body:
-        try:
-            payload = json.loads(request.body)
-        except:
-            return JsonResponse({"error": _("malformed JSON")}, 400)
-        try:
-            role = payload["role"]
-        except KeyError:
-            return JsonResponse({"error": _("`role` is required")}, 400)
-    else:
-        if not "role" in request.POST:
-            return JsonResponse({"error": _("`role` is required")}, 400)
-        role = request.POST["role"]
+    role = request.json.get("role", request.POST.get("role"))
+    if role is None:
+        return JsonResponse({"error": _("`role` is required")}, 400)
 
+    old_location = loc_mapper().translate_locator_to_location(locator)
     if role == "instructor":
-        if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
+        if not has_access(request.user, locator, role=INSTRUCTOR_ROLE_NAME):
             msg = {
                 "error": _("Only instructors may create other instructors")
             }
@@ -203,4 +197,3 @@ def _course_team_user(request, location, email):
         CourseEnrollment.enroll(user, old_location.course_id)
 
     return JsonResponse()
-