diff --git a/cms/djangoapps/auth/authz.py b/cms/djangoapps/auth/authz.py
deleted file mode 100644
index 9ad595c66808895abe1a8f73470bc1d8cd3f8225..0000000000000000000000000000000000000000
--- a/cms/djangoapps/auth/authz.py
+++ /dev/null
@@ -1,291 +0,0 @@
-"""
-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
-# but this implementation should be data compatible with the LMS implementation
-#
-#=======================================================================================================================
-from django.contrib.auth.models import User, Group
-from django.core.exceptions import PermissionDenied
-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, ItemNotFoundError
-import itertools
-
-
-# define a couple of simple roles, we just need ADMIN and EDITOR now for our purposes
-INSTRUCTOR_ROLE_NAME = 'instructor'
-STAFF_ROLE_NAME = 'staff'
-
-# This is the group of people who have permission to create new courses on edge or edx.
-COURSE_CREATOR_GROUP_NAME = "course_creator_group"
-
-# we're just making a Django group for each location/role combo
-# to do this we're just creating a Group name which is a formatted string
-# of those two variables
-
-
-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)
-
-    groupnames = []
-    if isinstance(location, Location):
-        try:
-            groupnames.append('{0}_{1}'.format(role, location.course_id))
-        except InvalidLocationError:  # will occur on old locations where location is not of category course
-            pass
-        try:
-            locator = loc_mapper().translate_location(location.course_id, location, False, False)
-            groupnames.append('{0}_{1}'.format(role, locator.package_id))
-        except (InvalidLocationError, ItemNotFoundError):
-            pass
-        # least preferred role_course format for legacy reasons
-        groupnames.append('{0}_{1}'.format(role, location.course))
-    elif isinstance(location, CourseLocator):
-        groupnames.append('{0}_{1}'.format(role, location.package_id))
-        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))
-            # 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.name for group in Group.objects.filter(name__in=groupnames)]
-    return groupnames, default
-
-
-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):
-    """
-    Create all permission groups for a new course and subscribe the caller into those roles
-    """
-    create_new_course_group(creator, location, INSTRUCTOR_ROLE_NAME)
-    create_new_course_group(creator, location, STAFF_ROLE_NAME)
-
-
-def create_new_course_group(creator, location, role):
-    '''
-    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()
-
-    return
-
-
-def _delete_course_group(location):
-    """
-    This is to be called only by either a command line code path or through a app which has already
-    asserted permissions
-    """
-    # remove all memberships
-    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):
-    """
-    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
-    """
-    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_or_create(name=get_course_groupname_for_role(location, role))
-    return _add_user_to_group(user, group)
-
-
-def add_user_to_creator_group(caller, user):
-    """
-    Adds the user to the group of course creators.
-
-    The caller must have staff access to perform this operation.
-
-    Note that on the edX site, we currently limit course creators to edX staff, and this
-    method is a no-op in that environment.
-    """
-    if not caller.is_active or not caller.is_authenticated or not caller.is_staff:
-        raise PermissionDenied
-
-    (group, _) = Group.objects.get_or_create(name=COURSE_CREATOR_GROUP_NAME)
-    return _add_user_to_group(user, group)
-
-
-def _add_user_to_group(user, group):
-    """
-    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
-    """
-    if user.is_active and user.is_authenticated:
-        user.groups.add(group)
-        user.save()
-        return True
-
-    return False
-
-
-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:
-        user = User.objects.get(email=email)
-    except:
-        pass
-
-    return user
-
-
-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
-    groupnames, _ = get_all_course_role_groupnames(location, role)
-    user.groups.remove(*user.groups.filter(name__in=groupnames))
-    user.save()
-
-
-def remove_user_from_creator_group(caller, user):
-    """
-    Removes user from the course creator group.
-
-    The caller must have staff access to perform this operation.
-    """
-    if not caller.is_active or not caller.is_authenticated or not caller.is_staff:
-        raise PermissionDenied
-
-    _remove_user_from_group(user, COURSE_CREATOR_GROUP_NAME)
-
-
-def _remove_user_from_group(user, group_name):
-    """
-    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
-    """
-    group = Group.objects.get(name=group_name)
-    user.groups.remove(group)
-    user.save()
-
-
-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
-        groupnames, _ = get_all_course_role_groupnames(location, role)
-        return user.groups.filter(name__in=groupnames).exists()
-
-    return False
-
-
-def is_user_in_creator_group(user):
-    """
-    Returns true if the user has permissions to create a course.
-
-    Will always return True if user.is_staff is True.
-
-    Note that on the edX site, we currently limit course creators to edX staff. On
-    other sites, this method checks that the user is in the course creator group.
-    """
-    if user.is_staff:
-        return True
-
-    # On edx, we only allow edX staff to create courses. This may be relaxed in the future.
-    if settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
-        return False
-
-    # Feature flag for using the creator group setting. Will be removed once the feature is complete.
-    if settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
-        return user.groups.filter(name=COURSE_CREATOR_GROUP_NAME).exists()
-
-    return True
-
-
-def get_users_with_instructor_role():
-    """
-    Returns all users with the role 'instructor'
-    """
-    return _get_users_with_role(INSTRUCTOR_ROLE_NAME)
-
-
-def get_users_with_staff_role():
-    """
-    Returns all users with the role 'staff'
-    """
-    return _get_users_with_role(STAFF_ROLE_NAME)
-
-
-def _get_users_with_role(role):
-    """
-    Returns all users with the specified role.
-    """
-    users = set()
-    for group in Group.objects.all():
-        if group.name.startswith(role + "_"):
-            for user in group.user_set.all():
-                users.add(user)
-    return users
diff --git a/cms/djangoapps/auth/tests/test_authz.py b/cms/djangoapps/auth/tests/test_authz.py
index cf0337628e0e63b5c1d1a05ca317c4e0322fdc08..289c911d4aae6a619b6dd3605c6631f6025a3612 100644
--- a/cms/djangoapps/auth/tests/test_authz.py
+++ b/cms/djangoapps/auth/tests/test_authz.py
@@ -8,10 +8,9 @@ from django.contrib.auth.models import User
 from xmodule.modulestore import Location
 from django.core.exceptions import PermissionDenied
 
-from auth.authz import add_user_to_creator_group, remove_user_from_creator_group, is_user_in_creator_group,\
-    create_all_course_groups, add_user_to_course_group, STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME,\
-    is_user_in_course_group_role, remove_user_from_course_group, get_users_with_staff_role,\
-    get_users_with_instructor_role
+from student.roles import CourseInstructorRole, CourseStaffRole, CourseCreatorRole
+from student.tests.factories import AdminFactory
+from student.auth import has_access, add_users, remove_users
 
 
 class CreatorGroupTest(TestCase):
@@ -27,98 +26,104 @@ class CreatorGroupTest(TestCase):
 
     def test_creator_group_not_enabled(self):
         """
