Пример #1
0
def get_friend_list(token):
    '''
    Return a list of fbids for the Facebook user associated with token
    '''
    fbid_list = []
    params = {
        'access_token': token,
    }
    url = 'https://graph.facebook.com/me/friends'

    while url is not None:
        resp = requests.get(url, params=params)
        resp_dict = util.json_loads(resp.text)

        if 'error' in resp_dict:
            if resp_dict.get('error').get('type') == 'OAuthException':
                raise FacebookOAuthException()
            raise Exception(resp.text)

        if 'data' in resp_dict:
            for entry in resp_dict['data']:
                fbid_list.append(entry['id'])
        else:
            raise Exception('"data" not in dict (%s)' % resp_dict)

        url = resp_dict.get('paging', {}).get('next')

    return fbid_list
Пример #2
0
def get_friend_list(token):
    """
    Return a list of fbids for the Facebook user associated with token
    """
    fbid_list = []
    params = {"access_token": token}
    url = "https://graph.facebook.com/me/friends"

    while url is not None:
        resp = requests.get(url, params=params)
        resp_dict = util.json_loads(resp.text)

        if "error" in resp_dict:
            if resp_dict.get("error").get("type") == "OAuthException":
                raise FacebookOAuthException()
            raise Exception(resp.text)

        if "data" in resp_dict:
            for entry in resp_dict["data"]:
                fbid_list.append(entry["id"])
        else:
            raise Exception('"data" not in dict (%s)' % resp_dict)

        url = resp_dict.get("paging", {}).get("next")

    return fbid_list
Пример #3
0
def upload_transcript():
    req = flask.request

    user = view_helpers.get_current_user()
    user_id = user.id

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_API,
        rmclogger.LOG_EVENT_TRANSCRIPT, {
            'user_id': user_id,
            'requset_form': req.form,
        },
    )

    def get_term_id(term_name):
        season, year = term_name.split()
        return m.Term.get_id_from_year_season(year, season)

    transcript_data = util.json_loads(req.form['transcriptData'])
    courses_by_term = transcript_data['coursesByTerm']

    # TODO(Sandy): Batch request fetch to mongo instead of fetch while looping
    for term in courses_by_term:
        term_id = get_term_id(term['name'])
        program_year_id = term['programYearId']

        for course_id in term['courseIds']:
            # TODO(Sandy): Fill in course weight and grade info here
            user.add_course(course_id.lower(), term_id, program_year_id)

    if courses_by_term:
        last_term = courses_by_term[0]
        term_id = get_term_id(last_term['name'])
        user.last_term_id = term_id
        user.last_program_year_id = last_term['programYearId']
    user.program_name = transcript_data['programName']

    student_id = transcript_data.get('studentId')
    if student_id:
        user.student_id = str(student_id)

    user.cache_mutual_course_ids(view_helpers.get_redis_instance())
    user.transcripts_imported += 1
    user.save()

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_TRANSCRIPT,
        rmclogger.LOG_EVENT_UPLOAD,
        user_id
    )
    return ''
Пример #4
0
def upload_transcript():
    req = flask.request

    user = view_helpers.get_current_user()
    user_id = user.id

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_API,
        rmclogger.LOG_EVENT_TRANSCRIPT, {
            'user_id': user_id,
            'requset_form': req.form,
        },
    )

    def get_term_id(term_name):
        season, year = term_name.split()
        return m.Term.get_id_from_year_season(year, season)

    transcript_data = util.json_loads(req.form['transcriptData'])
    courses_by_term = transcript_data['coursesByTerm']

    # TODO(Sandy): Batch request fetch to mongo instead of fetch while looping
    for term in courses_by_term:
        term_id = get_term_id(term['name'])
        program_year_id = term['programYearId']

        for course_id in term['courseIds']:
            # TODO(Sandy): Fill in course weight and grade info here
            user.add_course(course_id.lower(), term_id, program_year_id)

    if courses_by_term:
        last_term = courses_by_term[0]
        term_id = get_term_id(last_term['name'])
        user.last_term_id = term_id
        user.last_program_year_id = last_term['programYearId']
    user.program_name = transcript_data['programName']

    student_id = transcript_data.get('studentId')
    if student_id:
        user.student_id = str(student_id)

    user.cache_mutual_course_ids(view_helpers.get_redis_instance())
    user.transcripts_imported += 1
    user.save()

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_TRANSCRIPT,
        rmclogger.LOG_EVENT_UPLOAD,
        user_id
    )
    return ''
