Ejemplo n.º 1
0
    def delete(self, uuid1: str, uuid2: str, user: User) -> Response:

        graph = neo4j.get_instance()

        phenotype1 = graph.Phenotype.nodes.get_or_none(uuid=uuid1)
        if phenotype1 is None:
            raise NotFound(PHENOTYPE_NOT_FOUND)

        study = phenotype1.defined_in.single()
        self.verifyStudyAccess(study,
                               user=user,
                               error_type="Phenotype",
                               read=False)

        phenotype2 = graph.Phenotype.nodes.get_or_none(uuid=uuid2)
        if phenotype2 is None:
            raise NotFound(PHENOTYPE_NOT_FOUND)

        study = phenotype2.defined_in.single()
        self.verifyStudyAccess(study,
                               user=user,
                               error_type="Phenotype",
                               read=False)

        # [1] - FATHER -> [2]
        if phenotype1.father.is_connected(phenotype2):
            phenotype1.father.disconnect(phenotype2)
            # delete son relationship
            phenotype2.son.disconnect(phenotype1)
        # [] - MOTHER -> [2]
        elif phenotype1.mother.is_connected(phenotype2):
            phenotype1.mother.disconnect(phenotype2)
            # delete son relationship
            phenotype2.son.disconnect(phenotype1)

        # [1] <- FATHER - [2]  _or_  [1] <- MOTHER - [2]
        elif phenotype1.son.is_connected(phenotype2):
            phenotype1.son.disconnect(phenotype2)
            # delete mother or father relationship
            if phenotype2.mother.is_connected(phenotype1):
                phenotype2.mother.disconnect(phenotype1)
            if phenotype2.father.is_connected(phenotype1):
                phenotype2.father.disconnect(phenotype1)

        self.log_event(
            self.events.modify,
            phenotype1,
            {
                "relationship": "removed",
                "target": phenotype2.uuid
            },
        )
        return self.empty_response()
Ejemplo n.º 2
0
def inject_user(endpoint: EndpointResource, user_id: str,
                user: User) -> Dict[str, Any]:

    target_user = endpoint.auth.get_user(user_id=user_id)
    if target_user is None:
        raise NotFound("This user cannot be found or you are not authorized")

    # Non admins (i.e. Staff users) are not allowed to target Admins
    if endpoint.auth.is_admin(
            target_user) and not endpoint.auth.is_admin(user):
        raise NotFound("This user cannot be found or you are not authorized")

    return {"target_user": target_user}
Ejemplo n.º 3
0
    def post(self, uuid1: str, uuid2: str, user: User) -> Response:

        graph = neo4j.get_instance()

        if uuid1 == uuid2:
            raise BadRequest(
                f"Cannot set relationship between {uuid1} and itself")

        phenotype1 = graph.Phenotype.nodes.get_or_none(uuid=uuid1)
        if phenotype1 is None:
            raise NotFound(PHENOTYPE_NOT_FOUND)

        study = phenotype1.defined_in.single()
        self.verifyStudyAccess(study,
                               user=user,
                               error_type="Phenotype",
                               read=False)

        phenotype2 = graph.Phenotype.nodes.get_or_none(uuid=uuid2)
        if phenotype2 is None:
            raise NotFound(PHENOTYPE_NOT_FOUND)

        study = phenotype2.defined_in.single()
        self.verifyStudyAccess(study,
                               user=user,
                               error_type="Phenotype",
                               read=False)

        # check parent sex

        if phenotype2.sex == "male":
            relationship = "father"
            phenotype2.son.connect(phenotype1)
            phenotype1.father.connect(phenotype2)

        elif phenotype2.sex == "female":
            relationship = "mother"
            phenotype2.son.connect(phenotype1)
            phenotype1.mother.connect(phenotype2)

        self.log_event(
            self.events.modify,
            phenotype1,
            {
                "relationship": relationship,
                "target": phenotype2.uuid
            },
        )
        res = {"uuid": phenotype2.uuid, "name": phenotype2.name}
        return self.response(res)
