def get_survey_max_timestamp(request, *args, **kwargs): try: survey_id = request.GET["survey_id"] except KeyError: response = JsonResponse({ "ok": False, "error": "Please specify a survey_id." }) response.status_code = 400 return response api = OccupEyeApi() try: (survey_id_int, max_timestamp) = api.get_max_survey_timestamp(survey_id) except BadOccupEyeRequest: response = JsonResponse({ "ok": False, "error": "The survey_id you specified was not valid." }) response.status_code = 400 return response response = JsonResponse( { "ok": True, "survey_id": survey_id_int, "last_updated": max_timestamp }, custom_header_data=kwargs) return response
def get_survey_sensors(request, *args, **kwargs): try: survey_id = request.GET["survey_id"] except KeyError: response = JsonResponse({ "ok": False, "error": "Please specify a survey_id." }) response.status_code = 400 return response # # Check if state data should be returned # try: # return_states = request.GET["return_states"].lower() == "true" # except KeyError: # return_states = False api = OccupEyeApi() try: data = api.get_survey_sensors(survey_id # return_states=return_states ) except BadOccupEyeRequest: response = JsonResponse({ "ok": False, "error": "The survey_id you specified was not valid." }) response.status_code = 400 return response response = JsonResponse({"ok": True, **data}) return response
def get_survey_sensors(request, *args, **kwargs): try: survey_id = request.GET["survey_id"] except KeyError: response = JsonResponse({ "ok": False, "error": "Please specify a survey_id." }) response.status_code = 400 return response api = OccupEyeApi() try: data = api.get_survey_sensors(survey_id) except BadOccupEyeRequest: response = JsonResponse({ "ok": False, "error": "The survey_id you specified was not valid." }) response.status_code = 400 return response response = JsonResponse({"ok": True, **data}, custom_header_data=kwargs) return response
def userdeny(request): signer = TimestampSigner() try: signed_data = request.POST.get("signed_app_data") raw_data_str = signer.unsign(signed_data, max_age=300) except: response = PrettyJsonResponse({ "ok": False, "error": ("The signed data received was invalid." " Please try the login process again. " "If this issue persists, please contact support.") }) response.status_code = 400 return response try: data = json.loads(raw_data_str) except: response = PrettyJsonResponse({ "ok": False, "error": ("The JSON data was not in the expected format." " Please contact support.") }) response.status_code = 400 return response # We can trust this value because it came from a signed dictionary app = App.objects.get(client_id=data["client_id"]) state = data["state"] redir = "{}?result=denied&state={}".format(app.callback_url, state) # Now check if a token has been granted in the past. If so, invalidate it. # There shouldn't be a situation where more than one user/app token pair # exists but, just in case, let's invalidate them all. try: users = User.objects.filter(employee_id=data["user_upi"]) user = users[0] except (User.DoesNotExist, KeyError): response = PrettyJsonResponse({ "ok": False, "error": "User does not exist. This should never occur. " "Please contact support." }) response.status_code = 400 return response tokens = OAuthToken.objects.filter(app=app, user=user) for token in tokens: token.active = False token.save() # Send the user to the app's denied permission page return redirect(redir)
def create_app(request): if request.method != "POST": response = PrettyJsonResponse({ "success": False, "error": "Request is not of method POST" }) response.status_code = 400 return response try: name = request.POST["name"] user_id = request.session["user_id"] except (KeyError, AttributeError): response = PrettyJsonResponse({ "success": False, "message": "Request does not have name or user." }) response.status_code = 400 return response user = get_user_by_id(user_id) new_app = App(name=name, user=user) new_app.save() keen_add_event.delay("App created", { "appid": new_app.id, "name": new_app.name, "userid": user.id }) s = Scopes() return PrettyJsonResponse({ "success": True, "message": "App sucessfully created", "app": { "name": new_app.name, "id": new_app.id, "token": new_app.api_token, "created": new_app.created, "updated": new_app.last_updated, "oauth": { "client_id": new_app.client_id, "client_secret": new_app.client_secret, "callback_url": new_app.callback_url, "scopes": s.get_all_scopes() }, "webhook": { "verification_secret": new_app.webhook.verification_secret, } } })
def authorise(request): client_id = request.GET.get("client_id", None) state = request.GET.get("state", None) if not (client_id and state): response = PrettyJsonResponse({ "ok": False, "error": "incorrect parameters supplied" }) response.status_code = 400 return response try: # We only allow the process to happen if the app exists and has not # been flagged as deleted app = App.objects.filter(client_id=client_id, deleted=False)[0] except IndexError: response = PrettyJsonResponse({ "ok": False, "error": "App does not exist for client id" }) response.status_code = 400 return response if app.callback_url is None or app.callback_url.strip() == "": response = PrettyJsonResponse({ "ok": False, "error": ("This app does not have a callback URL set. " "If you are the developer of this app, " "please ensure you have set a valid callback " "URL for your application in the Dashboard. " "If you are a user, please contact the app's " "developer to rectify this.") }) response.status_code = 400 return response # Sign the app and state pair before heading to Shibboleth to help protect # against CSRF and XSS attacks signer = TimestampSigner() data = app.client_id + state signed_data = signer.sign(data) # Build Shibboleth callback URL url = os.environ.get("SHIBBOLETH_ROOT") + "/Login?target=" target = request.build_absolute_uri( "/oauth/shibcallback?appdata={}".format(signed_data)) target = quote(target) url += target # Send the user to Shibboleth to log in return redirect(url)
def get_map_image(request, *args, **kwargs): try: image_id = request.GET['image_id'] except KeyError: response = JsonResponse({ "ok": False, "error": "No Image ID provided." }, custom_header_data=kwargs) response.status_code = 400 return response api = OccupEyeApi() try: (image_b64, content_type) = api.get_image(image_id) except BadOccupEyeRequest: response = JsonResponse( { "ok": False, "error": ("The image with the ID you requested " "does not exist.") }, custom_header_data=kwargs) response.status_code = 400 return response image_format = request.GET.get("image_format", "base64") if image_format == "raw": return HttpResponse(content=b64decode(image_b64), custom_header_data=kwargs, content_type=content_type) elif image_format == "base64": response = JsonResponse( { "ok": True, "content_type": content_type, "data": image_b64 }, custom_header_data=kwargs) return response else: response = JsonResponse( { "ok": False, "error": ("You specified a response format that " "was not either raw or base64.") }, custom_header_data=kwargs) response.status_code = 400 return response
def regenerate_app_token(request): if request.method != "POST": response = PrettyJsonResponse({ "success": False, "error": "Request is not of method POST" }) response.status_code = 400 return response try: app_id = request.POST["app_id"] user_id = request.session["user_id"] except (KeyError, AttributeError): response = PrettyJsonResponse({ "success": False, "message": "Request does not have an app_id." }) response.status_code = 400 return response user = get_user_by_id(user_id) apps = App.objects.filter(id=app_id, user=user) if len(apps) == 0: response = PrettyJsonResponse({ "success": False, "message": "App does not exist." }) response.status_code = 400 return response else: app = apps[0] app.regenerate_token() new_api_token = app.api_token keen_add_event.delay("App token regenerated", { "appid": app.id, "userid": user.id }) return PrettyJsonResponse({ "success": True, "message": "App token sucessfully regenerated.", "app": { "id": app.id, "token": new_api_token, "date": app.last_updated } })
def deauthorise_app(request): # Find which user is requesting to deauthorise an app user = User.objects.get(id=request.session["user_id"]) # Find the app that the user wants to deauthorise client_id = request.GET.get("client_id", None) if client_id is None: response = PrettyJsonResponse({ "ok": False, "error": "A Client ID must be provided to deauthorise an app." }) response.status_code = 400 return response try: # We only allow the process to happen if the app exists and has not # been flagged as deleted app = App.objects.filter(client_id=client_id, deleted=False)[0] except IndexError: response = PrettyJsonResponse({ "ok": False, "error": "App does not exist with the Client ID provided." }) response.status_code = 400 return response try: token = OAuthToken.objects.get(app=app, user=user) except OAuthToken.DoesNotExist: response = PrettyJsonResponse({ "ok": False, "error": ("The app with the Client ID provided does not have a " "token for this user, so no action was taken.") }) response.status_code = 400 return response token.delete() response = PrettyJsonResponse({ "ok": True, "message": "App successfully deauthorised." }) response.status_code = 200 return response
def rename_app(request): if request.method != "POST": response = PrettyJsonResponse({ "success": False, "error": "Request is not of method POST" }) response.status_code = 400 return response try: app_id = request.POST["app_id"] new_name = request.POST["new_name"] user_id = request.session["user_id"] except (KeyError, AttributeError): response = PrettyJsonResponse({ "success": False, "message": "Request does not have app_id/new_name" }) response.status_code = 400 return response user = get_user_by_id(user_id) apps = App.objects.filter(id=app_id, user=user, deleted=False) if len(apps) == 0: response = PrettyJsonResponse({ "success": False, "message": "App does not exist." }) response.status_code = 400 return response else: app = apps[0] app.name = new_name app.save() keen_add_event.delay("App renamed", { "appid": app.id, "new_name": app.name, "userid": user.id }) return PrettyJsonResponse({ "success": True, "message": "App sucessfully renamed.", "date": app.last_updated })
def get_pc_availability(request, *args, **kwargs): try: r = requests.get(os.environ["PCA_LINK"]) except requests.exceptions.MissingSchema: resp = JsonResponse( { "ok": False, "error": ("Could not retrieve availability data." " Please try again later or contact us for support.") }, rate_limiting_data=kwargs) resp.status_code = 400 return resp try: e = etree.fromstring(r.content) except (ValueError, etree.XMLSyntaxError): resp = JsonResponse( { "ok": False, "error": ("Could not parse the desktop availability data." " Please try again later or contact us for support.") }, rate_limiting_data=kwargs) resp.status_code = 400 return resp data = [] for pc in e.findall("room"): _ = pc.get data.append({ "location": { "roomname": _("location"), "room_id": _("rid"), "latitude": _("latitude"), "longitude": _("longitude"), "building_name": _("buildingName"), "address": _("buildingAddress"), "postcode": _("buildingPostcode") }, "free_seats": _("free"), "total_seats": _("seats"), "room_status": _("info") }) return JsonResponse({"ok": True, "data": data}, rate_limiting_data=kwargs)
def get_department_modules(request, *args, **kwargs): """ Returns all modules taught by a particular department. """ try: department_id = request.GET["department"] except KeyError: response = JsonResponse({ "ok": False, "error": "Supply a Department ID using the department parameter." }, rate_limiting_data=kwargs) response.status_code = 400 return response modules = {"ok": True, "modules": []} lock = Lock.objects.all()[0] m = ModuleA if lock.a else ModuleB for module in m.objects.filter(owner=department_id, setid=_SETID): modules["modules"].append({ "module_id": module.moduleid, "name": module.name, "module_code": module.linkcode, "class_size": module.csize }) return JsonResponse(modules, rate_limiting_data=kwargs)
def get_modules_timetable_endpoint(request, *args, **kwargs): module_ids = request.GET.get("modules") if module_ids is None: return JsonResponse({ "ok": False, "error": "No module IDs provided." }, custom_header_data=kwargs) try: modules = module_ids.split(',') except ValueError: return JsonResponse( { "ok": False, "error": "Invalid module IDs provided." }, custom_header_data=kwargs) date_filter = request.GET.get("date_filter") custom_timetable = get_custom_timetable(modules, date_filter) if custom_timetable: response_json = {"ok": True, "timetable": custom_timetable} return JsonResponse(response_json, custom_header_data=kwargs) else: response_json = { "ok": False, "error": "One or more invalid Module IDs supplied." } response = JsonResponse(response_json, custom_header_data=kwargs) response.status_code = 400 return response
def get_department_courses_endpoint(request, *args, **kwargs): """ Returns all the courses in UCL with relevant ID """ try: department_id = request.GET["department"] except KeyError: response = JsonResponse( { "ok": False, "error": "No department ID provided." }, custom_header_data=kwargs) response.status_code = 400 return response courses = {"ok": True, "courses": []} for course in Course.objects.filter(owner=department_id, setid=_SETID, linkcode="YY"): courses["courses"].append({ "course_name": course.name, "course_id": course.courseid, "years": course.numyears }) return JsonResponse(courses, custom_header_data=kwargs)
def delete_app(request): if request.method != "POST": response = PrettyJsonResponse({ "success": False, "error": "Request is not of method POST" }) response.status_code = 400 return response try: app_id = request.POST["app_id"] user_id = request.session["user_id"] except KeyError: response = PrettyJsonResponse({ "success": False, "message": "Request does not have app_id." }) response.status_code = 400 return response user = get_user_by_id(user_id) apps = App.objects.filter(id=app_id, user=user) if len(apps) == 0: response = PrettyJsonResponse({ "success": False, "message": "App does not exist." }) response.status_code = 400 return response else: app = apps[0] app.deleted = True app.save() keen_add_event.delay("App deleted", { "appid": app_id, "userid": user.id }) return PrettyJsonResponse({ "success": True, "message": "App sucessfully deleted.", })
def delete_app(request): if request.method != "POST": response = PrettyJsonResponse({ "success": False, "error": "Request is not of method POST" }) response.status_code = 400 return response try: app_id = request.POST["app_id"] user_id = request.session["user_id"] except (KeyError, AttributeError): response = PrettyJsonResponse({ "success": False, "message": "Request does not have an app_id." }) response.status_code = 400 return response user = get_user_by_id(user_id) apps = App.objects.filter(id=app_id, user=user) if len(apps) == 0: response = PrettyJsonResponse({ "success": False, "message": "App does not exist." }) response.status_code = 400 return response else: app = apps[0] app.deleted = True webhook = app.webhook webhook.url = "" webhook.siteid = "" webhook.roomid = "" webhook.contact = "" webhook.enabled = False webhook.save() app.save() return PrettyJsonResponse({ "success": True, "message": "App sucessfully deleted.", })
def get_survey_sensors_summary(request, *args, **kwargs): survey_ids = request.GET.get("survey_ids", None) survey_filter = request.GET.get("survey_filter", "student") consts = OccupEyeConstants() if survey_filter not in consts.VALID_SURVEY_FILTERS: response = JsonResponse( { "ok": False, "error": ("The survey filter you provided is invalid. " "Valid survey filters are: ") + str(consts.VALID_SURVEY_FILTERS) }, custom_header_data=kwargs) response.status_code = 400 return response api = OccupEyeApi() try: data = api.get_survey_sensors_summary(survey_ids, survey_filter) except BadOccupEyeRequest: response = JsonResponse( { "ok": False, "error": ("One or more of the survey_ids you requested is not valid.") }, custom_header_data=kwargs) response.status_code = 400 return response response = JsonResponse({ "ok": True, "surveys": data }, custom_header_data=kwargs) return response
def refresh_verification_secret(request): if request.method != "POST": response = PrettyJsonResponse({ "ok": False, "message": ("Request is not of method POST") }) response.status_code = 400 return response try: app_id = request.POST["app_id"] user_id = request.session["user_id"] except KeyError: response = PrettyJsonResponse({ "ok": False, "message": ("Request is missing parameters. Should have app_id" " as well as a sessionid cookie") }) response.status_code = 400 return response if not user_owns_app(user_id, app_id): response = PrettyJsonResponse({ "ok": False, "message": ("App does not exist or user is lacking permission.") }) response.status_code = 400 return response app = App.objects.get(id=app_id) webhook = app.webhook new_secret = generate_secret() webhook.verification_secret = new_secret webhook.save() return PrettyJsonResponse({"ok": True, "new_secret": new_secret})
def get_student_number(request, *args, **kwargs): token = kwargs['token'] try: student_data = get_student_by_upi(token.user.employee_id) except IndexError: response = PrettyJsonResponse( { "ok": False, "error": "User is not a student." }, custom_header_data=kwargs) response.status_code = 400 return response data = {"ok": True, "student_number": student_data.studentid} return PrettyJsonResponse(data, custom_header_data=kwargs)
def people(request, *args, **kwargs): """ Backend for the /search endpoint. Provided a query will search for people in the UCL database with an attribute such as name or e-mail that matches the search parameter. """ if "query" not in request.GET: response = JsonResponse({ "ok": False, "error": "No query provided." }, custom_header_data=kwargs) response.status_code = 400 return response query = request.GET["query"] url = ("{}?{}={}".format( os.environ["SEARCH_API_URL"], os.environ["SEARCH_API_QUERY_PARAMS"], query, )) r = requests.get(url) results = r.json()["response"]["resultPacket"]["results"][:20] def serialize_person(person): return { "name": person["title"], "department": person["metaData"].get("7", ""), "email": person["metaData"].get("E", ""), "status": person["metaData"].get("g", ""), } people = [serialize_person(person) for person in results] return JsonResponse({ "ok": True, "people": people }, custom_header_data=kwargs)
def get_department_modules_endpoint(request, *args, **kwargs): """ Returns all modules taught by a particular department. """ try: department_id = request.GET["department"] except KeyError: response = JsonResponse( { "ok": False, "error": "Supply a Department ID using the department parameter." }, custom_header_data=kwargs) response.status_code = 400 return response modules = {"ok": True, "modules": get_departmental_modules(department_id)} return JsonResponse(modules, custom_header_data=kwargs)
def get_surveys(request, *args, **kwargs): api = OccupEyeApi() consts = OccupEyeConstants() survey_filter = request.GET.get("survey_filter", "student") if survey_filter not in consts.VALID_SURVEY_FILTERS: response = JsonResponse( { "ok": False, "error": ("The survey filter you provided is invalid. " "Valid survey filters are: ") + str(consts.VALID_SURVEY_FILTERS) }, custom_header_data=kwargs) response.status_code = 400 return response response_data = {"ok": True, "surveys": api.get_surveys(survey_filter)} return JsonResponse(response_data, custom_header_data=kwargs)
def get_department_courses(request, *args, **kwargs): """ Returns all the courses in UCL with relevant ID """ try: department_id = request.GET["department"] except KeyError: response = JsonResponse({ "ok": False, "error": "Supply a Department ID using the department parameter." }, rate_limiting_data=kwargs) response.status_code = 400 return response courses = {"ok": True, "courses": []} for course in Course.objects.filter(owner=department_id, setid=_SETID): courses["courses"].append({ "course_name": course.name, "course_id": course.courseid, "years": course.numyears }) return JsonResponse(courses, rate_limiting_data=kwargs)
def people(request, *args, **kwargs): if "query" not in request.GET: response = JsonResponse({ "ok": False, "error": "No query provided." }, rate_limiting_data=kwargs) response.status_code = 400 return response query = request.GET["query"] url = ("{}?{}={}".format( os.environ["SEARCH_API_URL"], os.environ["SEARCH_API_QUERY_PARAMS"], query, )) r = requests.get(url) results = r.json()["response"]["resultPacket"]["results"][:20] def serialize_person(person): return { "name": person["title"], "department": person["metaData"].get("7", ""), "email": person["metaData"].get("E", ""), "status": person["metaData"].get("g", ""), } people = [serialize_person(person) for person in results] return JsonResponse({ "ok": True, "people": people }, rate_limiting_data=kwargs)
def get_survey_sensors_summary(request, *args, **kwargs): survey_ids = request.GET.get("survey_ids", None) api = OccupEyeApi() try: data = api.get_survey_sensors_summary(survey_ids) except BadOccupEyeRequest: response = JsonResponse( { "ok": False, "error": ("One or more of the survey_ids you requested is not valid.") }, custom_header_data=kwargs) response.status_code = 400 return response response = JsonResponse({ "ok": True, "surveys": data }, custom_header_data=kwargs) return response
def get_course_modules_endpoint(request, *args, **kwargs): """ Returns all modules taught on a particular course. """ try: course_id = request.GET["course"] except KeyError: response = JsonResponse( { "ok": False, "error": "No course ID provided." }, custom_header_data=kwargs) response.status_code = 400 return response if not validate_amp_query_params(request.query_params): response = JsonResponse( { "ok": False, "error": "Given parameter is not of corrrect type" }, custom_header_data=kwargs) response.status_code = 400 return response if request.query_params.get('only_compulsory'): try: strtobool(request.query_params.get('only_compulsory')) except ValueError: response = JsonResponse( { "ok": False, "error": "Given parameter is not of correct type" }, custom_header_data=kwargs) response.status_code = 400 return response if request.query_params.get('only_available'): try: strtobool(request.query_params.get('only_available')) except ValueError: response = JsonResponse( { "ok": False, "error": "Given parameter is not of correct type" }, custom_header_data=kwargs) response.status_code = 400 return response if (request.query_params.get('only_available') and request.query_params.get('only_compulsory')): if (strtobool(request.query_params.get('only_available')) and strtobool(request.query_params.get('only_compulsory'))): response = JsonResponse( { "ok": False, "error": "only_available and only_compulsory" " cannot both be true" }, custom_header_data=kwargs) response.status_code = 400 return response modules = { "ok": True, "modules": get_course_modules(course_id, request.query_params) } return JsonResponse(modules, custom_header_data=kwargs)
def shibcallback(request): # Callback from Shib login. Get ALL the meta! appdata_signed = request.GET.get("appdata", None) if not appdata_signed: response = PrettyJsonResponse({ "ok": False, "error": ("No signed app data returned from Shibboleth." " Please use the authorise endpoint.") }) response.status_code = 400 return response signer = TimestampSigner() try: # Expire our signed tokens after five minutes for added security appdata = signer.unsign(appdata_signed, max_age=300) except signing.SignatureExpired: response = PrettyJsonResponse({ "ok": False, "error": ("Login data has expired. Please attempt to log in " "again. If the issues persist please contact the " "UCL API Team to rectify this.") }) response.status_code = 400 return response except signing.BadSignature: response = PrettyJsonResponse({ "ok": False, "error": ("Bad signature. Please attempt to log in again. " "If the issues persist please contact the UCL API " "Team to rectify this.") }) response.status_code = 400 return response client_id = appdata[:33] state = appdata[33:] # We can trust this value because it was extracted from the signed data # string sent via Shibboleth app = App.objects.get(client_id=client_id) # Sometimes UCL doesn't give us the expected headers. # If a critical header is missing we error out. # If non-critical headers are missing we simply put a placeholder string. try: # This is used to find the correct user eppn = request.META['HTTP_EPPN'] # We don't really use cn but because it's unique in the DB we can't # really put a place holder value. cn = request.META['HTTP_CN'] # (aka UPI), also unique in the DB employee_id = request.META['HTTP_EMPLOYEEID'] except KeyError: response = PrettyJsonResponse({ "ok": False, "error": ("UCL has sent incomplete headers. If the issues persist" "please contact the UCL API Team to rectify this.") }) response.status_code = 400 return response # TODO: Ask UCL what on earth are they doing by missing out headers, and # remind them we need to to be informed of these types of changes. # TODO: log to sentry that fields were missing... department = request.META.get('HTTP_DEPARTMENT', '') given_name = request.META.get('HTTP_GIVENNAME', '') display_name = request.META.get('HTTP_DISPLAYNAME', '') groups = request.META.get('HTTP_UCLINTRANETGROUPS', '') # We check whether the user is a member of any UCL Intranet Groups. # This is a quick litmus test to determine whether they should be able to # use an OAuth application. # We deny access to alumni, which does not have this Shibboleth attribute. # Test accounts also do not have this attribute, but we can check the # department attribute for the Shibtests department. # This lets App Store reviewers log in to apps that use the UCL API. if not groups: if department == "Shibtests" or eppn == SHIB_TEST_USER: groups = "shibtests" else: response = HttpResponse( ("Error 403 - denied. <br>" "Unfortunately, alumni are not permitted to use UCL Apps.")) response.status_code = 403 return response # If a user has never used the API before then we need to sign them up try: # TODO: Handle MultipleObjectsReturned exception. # email field isn't unique at database level (on our side). # Alternatively, switch to employee_id (which is unique). user = User.objects.get(email=eppn) except User.DoesNotExist: # create a new user user = User(email=eppn, full_name=display_name, given_name=given_name, department=department, cn=cn, raw_intranet_groups=groups, employee_id=employee_id) user.save() else: # User exists already, so update the values if new ones are non-empty. user = User.objects.get(email=eppn) user.employee_id = employee_id if display_name: user.full_name = display_name if given_name: user.given_name = given_name if department: user.department = department if groups: user.raw_intranet_groups = groups user.save() # Log the user into the system using their User ID request.session["user_id"] = user.id signer = TimestampSigner() response_data = { "client_id": app.client_id, "state": state, "user_upi": user.employee_id } response_data_str = json.dumps(response_data, cls=DjangoJSONEncoder) response_data_signed = signer.sign(response_data_str) s = Scopes() page_data = { "app_name": app.name, "creator": app.user.full_name, "client_id": app.client_id, "state": state, "scopes": s.scope_dict(app.scope.scope_number), "user": { "full_name": user.full_name, "cn": user.cn, "email": user.email, "department": user.department, "upi": user.employee_id }, "signed_data": response_data_signed } initial_data = json.dumps(page_data, cls=DjangoJSONEncoder) return render(request, 'permissions.html', {'initial_data': initial_data})
def myapps_shibboleth_callback(request): # should auth user login or signup # then redirect to my apps homepage # Sometimes UCL doesn't give us the expected headers. # If a critical header is missing we error out. # If non-critical headers are missing we simply put a placeholder string. try: # This is used to find the correct user eppn = request.META['HTTP_EPPN'] # We don't really use cn but because it's unique in the DB we can't # really put a place holder value. cn = request.META['HTTP_CN'] # (aka UPI), also unique in the DB employee_id = request.META['HTTP_EMPLOYEEID'] except KeyError: response = PrettyJsonResponse({ "ok": False, "error": ("UCL has sent incomplete headers. If the issues persist" "please contact the UCL API Team to rectify this.") }) response.status_code = 400 return response # TODO: Ask UCL what on earth are they doing by missing out headers, and # remind them we need to to be informed of these types of changes. # TODO: log to sentry that fields were missing... department = request.META.get('HTTP_DEPARTMENT', '') given_name = request.META.get('HTTP_GIVENNAME', '') display_name = request.META.get('HTTP_DISPLAYNAME', '') groups = request.META.get('HTTP_UCLINTRANETGROUPS', '') try: user = User.objects.get(email=eppn) # TODO: Handle MultipleObjectsReturned exception. # email field isn't unique at database level (on our side). # Alternatively, switch to employee_id (which is unique). except User.DoesNotExist: # create a new user new_user = User(email=eppn, full_name=display_name, given_name=given_name, department=department, cn=cn, raw_intranet_groups=groups, employee_id=employee_id) new_user.save() request.session["user_id"] = new_user.id else: # User exists already, so update the values if new ones are non-empty. user = User.objects.get(email=eppn) user.employee_id = employee_id if display_name: user.full_name = display_name if given_name: user.given_name = given_name if department: user.department = department if groups: user.raw_intranet_groups = groups user.save() return redirect("/oauth/myapps")
def token(request): code = get_var(request, "code") client_id = get_var(request, "client_id") client_secret = get_var(request, "client_secret") if not code or not client_id or not client_secret: response = PrettyJsonResponse({ "ok": False, "error": ("The client did not provide" " the requisite data to get a token.") }) response.status_code = 400 return response r = redis.Redis(host=REDIS_UCLAPI_HOST) try: data_json = r.get(code).decode('ascii') except: response = PrettyJsonResponse({ "ok": False, "error": ("The code received was invalid, or has expired." " Please try again.") }) response.status_code = 400 return response # Remove code from Redis once used to protect against replay attacks. # This is in a try...except to prevent against the edge case when the # code has expired between getting and deleting. try: r.delete(code) except: pass data = json.loads(data_json) client_id = data["client_id"] state = data["state"] upi = data["upi"] try: app = App.objects.filter(client_id=client_id, deleted=False)[0] except IndexError: response = PrettyJsonResponse({ "ok": False, "error": "App has been deleted or the Client ID is invalid." }) response.status_code = 400 return response if app.client_secret != client_secret: response = PrettyJsonResponse({ "ok": False, "error": "Client secret incorrect" }) response.status_code = 400 return response user = User.objects.get(employee_id=upi) # Since the data has passed verification at this point, and we have # checked the validity of the client secret, we can # now generate an OAuth access token for the user. # But first, we should check if a token has been generated already. # If a token does already exist then we should not add yet another one to # the database. We can just pass those keys to the app # again (in case it has lost them). try: token = OAuthToken.objects.get(app=app, user=user) # If the code gets here then the user has used this app before, # so let's check that the scope does # not need changing if not token.scope.scopeIsEqual(app.scope): # Remove the current scope from the token token.scope.delete() # Clone the scope of the app app_scope = app.scope app_scope.id = None app_scope.save() # Assign the new scope to the token token.scope = app_scope # Save the token with the new scope token.save() # If the user has denied this app access before and invalidated a token # then let's re-enabled that token because access is permitted again. if token.active is False: token.active = True token.save() except OAuthToken.DoesNotExist: # The user has never logged in before so let's clone the scope and # create a brand new OAuth token # Clone the scope defined in the app model app_scope = app.scope app_scope.id = None app_scope.save() # Now set up a new token with that scope token = OAuthToken(app=app, user=user, scope=app_scope) token.save() # Now that we have a token we can pass one back to the app # We sincerely hope they'll save this token! # The app can use the token to pull in any personal data (name, UPI, etc.) # later on, so we won't bother to give it to them just yet. s = Scopes() oauth_data = { "ok": True, "state": state, "client_id": app.client_id, "token": token.token, "access_token": token.token, "scope": json.dumps(s.scope_dict(token.scope.scope_number)) } return PrettyJsonResponse(oauth_data)
def userallow(request): signer = TimestampSigner() try: raw_data_str = signer.unsign(request.POST.get("signed_app_data"), max_age=300) except (signing.BadSignature, KeyError): response = PrettyJsonResponse({ "ok": False, "error": ("The signed data received was invalid." " Please try the login process again." " If this issue persists, please contact support.") }) response.status_code = 400 return response try: data = json.loads(raw_data_str) except ValueError: response = PrettyJsonResponse({ "ok": False, "error": ("The JSON data was not in the expected format." " Please contact support.") }) response.status_code = 400 return response # We can trust this app value because it was sent from a signed # data dictionary app = App.objects.get(client_id=data["client_id"]) state = data["state"] # Now we have the data we need to generate a random code and # store it in redis along with the request properties. # Once the client is redirected to they can make a request # with that code to obtain an OAuth token. This can then # be used to obtain User Data. code = generate_random_verification_code() r = redis.Redis(host=REDIS_UCLAPI_HOST) verification_data = { "client_id": app.client_id, "state": state, "upi": data["user_upi"] } verification_data_str = json.dumps(verification_data, cls=DjangoJSONEncoder) # Store this verification data in redis so that it can be obtained later # when the client wants to swap the code for a token. # The code will only be valid for 90 seconds after which redis will just # drop it and the process will be invalidated. r.set(code, verification_data_str, ex=90) # Now redirect the user back to the app, at long last. # Just in case they've tried to be super clever and host multiple apps with # the same callback URL, we'll provide the client ID along with the state return redirect(app.callback_url + "?result=allowed&code=" + code + "&client_id=" + app.client_id + "&state=" + state)