Пример #5
0
def code_for_token(code, config, cmd_line_debug=False):
    """Returns a dictionary containing the user's Facebook access token and
    seconds until it expires from now

    See https://developers.facebook.com/blog/post/2011/05/13/how-to--handle-expired-access-tokens/

    Right now, the resulting token is a short-lived token (~2 hours). But it's
    possible that this is wrong and that it should be a long-term token
    instead.  See https://developers.facebook.com/bugs/341793929223330/

    Args:
        code: The code we get from their fb_signed_request

    Returns {
        'access_token': 'token-here-blarg',
        'expires': 6200,
    }
    """
    # Since we're exchanging a client-side token, redirect_uri should be ''
    params = {
        'client_id': config['FB_APP_ID'],
        'redirect_uri': '',
        'client_secret': config['FB_APP_SECRET'],
        'code': code,
    }
    resp = requests.get('https://graph.facebook.com/oauth/access_token',
            params=params)

    if resp.status_code != 200:
        err = util.json_loads(resp.text)
        if (err.get('error').get('message') == USED_AUTH_CODE_MSG and
            err.get('error').get('code') == 100):
            logging.info('code_for_token failed (%d) with text:\n%s' % (
                    resp.status_code, resp.text))
        else:
            logging.warn('code_for_token failed (%d) with text:\n%s' % (
                    resp.status_code, resp.text))

    result = dict(urlparse.parse_qsl(resp.text))

    if cmd_line_debug:
        print "result dict:"
        print result
        return resp

    return result
Пример #6
0
def parse_signed_request(signed_request, secret):
    """
    Returns a dict of the the Facebook signed request object
    See https://developers.facebook.com/docs/authentication/signed_request/
    """
    l = signed_request.split(".", 2)
    encoded_sig = l[0]
    payload = l[1]

    sig = base64_url_decode(encoded_sig)
    data = util.json_loads(base64_url_decode(payload))

    if data.get("algorithm").upper() != "HMAC-SHA256":
        logging.error("Unknown algorithm during signed request decode")
        return None

    expected_sig = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest()

    if sig != expected_sig:
        return None

    return data
Пример #7
0
def get_friend_list(token):
    '''
    Return a list of fbids for the Facebook user associated with token
    '''
    params = {
        'access_token': token,
    }
    resp = requests.get('https://graph.facebook.com/me/friends', params=params)
    resp_dict = util.json_loads(resp.text)

    if 'error' in resp_dict:
        if resp_dict.get('error').get('type') == 'OAuthException':
            raise FacebookOAuthException()
        raise Exception(resp.text)

    fbid_list = []
    if 'data' in resp_dict:
        for entry in resp_dict['data']:
            fbid_list.append(entry['id'])
    else:
        raise Exception('"data" not in dict (%s)' % resp_dict)

    return fbid_list
Пример #8
0
def parse_signed_request(signed_request, secret):
    """
    Returns a dict of the the Facebook signed request object
    See https://developers.facebook.com/docs/authentication/signed_request/
    """
    l = signed_request.split('.', 2)
    encoded_sig = l[0]
    payload = l[1]

    sig = base64_url_decode(encoded_sig)
    data = util.json_loads(base64_url_decode(payload))

    if data.get('algorithm').upper() != 'HMAC-SHA256':
        logging.error('Unknown algorithm during signed request decode')
        return None

    expected_sig = (hmac.new(secret, msg=payload, digestmod=hashlib.sha256)
                        .digest())

    if sig != expected_sig:
        return None

    return data
Пример #9
0
 def test_json_loads(self):
     self.assertEquals({'foo': 1}, util.json_loads('{"foo":1}'))
