Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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)
Beispiel #5
0
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,
            }
        }
    })
Beispiel #6
0
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)
Beispiel #7
0
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
Beispiel #8
0
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
            }
        })
Beispiel #9
0
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
Beispiel #10
0
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
        })
Beispiel #11
0
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)
Beispiel #12
0
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)
Beispiel #13
0
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
Beispiel #14
0
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)
Beispiel #15
0
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.",
        })
Beispiel #16
0
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.",
        })
Beispiel #17
0
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
Beispiel #18
0
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})
Beispiel #19
0
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)
Beispiel #20
0
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)
Beispiel #21
0
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)
Beispiel #22
0
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)
Beispiel #23
0
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)
Beispiel #24
0
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)
Beispiel #25
0
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
Beispiel #26
0
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)
Beispiel #27
0
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})
Beispiel #28
0
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")
Beispiel #29
0
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)
Beispiel #30
0
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)