Ejemplo n.º 4
0
    def test_exceptions(self) -> None:

        with pytest.raises(RestApiException) as e:
            raise BadRequest("test")
        assert e.value.status_code == 400

        with pytest.raises(RestApiException) as e:
            raise Unauthorized("test")
        assert e.value.status_code == 401

        with pytest.raises(RestApiException) as e:
            raise Forbidden("test")
        assert e.value.status_code == 403

        with pytest.raises(RestApiException) as e:
            raise NotFound("test")
        assert e.value.status_code == 404

        with pytest.raises(RestApiException) as e:
            raise Conflict("test")
        assert e.value.status_code == 409

        with pytest.raises(RestApiException) as e:
            raise ServerError("test")
        assert e.value.status_code == 500

        with pytest.raises(RestApiException) as e:
            raise ServiceUnavailable("test")
        assert e.value.status_code == 503
Ejemplo n.º 5
0
def inject_group(endpoint: EndpointResource, group_id: str) -> Dict[str, Any]:

    group = endpoint.auth.get_group(group_id=group_id)
    if not group:
        raise NotFound("This group cannot be found")

    return {"group": group}
Ejemplo n.º 6
0
class Phenotypes(NIGEndpoint):

    # schema_expose = True
    labels = ["phenotype"]

    def link_hpo(
        self, graph: neo4j.NeoModel, phenotype: Any, hpo: List[str]
    ) -> List[str]:
        # disconnect all previous hpo
        for p in phenotype.hpo.all():
            phenotype.hpo.disconnect(p)

        connected_hpo = []
        for id in hpo:
            hpo = graph.HPO.nodes.get_or_none(hpo_id=id)
            if hpo:
                phenotype.hpo.connect(hpo)
                connected_hpo.append(id)
        return connected_hpo

    def link_geodata(
        self, graph: neo4j.NeoModel, phenotype: Any, geodata_uuid: str
    ) -> None:
        if previous := phenotype.birth_place.single():
            phenotype.birth_place.disconnect(previous)

        geodata = graph.GeoData.nodes.get_or_none(uuid=geodata_uuid)
        if geodata is None:
            raise NotFound("This birth place cannot be found")

        phenotype.birth_place.connect(geodata)
Ejemplo n.º 7
0
    def put(
        self,
        uuid: str,
        # should be an instance of neo4j.Study,
        # but typing is still not working with neomodel
        study: Any,
        # should be an instance of neo4j.Dataset,
        # but typing is still not working with neomodel
        dataset: Any,
        user: User,
        name: Optional[str] = None,
        description: Optional[str] = None,
        phenotype: Optional[str] = None,
        technical: Optional[str] = None,
    ) -> Response:

        kwargs = {}
        if phenotype:
            kwargs["phenotype"] = phenotype
            if previous := dataset.phenotype.single():
                dataset.phenotype.disconnect(previous)

            if phenotype != "-1":
                phenotype = study.phenotypes.get_or_none(uuid=phenotype)

                if phenotype is None:  # pragma: no cover
                    raise NotFound(PHENOTYPE_NOT_FOUND)

                dataset.phenotype.connect(phenotype)
Ejemplo n.º 8
0
    def send_file_streamed(
        filename: str,
        subfolder: Path,
        mime: Optional[str] = None,
        out_filename: Optional[str] = None,
    ) -> Response:

        Uploader.validate_upload_folder(subfolder)

        filename = secure_filename(filename)
        filepath = subfolder.joinpath(filename)

        if not filepath.is_file():
            raise NotFound("The requested file does not exist")

        if mime is None:
            mime = Downloader.guess_mime_type(filepath)

        log.info("Providing streamed content from {} (mime={})", filepath,
                 mime)

        response = Response(
            stream_with_context(Downloader.read_in_chunks(filepath)),
            mimetype=mime,
        )

        if not out_filename:
            out_filename = filepath.name

        response.headers[
            "Content-Disposition"] = f"attachment; filename={out_filename}"
        response.headers["Content-Length"] = filepath.stat().st_size
        return response
Ejemplo n.º 9
0
def inject_token(endpoint: EndpointResource, token_id: str) -> Dict[str, Any]:

    tokens = endpoint.auth.get_tokens(token_jti=token_id)

    if not tokens:
        raise NotFound("This token does not exist")

    return {"token": tokens[0]["token"]}