-        Tests that is_user_in_creator_group always returns True if ENABLE_CREATOR_GROUP
+        Tests that CourseCreatorRole().has_user always returns True if ENABLE_CREATOR_GROUP
         and DISABLE_COURSE_CREATION are both not turned on.
         """
-        self.assertTrue(is_user_in_creator_group(self.user))
+        self.assertTrue(has_access(self.user, CourseCreatorRole()))
 
     def test_creator_group_enabled_but_empty(self):
         """ Tests creator group feature on, but group empty. """
         with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
-            self.assertFalse(is_user_in_creator_group(self.user))
+            self.assertFalse(has_access(self.user, CourseCreatorRole()))
 
-            # Make user staff. This will cause is_user_in_creator_group to return True.
+            # Make user staff. This will cause CourseCreatorRole().has_user to return True.
             self.user.is_staff = True
-            self.assertTrue(is_user_in_creator_group(self.user))
+            self.assertTrue(has_access(self.user, CourseCreatorRole()))
 
     def test_creator_group_enabled_nonempty(self):
         """ Tests creator group feature on, user added. """
         with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
-            self.assertTrue(add_user_to_creator_group(self.admin, self.user))
-            self.assertTrue(is_user_in_creator_group(self.user))
+            add_users(self.admin, CourseCreatorRole(), self.user)
+            self.assertTrue(has_access(self.user, CourseCreatorRole()))
 
             # check that a user who has not been added to the group still returns false
             user_not_added = User.objects.create_user('testuser2', 'test+courses2@edx.org', 'foo2')
-            self.assertFalse(is_user_in_creator_group(user_not_added))
+            self.assertFalse(has_access(user_not_added, CourseCreatorRole()))
 
-            # remove first user from the group and verify that is_user_in_creator_group now returns false
-            remove_user_from_creator_group(self.admin, self.user)
-            self.assertFalse(is_user_in_creator_group(self.user))
-
-    def test_add_user_not_authenticated(self):
-        """
-        Tests that adding to creator group fails if user is not authenticated
-        """
-        self.user.is_authenticated = False
-        self.assertFalse(add_user_to_creator_group(self.admin, self.user))
-
-    def test_add_user_not_active(self):
-        """
-        Tests that adding to creator group fails if user is not active
-        """
-        self.user.is_active = False
-        self.assertFalse(add_user_to_creator_group(self.admin, self.user))
+            # remove first user from the group and verify that CourseCreatorRole().has_user now returns false
+            remove_users(self.admin, CourseCreatorRole(), self.user)
+            self.assertFalse(has_access(self.user, CourseCreatorRole()))
 
     def test_course_creation_disabled(self):
         """ Tests that the COURSE_CREATION_DISABLED flag overrides course creator group settings. """
         with mock.patch.dict('django.conf.settings.FEATURES',
                              {'DISABLE_COURSE_CREATION': True, "ENABLE_CREATOR_GROUP": True}):
             # Add user to creator group.
-            self.assertTrue(add_user_to_creator_group(self.admin, self.user))
+            add_users(self.admin, CourseCreatorRole(), self.user)
 
             # DISABLE_COURSE_CREATION overrides (user is not marked as staff).
-            self.assertFalse(is_user_in_creator_group(self.user))
+            self.assertFalse(has_access(self.user, CourseCreatorRole()))
 
-            # Mark as staff. Now is_user_in_creator_group returns true.
+            # Mark as staff. Now CourseCreatorRole().has_user returns true.
             self.user.is_staff = True
-            self.assertTrue(is_user_in_creator_group(self.user))
+            self.assertTrue(has_access(self.user, CourseCreatorRole()))
+
+            # Remove user from creator group. CourseCreatorRole().has_user still returns true because is_staff=True
+            remove_users(self.admin, CourseCreatorRole(), self.user)
+            self.assertTrue(has_access(self.user, CourseCreatorRole()))
+
+    def test_add_user_not_authenticated(self):
+        """
+        Tests that adding to creator group fails if user is not authenticated
+        """
+        with mock.patch.dict('django.conf.settings.FEATURES',
+                             {'DISABLE_COURSE_CREATION': False, "ENABLE_CREATOR_GROUP": True}):
+            self.user.is_authenticated = False
+            add_users(self.admin, CourseCreatorRole(), self.user)
+            self.assertFalse(has_access(self.user, CourseCreatorRole()))
 
-            # Remove user from creator group. is_user_in_creator_group still returns true because is_staff=True
-            remove_user_from_creator_group(self.admin, self.user)
-            self.assertTrue(is_user_in_creator_group(self.user))
+    def test_add_user_not_active(self):
+        """
+        Tests that adding to creator group fails if user is not active
+        """
+        with mock.patch.dict('django.conf.settings.FEATURES',
+                             {'DISABLE_COURSE_CREATION': False, "ENABLE_CREATOR_GROUP": True}):
+            self.user.is_active = False
+            add_users(self.admin, CourseCreatorRole(), self.user)
+            self.assertFalse(has_access(self.user, CourseCreatorRole()))
 
     def test_add_user_to_group_requires_staff_access(self):
         with self.assertRaises(PermissionDenied):
             self.admin.is_staff = False
-            add_user_to_creator_group(self.admin, self.user)
+            add_users(self.admin, CourseCreatorRole(), self.user)
 
         with self.assertRaises(PermissionDenied):
-            add_user_to_creator_group(self.user, self.user)
+            add_users(self.user, CourseCreatorRole(), self.user)
 
     def test_add_user_to_group_requires_active(self):
         with self.assertRaises(PermissionDenied):
             self.admin.is_active = False
-            add_user_to_creator_group(self.admin, self.user)
+            add_users(self.admin, CourseCreatorRole(), self.user)
 
     def test_add_user_to_group_requires_authenticated(self):
         with self.assertRaises(PermissionDenied):
             self.admin.is_authenticated = False
-            add_user_to_creator_group(self.admin, self.user)
+            add_users(self.admin, CourseCreatorRole(), self.user)
 
     def test_remove_user_from_group_requires_staff_access(self):
         with self.assertRaises(PermissionDenied):
             self.admin.is_staff = False
-            remove_user_from_creator_group(self.admin, self.user)
+            remove_users(self.admin, CourseCreatorRole(), self.user)
 
     def test_remove_user_from_group_requires_active(self):
         with self.assertRaises(PermissionDenied):
             self.admin.is_active = False
-            remove_user_from_creator_group(self.admin, self.user)
+            remove_users(self.admin, CourseCreatorRole(), self.user)
 
     def test_remove_user_from_group_requires_authenticated(self):
         with self.assertRaises(PermissionDenied):
             self.admin.is_authenticated = False
-            remove_user_from_creator_group(self.admin, self.user)
+            remove_users(self.admin, CourseCreatorRole(), self.user)
 
 
 class CourseGroupTest(TestCase):
@@ -128,6 +133,7 @@ class CourseGroupTest(TestCase):
 
     def setUp(self):
         """ Test case setup """
+        self.global_admin = AdminFactory()
         self.creator = User.objects.create_user('testcreator', 'testcreator+courses@edx.org', 'foo')
         self.staff = User.objects.create_user('teststaff', 'teststaff+courses@edx.org', 'foo')
         self.location = Location('i4x', 'mitX', '101', 'course', 'test')
@@ -137,67 +143,47 @@ class CourseGroupTest(TestCase):
         Tests adding user to course group (happy path).
         """
         # Create groups for a new course (and assign instructor role to the creator).
-        self.assertFalse(is_user_in_course_group_role(self.creator, self.location, INSTRUCTOR_ROLE_NAME))
-        create_all_course_groups(self.creator, self.location)
-        self.assertTrue(is_user_in_course_group_role(self.creator, self.location, INSTRUCTOR_ROLE_NAME))
+        self.assertFalse(has_access(self.creator, CourseInstructorRole(self.location)))
+        add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
+        add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
+        self.assertTrue(has_access(self.creator, CourseInstructorRole(self.location)))
 
         # Add another user to the staff role.
-        self.assertFalse(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))
-        self.assertTrue(add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME))
-        self.assertTrue(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))
+        self.assertFalse(has_access(self.staff, CourseStaffRole(self.location)))
+        add_users(self.creator, CourseStaffRole(self.location), self.staff)
+        self.assertTrue(has_access(self.staff, CourseStaffRole(self.location)))
 
     def test_add_user_to_course_group_permission_denied(self):
         """
         Verifies PermissionDenied if caller of add_user_to_course_group is not instructor role.
         """
-        create_all_course_groups(self.creator, self.location)
+        add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
+        add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
         with self.assertRaises(PermissionDenied):
-            add_user_to_course_group(self.staff, self.staff, self.location, STAFF_ROLE_NAME)
+            add_users(self.staff, CourseStaffRole(self.location), self.staff)
 
     def test_remove_user_from_course_group(self):
         """
         Tests removing user from course group (happy path).
         """
-        create_all_course_groups(self.creator, self.location)
+        add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
+        add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
 
-        self.assertTrue(add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME))
-        self.assertTrue(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))
+        add_users(self.creator, CourseStaffRole(self.location), self.staff)
+        self.assertTrue(has_access(self.staff, CourseStaffRole(self.location)))
 
-        remove_user_from_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
-        self.assertFalse(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))
+        remove_users(self.creator, CourseStaffRole(self.location), self.staff)
+        self.assertFalse(has_access(self.staff, CourseStaffRole(self.location)))
 
-        remove_user_from_course_group(self.creator, self.creator, self.location, INSTRUCTOR_ROLE_NAME)
-        self.assertFalse(is_user_in_course_group_role(self.creator, self.location, INSTRUCTOR_ROLE_NAME))
+        remove_users(self.creator, CourseInstructorRole(self.location), self.creator)
+        self.assertFalse(has_access(self.creator, CourseInstructorRole(self.location)))
 
     def test_remove_user_from_course_group_permission_denied(self):
         """
         Verifies PermissionDenied if caller of remove_user_from_course_group is not instructor role.
         """
-        create_all_course_groups(self.creator, self.location)
+        add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
+        another_staff = User.objects.create_user('another', 'teststaff+anothercourses@edx.org', 'foo')
+        add_users(self.global_admin, CourseStaffRole(self.location), self.creator, self.staff, another_staff)
         with self.assertRaises(PermissionDenied):
-            remove_user_from_course_group(self.staff, self.staff, self.location, STAFF_ROLE_NAME)
-
-    def test_get_staff(self):
-        # Do this test with staff in 2 different classes.
-        create_all_course_groups(self.creator, self.location)
-        add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
-
-        location2 = Location('i4x', 'mitX', '103', 'course', 'test2')
-        staff2 = User.objects.create_user('teststaff2', 'teststaff2+courses@edx.org', 'foo')
-        create_all_course_groups(self.creator, location2)
-        add_user_to_course_group(self.creator, staff2, location2, STAFF_ROLE_NAME)
-
-        self.assertSetEqual({self.staff, staff2, self.creator}, get_users_with_staff_role())
-
-    def test_get_instructor(self):
-        # Do this test with creators in 2 different classes.
-        create_all_course_groups(self.creator, self.location)
-        add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
-
-        location2 = Location('i4x', 'mitX', '103', 'course', 'test2')
-        creator2 = User.objects.create_user('testcreator2', 'testcreator2+courses@edx.org', 'foo')
-        staff2 = User.objects.create_user('teststaff2', 'teststaff2+courses@edx.org', 'foo')
-        create_all_course_groups(creator2, location2)
-        add_user_to_course_group(creator2, staff2, location2, STAFF_ROLE_NAME)
-
-        self.assertSetEqual({self.creator, creator2}, get_users_with_instructor_role())
+            remove_users(self.staff, CourseStaffRole(self.location), another_staff)
diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py
index cd9c00c09ca5660ae8b7cd5f934a858b9a5772dc..c97c5d170424f3f09b8bcebc57b22264991b035d 100644
--- a/cms/djangoapps/contentstore/features/common.py
+++ b/cms/djangoapps/contentstore/features/common.py
@@ -1,18 +1,20 @@
 # pylint: disable=C0111
 # pylint: disable=W0621
 
+import time
+import os
 from lettuce import world, step
-from nose.tools import assert_true, assert_in, assert_false  # pylint: disable=E0611
-
-from auth.authz import get_user_by_email, get_course_groupname_for_role
+from nose.tools import assert_true, assert_in  # pylint: disable=no-name-in-module
 from django.conf import settings
 
+from student.roles import CourseRole, CourseStaffRole, CourseInstructorRole
+from student.models import get_user
+
 from selenium.webdriver.common.keys import Keys
-import time
-import os
-from django.contrib.auth.models import Group
 
 from logging import getLogger
+from student.tests.factories import AdminFactory
+from student import auth
 logger = getLogger(__name__)
 
 from terrain.browser import reset_data
