Example #1
0
    async def post(self):
        body = self.get_json_body(allow_empty=True) or {}
        phone_number = self.current_user["_phonenumber"]
        user = await self.lookup_user(phone_number=phone_number)
        device_ids = await self.get_device_ids(user=user,
                                               phone_number=phone_number)

        self.validate_claims()

        await self.audit_log(
            phone_numbers=[phone_number],
            person_name="{sub_given_name} {sub_middle_name} {sub_family_name}".
            format(**self.current_user),
            person_id=self.current_user["sub"],
            person_organization="",
        )

        response = await base.response_from_gps_events(
            device_ids=device_ids,
            phone_number=phone_number,
            page_number=max(1, body.get("page_number", 1)),
            per_page=max(0, min(body.get("per_page", 30), 100)),
            time_from=isoformat(body.get("time_from")),
            time_to=isoformat(body.get("time_to")),
            caller="HN",
        )

        self.write(json.dumps(response))
Example #2
0
def get_gps_events(db,
                   device_ids,
                   page_number=1,
                   per_page=30,
                   time_from=None,
                   time_to=None):
    """Retrieve gps events from the database for one or more devices

    Returns gps events as a list of dicts
    """
    if isinstance(device_ids, str):
        device_ids = [device_ids]

    if time_from is None:
        time_from = utils.now_at_utc() - datetime.timedelta(days=90)
    if time_to is None:
        time_to = utils.now_at_utc() + datetime.timedelta(hours=1)

    events = []
    total = 0
    for (
            plaform,
            osversion,
            appversion,
            model,
            time_from,
            time_to,
            latitude,
            longitude,
            accuracy,
            speed,
            speed_accuracy,
            altitude,
            altitude_accuracy,
            total,
    ) in db.execute(
            "{CALL getdatabyUUIDList (?,?,?,?,?)}",
        (",".join(device_ids), time_from, time_to, page_number, per_page),
    ).fetchall():
        # from schema, *all* fields are nullable
        # so make sure that any casting handles None
        event = {
            "time_from": utils.isoformat(time_from),
            "time_to": utils.isoformat(time_to),
            "latitude": cast_nullable_float(latitude),
            "longitude": cast_nullable_float(longitude),
            "accuracy": accuracy,
            "speed": speed,
            "speed_accuracy": speed_accuracy,
            "altitude": altitude,
            "altitude_accuracy": altitude_accuracy,
        }
        events.append(event)
    return events, total
Example #3
0
def get_pin_codes(db, phone_number):
    pin_codes = []
    for (pin_code, created_at,) in db.execute(
        r"SELECT pin, created_at FROM dbo.getPinCodesByPhoneNumber(?)", phone_number
    ).fetchall():
        pin_codes.append(
            {"pin_code": pin_code, "created_at": utils.isoformat(created_at)}
        )
    return pin_codes
Example #4
0
    async def post(self):
        body = self.get_json_body()
        phone_number = body.get("phone_number")
        person_name = body.get("person_name")
        legal_means = body.get("legal_means")
        bad_fields = []
        if person_name == "" or person_name is None:
            bad_fields.append("person_name")
        if legal_means == "" or legal_means is None:
            bad_fields.append("legal_means")
        if len(bad_fields) > 0:
            raise web.HTTPError(
                400,
                "Field(s): {} must be non-empty".format(", ".join(bad_fields)))

        user = await self.lookup_user(phone_number=phone_number)
        device_ids = await self.get_device_ids(user=user,
                                               phone_number=phone_number)

        await self.audit_log(
            phone_numbers=[phone_number],
            person_name=body.get("person_name"),
            legal_means=body.get("legal_means"),
        )

        response = await response_from_gps_events(
            device_ids=device_ids,
            phone_number=phone_number,
            page_number=max(1, body.get("page_number", 1)),
            per_page=max(0, min(body.get("per_page", 30), 100)),
            time_from=utils.isoformat(body.get("time_from")),
            time_to=utils.isoformat(body.get("time_to")),
            caller="FHI",
        )

        self.write(json.dumps(response))
Example #5
0
def get_access_log(
    db,
    phone_number,
    person_name="",
    person_id="",
    person_organization="",
    organization="<none>",
    page_number=1,
    per_page=30,
):
    """Fetch the access log for a given phone number"""
    total = group_total = 0
    app_log.info(f"Getting access log for {utils.mask_phone(phone_number)}")
    events = []
    for (
            timestamp,
            number,
            p_name,
            p_id,
            p_org,
            technical_org,
            legal_means,
            group_total,
            total,
    ) in db.execute(
            "{CALL applogfetch(?, ?, ?, ?, ?, ?, ?)}",
            phone_number,
            person_name,
            person_id,
            person_organization,
            organization,
            page_number,
            per_page,
    ).fetchall():
        person_fields = {}
        if not p_id and p_name.isdigit():
            p_id = p_name
            p_name = ""
            app_log.warning(
                f"Including extra match from old person id record: {timestamp} {p_id}"
            )
        elif p_org is None:
            try:
                person_fields = json.loads(p_name)
            except ValueError:
                pass
            else:
                app_log.warning(
                    f"Including extra match from old JSON record: {timestamp} {person_fields}"
                )
        event = {
            "timestamp": utils.isoformat(timestamp),
            "phone_number": phone_number,
            "person_name": p_name or "",
            "person_organization": p_org or "",
            "person_id": p_id or "",
            "technical_organization": technical_org or "Norsk Helsenett",
            "legal_means": legal_means,
            "count": group_total,
        }
        event.update(person_fields)
        events.append(event)

    return events, total