Пример #10
0
def user_course():
    uc_data = util.json_loads(flask.request.data)
    user = view_helpers.get_current_user()

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_API,
        rmclogger.LOG_EVENT_USER_COURSE, {
            'uc_data': uc_data,
            'user_id': user.id,
        },
    )

    # Validate request object
    course_id = uc_data.get('course_id')
    term_id = uc_data.get('term_id')
    if course_id is None or term_id is None:
        logging.error("/api/user/course got course_id (%s) and term_id (%s)" %
            (course_id, term_id))
        # TODO(david): Perhaps we should have a request error function that
        # returns a 400
        raise exceptions.ImATeapot('No course_id or term_id set')

    if not m.UserCourse.can_review(term_id):
        logging.warning("%s attempted to rate %s in future/shortlist term %s"
                % (user.id, course_id, term_id))
        raise exceptions.ImATeapot(
                "Can't review a course in the future or shortlist")

    # Fetch existing UserCourse
    uc = m.UserCourse.objects(
        user_id=user.id,
        course_id=uc_data['course_id'],
        term_id=uc_data['term_id']
    ).first()

    if uc is None:
        logging.error("/api/user/course User course not found for "
            "user_id=%s course_id=%s term_id=%s" %
            (user.id, course_id, term_id))
        # TODO(david): Perhaps we should have a request error function that
        # returns a 400
        raise exceptions.ImATeapot('No user course found')

    orig_points = uc.num_points

    # TODO(Sandy): Consider the case where the user picked a professor and
    # rates them, but then changes the professor. We need to remove the ratings
    # from the old prof's aggregated ratings and add them to the new prof's
    # Maybe create professor if newly added
    if uc_data.get('new_prof_added'):

        new_prof_name = uc_data['new_prof_added']

        # TODO(mack): should do guess_names first, and use that to
        # generate the id
        prof_id = m.Professor.get_id_from_name(new_prof_name)
        uc.professor_id = prof_id

        # TODO(Sandy): Have some kind of sanity check for professor names.
        # Don't allow ridiculousness like "Santa Claus", "aksnlf",
        # "swear words"
        if m.Professor.objects(id=prof_id).count() == 0:
            first_name, last_name = m.Professor.guess_names(new_prof_name)
            m.Professor(
                id=prof_id,
                first_name=first_name,
                last_name=last_name,
            ).save()

        course = m.Course.objects.with_id(uc.course_id)
        course.professor_ids = list(set(course.professor_ids) | {prof_id})
        course.save()

        logging.info("Added new course professor %s (name: %s)" % (prof_id,
                new_prof_name))
    elif uc_data.get('professor_id'):
        uc.professor_id = uc_data['professor_id']
    else:
        uc.professor_id = None

    now = datetime.now()

    if uc_data.get('course_review'):
        # New course review data
        uc_data['course_review']['comment_date'] = now
        uc.course_review.update(**uc_data['course_review'])

    if uc_data.get('professor_review'):
        # New prof review data
        uc_data['professor_review']['comment_date'] = now
        uc.professor_review.update(**uc_data['professor_review'])

    uc.save()

    points_gained = uc.num_points - orig_points
    user.award_points(points_gained, view_helpers.get_redis_instance())
    user.save()

    return util.json_dumps({
        'professor_review.comment_date': uc['professor_review'][
            'comment_date'],
        'course_review.comment_date': uc['course_review']['comment_date'],
        'points_gained': points_gained,
    })