@@ -158,11 +160,9 @@ def add_course_author(user, course):
     Add the user to the instructor group of the course
     so they will have the permissions to see it in studio
     """
-    for role in ("staff", "instructor"):
-        groupname = get_course_groupname_for_role(course.location, role)
-        group, __ = Group.objects.get_or_create(name=groupname)
-        user.groups.add(group)
-    user.save()
+    global_admin = AdminFactory()
+    for role in (CourseStaffRole, CourseInstructorRole):
+        auth.add_users(global_admin, role(course.location), user)
 
 
 def create_a_course():
@@ -171,7 +171,7 @@ def create_a_course():
 
     user = world.scenario_dict.get("USER")
     if not user:
-        user = get_user_by_email('robot+studio@edx.org')
+        user = get_user('robot+studio@edx.org')
 
     add_course_author(user, course)
 
@@ -358,7 +358,7 @@ def other_user_login(step, name):
         login_form.find_by_name('submit').click()
     world.retry_on_exception(fill_login_form)
     assert_true(world.is_css_present('.new-course-button'))
-    world.scenario_dict['USER'] = get_user_by_email(name + '@edx.org')
+    world.scenario_dict['USER'] = get_user(name + '@edx.org')
 
 
 @step(u'the user "([^"]*)" exists( as a course (admin|staff member|is_staff))?$')
@@ -368,18 +368,17 @@ def create_other_user(_step, name, has_extra_perms, role_name):
     if has_extra_perms:
         if role_name == "is_staff":
             user.is_staff = True
+            user.save()
         else:
             if role_name == "admin":
                 # admins get staff privileges, as well
-                roles = ("staff", "instructor")
+                roles = (CourseStaffRole, CourseInstructorRole)
             else:
-                roles = ("staff",)
+                roles = (CourseStaffRole,)
             location = world.scenario_dict["COURSE"].location
+            global_admin = AdminFactory()
             for role in roles:
-                groupname = get_course_groupname_for_role(location, role)
-                group, __ = Group.objects.get_or_create(name=groupname)
-                user.groups.add(group)
-        user.save()
+                auth.add_users(global_admin, role(location), user)
 
 
 @step('I log out')
@@ -393,7 +392,7 @@ def i_edit_a_draft(_step):
 
 
 @step(u'I click on "replace with draft"$')
-def i_edit_a_draft(_step):
+def i_replace_w_draft(_step):
     world.css_click("a.publish-draft")
 
 
diff --git a/cms/djangoapps/contentstore/management/commands/clone_course.py b/cms/djangoapps/contentstore/management/commands/clone_course.py
index 04f0c890879db3954077ca42c5febe0d878d29e0..67f23d20eb6db644254472dd6047fa0753f5ddff 100644
--- a/cms/djangoapps/contentstore/management/commands/clone_course.py
+++ b/cms/djangoapps/contentstore/management/commands/clone_course.py
@@ -6,8 +6,7 @@ from xmodule.modulestore.store_utilities import clone_course
 from xmodule.modulestore.django import modulestore
 from xmodule.contentstore.django import contentstore
 from xmodule.course_module import CourseDescriptor
-
-from auth.authz import _copy_course_group
+from student.roles import CourseInstructorRole, CourseStaffRole
 
 
 #
@@ -20,7 +19,7 @@ class Command(BaseCommand):
     def handle(self, *args, **options):
         "Execute the command"
         if len(args) != 2:
-            raise CommandError("clone requires two arguments: <source-course_id> <dest-course_id>")
+            raise CommandError("clone requires 2 arguments: <source-course_id> <dest-course_id>")
 
         source_course_id = args[0]
         dest_course_id = args[1]
@@ -28,7 +27,7 @@ class Command(BaseCommand):
         mstore = modulestore('direct')
         cstore = contentstore()
 
-        org, course_num, run = dest_course_id.split("/")
+        org, course_num, _ = dest_course_id.split("/")
         mstore.ignore_write_events_on_courses.append('{0}/{1}'.format(org, course_num))
 
         print("Cloning course {0} to {1}".format(source_course_id, dest_course_id))
@@ -41,4 +40,10 @@ class Command(BaseCommand):
             mstore.refresh_cached_metadata_inheritance_tree(dest_location)
 
             print("copying User permissions...")
-            _copy_course_group(source_location, dest_location)
+            # purposely avoids auth.add_user b/c it doesn't have a caller to authorize
+            CourseInstructorRole(dest_location).add_users(
+                *CourseInstructorRole(source_location).users_with_role()
+            )
+            CourseStaffRole(dest_location).add_users(
+                *CourseStaffRole(source_location).users_with_role()
+            )
diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index c23d9bb44bc4d2d1a48633b01bda3a39255dd055..2f57158f0cbf48911ace79782edd941f27541469 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -20,8 +20,6 @@ from django.dispatch import Signal
 from contentstore.utils import get_modulestore
 from contentstore.tests.utils import parse_json, AjaxEnabledTestClient
 
-from auth.authz import add_user_to_creator_group
-
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from contentstore.tests.modulestore_config import TEST_MODULESTORE
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -57,6 +55,8 @@ import re
 
 from contentstore.utils import delete_course_and_groups
 from xmodule.modulestore.django import loc_mapper
+from student.roles import CourseCreatorRole
+from student import auth
 
 TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
 TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
@@ -1530,7 +1530,7 @@ class ContentStoreTest(ModuleStoreTestCase):
     def test_create_course_with_course_creator(self):
         """Test new course creation -- use course creator group"""
         with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
-            add_user_to_creator_group(self.user, self.user)
+            auth.add_users(self.user, CourseCreatorRole(), self.user)
             self.assert_created_course()
 
     def assert_course_permission_denied(self):
diff --git a/cms/djangoapps/contentstore/tests/test_permissions.py b/cms/djangoapps/contentstore/tests/test_permissions.py
index 8362fc1369b26a803f8f60f60d4f2a70befcfc6c..1bd63045a71e8b828b7022d8f1a07d601f73a0c4 100644
--- a/cms/djangoapps/contentstore/tests/test_permissions.py
+++ b/cms/djangoapps/contentstore/tests/test_permissions.py
@@ -1,6 +1,8 @@
 """
 Test CRUD for authorization.
 """
+import copy
+
 from django.test.utils import override_settings
 from django.contrib.auth.models import User, Group
 
@@ -9,10 +11,9 @@ 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
+from student.roles import CourseInstructorRole, CourseStaffRole
+from contentstore.views.access import has_course_access
+from student import auth
 
 
 @override_settings(MODULESTORE=TEST_MODULESTORE)
@@ -87,30 +88,35 @@ class TestCourseAccess(ModuleStoreTestCase):
         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.
+        # first check the course creator.has explicit access (don't use has_access as is_staff
+        # will trump the actual test)
         self.assertTrue(
-            self.user.groups.filter(
-                name="{}_{}".format(INSTRUCTOR_ROLE_NAME, self.course_locator.package_id)
-            ).exists(),
+            CourseInstructorRole(self.course_locator).has_user(self.user),
             "Didn't add creator as instructor."
         )
         users = copy.copy(self.users)
+        # doesn't use role.users_with_role b/c it's verifying the roles.py behavior
         user_by_role = {}
         # add the misc users to the course in different groups
-        for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
+        for role in [CourseInstructorRole, CourseStaffRole]:
             user_by_role[role] = []
-            groupnames, _ = authz.get_all_course_role_groupnames(self.course_locator, role)
+            # pylint: disable=protected-access
+            groupnames = role(self.course_locator)._group_names
+            self.assertGreater(len(groupnames), 1, "Only 0 or 1 groupname for {}".format(role.ROLE))
+            # NOTE: this loop breaks the roles.py abstraction by purposely assigning
+            # users to one of each possible groupname in order to test that has_course_access
+            # and remove_user work
             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))
+                self.assertTrue(has_course_access(user, self.course_locator), "{} does not have access".format(user))
+                self.assertTrue(has_course_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 role in [CourseInstructorRole, CourseStaffRole]:
             for user in user_by_role[role]:
                 self.assertContains(response, user.email)
         
@@ -119,9 +125,23 @@ class TestCourseAccess(ModuleStoreTestCase):
         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 role in [CourseInstructorRole, CourseStaffRole]:
+            auth.add_users(
+                self.user,
+                role(copy_course_locator),
+                *role(self.course_locator).users_with_role()
+            )
+        # verify access in copy course and verify that removal from source course w/ the various
+        # groupnames works
+        for role in [CourseInstructorRole, CourseStaffRole]:
             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))
+                # forcefully decache the groups: premise is that any real request will not have
+                # multiple objects repr the same user but this test somehow uses different instance
+                # in above add_users call
+                if hasattr(user, '_groups'):
+                    del user._groups
+
+                self.assertTrue(has_course_access(user, copy_course_locator), "{} no copy access".format(user))
+                self.assertTrue(has_course_access(user, copy_course_location), "{} no copy access".format(user))
+                auth.remove_users(self.user, role(self.course_locator), user)
+                self.assertFalse(has_course_access(user, self.course_locator), "{} remove didn't work".format(user))
diff --git a/cms/djangoapps/contentstore/tests/test_users.py b/cms/djangoapps/contentstore/tests/test_users.py
index ec8b5570a5342568862af6603751f809ec2399c5..27b87fe191930b67ce8d637f93f577a83b235563 100644
--- a/cms/djangoapps/contentstore/tests/test_users.py
+++ b/cms/djangoapps/contentstore/tests/test_users.py
@@ -4,9 +4,10 @@ Tests for contentstore/views/user.py.
 import json
 from .utils import CourseTestCase
 from django.contrib.auth.models import User, Group
-from auth.authz import get_course_groupname_for_role
 from student.models import CourseEnrollment
 from xmodule.modulestore.django import loc_mapper
+from student.roles import CourseStaffRole, CourseInstructorRole
+from student import auth
 
 
 class UsersTestCase(CourseTestCase):
@@ -29,8 +30,6 @@ 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_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')
@@ -39,9 +38,7 @@ class UsersTestCase(CourseTestCase):
         self.assertNotContains(resp, self.ext_user.email)
 
     def test_index_member(self):
-        group, _ = Group.objects.get_or_create(name=self.staff_groupname)
-        self.ext_user.groups.add(group)
-        self.ext_user.save()
+        auth.add_users(self.user, CourseStaffRole(self.course_locator), self.ext_user)
 
         resp = self.client.get(self.index_url, HTTP_ACCEPT='text/html')
         self.assertContains(resp, self.ext_user.email)
@@ -73,10 +70,9 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
         # no content: should not be in any roles
-        self.assertNotIn(self.staff_groupname, groups)
-        self.assertNotIn(self.inst_groupname, groups)
+        self.assertFalse(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
+        self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
         self.assert_not_enrolled()
 
     def test_detail_post_staff(self):
@@ -89,15 +85,12 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertIn(self.staff_groupname, groups)
-        self.assertNotIn(self.inst_groupname, groups)
+        self.assertTrue(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
+        self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
         self.assert_enrolled()
 
     def test_detail_post_staff_other_inst(self):
-        inst_group, _ = Group.objects.get_or_create(name=self.inst_groupname)
-        self.user.groups.add(inst_group)
-        self.user.save()
+        auth.add_users(self.user, CourseInstructorRole(self.course_locator), self.user)
 
         resp = self.client.post(
             self.detail_url,
@@ -108,15 +101,13 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertIn(self.staff_groupname, groups)
-        self.assertNotIn(self.inst_groupname, groups)
+        self.assertTrue(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
+        self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
         self.assert_enrolled()
         # check that other user is unchanged
         user = User.objects.get(email=self.user.email)
-        groups = [g.name for g in user.groups.all()]
-        self.assertNotIn(self.staff_groupname, groups)
-        self.assertIn(self.inst_groupname, groups)
+        self.assertTrue(auth.has_access(user, CourseInstructorRole(self.course_locator)))
+        self.assertFalse(CourseStaffRole(self.course_locator).has_user(user))
 
     def test_detail_post_instructor(self):
         resp = self.client.post(
@@ -128,9 +119,8 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertNotIn(self.staff_groupname, groups)
-        self.assertIn(self.inst_groupname, groups)
+        self.assertTrue(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
+        self.assertFalse(CourseStaffRole(self.course_locator).has_user(ext_user))
         self.assert_enrolled()
 
     def test_detail_post_missing_role(self):
@@ -154,15 +144,12 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertIn(self.staff_groupname, groups)
-        self.assertNotIn(self.inst_groupname, groups)
+        self.assertTrue(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
+        self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
         self.assert_enrolled()
 
     def test_detail_delete_staff(self):
-        group, _ = Group.objects.get_or_create(name=self.staff_groupname)
-        self.ext_user.groups.add(group)
-        self.ext_user.save()
+        auth.add_users(self.user, CourseStaffRole(self.course_locator), self.ext_user)
 
         resp = self.client.delete(
             self.detail_url,
@@ -171,15 +158,10 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertNotIn(self.staff_groupname, groups)
+        self.assertFalse(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
 
     def test_detail_delete_instructor(self):
-        group, _ = Group.objects.get_or_create(name=self.inst_groupname)
-        self.user.groups.add(group)
-        self.ext_user.groups.add(group)
-        self.user.save()
-        self.ext_user.save()
+        auth.add_users(self.user, CourseInstructorRole(self.course_locator), self.ext_user, self.user)
 
         resp = self.client.delete(
             self.detail_url,
@@ -188,13 +170,10 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertNotIn(self.inst_groupname, groups)
+        self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
 
     def test_delete_last_instructor(self):
-        group, _ = Group.objects.get_or_create(name=self.inst_groupname)
-        self.ext_user.groups.add(group)
-        self.ext_user.save()
+        auth.add_users(self.user, CourseInstructorRole(self.course_locator), self.ext_user)
 
         resp = self.client.delete(
             self.detail_url,
@@ -205,13 +184,10 @@ class UsersTestCase(CourseTestCase):
         self.assertIn("error", result)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertIn(self.inst_groupname, groups)
+        self.assertTrue(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
 
     def test_post_last_instructor(self):
-        group, _ = Group.objects.get_or_create(name=self.inst_groupname)
-        self.ext_user.groups.add(group)
-        self.ext_user.save()
+        auth.add_users(self.user, CourseInstructorRole(self.course_locator), self.ext_user)
 
         resp = self.client.post(
             self.detail_url,
@@ -223,12 +199,10 @@ class UsersTestCase(CourseTestCase):
         self.assertIn("error", result)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertIn(self.inst_groupname, groups)
+        self.assertTrue(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
 
     def test_permission_denied_self(self):
-        group, _ = Group.objects.get_or_create(name=self.staff_groupname)
-        self.user.groups.add(group)
+        auth.add_users(self.user, CourseStaffRole(self.course_locator), self.user)
         self.user.is_staff = False
         self.user.save()
 
@@ -244,8 +218,7 @@ class UsersTestCase(CourseTestCase):
         self.assertIn("error", result)
 
     def test_permission_denied_other(self):
-        group, _ = Group.objects.get_or_create(name=self.staff_groupname)
-        self.user.groups.add(group)
+        auth.add_users(self.user, CourseStaffRole(self.course_locator), self.user)
         self.user.is_staff = False
         self.user.save()
 
@@ -259,8 +232,7 @@ class UsersTestCase(CourseTestCase):
         self.assertIn("error", result)
 
     def test_staff_can_delete_self(self):
-        group, _ = Group.objects.get_or_create(name=self.staff_groupname)
-        self.user.groups.add(group)
+        auth.add_users(self.user, CourseStaffRole(self.course_locator), self.user)
         self.user.is_staff = False
         self.user.save()
 
@@ -270,16 +242,12 @@ class UsersTestCase(CourseTestCase):
         self.assertEqual(resp.status_code, 204)
         # reload user from DB
         user = User.objects.get(email=self.user.email)
-        groups = [g.name for g in user.groups.all()]
-        self.assertNotIn(self.staff_groupname, groups)
+        self.assertFalse(auth.has_access(user, CourseStaffRole(self.course_locator)))
 
     def test_staff_cannot_delete_other(self):
-        group, _ = Group.objects.get_or_create(name=self.staff_groupname)
-        self.user.groups.add(group)
+        auth.add_users(self.user, CourseStaffRole(self.course_locator), self.user, self.ext_user)
         self.user.is_staff = False
         self.user.save()
-        self.ext_user.groups.add(group)
-        self.ext_user.save()
 
         resp = self.client.delete(self.detail_url)
         self.assertEqual(resp.status_code, 400)
@@ -287,8 +255,7 @@ class UsersTestCase(CourseTestCase):
         self.assertIn("error", result)
         # reload user from DB
         ext_user = User.objects.get(email=self.ext_user.email)
-        groups = [g.name for g in ext_user.groups.all()]
-        self.assertIn(self.staff_groupname, groups)
+        self.assertTrue(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
 
     def test_user_not_initially_enrolled(self):
         # Verify that ext_user is not enrolled in the new course before being added as a staff member.
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index b7d9cf9e77594e8dc367de9a4587935f5f08fd0f..1aeb447331f2433533069932069f705fabb2244a 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -13,10 +13,10 @@ from xmodule.modulestore import Location
 from xmodule.modulestore.django import modulestore
 from xmodule.modulestore.exceptions import ItemNotFoundError
 from django_comment_common.utils import unseed_permissions_roles
-from auth.authz import _delete_course_group
 from xmodule.modulestore.store_utilities import delete_course
 from xmodule.course_module import CourseDescriptor
 from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES
+from student.roles import CourseInstructorRole, CourseStaffRole
 
 
 log = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ def delete_course_and_groups(course_id, commit=False):
     module_store = modulestore('direct')
     content_store = contentstore()
 
-    org, course_num, run = course_id.split("/")
+    org, course_num, _ = course_id.split("/")
     module_store.ignore_write_events_on_courses.append('{0}/{1}'.format(org, course_num))
 
     loc = CourseDescriptor.id_to_location(course_id)
@@ -47,7 +47,10 @@ def delete_course_and_groups(course_id, commit=False):
         # in the django layer, we need to remove all the user permissions groups associated with this course
         if commit:
             try:
-                _delete_course_group(loc)
+                staff_role = CourseStaffRole(loc)
+                staff_role.remove_users(*staff_role.users_with_role())
+                instructor_role = CourseInstructorRole(loc)
+                instructor_role.remove_users(*instructor_role.users_with_role())
             except Exception as err:
                 log.error("Error in deleting course groups for {0}: {1}".format(loc, err))
 
diff --git a/cms/djangoapps/contentstore/views/access.py b/cms/djangoapps/contentstore/views/access.py
index df4fbed623657c28fcc7b30c7c3d1e8065bf9e73..215c8becf2ecbfa3c459ae53899347120f990d3d 100644
--- a/cms/djangoapps/contentstore/views/access.py
+++ b/cms/djangoapps/contentstore/views/access.py
@@ -1,10 +1,10 @@
-from auth.authz import STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME
-from auth.authz import is_user_in_course_group_role
 from ..utils import get_course_location_for_item
 from xmodule.modulestore.locator import CourseLocator
+from student.roles import CourseStaffRole, GlobalStaff
+from student import auth
 
 
-def has_access(user, location, role=STAFF_ROLE_NAME):
+def has_course_access(user, location, role=CourseStaffRole):
     """
     Return True if user allowed to access this piece of data
     Note that the CMS permissions model is with respect to courses