Ejemplo n.º 10
0
    def post(
        self,
        uuid: str,
        name: str,
        description: str,
        # should be an instance of neo4j.Study,
        # but typing is still not working with neomodel
        study: Any,
        user: User,
        phenotype: Optional[str] = None,
        technical: Optional[str] = None,
    ) -> Response:

        graph = neo4j.get_instance()

        kwargs = {"name": name, "description": description}
        dataset = graph.Dataset(**kwargs).save()

        dataset.ownership.connect(user)
        dataset.parent_study.connect(study)
        if phenotype:
            kwargs["phenotype"] = phenotype
            phenotype = study.phenotypes.get_or_none(uuid=phenotype)
            if phenotype is None:  # pragma: no cover
                raise NotFound(PHENOTYPE_NOT_FOUND)
            dataset.phenotype.connect(phenotype)
        if technical:
            kwargs["technical"] = technical
            technical = study.technicals.get_or_none(uuid=technical)
            if technical is None:  # pragma: no cover
                raise NotFound(TECHMETA_NOT_FOUND)
            dataset.technical.connect(technical)

        path = self.getPath(user=user, dataset=dataset)

        try:
            path.mkdir(parents=True, exist_ok=False)
        # Almost impossible to have the same uuid was already used for an other study
        except FileExistsError as exc:  # pragma: no cover
            dataset.delete()
            raise Conflict(str(exc))

        self.log_event(self.events.create, dataset, kwargs)

        return self.response(dataset.uuid)
Ejemplo n.º 11
0
def verify_phenotype_access(
    endpoint: NIGEndpoint, uuid: str, user: User
) -> Dict[str, Any]:
    graph = neo4j.get_instance()
    phenotype = graph.Phenotype.nodes.get_or_none(uuid=uuid)
    if phenotype is None:
        raise NotFound(PHENOTYPE_NOT_FOUND)
    study = phenotype.defined_in.single()
    endpoint.verifyStudyAccess(study, user=user, error_type="Phenotype")
    return {"phenotype": phenotype, "study": study}
Ejemplo n.º 12
0
    def verifyDatasetAccess(
        self,
        dataset: Dataset,
        user: User,
        error_type: str = "Dataset",
        read: bool = False,
        raiseError: bool = True,
        update_status: bool = False,
    ) -> bool:

        not_found = self.getError(error_type)

        if dataset is None:
            if raiseError:
                raise NotFound(not_found)
            else:
                return False

        # The owner has always access
        owner = dataset.ownership.single()

        if owner is None:
            log.warning("Dataset with null owner: %s" % dataset.uuid)
            return False

        # The owner has always access
        if owner == user:
            return True

        # A member of the some group of the owner, has always access
        for group in owner.belongs_to.all():
            if group.members.is_connected(user):
                return True

        # An admin has always access for readonly or to modify dataset status
        if self.auth.is_admin(user):
            if read or update_status:
                return True

        if raiseError:
            raise NotFound(not_found)
        else:
            return False
Ejemplo n.º 13
0
    def put(self, uuid: str, filename: str, user: User) -> Response:

        graph = neo4j.get_instance()
        # check permission
        dataset = graph.Dataset.nodes.get_or_none(uuid=uuid)
        self.verifyDatasetAccess(dataset, user=user)

        study = dataset.parent_study.single()
        self.verifyStudyAccess(study, user=user, error_type="Dataset")

        path = self.getPath(user=user, dataset=dataset)
        completed, response = self.chunk_upload(Path(path), filename)
        log.debug("check {}", response)
        if completed:
            # get the file
            file = None
            for f in dataset.files.all():
                if f.name == filename:
                    file = f
            if not file:
                raise NotFound(FILE_NOT_FOUND)

            # check the final size
            filepath = self.getPath(user=user, file=file)
            filesize = filepath.stat().st_size
            # check the final size
            if filesize != file.size:
                log.debug(
                    "size expected: {},actual size: {}",
                    file.size,
                    filesize,
                )
                file.delete()
                graph.db.commit()
                filepath.unlink()
                raise ServerError(
                    "File has not been uploaded correctly: final size does not "
                    "correspond to total size. Please try a new upload", )
            # check the content of the file
            file_validation = validate_gzipped_fastq(filepath)
            if not file_validation[0]:
                # delete the file
                file.delete()
                graph.db.commit()
                filepath.unlink()
                raise BadRequest(file_validation[1])
            file.status = "uploaded"
            file.save()
            self.log_event(
                self.events.create,
                file,
                {filename: f"Upload completed in dataset {uuid}"},
            )

        return response
