Exemple #1
0
 def test_parse_role_lti_label(self):
     self.assertEqual([Role.STUDENT], Role.parse_role_lti("Student"))
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti("Student, Alumni"))
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti("Student ,Alumni"))
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti("Student,Alumni"))
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti(" Student , Alumni "))
Exemple #2
0
 def test_parse_role_unkown_multiple(self, logger):
     self.assertEqual(
         [Role.STUDENT, Role.ALUMNI],
         Role.parse_role_lti("Student, Unknown1, Unknown2, Alumni"))
     logger.warning.assert_has_calls([
         mock.call("Received unknown LTI role: 'Unknown1'"),
         mock.call("Received unknown LTI role: 'Unknown2'"),
     ])
Exemple #3
0
 def test_parse_role_lti_label_URN(self):
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti(
                          "urn:lti:instrole:ims/lis/Student, "
                          "Alumni"))
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti(
                          "urn:lti:instrole:ims/lis/Student ,"
                          "Alumni"))
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti(
                          "urn:lti:instrole:ims/lis/Student,"
                          "Alumni"))
     self.assertEqual([Role.STUDENT, Role.ALUMNI],
                      Role.parse_role_lti(
                          " urn:lti:instrole:ims/lis/Student , "
                          "Alumni "))
Exemple #4
0
def get_or_create_user(wclass_db: WimsClass, wclass: wimsapi.Class, parameters: Dict[str, Any]
                       ) -> Tuple[WimsUser, wimsapi.User]:
    """Get the WIMS' user database and wimsapi.User instances, create them if they does not
    exists.

    If at least one of the roles in the LTI request's parameters is in
    ROLES_ALLOWED_CREATE_WIMS_CLASS, the user will be connected as supervisor.

    Raises:
        - wimsapi.WimsAPIError if the WIMS' server denied a request.
        - requests.RequestException if the WIMS server could not be joined.

    Returns a tuple (user_db, user) where user_db is an instance of models.WimsUser and
    user an instance of wimsapi.User."""
    try:
        role = Role.parse_role_lti(parameters["roles"])
        if is_teacher(role):
            user_db = WimsUser.objects.get(lms_guid=None, wclass=wclass_db)
        else:
            user_db = WimsUser.objects.get(lms_guid=parameters['user_id'], wclass=wclass_db)
        user = wimsapi.User.get(wclass, user_db.quser)
    except WimsUser.DoesNotExist:
        user = create_user(parameters)
        
        i = 0
        while True:
            try:
                wclass.additem(user)
                break
            except wimsapi.WimsAPIError as e:
                # Raised if an user with the same quser already exists,
                # in this case, keep trying by appending integer to quser (jdoe, jdoe1,
                # jdoe2, ...), stopping after 100 tries.
                
                # Can also be raised if an error occured while communicating with the
                # WIMS server, hence the following test.
                if "user already exists" not in str(e) or i >= 100:  # pragma: no cover
                    raise
                
                if i:
                    user.quser = user.quser[:-len(str(i))]
                i += 1
                user.quser += str(i)
        
        user_db = WimsUser.objects.create(
            lms_guid=parameters["user_id"], wclass=wclass_db, quser=user.quser
        )
        logger.info("New user created (wims id: %s - lms id : %s) in class %d"
                    % (user.quser, str(user_db.lms_guid), wclass_db.id))
    
    return user_db, user
