Exemple #1
0
def _launch_assignment(assignment_id, user, result_source_did, outcome_url):
    assignment = (db(db.assignments.id == assignment_id).select(
        db.assignments.released).first())
    # If the assignment isn't valid, return instead of redirecting. The caller will report the error.
    if not assignment:
        return
    grade = (db((db.grades.auth_user == user.id)
                & (db.grades.assignment == assignment_id)).select(
                    db.grades.lis_result_sourcedid,
                    db.grades.lis_outcome_url).first())
    send_grade = (assignment and assignment.released and grade
                  and not grade.lis_result_sourcedid
                  and not grade.lis_outcome_url)

    # save the guid and url for reporting back the grade
    db.grades.update_or_insert(
        (db.grades.auth_user == user.id) &
        (db.grades.assignment == assignment_id),
        auth_user=user.id,
        assignment=assignment_id,
        lis_result_sourcedid=result_source_did,
        lis_outcome_url=outcome_url,
    )
    if send_grade:
        _try_to_send_lti_grade(user.id, assignment_id)

    redirect(
        URL("assignments",
            "doAssignment",
            vars={"assignment_id": assignment_id}))
Exemple #2
0
def student_autograde():
    """
    This is a safe endpoint that students can call from the assignment page
    to get a preliminary grade on their assignment. If in coursera_mode,
    the total for the assignment is calculated and stored in the db, and
    sent via LTI (if LTI is configured).
    """
    assignment_id = request.vars.assignment_id
    timezoneoffset = session.timezoneoffset if "timezoneoffset" in session else None

    res = _autograde(
        student_rownum=auth.user.id,
        assignment_id=assignment_id,
        timezoneoffset=timezoneoffset,
    )

    if not res["success"]:
        session.flash = "Failed to autograde questions for user id {} for assignment {}".format(
            auth.user.id, assignment_id)
        res = {"success": False}
    else:
        if settings.coursera_mode:
            res2 = _calculate_totals(student_rownum=auth.user.id,
                                     assignment_id=assignment_id)
            if not res2["success"]:
                session.flash = "Failed to compute totals for user id {} for assignment {}".format(
                    auth.user.id, assignment_id)
                res = {"success": False}
            else:
                _try_to_send_lti_grade(auth.user.id, assignment_id)
    return json.dumps(res)
def send_assignment_score_via_LTI():

    assignment_name = request.vars.assignment
    sid = request.vars.get('sid', None)
    assignment = db((db.assignments.name == assignment_name) & (
        db.assignments.course == auth.user.course_id)).select().first()
    student_row = db(
        (db.auth_user.username == sid)).select(db.auth_user.id).first()
    _try_to_send_lti_grade(student_row.id, assignment.id)
    return json.dumps({'success': True})
Exemple #4
0
def student_autograde():
    """
    This is a safe endpoint that students can call from the assignment page
    to get a preliminary grade on their assignment. If in coursera_mode,
    the total for the assignment is calculated and stored in the db, and
    sent via LTI (if LTI is configured).
    """
    assignment_id = request.vars.assignment_id
    timezoneoffset = session.timezoneoffset if "timezoneoffset" in session else None
    if not timezoneoffset and "RS_info" in request.cookies:
        parsed_js = json.loads(request.cookies["RS_info"].value)
        timezoneoffset = parsed_js.get("tz_offset", None)

    is_timed = request.vars.is_timed

    if assignment_id.isnumeric() is False:
        aidrow = (db(
            (db.assignments.name == assignment_id)
            & (db.assignments.course == auth.user.course_id)).select().first())
        if aidrow:
            assignment_id = aidrow.id
        else:
            res = {
                "success": False,
                "message": "Could not find this assignment"
            }
            return json.dumps(res)

    res = _autograde(
        student_rownum=auth.user.id,
        assignment_id=assignment_id,
        timezoneoffset=timezoneoffset,
    )

    if not res["success"]:
        session.flash = (
            "Failed to autograde questions for user id {} for assignment {}".
            format(auth.user.id, assignment_id))
        res = {"success": False}
    else:
        if settings.coursera_mode or is_timed:
            res2 = _calculate_totals(student_rownum=auth.user.id,
                                     assignment_id=assignment_id)
            if not res2["success"]:
                session.flash = (
                    "Failed to compute totals for user id {} for assignment {}"
                    .format(auth.user.id, assignment_id))
                res = {"success": False}
            else:
                _try_to_send_lti_grade(auth.user.id, assignment_id)
    return json.dumps(res)