Ejemplo n.º 14
0
    def verifyStudyAccess(
        self,
        study: Study,
        user: User,
        error_type: str = "Study",
        read: bool = False,
        raiseError: bool = True,
        update_dataset_status: bool = False,
    ) -> bool:

        not_found = self.getError(error_type)

        if study is None:
            if raiseError:
                raise NotFound(not_found)
            else:
                return False

        owner = study.ownership.single()

        if owner is None:  # pragma: no cover
            log.warning("Study with null owner: %s" % study.uuid)
            return False

        # The owner has always access
        if owner == user:
            return True

        # A member of the some group of the owner, has always access
        for group in owner.belongs_to.all():
            if group.members.is_connected(user):
                return True

        # An admin has always access for readonly or to update dataset status
        if self.auth.is_admin(user):
            if read or update_dataset_status:
                return True

        if raiseError:
            raise NotFound(not_found)
        else:
            return False
Ejemplo n.º 15
0
    def delete(self, token_id: str) -> Response:

        tokens = self.auth.get_tokens(token_jti=token_id)

        if not tokens:
            raise NotFound("This token does not exist")
        token = tokens[0]

        if not self.auth.invalidate_token(token=token["token"]):
            raise BadRequest(f"Failed token invalidation: '{token}'")
        return self.empty_response()
Ejemplo n.º 16
0
    def delete(self, group_id: str) -> Response:

        group = self.auth.get_group(group_id=group_id)
        if not group:
            raise NotFound("This group cannot be found")

        self.auth.delete_group(group)

        self.log_event(self.events.delete, group)

        return self.empty_response()
Ejemplo n.º 17
0
    def put(self, group_id: str, **kwargs: Any) -> Response:

        group = self.auth.get_group(group_id=group_id)
        if not group:
            raise NotFound("This group cannot be found")

        self.auth.db.update_properties(group, kwargs)

        self.auth.save_group(group)

        self.log_event(self.events.modify, group, kwargs)

        return self.empty_response()
Ejemplo n.º 18
0
    def delete(self, user_id: str) -> Response:

        user = self.auth.get_user(user_id=user_id)

        if user is None:
            raise NotFound(
                "This user cannot be found or you are not authorized")

        self.auth.delete_user(user)

        self.log_event(self.events.delete, user)

        return self.empty_response()
Ejemplo n.º 19
0
    def delete(self, uuid: str, user: User) -> Response:

        graph = neo4j.get_instance()

        phenotype = graph.Phenotype.nodes.get_or_none(uuid=uuid)
        if phenotype is None:
            raise NotFound(PHENOTYPE_NOT_FOUND)
        study = phenotype.defined_in.single()
        self.verifyStudyAccess(study, user=user, error_type="Phenotype")

        phenotype.delete()

        self.log_event(self.events.delete, phenotype)

        return self.empty_response()
Ejemplo n.º 20
0
    def delete(self, uuid: str, user: User) -> Response:

        graph = neo4j.get_instance()

        techmeta = graph.TechnicalMetadata.nodes.get_or_none(uuid=uuid)
        if techmeta is None:
            raise NotFound(TECHMETA_NOT_FOUND)
        study = techmeta.defined_in.single()
        self.verifyStudyAccess(study, user=user, error_type="Technical Metadata")

        techmeta.delete()

        self.log_event(self.events.delete, techmeta)

        return self.empty_response()
Ejemplo n.º 21
0
    def get(self, uuid: str, user: User) -> Response:

        graph = neo4j.get_instance()

        techmeta = graph.TechnicalMetadata.nodes.get_or_none(uuid=uuid)
        if not techmeta:
            raise NotFound(TECHMETA_NOT_FOUND)
        study = techmeta.defined_in.single()
        self.verifyStudyAccess(
            study, user=user, error_type="Technical Metadata", read=True
        )

        self.log_event(self.events.access, techmeta)

        return self.response(techmeta)