@@ -14,14 +14,9 @@ def has_access(user, location, role=STAFF_ROLE_NAME):
     will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our
     queries here as INSTRUCTOR has all the rights that STAFF do
     """
+    if GlobalStaff().has_user(user):
+        return True
     if not isinstance(location, CourseLocator):
+        # this can be expensive if location is not category=='course'
         location = get_course_location_for_item(location)
-    _has_access = is_user_in_course_group_role(user, location, role)
-    # if we're not in STAFF, perhaps we're in INSTRUCTOR groups
-    if not _has_access and role == STAFF_ROLE_NAME:
-        _has_access = is_user_in_course_group_role(
-                user,
-                location,
-                INSTRUCTOR_ROLE_NAME
-        )
-    return _has_access
+    return auth.has_access(user, role(location))
diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py
index 255717c73e45a59ab8bba3f4b7a7b3ea9523651e..cd59b811d7641629ff9625a8429fe6dc00f26fcc 100644
--- a/cms/djangoapps/contentstore/views/assets.py
+++ b/cms/djangoapps/contentstore/views/assets.py
@@ -21,13 +21,13 @@ from xmodule.modulestore import InvalidLocationError
 from xmodule.exceptions import NotFoundError
 from django.core.exceptions import PermissionDenied
 from xmodule.modulestore.django import loc_mapper
-from .access import has_access
 from xmodule.modulestore.locator import BlockUsageLocator
 
 from util.json_request import JsonResponse
 from django.http import HttpResponseNotFound
 from django.utils.translation import ugettext as _
 from pymongo import ASCENDING, DESCENDING
+from .access import has_course_access
 
 __all__ = ['assets_handler']
 
@@ -56,7 +56,7 @@ def assets_handler(request, tag=None, package_id=None, branch=None, version_guid
         json: delete an asset
     """
     location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-    if not has_access(request.user, location):
+    if not has_course_access(request.user, location):
         raise PermissionDenied()
 
     response_format = request.REQUEST.get('format', 'html')
diff --git a/cms/djangoapps/contentstore/views/checklist.py b/cms/djangoapps/contentstore/views/checklist.py
index 9fddf774c658cb79443cd9198779f4b890c4418e..123ff08f9a0a0230e4659ec6763569f0fc026786 100644
--- a/cms/djangoapps/contentstore/views/checklist.py
+++ b/cms/djangoapps/contentstore/views/checklist.py
@@ -15,7 +15,7 @@ from xmodule.modulestore.inheritance import own_metadata
 
 
 from ..utils import get_modulestore
-from .access import has_access
+from .access import has_course_access
 from xmodule.course_module import CourseDescriptor
 from xmodule.modulestore.locator import BlockUsageLocator
 