Exemple #5
0
def index():
    myrecord = None
    consumer = None
    masterapp = None
    userinfo = None

    user_id = request.vars.get('user_id', None)
    last_name = request.vars.get('lis_person_name_family', None)
    first_name = request.vars.get('lis_person_name_given', None)
    full_name = request.vars.get('lis_person_name_full', None)
    if full_name and not last_name:
        names = full_name.strip().split()
        last_name = names[-1]
        first_name = ' '.join(names[:-1])
    email = request.vars.get('lis_person_contact_email_primary', None)
    instructor = ("Instructor" in request.vars.get('roles', [])) or \
                 ("TeachingAssistant" in request.vars.get('roles', []))
    result_source_did=request.vars.get('lis_result_sourcedid', None)
    outcome_url=request.vars.get('lis_outcome_service_url', None)
    assignment_id = _param_converter(request.vars.get('assignment_id', None))
    practice = request.vars.get('practice', None)

    if user_id is None :
        return dict(logged_in=False, lti_errors=["user_id is required for this tool to function", request.vars], masterapp=masterapp)
    elif first_name is None :
        return dict(logged_in=False, lti_errors=["First Name is required for this tool to function", request.vars], masterapp=masterapp)
    elif last_name is None :
        return dict(logged_in=False, lti_errors=["Last Name is required for this tool to function", request.vars], masterapp=masterapp)
    elif email is None :
        return dict(logged_in=False, lti_errors=["Email is required for this tool to function", request.vars], masterapp=masterapp)
    else :
        userinfo = dict()
        userinfo['first_name'] = first_name
        userinfo['last_name'] = last_name
        # In the `Canvas Student View <https://community.canvaslms.com/docs/DOC-13122-415261153>`_ as of 7-Jan-2019, the ``lis_person_contact_email_primary`` is an empty string. In this case, use the userid instead.
        email = email or (user_id + '@junk.com')
        userinfo['email'] = email

    key = request.vars.get('oauth_consumer_key', None)
    if key is not None:
        myrecord = db(db.lti_keys.consumer==key).select().first()
        if myrecord is None :
            return dict(logged_in=False, lti_errors=["Could not find oauth_consumer_key", request.vars],
                        masterapp=masterapp)
        else:
            session.oauth_consumer_key = key
    if myrecord is not None :
        masterapp = myrecord.application
        if len(masterapp) < 1 :
            masterapp = 'welcome'
        session.connect(request, response, masterapp=masterapp, db=db)

        oauth_server = oauth2.Server()
        oauth_server.add_signature_method(oauth2.SignatureMethod_PLAINTEXT())
        oauth_server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1())

        # Use ``setting.lti_uri`` if it's defined; otherwise, use the current URI (which must be built from its components). Don't include query parameters, which causes a failure in OAuth security validation.
        full_uri = settings.get('lti_uri', '{}://{}{}'.format(
            request.env.wsgi_url_scheme, request.env.http_host, request.url
        ))
        oauth_request = oauth2.Request.from_request(
            'POST', full_uri, None, dict(request.vars),
            query_string=request.env.query_string
        )
        # Fix encoding -- the signed keys are in bytes, but the oauth2 Request constructor translates everything to a string. Therefore, they never compare as equal. ???
        if isinstance(oauth_request.get('oauth_signature'), six.string_types):
            oauth_request['oauth_signature'] = oauth_request['oauth_signature'].encode('utf-8')
        consumer = oauth2.Consumer(myrecord.consumer, myrecord.secret)

        try:
            oauth_server.verify_request(oauth_request, consumer, None)
        except oauth2.Error as err:
            return dict(logged_in=False, lti_errors=["OAuth Security Validation failed:"+err.message, request.vars],
                        masterapp=masterapp)
            consumer = None

    # Time to create / update / login the user
    if userinfo and (consumer is not None):
        userinfo['username'] = email
        # Only assign a password if we're creating the user. The
        # ``get_or_create_user`` method checks for an existing user using both
        # the username and the email.
        update_fields = ['email', 'first_name', 'last_name']
        if not db(
            (db.auth_user.username == userinfo['username']) |
            (db.auth_user.email == userinfo['email'])
        ).select(db.auth_user.id).first():
            pw = db.auth_user.password.validate(str(uuid.uuid4()))[0]
            userinfo['password'] = pw
            update_fields.append('password')
        user = auth.get_or_create_user(userinfo, update_fields=update_fields)
        if user is None:
            return dict(logged_in=False, lti_errors=["Unable to create user record", request.vars],
                        masterapp=masterapp)
        # user exists; make sure course name and id are set based on custom parameters passed, if this is for runestone. As noted for ``assignment_id``, parameters are passed as a two-element list.
        course_id = _param_converter(request.vars.get('custom_course_id', None))
        section_id = _param_converter(request.vars.get('custom_section_id', None))
        if course_id:
            user['course_id'] = course_id
            user['course_name'] = getCourseNameFromId(course_id)    # need to set course_name because calls to verifyInstructor use it
            user['section'] = section_id
            user.update_record()

            # Update instructor status.
            if instructor:
                # Give the instructor free access to the book.
                db.user_courses.update_or_insert(user_id=user.id, course_id=course_id)
                db.course_instructor.update_or_insert(instructor=user.id, course=course_id)
            else:
                db((db.course_instructor.instructor == user.id) &
                   (db.course_instructor.course == course_id)).delete()

            # Before creating a new user_courses record, present payment or donation options.
            if not db((db.user_courses.user_id==user.id) &
                      (db.user_courses.course_id==course_id)).select().first():
                # Store the current URL, so this request can be completed after creating the user.
                session.lti_url_next = full_uri
                auth.login_user(user)
                redirect(URL(c='default'))

        if section_id:
            # set the section in the section_users table
            # test this
            db.section_users.update_or_insert(db.section_users.auth_user == user['id'], auth_user=user['id'], section = section_id)

        auth.login_user(user)

    if assignment_id:
        # If the assignment is released, but this is the first time a student has visited the assignment, auto-upload the grade.
        assignment = db(db.assignments.id == assignment_id).select(
            db.assignments.released).first()
        grade = db(
            (db.grades.auth_user == user.id) &
            (db.grades.assignment == assignment_id)
        ).select(db.grades.lis_result_sourcedid, db.grades.lis_outcome_url).first()
        send_grade = (assignment and assignment.released and grade and
                      not grade.lis_result_sourcedid and
                      not grade.lis_outcome_url)

        # save the guid and url for reporting back the grade
        db.grades.update_or_insert((db.grades.auth_user == user.id) & (db.grades.assignment == assignment_id),
                                   auth_user=user.id,
                                   assignment=assignment_id,
                                   lis_result_sourcedid=result_source_did,
                                   lis_outcome_url=outcome_url)
        if send_grade:
            _try_to_send_lti_grade(user.id, assignment_id)

        redirect(URL('assignments', 'doAssignment', vars={'assignment_id':assignment_id}))

    elif practice:
        if outcome_url and result_source_did:
            db.practice_grades.update_or_insert((db.practice_grades.auth_user == user.id),
                                                auth_user=user.id,
                                                lis_result_sourcedid=result_source_did,
                                                lis_outcome_url=outcome_url,
                                                course_name=getCourseNameFromId(course_id))
        else: # don't overwrite outcome_url and result_source_did
            db.practice_grades.update_or_insert((db.practice_grades.auth_user == user.id),
                                                auth_user=user.id,
                                                course_name=getCourseNameFromId(course_id))
        redirect(URL('assignments', 'settz_then_practice', vars={'course_name':user['course_name']}))

    redirect(get_course_url('index.html'))