Exemple #5
0
def wims_exam(request: HttpRequest, wims_pk: int,
              exam_pk: int) -> HttpResponse:
    """Redirect the client to the WIMS server corresponding to <wims_pk> and exam <exam_pk>.

        Will retrieve/create the right WIMS' class/user according to informations in request.POST.

        Raises:
            - Http404 if no LMS corresponding to request.POST["tool_consumer_instance_guid"]
                has been found in the database.
            - PermissionDenied if the class corresponding to request.POST['context_id'] does not
                exists and roles in request.POST['context_id'] are not one of
                settings.ROLES_ALLOWED_CREATE_WIMS_CLASS.

        Returns:
            - HttpResponseRedirect redirecting the user to WIMS, logged in his WIMS' class.
            - HttpResponse(status=502) if an error occured while communicating with the WIMS.
            - HttpResponse(status=504) if the WIMS server could not be joined."""
    if request.method == "GET":
        uri = request.build_absolute_uri()
        return HttpResponseNotAllowed(["POST"],
                                      GET_ERROR_MSG % (uri[:-1], uri))
    if request.method != "POST":
        return HttpResponseNotAllowed(
            ["POST"], "405 Method Not Allowed: '%s'" % request.method)

    try:
        parameters = parse_parameters(request.POST)
        logger.info("Request received from '%s'" %
                    request.META.get('HTTP_REFERER', "Unknown"))
        check_parameters(parameters)
        check_custom_parameters(parameters)
        is_valid_request(request)
    except BadRequestException as e:
        logger.info(str(e))
        return HttpResponseBadRequest(str(e))

    # Retrieve the WIMS server
    try:
        wims_srv = WIMS.objects.get(pk=wims_pk)
    except WIMS.DoesNotExist:
        raise Http404("Unknown WIMS server of id '%d'" % wims_pk)

    # Retrieve the LMS
    try:
        lms = LMS.objects.get(guid=parameters["tool_consumer_instance_guid"])
    except LMS.DoesNotExist:
        raise Http404("No LMS found with guid '%s'" %
                      parameters["tool_consumer_instance_guid"])

    wapi = wimsapi.WimsAPI(wims_srv.url, wims_srv.ident, wims_srv.passwd)

    try:
        # Check that the WIMS server is available
        bol, response = wapi.checkident(verbose=True)
        if not bol:
            raise wimsapi.WimsAPIError(response['message'])

        # Get the class
        wclass_db = WimsClass.objects.get(wims=wims_srv,
                                          lms=lms,
                                          lms_guid=parameters['context_id'])

        try:
            wclass = wimsapi.Class.get(wims_srv.url,
                                       wims_srv.ident,
                                       wims_srv.passwd,
                                       wclass_db.qclass,
                                       wims_srv.rclass,
                                       timeout=settings.WIMSAPI_TIMEOUT)
        except wimsapi.WimsAPIError as e:
            if "not existing" in str(
                    e):  # Class was deleted on the WIMS server
                qclass = wclass_db.qclass
                logger.info((
                    "Deleting class (id : %d - wims id : %s - lms id : %s) as it was"
                    "deleted from the WIMS server") %
                            (wclass_db.id, str(
                                wclass_db.qclass), str(wclass_db.lms_guid)))
                wclass_db.delete()
                return HttpResponseNotFound((
                    "Class of ID %s could not be found on the WIMS server. Maybe it has been "
                    "deleted from the WIMS server. Use this LTI link on your LMS to create a new "
                    "WIMS class: %s") % (qclass,
                                         request.build_absolute_uri(
                                             reverse("lti:wims_class",
                                                     args=[wims_pk]))))
            raise  # Unknown error (pragma: no cover)

        # Check whether the user already exists, creating it otherwise
        user_db, user = get_or_create_user(wclass_db, wclass, parameters)

        # Check whether the exam already exists, creating it otherwise
        exam_db, exam = get_exam(wclass_db, wclass, exam_pk, parameters)
        if int(exam.exammode) not in [1, 2]:  # not active or expired
            return HttpResponseForbidden(
                "This exam (%s) is currently unavailable (%s)" %
                (str(exam.qexam), MODE[int(exam.exammode)]))

        # Storing the URL and ID to send the grade back to the LMS
        try:
            gl = GradeLinkExam.objects.get(user=user_db, activity=exam_db)
            gl.sourcedid = parameters["lis_result_sourcedid"]
            gl.url = parameters["lis_outcome_service_url"]
            gl.save()
        except GradeLinkExam.DoesNotExist:
            GradeLinkExam.objects.create(
                user=user_db,
                activity=exam_db,
                lms=lms,
                sourcedid=parameters["lis_result_sourcedid"],
                url=parameters["lis_outcome_service_url"])

        # If user is a teacher, send all grade back to the LMS
        role = Role.parse_role_lti(parameters["roles"])
        if is_teacher(role):
            GradeLinkExam.send_back_all(exam_db)

        # Trying to authenticate the user on the WIMS server
        bol, response = wapi.authuser(wclass.qclass, wclass.rclass, user.quser)
        if not bol:  # pragma: no cover
            raise wimsapi.WimsAPIError(response['message'])

        params = (
            "&lang=%s&module=adm%%2Fclass%%2Fexam&+job=student&+exam=%s" %
            (wclass.lang, str(exam.qexam)))
        url = response["home_url"] + params

    except WimsClass.DoesNotExist as e:
        logger.info(str(e))
        return HttpResponseNotFound("Could not find class of id '%s'" %
                                    parameters['context_id'])

    except wimsapi.WimsAPIError as e:  # WIMS server responded with ERROR
        logger.info(str(e))
        return HttpResponse(str(e), status=502)

    except requests.RequestException:
        logger.exception("Could not join the WIMS server '%s'" % wims_srv.url)
        return HttpResponse("Could not join the WIMS server '%s'" %
                            wims_srv.url,
                            status=504)

    return redirect(url)