@@ -37,7 +37,7 @@ def checklists_handler(request, tag=None, package_id=None, branch=None, version_
         json: updates the checked state for items within a particular checklist. checklist_index is required.
     """
     location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-    if not has_access(request.user, location):
+    if not has_course_access(request.user, location):
         raise PermissionDenied()
 
     old_location = loc_mapper().translate_locator_to_location(location)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 6dd5b6c2933684bd54de866fb85a5c3e446ba7e9..38b05c094c0bd6ee7372b7b4da8d0a4b5bb0cd62 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -29,7 +29,7 @@ from contentstore.utils import get_lms_link_for_item, compute_unit_state, UnitSt
 
 from models.settings.course_grading import CourseGradingModel
 
-from .access import has_access
+from .access import has_course_access
 
 __all__ = ['OPEN_ENDED_COMPONENT_TYPES',
            'ADVANCED_COMPONENT_POLICY_KEY',
@@ -311,7 +311,7 @@ def _get_item_in_course(request, locator):
 
     Verifies that the caller has permission to access this item.
     """
-    if not has_access(request.user, locator):
+    if not has_course_access(request.user, locator):
         raise PermissionDenied()
 
     old_location = loc_mapper().translate_locator_to_location(locator)
diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py
index d0848595367d9ed422717186d47421015f92f787..5c0eea910c6259eb86b2a158e29ab679c882fd95 100644
--- a/cms/djangoapps/contentstore/views/course.py
+++ b/cms/djangoapps/contentstore/views/course.py
@@ -35,10 +35,9 @@ from models.settings.course_details import CourseDetails, CourseSettingsEncoder
 
 from models.settings.course_grading import CourseGradingModel
 from models.settings.course_metadata import CourseMetadata
-from auth.authz import create_all_course_groups, is_user_in_creator_group
 from util.json_request import expect_json
 
-from .access import has_access
+from .access import has_course_access
 from .tabs import initialize_course_tabs
 from .component import (
     OPEN_ENDED_COMPONENT_TYPES, NOTE_COMPONENT_TYPES,
@@ -52,6 +51,8 @@ from xmodule.html_module import AboutDescriptor
 from xmodule.modulestore.locator import BlockUsageLocator
 from course_creators.views import get_course_creator_status, add_user_with_status_unrequested
 from contentstore import utils
+from student.roles import CourseInstructorRole, CourseStaffRole, CourseCreatorRole
+from student import auth
 
 __all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler',
            'settings_handler',
@@ -66,7 +67,7 @@ def _get_locator_and_course(package_id, branch, version_guid, block_id, user, de
     for the view functions in this file.
     """
     locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block_id)
-    if not has_access(user, locator):
+    if not has_course_access(user, locator):
         raise PermissionDenied()
     course_location = loc_mapper().translate_locator_to_location(locator)
     course_module = modulestore().get_item(course_location, depth=depth)
@@ -102,7 +103,7 @@ def course_handler(request, tag=None, package_id=None, branch=None, version_guid
             raise NotImplementedError('coming soon')
         elif request.method == 'POST':  # not sure if this is only post. If one will have ids, it goes after access
             return create_new_course(request)
-        elif not has_access(
+        elif not has_course_access(
             request.user,
             BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
         ):
@@ -136,7 +137,7 @@ def course_listing(request):
         """
         Get courses to which this user has access
         """
-        return (has_access(request.user, course.location)
+        return (has_course_access(request.user, course.location)
                 # pylint: disable=fixme
                 # TODO remove this condition when templates purged from db
                 and course.location.course != 'templates'
@@ -207,7 +208,7 @@ def create_new_course(request):
 
     Returns the URL for the course overview page.
     """
-    if not is_user_in_creator_group(request.user):
+    if not auth.has_access(request.user, CourseCreatorRole()):
         raise PermissionDenied()
 
     org = request.json.get('org')
@@ -297,7 +298,10 @@ def create_new_course(request):
     initialize_course_tabs(new_course)
 
     new_location = loc_mapper().translate_location(new_course.location.course_id, new_course.location, False, True)
-    create_all_course_groups(request.user, new_location)
+    # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course
+    # however, we can assume that b/c this user had authority to create the course, the user can add themselves
+    CourseInstructorRole(new_location).add_users(request.user)
+    auth.add_users(request.user, CourseStaffRole(new_location), request.user)
 
     # seed the forums
     seed_permissions_roles(new_course.location.course_id)
@@ -370,7 +374,7 @@ def course_info_update_handler(request, tag=None, package_id=None, branch=None,
         provided_id = None
 
     # check that logged in user has permissions to this item (GET shouldn't require this level?)
-    if not has_access(request.user, updates_location):
+    if not has_course_access(request.user, updates_location):
         raise PermissionDenied()
 
     if request.method == 'GET':
diff --git a/cms/djangoapps/contentstore/views/import_export.py b/cms/djangoapps/contentstore/views/import_export.py
index f532734dafed9425e00edda24d9e4eed57dd77dd..1bb8fea4a6d50664683c4c9dbc534cba12b580e3 100644
--- a/cms/djangoapps/contentstore/views/import_export.py
+++ b/cms/djangoapps/contentstore/views/import_export.py
@@ -22,7 +22,6 @@ from django.views.decorators.http import require_http_methods, require_GET
 from django.utils.translation import ugettext as _
 
 from edxmako.shortcuts import render_to_response
-from auth.authz import create_all_course_groups
 
 from xmodule.modulestore.xml_importer import import_from_xml
 from xmodule.contentstore.django import contentstore
@@ -31,10 +30,12 @@ from xmodule.modulestore.django import modulestore, loc_mapper
 from xmodule.exceptions import SerializationError
 
 from xmodule.modulestore.locator import BlockUsageLocator
-from .access import has_access
+from .access import has_course_access
 
 from util.json_request import JsonResponse
 from extract_tar import safetar_extractall
+from student.roles import CourseInstructorRole, CourseStaffRole
+from student import auth
 
 
 __all__ = ['import_handler', 'import_status_handler', 'export_handler']
@@ -61,7 +62,7 @@ def import_handler(request, tag=None, package_id=None, branch=None, version_guid
         json: import a course via the .tar.gz file specified in request.FILES
     """
     location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-    if not has_access(request.user, location):
+    if not has_course_access(request.user, location):
         raise PermissionDenied()
 
     old_location = loc_mapper().translate_locator_to_location(location)
@@ -225,13 +226,15 @@ def import_handler(request, tag=None, package_id=None, branch=None, version_guid
                         draft_store=modulestore()
                     )
 
-                    logging.debug('new course at {0}'.format(course_items[0].location))
+                    new_location = course_items[0].location
+                    logging.debug('new course at {0}'.format(new_location))
 
                     session_status[key] = 3
                     request.session.modified = True
 
-                    create_all_course_groups(request.user, course_items[0].location)
-                    logging.debug('created all course groups at {0}'.format(course_items[0].location))
+                    auth.add_users(request.user, CourseInstructorRole(new_location), request.user)
+                    auth.add_users(request.user, CourseStaffRole(new_location), request.user)
+                    logging.debug('created all course groups at {0}'.format(new_location))
 
                 # Send errors to client with stage at which error occured.
                 except Exception as exception:   # pylint: disable=W0703
@@ -272,7 +275,7 @@ def import_status_handler(request, tag=None, package_id=None, branch=None, versi
 
     """
     location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-    if not has_access(request.user, location):
+    if not has_course_access(request.user, location):
         raise PermissionDenied()
 
     try:
@@ -303,7 +306,7 @@ def export_handler(request, tag=None, package_id=None, branch=None, version_guid
     which describes the error.
     """
     location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-    if not has_access(request.user, location):
+    if not has_course_access(request.user, location):
         raise PermissionDenied()
 
     old_location = loc_mapper().translate_locator_to_location(location)
diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py
index d6a8ff3abb4ba67efefa7d85607c70572548acca..9c1fcbad6002ccf10767478fad0ee630ba4339be 100644
--- a/cms/djangoapps/contentstore/views/item.py
+++ b/cms/djangoapps/contentstore/views/item.py
@@ -28,7 +28,7 @@ from ..transcripts_utils import manage_video_subtitles_save
 
 from ..utils import get_modulestore
 
-from .access import has_access
+from .access import has_course_access
 from .helpers import _xmodule_recurse
 from preview import handler_prefix, get_preview_html
 from edxmako.shortcuts import render_to_response, render_to_string
@@ -81,7 +81,7 @@ def xblock_handler(request, tag=None, package_id=None, branch=None, version_guid
     """
     if package_id is not None:
         locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-        if not has_access(request.user, locator):
+        if not has_course_access(request.user, locator):
             raise PermissionDenied()
         old_location = loc_mapper().translate_locator_to_location(locator)
 
@@ -250,7 +250,7 @@ def _create_item(request):
 
     display_name = request.json.get('display_name')
 
-    if not has_access(request.user, parent_location):
+    if not has_course_access(request.user, parent_location):
         raise PermissionDenied()
 
     parent = get_modulestore(category).get_item(parent_location)
@@ -335,7 +335,7 @@ def orphan_handler(request, tag=None, package_id=None, branch=None, version_guid
     # DHM: when split becomes back-end, move or conditionalize this conversion
     old_location = loc_mapper().translate_locator_to_location(location)
     if request.method == 'GET':
-        if has_access(request.user, old_location):
+        if has_course_access(request.user, old_location):
             return JsonResponse(modulestore().get_orphans(old_location, DETACHED_CATEGORIES, 'draft'))
         else:
             raise PermissionDenied()
diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py
index e4f9985b3a8b90937d5e4dd1f73e631273695b6b..d917fbbc99435784f9d7516b749c187ab707583d 100644
--- a/cms/djangoapps/contentstore/views/tabs.py
+++ b/cms/djangoapps/contentstore/views/tabs.py
@@ -1,7 +1,7 @@
 """
 Views related to course tabs
 """
-from access import has_access
+from access import has_course_access
 from util.json_request import expect_json, JsonResponse
 
 from django.http import HttpResponseNotFound
@@ -63,7 +63,7 @@ def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=N
     Instead use the general xblock URL (see item.xblock_handler).
     """
     locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-    if not has_access(request.user, locator):
+    if not has_course_access(request.user, locator):
         raise PermissionDenied()
 
     old_location = loc_mapper().translate_locator_to_location(locator)
diff --git a/cms/djangoapps/contentstore/views/transcripts_ajax.py b/cms/djangoapps/contentstore/views/transcripts_ajax.py
index 9d5cdf8e80f7b90ab40b769b0676401aa715735d..28a674cb89b7317b29d3ac5073e6fe957ebb2e6d 100644
--- a/cms/djangoapps/contentstore/views/transcripts_ajax.py
+++ b/cms/djangoapps/contentstore/views/transcripts_ajax.py
@@ -37,7 +37,7 @@ from ..transcripts_utils import (
     TranscriptsRequestValidationException
 )
 
-from .access import has_access
+from .access import has_course_access
 
 __all__ = [
     'upload_transcripts',
@@ -546,11 +546,11 @@ def _get_item(request, data):
     locator = BlockUsageLocator(data.get('locator'))
     old_location = loc_mapper().translate_locator_to_location(locator)
 
-    # This is placed before has_access() to validate the location,
-    # because has_access() raises InvalidLocationError if location is invalid.
+    # This is placed before has_course_access() to validate the location,
+    # because has_course_access() raises InvalidLocationError if location is invalid.
     item = modulestore().get_item(old_location)
 
-    if not has_access(request.user, locator):
+    if not has_course_access(request.user, locator):
         raise PermissionDenied()
 
     return item
diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py
index 02327bb82277dd807f45eb319d00a6e426818fef..468263d8d67219458a211f974c968d9f655be756 100644
--- a/cms/djangoapps/contentstore/views/user.py
+++ b/cms/djangoapps/contentstore/views/user.py
@@ -1,6 +1,5 @@
-import json
 from django.core.exceptions import PermissionDenied
-from django.contrib.auth.models import User, Group
+from django.contrib.auth.models import User
 from django.contrib.auth.decorators import login_required
 from django.views.decorators.http import require_http_methods
 from django.utils.translation import ugettext as _
@@ -10,17 +9,15 @@ from edxmako.shortcuts import render_to_response
 
 from xmodule.modulestore.django import modulestore, loc_mapper
 from util.json_request import JsonResponse, expect_json
-from auth.authz import (
-    STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME, get_course_groupname_for_role,
-    get_course_role_users
-)
+from student.roles import CourseRole, CourseInstructorRole, CourseStaffRole, GlobalStaff
 from course_creators.views import user_requested_access
 
-from .access import has_access
+from .access import has_course_access
 
 from student.models import CourseEnrollment
 from xmodule.modulestore.locator import BlockUsageLocator
 from django.http import HttpResponseNotFound
+from student import auth
 
 
 __all__ = ['request_course_creator', 'course_team_handler']
@@ -53,7 +50,7 @@ def course_team_handler(request, tag=None, package_id=None, branch=None, version
         json: remove a particular course team member from the course team (email is required).
     """
     location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
-    if not has_access(request.user, location):
+    if not has_course_access(request.user, location):
         raise PermissionDenied()
 
     if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
@@ -71,19 +68,19 @@ def _manage_users(request, locator):
     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, locator):
+    if not has_course_access(request.user, locator):
         raise PermissionDenied()
 
     course_module = modulestore().get_item(old_location)
-    instructors = get_course_role_users(locator, INSTRUCTOR_ROLE_NAME)
+    instructors = CourseInstructorRole(locator).users_with_role()
     # 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)
+    staff = set(CourseStaffRole(locator).users_with_role()).union(instructors)
 
     return render_to_response('manage_users.html', {
         'context_course': course_module,
         'staff': staff,
         'instructors': instructors,
-        'allow_actions': has_access(request.user, locator, role=INSTRUCTOR_ROLE_NAME),
+        'allow_actions': has_course_access(request.user, locator, role=CourseInstructorRole),
     })
 
 
@@ -93,10 +90,10 @@ 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, locator, role=INSTRUCTOR_ROLE_NAME):
+    if has_course_access(request.user, locator, role=CourseInstructorRole):
         # instructors have full permissions
         pass
-    elif has_access(request.user, locator, role=STAFF_ROLE_NAME) and email == request.user.email:
+    elif has_course_access(request.user, locator, role=CourseStaffRole) and email == request.user.email:
         # staff can only affect themselves
         pass
     else:
@@ -107,15 +104,13 @@ def _course_team_user(request, locator, email):
 
     try:
         user = User.objects.get(email=email)
-    except:
+    except Exception:
         msg = {
             "error": _("Could not find user by email address '{email}'.").format(email=email),
         }
         return JsonResponse(msg, 404)
 
-    # role hierarchy: "instructor" has more permissions than "staff" (in a course)
-    roles = ["instructor", "staff"]
-
+    # role hierarchy: globalstaff > "instructor" > "staff" (in a course)
     if request.method == "GET":
         # just return info about the user
         msg = {
@@ -123,12 +118,10 @@ def _course_team_user(request, locator, email):
             "active": user.is_active,
             "role": None,
         }
-        # 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(locator, role)
-            if role_groupname in groupnames:
-                msg["role"] = role
+        # what's the highest role that this user has? (How should this report global staff?)
+        for role in [CourseInstructorRole(locator), CourseStaffRole(locator)]:
+            if role.has_user(user):
+                msg["role"] = role.ROLE
                 break
         return JsonResponse(msg)
 
@@ -139,29 +132,20 @@ def _course_team_user(request, locator, email):
         }
         return JsonResponse(msg, 400)
 
-    # make sure that the role groups exist
-    groups = {}
-    for role in roles:
-        groupname = get_course_groupname_for_role(locator, role)
-        group, __ = Group.objects.get_or_create(name=groupname)
-        groups[role] = group
-
     if request.method == "DELETE":
         # remove all roles in this course from this user: but fail if the user
         # is the last instructor in the course team
-        instructors = set(groups["instructor"].user_set.all())
-        staff = set(groups["staff"].user_set.all())
-        if user in instructors and len(instructors) == 1:
-            msg = {
-                "error": _("You may not remove the last instructor from a course")
-            }
-            return JsonResponse(msg, 400)
+        instructors = CourseInstructorRole(locator)
+        if instructors.has_user(user):
+            if instructors.users_with_role().count() == 1:
+                msg = {
+                    "error": _("You may not remove the last instructor from a course")
+                }
+                return JsonResponse(msg, 400)
+            else:
+                instructors.remove_users(request.user, user)
 
-        if user in instructors:
-            user.groups.remove(groups["instructor"])
-        if user in staff:
-            user.groups.remove(groups["staff"])
-        user.save()
+        auth.remove_users(request.user, CourseStaffRole(locator), user)
         return JsonResponse()
 
     # all other operations require the requesting user to specify a role
@@ -171,28 +155,30 @@ def _course_team_user(request, locator, email):
 
     old_location = loc_mapper().translate_locator_to_location(locator)
     if role == "instructor":
-        if not has_access(request.user, locator, role=INSTRUCTOR_ROLE_NAME):
+        if not has_course_access(request.user, locator, role=CourseInstructorRole):
             msg = {
                 "error": _("Only instructors may create other instructors")
             }
             return JsonResponse(msg, 400)
-        user.groups.add(groups["instructor"])
-        user.save()
+        auth.add_users(request.user, CourseInstructorRole(locator), user)
         # auto-enroll the course creator in the course so that "View Live" will work.
         CourseEnrollment.enroll(user, old_location.course_id)
     elif role == "staff":
+        # add to staff regardless (can't do after removing from instructors as will no longer
+        # be allowed)
+        auth.add_users(request.user, CourseStaffRole(locator), user)
         # if we're trying to downgrade a user from "instructor" to "staff",
         # make sure we have at least one other instructor in the course team.
-        instructors = set(groups["instructor"].user_set.all())
-        if user in instructors:
-            if len(instructors) == 1:
+        instructors = CourseInstructorRole(locator)
+        if instructors.has_user(user):
+            if instructors.users_with_role().count() == 1:
                 msg = {
                     "error": _("You may not remove the last instructor from a course")
                 }
                 return JsonResponse(msg, 400)
-            user.groups.remove(groups["instructor"])
-        user.groups.add(groups["staff"])
-        user.save()
+            else:
+                instructors.remove_users(request.user, user)
+
         # auto-enroll the course creator in the course so that "View Live" will work.
         CourseEnrollment.enroll(user, old_location.course_id)
 
diff --git a/cms/djangoapps/course_creators/tests/test_admin.py b/cms/djangoapps/course_creators/tests/test_admin.py
index 4d28f263997effafce6e9cc58b75eef555585352..ec27053444c159b153c78e5b3c78a8177e1fbbbe 100644
--- a/cms/djangoapps/course_creators/tests/test_admin.py
+++ b/cms/djangoapps/course_creators/tests/test_admin.py
@@ -10,8 +10,9 @@ import mock
 
 from course_creators.admin import CourseCreatorAdmin
 from course_creators.models import CourseCreator
-from auth.authz import is_user_in_creator_group
 from django.core import mail
+from student.roles import CourseCreatorRole
+from student import auth
 
 
 def mock_render_to_string(template_name, context):
@@ -54,7 +55,7 @@ class CourseCreatorAdminTest(TestCase):
         def change_state_and_verify_email(state, is_creator):
             """ Changes user state, verifies creator status, and verifies e-mail is sent based on transition """
             self._change_state(state)
-            self.assertEqual(is_creator, is_user_in_creator_group(self.user))
+            self.assertEqual(is_creator, auth.has_access(self.user, CourseCreatorRole()))
 
             context = {'studio_request_email': self.studio_request_email}
             if state == CourseCreator.GRANTED:
@@ -72,7 +73,7 @@ class CourseCreatorAdminTest(TestCase):
         with mock.patch.dict('django.conf.settings.FEATURES', self.enable_creator_group_patch):
 
             # User is initially unrequested.
-            self.assertFalse(is_user_in_creator_group(self.user))
+            self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))
 
             change_state_and_verify_email(CourseCreator.GRANTED, True)
 
diff --git a/cms/djangoapps/course_creators/tests/test_views.py b/cms/djangoapps/course_creators/tests/test_views.py
index dbd92365b7deb8e88ab39245b98b72ea5c6cbac3..c88d0df72ac1cebd0327afce2d8e8dce66152fee 100644
--- a/cms/djangoapps/course_creators/tests/test_views.py
+++ b/cms/djangoapps/course_creators/tests/test_views.py
@@ -8,9 +8,9 @@ from django.core.exceptions import PermissionDenied
 
 from course_creators.views import add_user_with_status_unrequested, add_user_with_status_granted
 from course_creators.views import get_course_creator_status, update_course_creator_group, user_requested_access
-from course_creators.models import CourseCreator
-from auth.authz import is_user_in_creator_group
 import mock
+from student.roles import CourseCreatorRole
+from student import auth
 
 
 class CourseCreatorView(TestCase):
@@ -48,7 +48,7 @@ class CourseCreatorView(TestCase):
     def test_add_granted(self):
         with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
             # Calling add_user_with_status_granted impacts is_user_in_course_group_role.
-            self.assertFalse(is_user_in_creator_group(self.user))
+            self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))
 
             add_user_with_status_granted(self.admin, self.user)
             self.assertEqual('granted', get_course_creator_status(self.user))