Пример #11
0
def upload_schedule():
    req = flask.request
    user = view_helpers.get_current_user()

    schedule_data = util.json_loads(req.form.get('schedule_data'))
    processed_items = schedule_data['processed_items']
    failed_items = schedule_data['failed_items']
    term_name = schedule_data['term_name']
    term_id = m.Term.id_from_name(term_name)

    # FIXME TODO(david): Save these in models and display on schedule
    #failed_items = schedule_data['failed_items']

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_API,
        rmclogger.LOG_EVENT_SCHEDULE, {
            'schedule_data': schedule_data,
            'term_id': term_id,
            'user_id': user.id,
        },
    )

    now = datetime.now()

    user.last_good_schedule_paste = req.form.get('schedule_text')
    user.last_good_schedule_paste_date = now
    user.save()

    # Remove existing schedule items for the user for the given term
    for usi in m.UserScheduleItem.objects(user_id=user.id, term_id=term_id):
        usi.delete()

    for item in processed_items:
        try:
            # Create this UserScheduleItem
            first_name, last_name = m.Professor.guess_names(item['prof_name'])
            prof_id = m.Professor.get_id_from_name(
                first_name=first_name,
                last_name=last_name,
            )
            if first_name and last_name:
                if not m.Professor.objects.with_id(prof_id):
                    m.Professor(
                        id=prof_id,
                        first_name=first_name,
                        last_name=last_name,
                    ).save()

            usi = m.UserScheduleItem(
                user_id=user.id,
                class_num=item['class_num'],
                building=item['building'],
                room=item.get('room'),
                section_type=item['section_type'].upper(),
                section_num=item['section_num'],
                start_date=datetime.utcfromtimestamp(item['start_date']),
                end_date=datetime.utcfromtimestamp(item['end_date']),
                course_id=item['course_id'],
                prof_id=prof_id,
                term_id=term_id,
            )
            try:
                usi.save()
            except me.NotUniqueError as ex:
                # Likely the case where the user pastes in two or more valid
                # schedules into the same input box
                logging.info('Duplicate error on UserScheduleItem .save(): %s'
                        % (ex))

            # Add this item to the user's course history
            # FIXME(Sandy): See if we can get program_year_id from Quest
            # Or just increment their last one
            user.add_course(usi.course_id, usi.term_id)

        except KeyError:
            logging.error("Invalid item in uploaded schedule: %s" % (item))

    # Add courses that failed to fully parse, probably due to unavailable times
    for course_id in set(failed_items):
        fsi = m.FailedScheduleItem(
            user_id=user.id,
            course_id=course_id,
            parsed_date=now,
        )

        try:
            fsi.save()
        except me.NotUniqueError as ex:
            # This should never happen since we're iterating over a set
            logging.warn('WTF this should never happen.')
            logging.warn('Duplicate error FailedScheduleItem.save(): %s' % ex)

        user.add_course(course_id, term_id)

    user.schedules_imported += 1
    user.save()

    schedule_screenshot.update_screenshot_async(user)

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_SCHEDULE,
        rmclogger.LOG_EVENT_UPLOAD,
        user.id
    )

    return ''
Пример #12
0
def upload_schedule():
    req = flask.request
    user = view_helpers.get_current_user()

    schedule_data = util.json_loads(req.form.get('schedule_data'))
    processed_items = schedule_data['processed_items']
    failed_items = schedule_data['failed_items']
    term_name = schedule_data['term_name']
    term_id = m.Term.id_from_name(term_name)

    # FIXME TODO(david): Save these in models and display on schedule
    #failed_items = schedule_data['failed_items']

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_API,
        rmclogger.LOG_EVENT_SCHEDULE,
        {
            'schedule_data': schedule_data,
            'term_id': term_id,
            'user_id': user.id,
        },
    )

    now = datetime.now()

    user.last_good_schedule_paste = req.form.get('schedule_text')
    user.last_good_schedule_paste_date = now
    user.save()

    # Remove existing schedule items for the user for the given term
    for usi in m.UserScheduleItem.objects(user_id=user.id, term_id=term_id):
        usi.delete()

    for item in processed_items:
        try:
            # Create this UserScheduleItem
            first_name, last_name = m.Professor.guess_names(item['prof_name'])
            prof_id = m.Professor.get_id_from_name(
                first_name=first_name,
                last_name=last_name,
            )
            if first_name and last_name:
                if not m.Professor.objects.with_id(prof_id):
                    m.Professor(
                        id=prof_id,
                        first_name=first_name,
                        last_name=last_name,
                    ).save()

            usi = m.UserScheduleItem(
                user_id=user.id,
                class_num=item['class_num'],
                building=item['building'],
                room=item.get('room'),
                section_type=item['section_type'].upper(),
                section_num=item['section_num'],
                start_date=datetime.utcfromtimestamp(item['start_date']),
                end_date=datetime.utcfromtimestamp(item['end_date']),
                course_id=item['course_id'],
                prof_id=prof_id,
                term_id=term_id,
            )
            try:
                usi.save()
            except me.NotUniqueError as ex:
                # Likely the case where the user pastes in two or more valid
                # schedules into the same input box
                logging.info(
                    'Duplicate error on UserScheduleItem .save(): %s' % (ex))

            # Add this item to the user's course history
            # FIXME(Sandy): See if we can get program_year_id from Quest
            # Or just increment their last one
            user.add_course(usi.course_id, usi.term_id)

        except KeyError:
            logging.error("Invalid item in uploaded schedule: %s" % (item))

    # Add courses that failed to fully parse, probably due to unavailable times
    for course_id in set(failed_items):
        fsi = m.FailedScheduleItem(
            user_id=user.id,
            course_id=course_id,
            parsed_date=now,
        )

        try:
            fsi.save()
        except me.NotUniqueError as ex:
            # This should never happen since we're iterating over a set
            logging.warn('WTF this should never happen.')
            logging.warn('Duplicate error FailedScheduleItem.save(): %s' % ex)

        user.add_course(course_id, term_id)

    user.schedules_imported += 1
    user.save()

    schedule_screenshot.update_screenshot_async(user)

    rmclogger.log_event(rmclogger.LOG_CATEGORY_SCHEDULE,
                        rmclogger.LOG_EVENT_UPLOAD, user.id)

    return ''