Exemple #6
0
def index():
    myrecord = None
    consumer = None
    masterapp = None
    userinfo = None

    user_id = request.vars.get("user_id", None)
    last_name = request.vars.get("lis_person_name_family", None)
    first_name = request.vars.get("lis_person_name_given", None)
    full_name = request.vars.get("lis_person_name_full", None)
    if full_name and not last_name:
        names = full_name.strip().split()
        last_name = names[-1]
        first_name = " ".join(names[:-1])
    email = request.vars.get("lis_person_contact_email_primary", None)
    instructor = ("Instructor" in request.vars.get(
        "roles", [])) or ("TeachingAssistant" in request.vars.get("roles", []))
    result_source_did = request.vars.get("lis_result_sourcedid", None)
    outcome_url = request.vars.get("lis_outcome_service_url", None)
    # Deprecated: the use of the non-LTI-compliant name ``assignment_id``. The parameter should be ``custom_assignment_id``.
    assignment_id = _param_converter(
        request.vars.get("custom_assignment_id",
                         request.vars.get("assignment_id", None)))
    practice = request.vars.get("practice", None)

    if user_id is None:
        return dict(
            logged_in=False,
            lti_errors=[
                "user_id is required for this tool to function", request.vars
            ],
            masterapp=masterapp,
        )
    elif first_name is None:
        return dict(
            logged_in=False,
            lti_errors=[
                "First Name is required for this tool to function",
                request.vars,
            ],
            masterapp=masterapp,
        )
    elif last_name is None:
        return dict(
            logged_in=False,
            lti_errors=[
                "Last Name is required for this tool to function",
                request.vars,
            ],
            masterapp=masterapp,
        )
    elif email is None:
        return dict(
            logged_in=False,
            lti_errors=[
                "Email is required for this tool to function", request.vars
            ],
            masterapp=masterapp,
        )
    else:
        userinfo = dict()
        userinfo["first_name"] = first_name
        userinfo["last_name"] = last_name
        # In the `Canvas Student View <https://community.canvaslms.com/docs/DOC-13122-415261153>`_ as of 7-Jan-2019, the ``lis_person_contact_email_primary`` is an empty string. In this case, use the userid instead.
        email = email or (user_id + "@junk.com")
        userinfo["email"] = email

    key = request.vars.get("oauth_consumer_key", None)
    if key is not None:
        myrecord = db(db.lti_keys.consumer == key).select().first()
        if myrecord is None:
            return dict(
                logged_in=False,
                lti_errors=["Could not find oauth_consumer_key", request.vars],
                masterapp=masterapp,
            )
        else:
            session.oauth_consumer_key = key
    if myrecord is not None:
        masterapp = myrecord.application
        if len(masterapp) < 1:
            masterapp = "welcome"
        session.connect(request, response, masterapp=masterapp, db=db)

        oauth_server = oauth2.Server()
        oauth_server.add_signature_method(oauth2.SignatureMethod_PLAINTEXT())
        oauth_server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1())

        # Use ``setting.lti_uri`` if it's defined; otherwise, use the current URI (which must be built from its components). Don't include query parameters, which causes a failure in OAuth security validation.
        full_uri = settings.get(
            "lti_uri",
            "{}://{}{}".format(request.env.wsgi_url_scheme,
                               request.env.http_host, request.url),
        )
        oauth_request = oauth2.Request.from_request(
            "POST",
            full_uri,
            None,
            dict(request.vars),
            query_string=request.env.query_string,
        )
        # Fix encoding -- the signed keys are in bytes, but the oauth2 Request constructor translates everything to a string. Therefore, they never compare as equal. ???
        if isinstance(oauth_request.get("oauth_signature"), str):
            oauth_request["oauth_signature"] = oauth_request[
                "oauth_signature"].encode("utf-8")
        consumer = oauth2.Consumer(myrecord.consumer, myrecord.secret)

        try:
            oauth_server.verify_request(oauth_request, consumer, None)
        except oauth2.Error as err:
            return dict(
                logged_in=False,
                lti_errors=[
                    "OAuth Security Validation failed:" + err.message,
                    request.vars,
                ],
                masterapp=masterapp,
            )
            consumer = None

    # Time to create / update / login the user
    if userinfo and (consumer is not None):
        userinfo["username"] = email
        # Only assign a password if we're creating the user. The
        # ``get_or_create_user`` method checks for an existing user using both
        # the username and the email.
        update_fields = ["email", "first_name", "last_name"]
        if (not db((db.auth_user.username == userinfo["username"])
                   | (db.auth_user.email == userinfo["email"])).select(
                       db.auth_user.id).first()):
            pw = db.auth_user.password.validate(str(uuid.uuid4()))[0]
            userinfo["password"] = pw
            update_fields.append("password")
        user = auth.get_or_create_user(userinfo, update_fields=update_fields)
        if user is None:
            return dict(
                logged_in=False,
                lti_errors=["Unable to create user record", request.vars],
                masterapp=masterapp,
            )
        # user exists; make sure course name and id are set based on custom parameters passed, if this is for runestone. As noted for ``assignment_id``, parameters are passed as a two-element list.
        course_id = _param_converter(request.vars.get("custom_course_id",
                                                      None))
        if course_id:
            user["course_id"] = course_id
            user["course_name"] = getCourseNameFromId(
                course_id
            )  # need to set course_name because calls to verifyInstructor use it
            user.update_record()

            # Update instructor status.
            if instructor:
                # Give the instructor free access to the book.
                db.user_courses.update_or_insert(user_id=user.id,
                                                 course_id=course_id)
                db.course_instructor.update_or_insert(instructor=user.id,
                                                      course=course_id)
            else:
                # Make sure previous instructors are removed from the instructor list.
                db((db.course_instructor.instructor == user.id)
                   & (db.course_instructor.course == course_id)).delete()

            # Before creating a new user_courses record:
            if (not db((db.user_courses.user_id == user.id)
                       & (db.user_courses.course_id == course_id)).select().
                    first()):
                # In academy mode, present payment or donation options, per the discussion at https://github.com/RunestoneInteractive/RunestoneServer/pull/1322.
                if settings.academy_mode:
                    # To do so, store the current URL, so this request can be completed after creating the user.
                    # TODO: this doesn't work, since the ``course_id``` and ``assignment_id`` aren't saved in this redirect. Therefore, these should be stored (perhaps in ``session``) then used after a user pays / donates.
                    session.lti_url_next = full_uri
                    auth.login_user(user)
                    redirect(URL(c="default"))
                else:
                    # Otherwise, simply create the user.
                    db.user_courses.update_or_insert(user_id=user.id,
                                                     course_id=course_id)

        auth.login_user(user)

    if assignment_id:
        # If the assignment is released, but this is the first time a student has visited the assignment, auto-upload the grade.
        assignment = (db(db.assignments.id == assignment_id).select(
            db.assignments.released).first())
        grade = (db((db.grades.auth_user == user.id)
                    & (db.grades.assignment == assignment_id)).select(
                        db.grades.lis_result_sourcedid,
                        db.grades.lis_outcome_url).first())
        send_grade = (assignment and assignment.released and grade
                      and not grade.lis_result_sourcedid
                      and not grade.lis_outcome_url)

        # save the guid and url for reporting back the grade
        db.grades.update_or_insert(
            (db.grades.auth_user == user.id) &
            (db.grades.assignment == assignment_id),
            auth_user=user.id,
            assignment=assignment_id,
            lis_result_sourcedid=result_source_did,
            lis_outcome_url=outcome_url,
        )
        if send_grade:
            _try_to_send_lti_grade(user.id, assignment_id)

        redirect(
            URL("assignments",
                "doAssignment",
                vars={"assignment_id": assignment_id}))

    elif practice:
        if outcome_url and result_source_did:
            db.practice_grades.update_or_insert(
                (db.practice_grades.auth_user == user.id),
                auth_user=user.id,
                lis_result_sourcedid=result_source_did,
                lis_outcome_url=outcome_url,
                course_name=getCourseNameFromId(course_id),
            )
        else:  # don't overwrite outcome_url and result_source_did
            db.practice_grades.update_or_insert(
                (db.practice_grades.auth_user == user.id),
                auth_user=user.id,
                course_name=getCourseNameFromId(course_id),
            )
        redirect(
            URL(
                "assignments",
                "settz_then_practice",
                vars={"course_name": user["course_name"]},
            ))

    redirect(get_course_url("index.html"))