Example #6
0
    async def post(self):
        body = self.get_json_body()
        phone_number = body["phone_number"]
        user = await self.lookup_user(phone_number)

        await self.audit_log(phone_numbers=[phone_number])

        phone_number = user["displayName"]
        mask_number = user["logName"]

        device_ids = []

        request_id = str(uuid.uuid4())
        app_log.info(
            f"Submitting analysis jobs for {mask_number}: {request_id}")
        request_info = f"lookup:{request_id}:info"
        # create job queue in redis

        db = get_redis()
        result_keys = []

        async for device_id in graph.device_ids_for_user(user):
            device_ids.append(device_id)
            result_key = f"lookup:{request_id}:result:{device_id}"
            result_keys.append(result_key)
            app_log.info(f"Submitting analysis job for {device_id}")
            job = {
                "request_id": request_id,
                "device_id": device_id,
                "result_key": result_key,
                "time_from": utils.isoformat(body.get("time_from")),
                "time_to": utils.isoformat(body.get("time_to")),
                "expiry": LOOKUP_RESULT_EXPIRY,
            }
            db.rpush(REDIS_JOBQUEUE_NAME, json.dumps(job).encode("utf8"))
            # push device id onto job queue
        if not device_ids:
            app_log.info(f"Phone number {mask_number} has no devices")
            self.set_status(404)
            self.write(
                json.dumps({
                    "phone_number": phone_number,
                    "found_in_system": False
                }))
            return

        app_log.info(f"Storing request info for {request_id}")
        db.set(
            request_info,
            json.dumps({
                "phone_number": phone_number,
                "result_keys": result_keys,
                "device_ids": device_ids,
            }),
            ex=LOOKUP_RESULT_EXPIRY,
        )

        self.set_status(202)
        self.write(
            json.dumps({
                "request_id":
                request_id,
                # todo: figure out how to get this right?
                # /fhi/ prefix isn't available from APIM
                # but it's wrong when not behind APIM
                "result_url":
                f"https://{self.request.host}/fhi/lookup/{request_id}",
                "result_expires":
                utils.isoformat(utils.now_at_utc() + datetime.timedelta(
                    seconds=LOOKUP_RESULT_EXPIRY)),
            }))
Example #7
0
    async def get(self, request_id):
        app_log.info(f"Looking up result for {request_id}")
        request_info = f"lookup:{request_id}:info"
        db = get_redis()
        item = db.get(request_info)
        if not item:
            raise web.HTTPError(404, "No such request")

        info = json.loads(item.decode("utf8"))
        device_ids = info["device_ids"]
        phone_number = info["phone_number"]

        # TODO: is it worth logging retrieval in audit log separately request?
        # without separate auth, this isn't useful
        mask_number = utils.mask_phone(phone_number)
        result_keys = info["result_keys"]
        num_ready = db.exists(*result_keys)
        progress = f"{num_ready}/{len(result_keys)}"
        if num_ready < len(info["result_keys"]):
            app_log.info(
                f"Lookup request {request_id} not ready yet: {progress}")
            self.set_status(202)
            self.write(
                json.dumps({
                    "message":
                    f"Not finished processing (completed {progress} tasks)"
                }))
            return

        app_log.info(f"Lookup request {request_id} complete: {progress}")

        # we are done! Collect and return the report
        results = [
            json.loads(item.decode("utf8")) for item in db.mget(*result_keys)
        ]
        contacts = []

        for result in results:
            if result["status"] != "success":
                app_log.error(
                    f"Error processing {result['device_id']}: {result['message']}"
                )
                raise web.HTTPError(
                    500,
                    "Error in analysis pipeline. Please report the input parameters to the analysis team.",
                )
            if not result["result"]:
                app_log.info(f"Empty result for {result['device_id']}")
                continue
            device_result = result["result"]
            contact = {}
            for device_id, contact_info in device_result.items():
                contact_number = await graph.phone_number_for_device_id(
                    device_id)
                if contact_number:
                    if contact_number == phone_number:
                        app_log.warning(
                            f"Omitting contact with self for {mask_number}")
                    else:
                        await self.audit_log(phone_numbers=[contact_number])
                        if pin.PIN_ENABLED:
                            pin_code = await fetch_pin(
                                phone_number=contact_number)
                            contact_info["pin_code"] = pin_code
                        contact[contact_number] = contact_info
                else:
                    app_log.warning(
                        f"Omitting contact for {device_id} with no phone number"
                    )
            if contact:
                contacts.append(contact)

        app_log.info(f"Checking {len(device_ids)} devices for activity")
        last_activity = None
        for device_id in device_ids:
            # use get_device here, not get_devices
            # because get_devices doesn't report last activity accurately
            try:
                device = await devices.get_device(device_id)
            except Exception as e:
                if "not found" in str(e).lower():
                    app_log.warning(f"Device {device_id} not in IoTHub")
                    continue
                else:
                    raise
            device_last_activity = parse_date(device["lastActivityTime"])
            if device_last_activity < devices.before_times:
                app_log.info(
                    f"Device {device['deviceId']} appears to have no activity")
                continue
            if last_activity is None or device_last_activity >= last_activity:
                last_activity = device_last_activity

        self.write(
            json.dumps({
                "phone_number": phone_number,
                "found_in_system": True,
                "last_activity": utils.isoformat(last_activity),
                "contacts": contacts,
            }))