Exemple #6
0
 def test_parse_role_unkown_one(self, logger):
     self.assertEqual([], Role.parse_role_lti("Unknown"))
     logger.warning.assert_called_with(
         "Received unknown LTI role: 'Unknown'")
Exemple #7
0
def get_or_create_class(lms: LMS, wims_srv: WIMS, wapi: wimsapi.WimsAPI,
                        parameters: Dict[str, Any]) -> Tuple[WimsClass, wimsapi.Class]:
    """Get the WIMS' class database and wimsapi.Class instances, create them if they does not
    exists.

    Raises:
        - exceptions.PermissionDenied if the class does not exists and none of the roles in
            the LTI request's parameters is in ROLES_ALLOWED_CREATE_WIMS_CLASS.
        - wimsapi.WimsAPIError if the WIMS' server denied a request.
        - requests.RequestException if the WIMS server could not be joined.

    Returns a tuple (wclass_db, wclass) where wclas_db is an instance of models.WimsClass and
    wclass an instance of wimsapi.Class."""
    try:
        wclass_db = WimsClass.objects.get(wims=wims_srv, lms=lms,
                                          lms_guid=parameters['context_id'])
        
        try:
            wclass = wimsapi.Class.get(wapi.url, wapi.ident, wapi.passwd, wclass_db.qclass,
                                       wims_srv.rclass)
        except wimsapi.WimsAPIError as e:
            if "not existing" in str(e):  # Class was deleted on the WIMS server
                logger.info(("Deleting class (id : %d - wims id : %s - lms id : %s) as it was"
                             "deleted from the WIMS server.")
                            % (wclass_db.id, str(wclass_db.qclass), str(wclass_db.lms_guid)))
                wclass_db.delete()
                raise WimsClass.DoesNotExist
            raise  # Unknown error (pragma: no cover)
    
    except WimsClass.DoesNotExist:
        role = Role.parse_role_lti(parameters["roles"])
        if not is_teacher(role):
            logger.warning(str(role))
            msg = ("You must have at least one of these roles to create a Wims class: %s. Your "
                   "roles: %s")
            msg %= (str([r.value for r in settings.ROLES_ALLOWED_CREATE_WIMS_CLASS]),
                    str([r.value for r in role]))
            raise PermissionDenied(msg)
        
        wclass = create_class(wims_srv, parameters)
        wclass.save(wapi.url, wapi.ident, wapi.passwd, timeout=settings.WIMSAPI_TIMEOUT)
        wclass_db = WimsClass.objects.create(
            lms=lms, lms_guid=parameters["context_id"],
            wims=wims_srv, qclass=wclass.qclass, name=wclass.name
        )
        logger.info("New class created (id : %d - wims id : %s - lms id : %s)"
                    % (wclass_db.id, str(wclass.qclass), str(wclass_db.lms_guid)))
        WimsUser.objects.create(wclass=wclass_db, quser="******")
        logger.info("New user created (wims id : supervisor - lms id : None) in class %d"
                    % wclass_db.id)
        
        try:
            title, body = generate_mail(wclass_db, wclass)
            send_mail(
                title,
                body,
                settings.SERVER_EMAIL,
                [wclass.supervisor.email],
            )
        except Exception:
            logger.exception("An exception occurred while sending email:")
    
    return wclass_db, wclass