@@ -57,15 +57,15 @@ class CourseCreatorView(TestCase):
             add_user_with_status_unrequested(self.user)
             self.assertEqual('granted', get_course_creator_status(self.user))
 
-            self.assertTrue(is_user_in_creator_group(self.user))
+            self.assertTrue(auth.has_access(self.user, CourseCreatorRole()))
 
     def test_update_creator_group(self):
         with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
-            self.assertFalse(is_user_in_creator_group(self.user))
+            self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))
             update_course_creator_group(self.admin, self.user, True)
-            self.assertTrue(is_user_in_creator_group(self.user))
+            self.assertTrue(auth.has_access(self.user, CourseCreatorRole()))
             update_course_creator_group(self.admin, self.user, False)
-            self.assertFalse(is_user_in_creator_group(self.user))
+            self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))
 
     def test_user_requested_access(self):
         add_user_with_status_unrequested(self.user)
diff --git a/cms/djangoapps/course_creators/views.py b/cms/djangoapps/course_creators/views.py
index e9b38ed16951d9a3d7249ef19b42d1e6b7b4ab07..c0c1b4a4528cd47dd888033dc71060976d8e131f 100644
--- a/cms/djangoapps/course_creators/views.py
+++ b/cms/djangoapps/course_creators/views.py
@@ -2,8 +2,8 @@
 Methods for interacting programmatically with the user creator table.
 """
 from course_creators.models import CourseCreator
-
-from auth.authz import add_user_to_creator_group, remove_user_from_creator_group
+from student.roles import CourseCreatorRole
+from student import auth
 
 
 def add_user_with_status_unrequested(user):
@@ -43,9 +43,9 @@ def update_course_creator_group(caller, user, add):
     Caller must have staff permissions.
     """
     if add:
-        add_user_to_creator_group(caller, user)
+        auth.add_users(caller, CourseCreatorRole(), user)
     else:
-        remove_user_from_creator_group(caller, user)
+        auth.remove_users(caller, CourseCreatorRole(), user)
 
 
 def get_course_creator_status(user):
diff --git a/cms/envs/common.py b/cms/envs/common.py
index eb7d600c209ddc7d0743eb69622d5b90ebf76638..999b344332648fd595645876ffc8fda0c5a21c7e 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -203,7 +203,7 @@ TEMPLATE_DEBUG = False
 
 # Site info
 SITE_ID = 1
-SITE_NAME = "localhost:8000"
+SITE_NAME = "localhost:8001"
 HTTPS = 'on'
 ROOT_URLCONF = 'cms.urls'
 IGNORABLE_404_ENDS = ('favicon.ico')
diff --git a/cms/templates/manage_users.html b/cms/templates/manage_users.html
index 6e7e8c80125ce6822f8aba0e68b4300f7cb2d3df..5ad8aaea26c8a1b1c42d1537524f7e5e0326c6f7 100644
--- a/cms/templates/manage_users.html
+++ b/cms/templates/manage_users.html
@@ -1,7 +1,8 @@
+<%! import json %>
 <%! from django.utils.translation import ugettext as _ %>
 <%! from django.core.urlresolvers import reverse %>
-<%! from auth.authz import is_user_in_course_group_role %>
-<%! import json %>
+<%! from student.roles import CourseInstructorRole %>
+<%! from student.auth import has_access %>
 <%! from xmodule.modulestore.django import loc_mapper %>
 <%inherit file="base.html" />
 <%block name="title">${_("Course Team Settings")}</%block>
@@ -65,7 +66,7 @@
 
         <li class="user-item" data-email="${user.email}" data-url="${new_location.url_reverse('course_team/', user.email) }">
 
-        <% is_instuctor = is_user_in_course_group_role(user, context_course.location, 'instructor', check_staff=False) %>
+        <% is_instuctor = has_access(user, CourseInstructorRole(context_course.location)) %>
           % if is_instuctor:
           <span class="wrapper-ui-badge">
             <span class="flag flag-role flag-role-admin is-hanging">
@@ -120,7 +121,7 @@
         % endfor
       </ol>
 
-      <% user_is_instuctor = is_user_in_course_group_role(request.user, context_course.location, 'instructor', check_staff=False) %>
+      <% user_is_instuctor = has_access(request.user, CourseInstructorRole(context_course.location)) %>
       % if user_is_instuctor and len(staff) == 1:
       <div class="notice notice-incontext notice-create has-actions">
         <div class="msg">
diff --git a/common/djangoapps/student/auth.py b/common/djangoapps/student/auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..97583f69dc300af65906cfd4565549cecf78dccf
--- /dev/null
+++ b/common/djangoapps/student/auth.py
@@ -0,0 +1,86 @@
+"""
+The application interface to roles which checks whether any user trying to change
+authorization has authorization to do so, which infers authorization via role hierarchy
+(GlobalStaff is superset of auths of course instructor, ...), which consults the config
+to decide whether to check course creator role, and other such functions.
+"""
+from django.core.exceptions import PermissionDenied
+from django.conf import settings
+
+from student.roles import GlobalStaff, CourseCreatorRole, CourseStaffRole, CourseInstructorRole, CourseRole, \
+    CourseBetaTesterRole
+
+
+def has_access(user, role):
+    """
+    Check whether this user has access to this role (either direct or implied)
+    :param user:
+    :param role: an AccessRole
+    """
+    if not user.is_active:
+        return False
+    # do cheapest check first even tho it's not the direct one
+    if GlobalStaff().has_user(user):
+        return True
+    # CourseCreator is odd b/c it can be disabled via config
+    if isinstance(role, CourseCreatorRole):
+        # completely shut down course creation setting
+        if settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
+            return False
+        # wide open course creation setting
+        if not settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
+            return True
+
+    if role.has_user(user):
+        return True
+    # if not, then check inferred permissions
+    if (isinstance(role, (CourseStaffRole, CourseBetaTesterRole)) and
+            CourseInstructorRole(role.location).has_user(user)):
+        return True
+    return False
+
+
+def add_users(caller, role, *users):
+    """
+    The caller requests adding the given users to the role. Checks that the caller
+    has sufficient authority.
+
+    :param caller: a user
+    :param role: an AccessRole
+    """
+    _check_caller_authority(caller, role)
+    role.add_users(*users)
+
+
+def remove_users(caller, role, *users):
+    """
+    The caller requests removing the given users from the role. Checks that the caller
+    has sufficient authority.
+
+    :param caller: a user
+    :param role: an AccessRole
+    """
+    # can always remove self (at this layer)
+    if not(len(users) == 1 and caller == users[0]):
+        _check_caller_authority(caller, role)
+    role.remove_users(*users)
+
+
+def _check_caller_authority(caller, role):
+    """
+    Internal function to check whether the caller has authority to manipulate this role
+    :param caller: a user
+    :param role: an AccessRole
+    """
+    if not (caller.is_authenticated and caller.is_active):
+        raise PermissionDenied
+    # superuser
+    if GlobalStaff().has_user(caller):
+        return
+
+    if isinstance(role, (GlobalStaff, CourseCreatorRole)):
+        raise PermissionDenied
+    elif isinstance(role, CourseRole):  # instructors can change the roles w/in their course
+        if not has_access(caller, CourseInstructorRole(role.location)):
+            raise PermissionDenied
+
diff --git a/common/djangoapps/student/roles.py b/common/djangoapps/student/roles.py
index d99a5ecb9136f3a5fd685bf6ea13e5cf00a94211..9da258e19beacc7d801afb1f61020061d8585ef8 100644
--- a/common/djangoapps/student/roles.py
+++ b/common/djangoapps/student/roles.py
@@ -64,11 +64,13 @@ class GlobalStaff(AccessRole):
 
     def add_users(self, *users):
         for user in users:
-            user.is_staff = True
-            user.save()
+            if (user.is_authenticated and user.is_active):
+                user.is_staff = True
+                user.save()
 
     def remove_users(self, *users):
         for user in users:
+            # don't check is_authenticated nor is_active on purpose
             user.is_staff = False
             user.save()
 
@@ -96,10 +98,10 @@ class GroupBasedRole(AccessRole):
         """
         Return whether the supplied django user has access to this role.
         """
-        # pylint: disable=protected-access
-        if not user.is_authenticated():
+        if not (user.is_authenticated and user.is_active):
             return False
 
+        # pylint: disable=protected-access
         if not hasattr(user, '_groups'):
             user._groups = set(name.lower() for name in user.groups.values_list('name', flat=True))
 
@@ -109,8 +111,12 @@ class GroupBasedRole(AccessRole):
         """
         Add the supplied django users to this role.
         """
+        # silently ignores anonymous and inactive users so that any that are
+        # legit get updated.
+        users = [user for user in users if user.is_authenticated and user.is_active]
         group, _ = Group.objects.get_or_create(name=self._group_names[0])
         group.user_set.add(*users)
+        # remove cache
         for user in users:
             if hasattr(user, '_groups'):
                 del user._groups
@@ -119,8 +125,10 @@ class GroupBasedRole(AccessRole):
         """
         Remove the supplied django users from this role.
         """
-        group, _ = Group.objects.get_or_create(name=self._group_names[0])
-        group.user_set.remove(*users)
+        groups = Group.objects.filter(name__in=self._group_names)
+        for group in groups:
+            group.user_set.remove(*users)
+        # remove cache
         for user in users:
             if hasattr(user, '_groups'):
                 del user._groups
