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
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
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 ''
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
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
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
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
def test_json_loads(self): self.assertEquals({'foo': 1}, util.json_loads('{"foo":1}'))
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, })
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 ''
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 ''
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, })