Ejemplo n.º 22
0
    def getPath(
        self,
        user: User,
        study: Optional[Study] = None,
        dataset: Optional[Dataset] = None,
        file: Optional[File] = None,
        read: bool = False,
        get_output_dir: bool = False,
    ) -> Path:

        if get_output_dir:
            dir_root = OUTPUT_ROOT
        else:
            dir_root = INPUT_ROOT

        group = user.belongs_to.single()
        if not group:
            raise NotFound("User group not found")

        if study:
            return dir_root.joinpath(group.uuid, study.uuid)

        if dataset:
            study = dataset.parent_study.single()
            if read:
                # it can be an admin so i have to get the group uuid of the dataset
                owner = dataset.ownership.single()
                group = owner.belongs_to.single()
            return dir_root.joinpath(group.uuid, study.uuid, dataset.uuid)

        if file:
            dataset = file.dataset.single()
            study = dataset.parent_study.single()
            if read:
                # it can be an admin so i have to get the group uuid of the dataset
                owner = dataset.ownership.single()
                group = owner.belongs_to.single()

            return dir_root.joinpath(group.uuid, study.uuid, dataset.uuid,
                                     file.name)

        raise BadRequest(  # pragma: no cover
            "Can't get a path without a study specification")
Ejemplo n.º 23
0
    def put(self, uuid: str, user: User, **kwargs: Any) -> Response:

        graph = neo4j.get_instance()

        techmeta = graph.TechnicalMetadata.nodes.get_or_none(uuid=uuid)
        if techmeta is None:
            raise NotFound(TECHMETA_NOT_FOUND)
        study = techmeta.defined_in.single()
        self.verifyStudyAccess(study, user=user, error_type="Technical Metadata")

        # kit = v.get("enrichment_kit", None)
        # if kit is not None and "value" in kit:
        #     v["enrichment_kit"] = kit["value"]

        graph.update_properties(techmeta, kwargs)
        techmeta.save()

        self.log_event(self.events.modify, techmeta, kwargs)

        return self.empty_response()
Ejemplo n.º 24
0
    def delete(self, uuid: str, user: User) -> Response:

        graph = neo4j.get_instance()

        file = graph.File.nodes.get_or_none(uuid=uuid)
        if file is None:
            raise NotFound(FILE_NOT_FOUND)
        dataset = file.dataset.single()
        self.verifyDatasetAccess(dataset, user=user, error_type="File")
        study = dataset.parent_study.single()
        self.verifyStudyAccess(study, user=user, error_type="File")
        path = self.getPath(user=user, file=file)

        file.delete()

        if path.exists():
            path.unlink()

        self.log_event(self.events.delete, file)

        return self.empty_response()
Ejemplo n.º 25
0
    def send_file_content(
        filename: str,
        subfolder: Path,
        mime: Optional[str] = None,
    ) -> Response:

        Uploader.validate_upload_folder(subfolder)

        filename = secure_filename(filename)
        filepath = subfolder.joinpath(filename)
        if not filepath.is_file():
            raise NotFound("The requested file does not exist")

        if mime is None:
            mime = Downloader.guess_mime_type(filepath)

        log.info("Sending file content from {}", filepath)

        # This function is mainly used for displayable files like images and video
        # so that DO NOT SET as_attachment=True that would force the download
        return send_from_directory(subfolder, filename, mimetype=mime)
Ejemplo n.º 26
0
class AdminUsers(EndpointResource):

    depends_on = ["MAIN_LOGIN_ENABLE"]
    labels = ["admin"]
    private = True

    @decorators.auth.require_all(Role.ADMIN)
    @decorators.marshal_with(get_output_schema(), code=200)
    @decorators.endpoint(
        path="/admin/users",
        summary="List of users",
        responses={200: "List of users successfully retrieved"},
    )
    @decorators.endpoint(
        path="/admin/users/<user_id>",
        summary="Obtain information on a single user",
        responses={200: "User information successfully retrieved"},
    )
    def get(self, user_id: Optional[str] = None) -> Response:

        user = None
        users = None

        if not user_id:
            users = self.auth.get_users()
        elif user := self.auth.get_user(user_id=user_id):
            users = [user]

        if users is None:
            raise NotFound(
                "This user cannot be found or you are not authorized")

        if Connector.authentication_service == "neo4j":
            for u in users:
                u.belongs_to = u.belongs_to.single()

        if user:
            self.log_event(self.events.access, user)

        return self.response(users)
