示例#1
0
    def update_task_index_for_upload(
        cls,
        session: SqlASession,
        tablechanges: UploadTableChanges,
        indexed_at_utc: Pendulum,
    ) -> None:
        """
        Updates the index for a device's upload.

        - Deletes index entries for records that are on the way out.
        - Creates index entries for records that are on the way in.
        - Deletes/recreates index entries for records being preserved.

        Args:
            session:
                an SQLAlchemy Session
            tablechanges:
                a :class:`camcops_server.cc_modules.cc_client_api_core.UploadTableChanges`
                object describing the changes to a table
            indexed_at_utc:
                current time in UTC
        """  # noqa
        tasktablename = tablechanges.tablename
        d = tablename_to_task_class_dict()
        try:
            taskclass = d[tasktablename]  # may raise KeyError
        except KeyError:
            fail_user_error(f"Bug: no such task table: {tasktablename!r}")

        # noinspection PyUnresolvedReferences
        idxtable = cls.__table__  # type: Table
        idxcols = idxtable.columns

        # Delete the old.
        delete_index_pks = tablechanges.task_delete_index_pks
        if delete_index_pks:
            log.debug(
                "Deleting old task indexes: {}, server PKs {}",
                tasktablename,
                delete_index_pks,
            )
            # noinspection PyProtectedMember
            session.execute(
                idxtable.delete()
                .where(idxcols.task_table_name == tasktablename)
                .where(idxcols.task_pk.in_(delete_index_pks))
            )

        # Create the new.
        reindex_pks = tablechanges.task_reindex_pks
        if reindex_pks:
            log.debug(
                "Recreating task indexes: {}, server PKs {}",
                tasktablename,
                reindex_pks,
            )
            # noinspection PyUnboundLocalVariable,PyProtectedMember
            q = session.query(taskclass).filter(taskclass._pk.in_(reindex_pks))
            for task in q:
                cls.index_task(task, session, indexed_at_utc=indexed_at_utc)
 def ensure_device_registered(self) -> None:
     """
     Ensure the device is registered. Raises :exc:`UserErrorException`
     on failure.
     """
     if not self.is_device_registered():
         fail_user_error("Unregistered device")
示例#3
0
    def __init__(self, req: "CamcopsRequest") -> None:
        # Check the basics
        if req.method != RequestMethod.POST:
            raise HTTPBadRequest("Must use POST method")
            # ... this is for humans to view, so it has a pretty error

        # Read key things
        self.req = req
        self.operation = req.get_str_param(TabletParam.OPERATION)
        self.device_name = req.get_str_param(TabletParam.DEVICE)
        self.username = req.get_str_param(TabletParam.USER)
        self.password = req.get_str_param(TabletParam.PASSWORD)
        self.session_id = req.get_int_param(TabletParam.SESSION_ID)
        self.session_token = req.get_str_param(TabletParam.SESSION_TOKEN)
        self.tablet_version_str = req.get_str_param(TabletParam.CAMCOPS_VERSION)  # noqa
        try:
            self.tablet_version_ver = make_version(self.tablet_version_str)
        except ValueError:
            fail_user_error("CamCOPS tablet version nonsensical: {!r}".format(
                self.tablet_version_str))

        # Basic security check: no pretending to be the server
        if self.device_name == DEVICE_NAME_FOR_SERVER:
            fail_user_error("Tablets cannot use the device name {!r}".format(
                DEVICE_NAME_FOR_SERVER))
        if self.username == USER_NAME_FOR_SYSTEM:
            fail_user_error("Tablets cannot use the username {!r}".format(
                USER_NAME_FOR_SYSTEM))

        self._device_obj = None  # type: Device

        # Ensure table version is OK
        if self.tablet_version_ver < MINIMUM_TABLET_VERSION:
            fail_user_error(
                "Tablet CamCOPS version too old: is {v}, need {r}".format(
                    v=self.tablet_version_str,
                    r=MINIMUM_TABLET_VERSION))
        # Other version things are done via properties

        # Upload efficiency
        self._dirty_table_names = set()  # type: Set[str]

        # Report
        log.info("Incoming client API connection from IP={i}, port={p}, "
                 "device_name={dn!r}, "
                 # "device_id={di}, "
                 "camcops_version={v}, "
                 "username={u}, operation={o}",
                 i=req.remote_addr,
                 p=req.remote_port,
                 dn=self.device_name,
                 # di=self.device_id,
                 v=self.tablet_version_str,
                 u=self.username,
                 o=self.operation)
 def ensure_valid_user_for_device_registration(self) -> None:
     """
     Ensure the username/password combination is valid for device
     registration. Raises :exc:`UserErrorException` on failure.
     """
     user = self.req.user
     if not user:
         fail_user_error(INVALID_USERNAME_PASSWORD)
     if user.upload_group_id is None:
         fail_user_error(NO_UPLOAD_GROUP_SET + user.username)
     if not user.may_register_devices:
         fail_user_error("User not authorized to register devices for "
                         "selected group")
 def ensure_valid_device_and_user_for_uploading(self) -> None:
     """
     Ensure the device/username/password combination is valid for uploading.
     Raises :exc:`UserErrorException` on failure.
     """
     user = self.req.user
     if not user:
         fail_user_error(INVALID_USERNAME_PASSWORD)
     if user.upload_group_id is None:
         fail_user_error(NO_UPLOAD_GROUP_SET + user.username)
     if not user.may_upload:
         fail_user_error("User not authorized to upload to selected group")
     # Username/password combination found and is valid. Now check device.
     self.ensure_device_registered()
示例#6
0
    def test_client_api_basics(self) -> None:
        self.announce("test_client_api_basics")

        with self.assertRaises(UserErrorException):
            fail_user_error("testmsg")
        with self.assertRaises(ServerErrorException):
            fail_server_error("testmsg")
        with self.assertRaises(UserErrorException):
            fail_unsupported_operation("duffop")

        # Encoding/decoding tests
        # data = bytearray("hello")
        data = b"hello"
        enc_b64data = base64_64format_encode(data)
        enc_hexdata = hex_xformat_encode(data)
        not_enc_1 = "X'012345'"
        not_enc_2 = "64'aGVsbG8='"
        teststring = """one, two, 3, 4.5, NULL, 'hello "hi
            with linebreak"', 'NULL', 'quote''s here', {b}, {h}, {s1}, {s2}"""
        sql_csv_testdict = {
            teststring.format(
                b=enc_b64data,
                h=enc_hexdata,
                s1=sql_quote_string(not_enc_1),
                s2=sql_quote_string(not_enc_2),
            ): [
                "one",
                "two",
                3,
                4.5,
                None,
                'hello "hi\n            with linebreak"',
                "NULL",
                "quote's here",
                data,
                data,
                not_enc_1,
                not_enc_2,
            ],
            "": [],
        }
        for k, v in sql_csv_testdict.items():
            r = decode_values(k)
            self.assertEqual(
                r,
                v,
                "Mismatch! Result: {r!s}\n"
                "Should have been: {v!s}\n"
                "Key was: {k!s}".format(r=r, v=v, k=k),
            )

        # Newline encoding/decodine
        ts2 = (
            "slash \\ newline \n ctrl_r \r special \\n other special \\r "
            "quote ' doublequote \" "
        )
        self.assertEqual(
            unescape_newlines(escape_newlines(ts2)),
            ts2,
            "Bug in escape_newlines() or unescape_newlines()",
        )