def test_revoke_twice(self): user = self.staff[0] revoke_access(self.course, user, 'staff') group = Group.objects.get( name=get_access_group_name(self.course, 'staff') ) self.assertNotIn(user, group.user_set.all())
def bulk_beta_modify_access(request, course_id): """ Enroll or unenroll users in beta testing program. Query parameters: - emails is string containing a list of emails separated by anything split_input_list can handle. - action is one of ['add', 'remove'] """ action = request.GET.get('action') emails_raw = request.GET.get('emails') emails = _split_input_list(emails_raw) email_students = request.GET.get('email_students') in ['true', 'True', True] results = [] rolename = 'beta' course = get_course_by_id(course_id) email_params = {} if email_students: email_params = get_email_params(course, auto_enroll=False) for email in emails: try: error = False user_does_not_exist = False user = User.objects.get(email=email) if action == 'add': allow_access(course, user, rolename) elif action == 'remove': revoke_access(course, user, rolename) else: return HttpResponseBadRequest(strip_tags( "Unrecognized action '{}'".format(action) )) except User.DoesNotExist: error = True user_does_not_exist = True # catch and log any unexpected exceptions # so that one error doesn't cause a 500. except Exception as exc: # pylint: disable=broad-except log.exception("Error while #{}ing student") log.exception(exc) error = True else: # If no exception thrown, see if we should send an email if email_students: send_beta_role_email(action, user, email_params) finally: # Tabulate the action result of this email address results.append({ 'email': email, 'error': error, 'userDoesNotExist': user_does_not_exist }) response_payload = { 'action': action, 'results': results, } return JsonResponse(response_payload)
def test_revoke_badrolename(self): user = UserFactory() revoke_access(self.course, user, 'robot-not-a-level') group = Group.objects.get( name=get_access_group_name(self.course, 'robot-not-a-level') ) self.assertNotIn(user, group.user_set.all())
def change_existing_ccx_coaches_to_staff(apps, schema_editor): """ Modify all coaches of CCX courses so that they have the staff role on the CCX course they coach, but retain the CCX Coach role on the parent course. Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema (unused) """ CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX') db_alias = schema_editor.connection.alias if not db_alias == 'default': # This migration is not intended to run against the student_module_history database and # will fail if it does. Ensure that it'll only run against the default database. return list_ccx = CustomCourseForEdX.objects.using(db_alias).all() for ccx in list_ccx: ccx_locator = CCXLocator.from_course_locator(ccx.course_id, six.text_type(ccx.id)) try: course = get_course_by_id(ccx_locator) except Http404: log.error('Could not migrate access for CCX course: %s', six.text_type(ccx_locator)) else: coach = User.objects.get(id=ccx.coach.id) allow_access(course, coach, 'staff', send_email=False) revoke_access(course, coach, 'ccx_coach', send_email=False) log.info( 'The CCX coach of CCX %s has been switched from "CCX Coach" to "Staff".', six.text_type(ccx_locator) )
def revert_ccx_staff_to_coaches(apps, schema_editor): """ Modify all staff on CCX courses so that they no longer have the staff role on the course that they coach. Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema (unused) """ CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX') db_alias = schema_editor.connection.alias if not db_alias == 'default': return list_ccx = CustomCourseForEdX.objects.using(db_alias).all() for ccx in list_ccx: ccx_locator = CCXLocator.from_course_locator(ccx.course_id, six.text_type(ccx.id)) try: course = get_course_by_id(ccx_locator) except Http404: log.error('Could not migrate access for CCX course: %s', six.text_type(ccx_locator)) else: coach = User.objects.get(id=ccx.coach.id) allow_access(course, coach, 'ccx_coach', send_email=False) revoke_access(course, coach, 'staff', send_email=False) log.info( 'The CCX coach of CCX %s has been switched from "Staff" to "CCX Coach".', six.text_type(ccx_locator) )
def modify_access(request, course_id): """ Modify staff/instructor access of other user. Requires instructor access. NOTE: instructors cannot remove their own instructor access. Query parameters: unique_student_identifer is the target user's username or email rolename is one of ['instructor', 'staff', 'beta'] action is one of ['allow', 'revoke'] """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, "instructor", course_id, depth=None) try: user = get_student_from_identifier(request.GET.get("unique_student_identifier")) except User.DoesNotExist: response_payload = { "unique_student_identifier": request.GET.get("unique_student_identifier"), "userDoesNotExist": True, } return JsonResponse(response_payload) # Check that user is active, because add_users # in common/djangoapps/student/roles.py fails # silently when we try to add an inactive user. if not user.is_active: response_payload = {"unique_student_identifier": user.username, "inactiveUser": True} return JsonResponse(response_payload) rolename = request.GET.get("rolename") action = request.GET.get("action") if not rolename in ["instructor", "staff", "beta"]: return HttpResponseBadRequest(strip_tags("unknown rolename '{}'".format(rolename))) # disallow instructors from removing their own instructor access. if rolename == "instructor" and user == request.user and action != "allow": response_payload = { "unique_student_identifier": user.username, "rolename": rolename, "action": action, "removingSelfAsInstructor": True, } return JsonResponse(response_payload) if action == "allow": allow_access(course, user, rolename) elif action == "revoke": revoke_access(course, user, rolename) else: return HttpResponseBadRequest(strip_tags("unrecognized action '{}'".format(action))) response_payload = { "unique_student_identifier": user.username, "rolename": rolename, "action": action, "success": "yes", } return JsonResponse(response_payload)
def change_existing_ccx_coaches_to_staff(apps, schema_editor): """ Modify all coaches of CCX courses so that they have the staff role on the CCX course they coach, but retain the CCX Coach role on the parent course. Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema (unused) """ CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX') db_alias = schema_editor.connection.alias if not db_alias == 'default': # This migration is not intended to run against the student_module_history database and # will fail if it does. Ensure that it'll only run against the default database. return list_ccx = CustomCourseForEdX.objects.using(db_alias).all() for ccx in list_ccx: ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id)) try: course = get_course_by_id(ccx_locator) except Http404: log.error('Could not migrate access for CCX course: %s', unicode(ccx_locator)) else: coach = User.objects.get(id=ccx.coach.id) allow_access(course, coach, 'staff', send_email=False) revoke_access(course, coach, 'ccx_coach', send_email=False) log.info( 'The CCX coach of CCX %s has been switched from "CCX Coach" to "Staff".', unicode(ccx_locator))
def bulk_beta_modify_access(request, course_id): """ Enroll or unenroll users in beta testing program. Query parameters: - identifiers is string containing a list of emails and/or usernames separated by anything split_input_list can handle. - action is one of ['add', 'remove'] """ action = request.GET.get("action") identifiers_raw = request.GET.get("identifiers") identifiers = _split_input_list(identifiers_raw) email_students = request.GET.get("email_students") in ["true", "True", True] auto_enroll = request.GET.get("auto_enroll") in ["true", "True", True] results = [] rolename = "beta" course = get_course_by_id(course_id) email_params = {} if email_students: email_params = get_email_params(course, auto_enroll=auto_enroll) for identifier in identifiers: try: error = False user_does_not_exist = False user = get_student_from_identifier(identifier) if action == "add": allow_access(course, user, rolename) elif action == "remove": revoke_access(course, user, rolename) else: return HttpResponseBadRequest(strip_tags("Unrecognized action '{}'".format(action))) except User.DoesNotExist: error = True user_does_not_exist = True # catch and log any unexpected exceptions # so that one error doesn't cause a 500. except Exception as exc: # pylint: disable=broad-except log.exception("Error while #{}ing student") log.exception(exc) error = True else: # If no exception thrown, see if we should send an email if email_students: send_beta_role_email(action, user, email_params) # See if we should autoenroll the student if auto_enroll: # Check if student is already enrolled if not CourseEnrollment.is_enrolled(user, course_id): CourseEnrollment.enroll(user, course_id) finally: # Tabulate the action result of this email address results.append({"identifier": identifier, "error": error, "userDoesNotExist": user_does_not_exist}) response_payload = {"action": action, "results": results} return JsonResponse(response_payload)
def modify_access(request, course_id): """ Modify staff/instructor access of other user. Requires instructor access. NOTE: instructors cannot remove their own instructor access. Query parameters: email is the target users email rolename is one of ['instructor', 'staff', 'beta'] action is one of ['allow', 'revoke'] """ course = get_course_with_access( request.user, course_id, 'instructor', depth=None ) email = request.GET.get('email') rolename = request.GET.get('rolename') action = request.GET.get('action') if not rolename in ['instructor', 'staff', 'beta']: return HttpResponseBadRequest( "unknown rolename '{}'".format(rolename) ) user = User.objects.get(email=email) # disallow instructors from removing their own instructor access. if rolename == 'instructor' and user == request.user and action != 'allow': return HttpResponseBadRequest( "An instructor cannot remove their own instructor access." ) if action == 'allow': access.allow_access(course, user, rolename) elif action == 'revoke': access.revoke_access(course, user, rolename) else: return HttpResponseBadRequest("unrecognized action '{}'".format(action)) response_payload = { 'email': email, 'rolename': rolename, 'action': action, 'success': 'yes', } response = HttpResponse( json.dumps(response_payload), content_type="application/json" ) return response
def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, send_email=True): """ Remove staff and instructor roles on ccx to all the staff and instructors members of master course. Arguments: master_course (CourseDescriptorWithMixins): Master course instance. ccx_key (CCXLocator): CCX course key. display_name (str): ccx display name for email. send_email (bool): flag to switch on or off email to the users on revoke access. """ list_staff = list_with_level(master_course, 'staff') list_instructor = list_with_level(master_course, 'instructor') with ccx_course(ccx_key) as course_ccx: list_staff_ccx = list_with_level(course_ccx, 'staff') list_instructor_ccx = list_with_level(course_ccx, 'instructor') email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) for staff in list_staff: if staff in list_staff_ccx: # revoke 'staff' access on ccx. revoke_access(course_ccx, staff, 'staff') # Unenroll the staff on ccx. unenroll_email( course_id=ccx_key, student_email=staff.email, email_students=send_email, email_params=email_params, ) for instructor in list_instructor: if instructor in list_instructor_ccx: # revoke 'instructor' access on ccx. revoke_access(course_ccx, instructor, 'instructor') # Unenroll the instructor on ccx. unenroll_email( course_id=ccx_key, student_email=instructor.email, email_students=send_email, email_params=email_params, )
def modify_access(request, course_id): """ Modify staff/instructor access of other user. Requires instructor access. NOTE: instructors cannot remove their own instructor access. Query parameters: unique_student_identifer is the target user's username or email rolename is one of ['instructor', 'staff', 'beta'] action is one of ['allow', 'revoke'] """ course = get_course_with_access( request.user, course_id, 'instructor', depth=None ) user = get_student_from_identifier(request.GET.get('unique_student_identifier')) rolename = request.GET.get('rolename') action = request.GET.get('action') if not rolename in ['instructor', 'staff', 'beta']: return HttpResponseBadRequest(strip_tags( "unknown rolename '{}'".format(rolename) )) # disallow instructors from removing their own instructor access. if rolename == 'instructor' and user == request.user and action != 'allow': return HttpResponseBadRequest( "An instructor cannot remove their own instructor access." ) if action == 'allow': allow_access(course, user, rolename) elif action == 'revoke': revoke_access(course, user, rolename) else: return HttpResponseBadRequest(strip_tags( "unrecognized action '{}'".format(action) )) response_payload = { 'unique_student_identifier': user.username, 'rolename': rolename, 'action': action, 'success': 'yes', } return JsonResponse(response_payload)
def modify_access(request, course_id): """ Modify staff/instructor access of other user. Requires instructor access. NOTE: instructors cannot remove their own instructor access. Query parameters: email is the target users email rolename is one of ['instructor', 'staff', 'beta'] action is one of ['allow', 'revoke'] """ course = get_course_with_access( request.user, course_id, 'instructor', depth=None ) email = strip_if_string(request.GET.get('email')) rolename = request.GET.get('rolename') action = request.GET.get('action') if not rolename in ['instructor', 'staff', 'beta']: return HttpResponseBadRequest( "unknown rolename '{}'".format(rolename) ) user = User.objects.get(email=email) # disallow instructors from removing their own instructor access. if rolename == 'instructor' and user == request.user and action != 'allow': return HttpResponseBadRequest( "An instructor cannot remove their own instructor access." ) if action == 'allow': allow_access(course, user, rolename) elif action == 'revoke': revoke_access(course, user, rolename) else: return HttpResponseBadRequest("unrecognized action '{}'".format(action)) response_payload = { 'email': email, 'rolename': rolename, 'action': action, 'success': 'yes', } return JsonResponse(response_payload)
def remove_affiliate_course_enrollments(sender, instance, **kwargs): # pylint: disable=unused-argument 'Remove all privileges over all affiliate courses.' for ccx in instance.affiliate.courses: ccx_locator = CCXLocator.from_course_locator(ccx.course_id, ccx.id) course = get_course_by_id(ccx_locator) revoke_access(course, instance.member, instance.role, False) # Remove CCX coach on FastTrac course if the user is a staff member in ONLY the affiliate # for which the membership has been deleted. is_staff_in_other_affiliate = AffiliateMembership.objects.filter( member=instance.member, role__in=AffiliateMembership.STAFF_ROLES ).exists() if instance.role in AffiliateMembership.STAFF_ROLES and not is_staff_in_other_affiliate: course_overviews = CourseOverview.objects.exclude(id__startswith='ccx-') for course_overview in course_overviews: course_id = course_overview.id course = get_course_by_id(course_id) revoke_access(course, instance.member, AffiliateMembership.CCX_COACH, False)
def reverse_add_master_course_staff_to_ccx(master_course, ccx_key, display_name): """ Remove staff of ccx. Arguments: master_course (CourseDescriptorWithMixins): Master course instance ccx_key (CCXLocator): CCX course key display_name (str): ccx display name for email """ list_staff = list_with_level(master_course, 'staff') list_instructor = list_with_level(master_course, 'instructor') with ccx_course(ccx_key) as course_ccx: email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) for staff in list_staff: # allow 'staff' access on ccx to staff of master course revoke_access(course_ccx, staff, 'staff') # Enroll the staff in the ccx unenroll_email( course_id=ccx_key, student_email=staff.email, email_students=True, email_params=email_params, ) for instructor in list_instructor: # allow 'instructor' access on ccx to instructor of master course revoke_access(course_ccx, instructor, 'instructor') # Enroll the instructor in the ccx unenroll_email( course_id=ccx_key, student_email=instructor.email, email_students=True, email_params=email_params, )
def test_revoke_beta(self): user = self.beta_testers[0] revoke_access(self.course, user, 'beta') self.assertFalse(CourseBetaTesterRole(self.course.id).has_user(user))
def test_revoke_badrolename(self): user = UserFactory() revoke_access(self.course, user, 'robot-not-a-level') group = Group.objects.get( name=get_access_group_name(self.course, 'robot-not-a-level')) self.assertNotIn(user, group.user_set.all())
def edit_ccx(request, course, ccx=None, **kwargs): if not ccx: raise Http404 name = request.POST.get('name') delivery_mode = request.POST.get('delivery_mode') location_city = request.POST.get('city') location_state = request.POST.get('state') location_postal_code = request.POST.get('postal_code') time = '{} {}Z'.format(request.POST.get('date'), request.POST.get('time')) enrollment_end_date = '{} {}Z'.format( request.POST.get('enrollment_end_date'), request.POST.get('enrollment_end_time')) end_date = '{} {}Z'.format(request.POST.get('end_date'), request.POST.get('end_time')) fee = request.POST.get('fee') course_description = request.POST.get('course_description') enrollment_type = request.POST.get('enrollment_type') facilitators = dict(request.POST).get('facilitators') ccx.display_name = name ccx.delivery_mode = delivery_mode ccx.location_city = location_city ccx.location_state = location_state ccx.location_postal_code = location_postal_code ccx.enrollment_type = enrollment_type ccx.time = time ccx.enrollment_end_date = enrollment_end_date ccx.end_date = end_date ccx.fee = ast.literal_eval(fee) ccx.course_description = course_description ccx.save() current_facilitator_ids = CourseAccessRole.objects.filter( course_id=ccx.ccx_course_id, role=AffiliateMembership.CCX_COACH).values_list('user_id', flat=True) removed_facilitator_ids = set(current_facilitator_ids).difference( set(facilitators)) added_facilitator_ids = set(facilitators).difference( set(current_facilitator_ids)) ccx_id = CCXLocator.from_course_locator(course.id, ccx.pk) course_obj = get_course_by_id(ccx.ccx_course_id, depth=None) for facilitator_id in removed_facilitator_ids: user = User.objects.get(id=facilitator_id) revoke_access(course_obj, user, AffiliateMembership.CCX_COACH, False) email_params = get_email_params(course, auto_enroll=True, course_key=ccx_id, display_name=ccx.display_name) for facilitator_id in added_facilitator_ids: user = User.objects.get(id=facilitator_id) enroll_email(course_id=ccx_id, student_email=user.email, auto_enroll=True, email_students=True, email_params=email_params) allow_access(course_obj, user, AffiliateMembership.CCX_COACH, False) url = reverse('ccx_coach_dashboard', kwargs={'course_id': ccx_id}) return redirect(url)
def test_revoke_twice(self): user = self.staff[0] revoke_access(self.course, user, 'staff') self.assertFalse(CourseStaffRole(self.course.id).has_user(user))
def populate_user(user, authentication_response): attr = authentication_response.find(CAS + 'authenticationSuccess/' + CAS + 'attributes', namespaces=NSMAP) if attr is not None: staff_flag = attr.find(CAS + 'is_staff', NSMAP) if staff_flag is not None: user.is_staff = (staff_flag.text or '').upper() == 'TRUE' superuser_flag = attr.find(CAS + 'is_superuser', NSMAP) if superuser_flag is not None: user.is_superuser = (superuser_flag.text or '').upper() == 'TRUE' active_flag = attr.find(CAS + 'is_active', NSMAP) if active_flag is not None: user.is_active = (active_flag.text or '').upper() == 'TRUE' # Limiting by maximum lengths. # Max length of firstname/lastname is 30. # Max length of a email is 75. first_name = attr.find(CAS + 'givenName', NSMAP) if first_name is not None: user.first_name = (first_name.text or '')[0:30] last_name = attr.find(CAS + 'sn', NSMAP) if last_name is not None: user.last_name = (last_name.text or '')[0:30] email = attr.find(CAS + 'email', NSMAP) if email is not None: user.email = (email.text or '')[0:75] # Here we handle things that go into UserProfile instead. # This is a dirty hack and you shouldn't do that. # However, I don't think it's going to work when imported outside of the function body. from student.models import UserProfile # Make the user's password unusable. But only if they don't have an unusable password already, # to prevent SessionAuthenticationMiddleware from logging them out because their password changed. if user.has_usable_password(): user.set_unusable_password() user.save() # If the user doesn't yet have a profile, it means it's a new one and we need to create it a profile. # but we need to save the user first. user_profile, created = UserProfile.objects.get_or_create( user=user, defaults={'name': user.username}) # There should be more variables, but let's settle on the actual model first. full_name = attr.find(CAS + 'fullName', NSMAP) if full_name is not None: user_profile.name = full_name.text or '' user_profile.save() # Now the really fun bit. Signing the user up for courses given. coursetag = attr.find(CAS + 'courses', NSMAP) from student.models import CourseEnrollment from opaque_keys.edx.locator import CourseLocator from opaque_keys import InvalidKeyError from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError if coursetag is not None: try: courses = json.loads(coursetag.text) assert isinstance(courses, list) except (ValueError, AssertionError): # We failed to parse the tag and get a list, so we leave. log.error("Course list failed to parse.") return # We got a list. Compare it to existing enrollments. existing_enrollments = CourseEnrollment.objects.filter( user=user, is_active=True).values_list('course_id', flat=True) for course in courses: if course and not course in existing_enrollments: try: locator = CourseLocator.from_string(course) except (InvalidKeyError, AttributeError) as e: log.error( "Invalid course identifier {}".format(course)) continue try: course = modulestore().get_course(locator) except ItemNotFoundError: log.error("Course {} does not exist.".format(course)) continue CourseEnrollment.enroll(user, locator) # Now we need to unsub the user from courses for which they are not enrolled. for course in existing_enrollments: if not course in courses: try: locator = CourseLocator.from_string(course) except (InvalidKeyError, AttributeError) as e: log.error( "Invalid course identifier {} in existing enrollments." .format(course)) continue CourseEnrollment.unenroll(user, locator) # Now implement CourseEnrollmentAllowed objects, because otherwise they will only ever fire when # users click a link in the registration email -- which can never happen here. # Considering the new setup, I doubt this will ever be useful. if created: from student.models import CourseEnrollmentAllowed for cea in CourseEnrollmentAllowed.objects.filter( email=user.email, auto_enroll=True): CourseEnrollment.enroll(user, cea.course_id) # Now, deal with course administration packets. course_admin_tag = attr.find(CAS + 'course_administration_update', NSMAP) if course_admin_tag is not None: try: courses = json.loads(course_admin_tag.text) assert isinstance(courses, dict) except (ValueError, AssertionError): # We failed to parse the tag, so we leave. log.error( "Could not parse course administration block: <<{}>>". format(course_admin_tag.text)) return from instructor.access import list_with_level, allow_access, revoke_access from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA from django.contrib.auth.models import User for course_id, admin_block in courses.iteritems(): try: locator = CourseLocator.from_string(course_id) except (InvalidKeyError, AttributeError) as e: log.error("Invalid course identifier {}".format(course_id)) continue try: course = modulestore().get_course(locator) except ItemNotFoundError: log.error("Course {} does not exist.".format(course_id)) continue if not course: continue # Course roles are relatively easy. for block_name, role in [('admin', 'instructor'), ('staff', 'staff'), ('beta', 'beta')]: role_list = admin_block.get(block_name, []) existing = list_with_level(course, role) for username in role_list: try: user = User.objects.get(username=username) except User.DoesNotExist: continue if not user in existing: allow_access(course, user, role) try: CourseEnrollment.enroll(user, locator) except: pass for user in existing: if not user.username in role_list: revoke_access(course, user, role) # Forum roles, considerably different. for block_name, rolename in [ ('forum_admin', FORUM_ROLE_ADMINISTRATOR), ('forum_moderator', FORUM_ROLE_MODERATOR), ('forum_assistant', FORUM_ROLE_COMMUNITY_TA) ]: role_list = admin_block.get(block_name, []) try: role = Role.objects.get(course_id=locator, name=rolename) except Role.DoesNotExist: continue existing = role.users.all() for user in existing: if not user.username in role_list: role.users.remove(user) for username in role_list: try: user = User.objects.get(username=username) except User.DoesNotExist: continue if not user in existing: role.users.add(user) try: CourseEnrollment.enroll(user, locator) except: pass pass
def test_revoke_badrolename(self): user = UserFactory() revoke_access(self.course, user, 'robot-not-a-level')
def bulk_beta_modify_access(request, course_id): """ Enroll or unenroll users in beta testing program. Query parameters: - identifiers is string containing a list of emails and/or usernames separated by anything split_input_list can handle. - action is one of ['add', 'remove'] """ action = request.GET.get('action') identifiers_raw = request.GET.get('identifiers') identifiers = _split_input_list(identifiers_raw) email_students = request.GET.get('email_students') in [ 'true', 'True', True ] auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True] results = [] rolename = 'beta' course = get_course_by_id(course_id) email_params = {} if email_students: email_params = get_email_params(course, auto_enroll=auto_enroll) for identifier in identifiers: try: error = False user_does_not_exist = False user = get_student_from_identifier(identifier) if action == 'add': allow_access(course, user, rolename) elif action == 'remove': revoke_access(course, user, rolename) else: return HttpResponseBadRequest( strip_tags("Unrecognized action '{}'".format(action))) except User.DoesNotExist: error = True user_does_not_exist = True # catch and log any unexpected exceptions # so that one error doesn't cause a 500. except Exception as exc: # pylint: disable=broad-except log.exception("Error while #{}ing student") log.exception(exc) error = True else: # If no exception thrown, see if we should send an email if email_students: send_beta_role_email(action, user, email_params) # See if we should autoenroll the student if auto_enroll: # Check if student is already enrolled if not CourseEnrollment.is_enrolled(user, course_id): CourseEnrollment.enroll(user, course_id) finally: # Tabulate the action result of this email address results.append({ 'identifier': identifier, 'error': error, 'userDoesNotExist': user_does_not_exist }) response_payload = { 'action': action, 'results': results, } return JsonResponse(response_payload)
def modify_access(request, course_id): """ Modify staff/instructor access of other user. Requires instructor access. NOTE: instructors cannot remove their own instructor access. Query parameters: unique_student_identifer is the target user's username or email rolename is one of ['instructor', 'staff', 'beta'] action is one of ['allow', 'revoke'] """ course = get_course_with_access(request.user, course_id, 'instructor', depth=None) try: user = get_student_from_identifier( request.GET.get('unique_student_identifier')) except User.DoesNotExist: response_payload = { 'unique_student_identifier': request.GET.get('unique_student_identifier'), 'userDoesNotExist': True, } return JsonResponse(response_payload) # Check that user is active, because add_users # in common/djangoapps/student/roles.py fails # silently when we try to add an inactive user. if not user.is_active: response_payload = { 'unique_student_identifier': user.username, 'inactiveUser': True, } return JsonResponse(response_payload) rolename = request.GET.get('rolename') action = request.GET.get('action') if not rolename in ['instructor', 'staff', 'beta']: return HttpResponseBadRequest( strip_tags("unknown rolename '{}'".format(rolename))) # disallow instructors from removing their own instructor access. if rolename == 'instructor' and user == request.user and action != 'allow': response_payload = { 'unique_student_identifier': user.username, 'rolename': rolename, 'action': action, 'removingSelfAsInstructor': True, } return JsonResponse(response_payload) if action == 'allow': allow_access(course, user, rolename) elif action == 'revoke': revoke_access(course, user, rolename) else: return HttpResponseBadRequest( strip_tags("unrecognized action '{}'".format(action))) response_payload = { 'unique_student_identifier': user.username, 'rolename': rolename, 'action': action, 'success': 'yes', } return JsonResponse(response_payload)
def test_revoke_beta(self): user = self.beta_testers[0] revoke_access(self.course, user, 'beta') self.assertNotIn(user, list_with_level(self.course, 'beta'))
def populate_user(user, authentication_response): attr = authentication_response.find(CAS + 'authenticationSuccess/' + CAS + 'attributes' , namespaces=NSMAP) if attr is not None: staff_flag = attr.find(CAS + 'is_staff', NSMAP) if staff_flag is not None: user.is_staff = (staff_flag.text or '').upper() == 'TRUE' superuser_flag = attr.find(CAS + 'is_superuser', NSMAP) if superuser_flag is not None: user.is_superuser = (superuser_flag.text or '').upper() == 'TRUE' active_flag = attr.find(CAS + 'is_active', NSMAP) if active_flag is not None: user.is_active = (active_flag.text or '').upper() == 'TRUE' # Limiting by maximum lengths. # Max length of firstname/lastname is 30. # Max length of a email is 75. first_name = attr.find(CAS + 'givenName', NSMAP) if first_name is not None: user.first_name = (first_name.text or '')[0:30] last_name = attr.find(CAS + 'sn', NSMAP) if last_name is not None: user.last_name = (last_name.text or '')[0:30] email = attr.find(CAS + 'email', NSMAP) if email is not None: user.email = (email.text or '')[0:75] # Here we handle things that go into UserProfile instead. # This is a dirty hack and you shouldn't do that. # However, I don't think it's going to work when imported outside of the function body. from student.models import UserProfile # Make the user's password unusable. But only if they don't have an unusable password already, # to prevent SessionAuthenticationMiddleware from logging them out because their password changed. if user.has_usable_password(): user.set_unusable_password() user.save() # If the user doesn't yet have a profile, it means it's a new one and we need to create it a profile. # but we need to save the user first. user_profile, created = UserProfile.objects.get_or_create(user=user, defaults={'name':user.username}) # There should be more variables, but let's settle on the actual model first. full_name = attr.find(CAS + 'fullName', NSMAP) if full_name is not None: user_profile.name = full_name.text or '' user_profile.save() # Now the really fun bit. Signing the user up for courses given. coursetag = attr.find(CAS + 'courses', NSMAP) from student.models import CourseEnrollment from opaque_keys.edx.locator import CourseLocator from opaque_keys import InvalidKeyError from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError if coursetag is not None: try: courses = json.loads(coursetag.text) assert isinstance(courses,list) except (ValueError, AssertionError): # We failed to parse the tag and get a list, so we leave. log.error("Course list failed to parse.") return # We got a list. Compare it to existing enrollments. existing_enrollments = CourseEnrollment.objects.filter(user=user, is_active=True).values_list('course_id',flat=True) for course in courses: if course and not course in existing_enrollments: try: locator = CourseLocator.from_string(course) except (InvalidKeyError, AttributeError) as e: log.error("Invalid course identifier {}".format(course)) continue try: course = modulestore().get_course(locator) except ItemNotFoundError: log.error("Course {} does not exist.".format(course)) continue CourseEnrollment.enroll(user,locator) # Now we need to unsub the user from courses for which they are not enrolled. for course in existing_enrollments: if not course in courses: try: locator = CourseLocator.from_string(course) except (InvalidKeyError, AttributeError) as e: log.error("Invalid course identifier {} in existing enrollments.".format(course)) continue CourseEnrollment.unenroll(user, locator) # Now implement CourseEnrollmentAllowed objects, because otherwise they will only ever fire when # users click a link in the registration email -- which can never happen here. # Considering the new setup, I doubt this will ever be useful. if created: from student.models import CourseEnrollmentAllowed for cea in CourseEnrollmentAllowed.objects.filter(email=user.email, auto_enroll=True): CourseEnrollment.enroll(user, cea.course_id) # Now, deal with course administration packets. course_admin_tag = attr.find(CAS + 'course_administration_update', NSMAP) if course_admin_tag is not None: try: courses = json.loads(course_admin_tag.text) assert isinstance(courses,dict) except (ValueError, AssertionError): # We failed to parse the tag, so we leave. log.error("Could not parse course administration block: <<{}>>".format(course_admin_tag.text)) return from instructor.access import list_with_level, allow_access, revoke_access from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA from django.contrib.auth.models import User for course_id, admin_block in courses.iteritems(): try: locator = CourseLocator.from_string(course_id) except (InvalidKeyError, AttributeError) as e: log.error("Invalid course identifier {}".format(course_id)) continue try: course = modulestore().get_course(locator) except ItemNotFoundError: log.error("Course {} does not exist.".format(course_id)) continue if not course: continue # Course roles are relatively easy. for block_name, role in [('admin','instructor'), ('staff','staff'), ('beta','beta')]: role_list = admin_block.get(block_name,[]) existing = list_with_level(course,role) for username in role_list: try: user = User.objects.get(username=username) except User.DoesNotExist: continue if not user in existing: allow_access(course, user, role) try: CourseEnrollment.enroll(user, locator) except: pass for user in existing: if not user.username in role_list: revoke_access(course, user, role) # Forum roles, considerably different. for block_name, rolename in [('forum_admin',FORUM_ROLE_ADMINISTRATOR), ('forum_moderator',FORUM_ROLE_MODERATOR), ('forum_assistant',FORUM_ROLE_COMMUNITY_TA)]: role_list = admin_block.get(block_name,[]) try: role = Role.objects.get(course_id=locator, name=rolename) except Role.DoesNotExist: continue existing = role.users.all() for user in existing: if not user.username in role_list: role.users.remove(user) for username in role_list: try: user = User.objects.get(username=username) except User.DoesNotExist: continue if not user in existing: role.users.add(user) try: CourseEnrollment.enroll(user, locator) except: pass pass
def test_revoke_twice(self): user = self.staff[0] revoke_access(self.course, user, "staff") self.assertFalse(CourseStaffRole(self.course.location).has_user(user))
def modify_access(request, course_id): """ Modify staff/instructor access of other user. Requires instructor access. NOTE: instructors cannot remove their own instructor access. Query parameters: unique_student_identifer is the target user's username or email rolename is one of ['instructor', 'staff', 'beta'] action is one of ['allow', 'revoke'] """ course = get_course_with_access( request.user, course_id, 'instructor', depth=None ) try: user = get_student_from_identifier(request.GET.get('unique_student_identifier')) except User.DoesNotExist: response_payload = { 'unique_student_identifier': request.GET.get('unique_student_identifier'), 'userDoesNotExist': True, } return JsonResponse(response_payload) # Check that user is active, because add_users # in common/djangoapps/student/roles.py fails # silently when we try to add an inactive user. if not user.is_active: response_payload = { 'unique_student_identifier': user.username, 'inactiveUser': True, } return JsonResponse(response_payload) rolename = request.GET.get('rolename') action = request.GET.get('action') if not rolename in ['instructor', 'staff', 'beta']: return HttpResponseBadRequest(strip_tags( "unknown rolename '{}'".format(rolename) )) # disallow instructors from removing their own instructor access. if rolename == 'instructor' and user == request.user and action != 'allow': response_payload = { 'unique_student_identifier': user.username, 'rolename': rolename, 'action': action, 'removingSelfAsInstructor': True, } return JsonResponse(response_payload) if action == 'allow': allow_access(course, user, rolename) elif action == 'revoke': revoke_access(course, user, rolename) else: return HttpResponseBadRequest(strip_tags( "unrecognized action '{}'".format(action) )) response_payload = { 'unique_student_identifier': user.username, 'rolename': rolename, 'action': action, 'success': 'yes', } return JsonResponse(response_payload)
def test_revoke(self): user = self.staff[0] revoke_access(self.course, user, 'staff') group = Group.objects.get( name=get_access_group_name(self.course, 'staff')) self.assertNotIn(user, group.user_set.all())
def test_revoke_beta(self): user = self.beta_testers[0] revoke_access(self.course, user, "beta") self.assertFalse(CourseBetaTesterRole(self.course.location).has_user(user))