Ejemplo n.º 27
0
    def post(self, **kwargs: Any) -> Response:

        roles: List[str] = kwargs.pop("roles", [])
        payload = kwargs.copy()
        group_id = kwargs.pop("group")

        email_notification = kwargs.pop("email_notification", False)

        unhashed_password = kwargs["password"]

        # If created by admins users must accept privacy at first login
        kwargs["privacy_accepted"] = False

        try:
            user = self.auth.create_user(kwargs, roles)
            self.auth.save_user(user)
        except DatabaseDuplicatedEntry as e:
            if Connector.authentication_service == "sqlalchemy":
                self.auth.db.session.rollback()
            raise Conflict(str(e))

        group = self.auth.get_group(group_id=group_id)
        if not group:
            # Can't be reached because grup_id is prefiltered by marshmallow
            raise NotFound("This group cannot be found")  # pragma: no cover

        self.auth.add_user_to_group(user, group)

        if email_notification and unhashed_password is not None:
            smtp_client = smtp.get_instance()
            send_notification(smtp_client,
                              user,
                              unhashed_password,
                              is_update=False)

        self.log_event(self.events.create, user, payload)

        return self.response(user.uuid)
Ejemplo n.º 28
0
    def post(self, user: User, **kwargs: Any) -> Response:

        roles: List[str] = kwargs.pop("roles", [])

        # The role is already refused by webards... This is an additional check
        # to improve the security, but can't be reached
        if not self.auth.is_admin(
                user) and Role.ADMIN in roles:  # pragma: no cover
            raise Forbidden("This role is not allowed")

        payload = kwargs.copy()
        group_id = kwargs.pop("group")

        email_notification = kwargs.pop("email_notification", False)

        unhashed_password = kwargs["password"]

        # If created by admins users must accept privacy at first login
        kwargs["privacy_accepted"] = False

        user = self.auth.create_user(kwargs, roles)
        self.auth.save_user(user)

        group = self.auth.get_group(group_id=group_id)
        if not group:
            # Can't be reached because group_id is prefiltered by marshmallow
            raise NotFound("This group cannot be found")  # pragma: no cover

        self.auth.add_user_to_group(user, group)

        if email_notification and unhashed_password is not None:
            notify_new_credentials_to_user(user, unhashed_password)

        self.log_event(self.events.create, user, payload)

        return self.response(user.uuid)
Ejemplo n.º 29
0
    def get(self, uuid: str, user: User) -> Response:

        graph = neo4j.get_instance()

        file = graph.File.nodes.get_or_none(uuid=uuid)
        if file is None:
            raise NotFound(FILE_NOT_FOUND)

        dataset = file.dataset.single()
        self.verifyDatasetAccess(dataset,
                                 user=user,
                                 error_type="File",
                                 read=True)

        study = dataset.parent_study.single()
        self.verifyStudyAccess(study, user=user, error_type="File", read=True)

        # check if file exists in the folder
        path = self.getPath(user=user, dataset=dataset, read=True)

        if not path.joinpath(file.name).exists():
            file.status = "unknown"
            file.save()
        else:
            # check if the status is correct
            if file.status == "unknown":
                filepath = self.getPath(user=user, file=file, read=True)
                if filepath.stat().st_size != file.size:
                    file.status = "importing"
                else:
                    file.status = "uploaded"
                file.save()

        self.log_event(self.events.access, file)

        return self.response(file)
Ejemplo n.º 30
0
    def test_exceptions(self) -> None:

        try:
            raise BadRequest("test")
        except RestApiException as e:
            assert e.status_code == 400

        try:
            raise Unauthorized("test")
        except RestApiException as e:
            assert e.status_code == 401

        try:
            raise Forbidden("test")
        except RestApiException as e:
            assert e.status_code == 403

        try:
            raise NotFound("test")
        except RestApiException as e:
            assert e.status_code == 404

        try:
            raise Conflict("test")
        except RestApiException as e:
            assert e.status_code == 409

        try:
            raise ServerError("test")
        except RestApiException as e:
            assert e.status_code == 500

        try:
            raise ServiceUnavailable("test")
        except RestApiException as e:
            assert e.status_code == 503