@@ -144,31 +152,33 @@ class CourseRole(GroupBasedRole):
         """
         # TODO: figure out how to make the group name generation lazy so it doesn't force the
         # loc mapping?
-        location = Locator.to_locator_or_location(location)
+        self.location = Locator.to_locator_or_location(location)
+        self.role = role
         # direct copy from auth.authz.get_all_course_role_groupnames will refactor to one impl asap
         groupnames = []
 
         # pylint: disable=no-member
-        if isinstance(location, Location):
+        if isinstance(self.location, Location):
             try:
-                groupnames.append('{0}_{1}'.format(role, location.course_id))
+                groupnames.append('{0}_{1}'.format(role, self.location.course_id))
+                course_context = self.location.course_id  # course_id is valid for translation
             except InvalidLocationError:  # will occur on old locations where location is not of category course
                 if course_context is None:
                     raise CourseContextRequired()
                 else:
                     groupnames.append('{0}_{1}'.format(role, course_context))
             try:
-                locator = loc_mapper().translate_location(location.course_id, location, False, False)
+                locator = loc_mapper().translate_location(course_context, self.location, False, False)
                 groupnames.append('{0}_{1}'.format(role, locator.package_id))
             except (InvalidLocationError, ItemNotFoundError):
                 # if it's never been mapped, the auth won't be via the Locator syntax
                 pass
             # least preferred legacy role_course format
-            groupnames.append('{0}_{1}'.format(role, location.course))
-        elif isinstance(location, CourseLocator):
-            groupnames.append('{0}_{1}'.format(role, location.package_id))
+            groupnames.append('{0}_{1}'.format(role, self.location.course))
+        elif isinstance(self.location, CourseLocator):
+            groupnames.append('{0}_{1}'.format(role, self.location.package_id))
             # handle old Location syntax
-            old_location = loc_mapper().translate_locator_to_location(location, get_course=True)
+            old_location = loc_mapper().translate_locator_to_location(self.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))
@@ -191,20 +201,23 @@ class OrgRole(GroupBasedRole):
 
 class CourseStaffRole(CourseRole):
     """A Staff member of a course"""
+    ROLE = 'staff'
     def __init__(self, *args, **kwargs):
-        super(CourseStaffRole, self).__init__('staff', *args, **kwargs)
+        super(CourseStaffRole, self).__init__(self.ROLE, *args, **kwargs)
 
 
 class CourseInstructorRole(CourseRole):
     """A course Instructor"""
+    ROLE = 'instructor'
     def __init__(self, *args, **kwargs):
-        super(CourseInstructorRole, self).__init__('instructor', *args, **kwargs)
+        super(CourseInstructorRole, self).__init__(self.ROLE, *args, **kwargs)
 
 
 class CourseBetaTesterRole(CourseRole):
     """A course Beta Tester"""
+    ROLE = 'beta_testers'
     def __init__(self, *args, **kwargs):
-        super(CourseBetaTesterRole, self).__init__('beta_testers', *args, **kwargs)
+        super(CourseBetaTesterRole, self).__init__(self.ROLE, *args, **kwargs)
 
 
 class OrgStaffRole(OrgRole):
@@ -217,3 +230,13 @@ class OrgInstructorRole(OrgRole):
     """An organization instructor"""
     def __init__(self, *args, **kwargs):
         super(OrgInstructorRole, self).__init__('instructor', *args, **kwargs)
+
+
+class CourseCreatorRole(GroupBasedRole):
+    """
+    This is the group of people who have permission to create new courses (we may want to eventually
+    make this an org based role).
+    """
+    ROLE = "course_creator_group"
+    def __init__(self, *args, **kwargs):
+        super(CourseCreatorRole, self).__init__(self.ROLE, *args, **kwargs)
diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py
index 952d017283e1a03c1362f294efe023eadd964671..d20d532c97002632357a17c1108101680fae9c26 100644
--- a/lms/djangoapps/bulk_email/tests/test_email.py
+++ b/lms/djangoapps/bulk_email/tests/test_email.py
@@ -53,7 +53,8 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
         self.instructor = InstructorFactory(course=self.course.location)
 
         # Create staff
-        self.staff = [StaffFactory(course=self.course.location) for _ in xrange(STAFF_COUNT)]
+        self.staff = [StaffFactory(course=self.course.location)
+                      for _ in xrange(STAFF_COUNT)]
 
         # Create students
         self.students = [UserFactory() for _ in xrange(STUDENT_COUNT)]
diff --git a/lms/djangoapps/course_wiki/tests/test_access.py b/lms/djangoapps/course_wiki/tests/test_access.py
index 72a0e7dcbd891b51e2d4b590f90fa7dd3d0fb305..aca2223d13274f0ecd358fdb5cf722525df3ac9d 100644
--- a/lms/djangoapps/course_wiki/tests/test_access.py
+++ b/lms/djangoapps/course_wiki/tests/test_access.py
@@ -25,7 +25,10 @@ class TestWikiAccessBase(ModuleStoreTestCase):
         self.wiki = get_or_create_root()
 
         self.course_math101 = CourseFactory.create(org='org', number='math101', display_name='Course')
-        self.course_math101_staff = [InstructorFactory(course=self.course_math101.location), StaffFactory(course=self.course_math101.location)]
+        self.course_math101_staff = [
+            InstructorFactory(course=self.course_math101.location),
+            StaffFactory(course=self.course_math101.location)
+        ]
 
         wiki_math101 = self.create_urlpath(self.wiki, course_wiki_slug(self.course_math101))
         wiki_math101_page = self.create_urlpath(wiki_math101, 'Child')
@@ -44,9 +47,15 @@ class TestWikiAccess(TestWikiAccessBase):
         super(TestWikiAccess, self).setUp()
 
         self.course_310b = CourseFactory.create(org='org', number='310b', display_name='Course')
-        self.course_310b_staff = [InstructorFactory(course=self.course_310b.location), StaffFactory(course=self.course_310b.location)]
+        self.course_310b_staff = [
+            InstructorFactory(course=self.course_310b.location),
+            StaffFactory(course=self.course_310b.location)
+        ]
         self.course_310b_ = CourseFactory.create(org='org', number='310b_', display_name='Course')
-        self.course_310b__staff = [InstructorFactory(course=self.course_310b_.location), StaffFactory(course=self.course_310b_.location)]
+        self.course_310b__staff = [
+            InstructorFactory(course=self.course_310b_.location),
+            StaffFactory(course=self.course_310b_.location)
+        ]
 
         self.wiki_310b = self.create_urlpath(self.wiki, course_wiki_slug(self.course_310b))
         self.wiki_310b_ = self.create_urlpath(self.wiki, course_wiki_slug(self.course_310b_))
@@ -105,7 +114,10 @@ class TestWikiAccessForNumericalCourseNumber(TestWikiAccessBase):
         super(TestWikiAccessForNumericalCourseNumber, self).setUp()
 
         self.course_200 = CourseFactory.create(org='org', number='200', display_name='Course')
-        self.course_200_staff = [InstructorFactory(course=self.course_200.location), StaffFactory(course=self.course_200.location)]
+        self.course_200_staff = [
+            InstructorFactory(course=self.course_200.location),
+            StaffFactory(course=self.course_200.location)
+        ]
 
         wiki_200 = self.create_urlpath(self.wiki, course_wiki_slug(self.course_200))
         wiki_200_page = self.create_urlpath(wiki_200, 'Child')
@@ -126,7 +138,10 @@ class TestWikiAccessForOldFormatCourseStaffGroups(TestWikiAccessBase):
 
         self.course_math101c = CourseFactory.create(org='org', number='math101c', display_name='Course')
         Group.objects.get_or_create(name='instructor_math101c')
-        self.course_math101c_staff = [InstructorFactory(course=self.course_math101c.location), StaffFactory(course=self.course_math101c.location)]
+        self.course_math101c_staff = [
+            InstructorFactory(course=self.course_math101c.location),
+            StaffFactory(course=self.course_math101c.location)
+        ]
 
         wiki_math101c = self.create_urlpath(self.wiki, course_wiki_slug(self.course_math101c))
         wiki_math101c_page = self.create_urlpath(wiki_math101c, 'Child')
diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py
index acb307e4650e6940a450966c4ca5185ef6c513b5..acc39c500107e3462ef3b4f3f4281643436f951d 100644
--- a/lms/djangoapps/courseware/tests/test_view_authentication.py
+++ b/lms/djangoapps/courseware/tests/test_view_authentication.py
@@ -3,7 +3,6 @@ import pytz
 
 from mock import patch
 
-from django.contrib.auth.models import User
 from django.core.urlresolvers import reverse
 from django.test.utils import override_settings
 
@@ -128,6 +127,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
             display_name='Welcome'
         )
 
+        self.global_staff_user = GlobalStaffFactory()
         self.unenrolled_user = UserFactory(last_name="Unenrolled")
 
         self.enrolled_user = UserFactory(last_name="Enrolled")
@@ -135,10 +135,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         CourseEnrollmentFactory(user=self.enrolled_user, course_id=self.test_course.id)
 
         self.staff_user = StaffFactory(course=self.course.location)
-        self.instructor_user = InstructorFactory(course=self.course.location)
+        self.instructor_user = InstructorFactory(
+            course=self.course.location)
         self.org_staff_user = OrgStaffFactory(course=self.course.location)
-        self.org_instructor_user = OrgInstructorFactory(course=self.course.location)
-        self.global_staff_user = GlobalStaffFactory()
+        self.org_instructor_user = OrgInstructorFactory(
+            course=self.course.location)
 
     def test_redirection_unenrolled(self):
         """
diff --git a/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py b/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
index 7f05023b30163ad9ae9ca21828068adf661f3b72..b3a329ee05095e3db34d8845fd3ee798b421d755 100644
--- a/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
+++ b/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
@@ -44,11 +44,7 @@ class TestInstructorDashboardAnonCSV(ModuleStoreTestCase, LoginEnrollmentTestCas
         self.activate_user(self.student)
         self.activate_user(self.instructor)
 
-        def make_instructor(course):
-            """ Create an instructor for the course."""
-            CourseStaffRole(course.location).add_users(User.objects.get(email=self.instructor))
-
-        make_instructor(self.toy)
+        CourseStaffRole(self.toy.location).add_users(User.objects.get(email=self.instructor))
 
         self.logout()
         self.login(self.instructor, self.password)
diff --git a/lms/djangoapps/instructor/tests/test_legacy_download_csv.py b/lms/djangoapps/instructor/tests/test_legacy_download_csv.py
index d6bcb7a065022d6d61deaad37fc173999694bf38..a03f029ff3e49575cfd37bf5a7f58307a8df47be 100644
--- a/lms/djangoapps/instructor/tests/test_legacy_download_csv.py
+++ b/lms/djangoapps/instructor/tests/test_legacy_download_csv.py
@@ -41,11 +41,7 @@ class TestInstructorDashboardGradeDownloadCSV(ModuleStoreTestCase, LoginEnrollme
         self.activate_user(self.student)
         self.activate_user(self.instructor)
 
-        def make_instructor(course):
-            """ Create an instructor for the course. """
-            CourseStaffRole(course.location).add_users(User.objects.get(email=self.instructor))
-
-        make_instructor(self.toy)
+        CourseStaffRole(self.toy.location).add_users(User.objects.get(email=self.instructor))
 
         self.logout()
         self.login(self.instructor, self.password)