Пример #13
0
def user_course():
    uc_data = util.json_loads(flask.request.data)
    user = view_helpers.get_current_user()

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_API,
        rmclogger.LOG_EVENT_USER_COURSE,
        {
            'uc_data': uc_data,
            'user_id': user.id,
        },
    )

    # Validate request object
    course_id = uc_data.get('course_id')
    term_id = uc_data.get('term_id')
    if course_id is None or term_id is None:
        logging.error("/api/user/course got course_id (%s) and term_id (%s)" %
                      (course_id, term_id))
        # TODO(david): Perhaps we should have a request error function that
        # returns a 400
        raise exceptions.ImATeapot('No course_id or term_id set')

    if not m.UserCourse.can_review(term_id):
        logging.warning("%s attempted to rate %s in future/shortlist term %s" %
                        (user.id, course_id, term_id))
        raise exceptions.ImATeapot(
            "Can't review a course in the future or shortlist")

    # Fetch existing UserCourse
    uc = m.UserCourse.objects(user_id=user.id,
                              course_id=uc_data['course_id'],
                              term_id=uc_data['term_id']).first()

    if uc is None:
        logging.error("/api/user/course User course not found for "
                      "user_id=%s course_id=%s term_id=%s" %
                      (user.id, course_id, term_id))
        # TODO(david): Perhaps we should have a request error function that
        # returns a 400
        raise exceptions.ImATeapot('No user course found')

    orig_points = uc.num_points

    # TODO(Sandy): Consider the case where the user picked a professor and
    # rates them, but then changes the professor. We need to remove the ratings
    # from the old prof's aggregated ratings and add them to the new prof's
    # Maybe create professor if newly added
    if uc_data.get('new_prof_added'):

        new_prof_name = uc_data['new_prof_added']

        # TODO(mack): should do guess_names first, and use that to
        # generate the id
        prof_id = m.Professor.get_id_from_name(new_prof_name)
        uc.professor_id = prof_id

        # TODO(Sandy): Have some kind of sanity check for professor names.
        # Don't allow ridiculousness like "Santa Claus", "aksnlf",
        # "swear words"
        if m.Professor.objects(id=prof_id).count() == 0:
            first_name, last_name = m.Professor.guess_names(new_prof_name)
            m.Professor(
                id=prof_id,
                first_name=first_name,
                last_name=last_name,
            ).save()

        course = m.Course.objects.with_id(uc.course_id)
        course.professor_ids = list(set(course.professor_ids) | {prof_id})
        course.save()

        logging.info("Added new course professor %s (name: %s)" %
                     (prof_id, new_prof_name))
    elif uc_data.get('professor_id'):
        uc.professor_id = uc_data['professor_id']
    else:
        uc.professor_id = None

    now = datetime.now()

    if uc_data.get('course_review'):
        # New course review data
        uc_data['course_review']['comment_date'] = now
        uc.course_review.update(**uc_data['course_review'])

    if uc_data.get('professor_review'):
        # New prof review data
        uc_data['professor_review']['comment_date'] = now
        uc.professor_review.update(**uc_data['professor_review'])

    uc.save()

    points_gained = uc.num_points - orig_points
    user.award_points(points_gained, view_helpers.get_redis_instance())
    user.save()

    return util.json_dumps({
        'professor_review.comment_date':
        uc['professor_review']['comment_date'],
        'course_review.comment_date':
        uc['course_review']['comment_date'],
        'points_gained':
        points_gained,
    })