Esempio n. 1
0
    def ping(self, *args, **kwargs):
        """
		Reply to a ping to the server.
		This function always returns a success notice.
		Its purpose is to ensure that there is a link with the server.

		:return: A success response.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()
        response.status_code = 200
        return response
Esempio n. 2
0
	def create_participant(self, env):
		response = Response()
		response.add_header("Content-Type", "application/json")
		if self.provider.is_authorized(env.get("HTTP_AUTHORIZATION"), "authentication"):
			try:
				request_body_size = int(env.get('CONTENT_LENGTH', 0))
			except (ValueError):
				request_body_size = 0

			print(env.get("CONTENT_TYPE", "No content type"))
			request_body = env['wsgi.input'].read(request_body_size).decode()
			request_body = dict(parse.parse_qsl(request_body))
			self._pcon.insert_participant(request_body["username"])

			response.status_code = 200
			response.body = json.dumps({
				"status": "success",
			})
			return response
		else:
			response.status_code = 403
			response.body = json.dumps({
				"status": "denied",
			})
			return response
Esempio n. 3
0
    def has_card(self, username, temp, study_id, *args, **kwargs):
        """
		Check whether the participant with the given username has a card.

		:param username: The participant's unique username.
		:type username: str
		:param temp: A boolean indicating whether the query should look at the temporary card.
					 If it is set to false, the credential-ready card is queried instead.
					 The boolean is actually provided as a string and converted.
		:type temp: str
		:param study_id: The ID of the study that is being considered.
						 Depending on the configuration, an identity will be sought for the participant used for this study.
		:type study_id: str

		:return: A response containing a boolean indicating whether the participant has a card.
			Any errors that may arise are included.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        temp = temp.lower() == "true"
        exists = self._card_exists(username, temp, study_id, *args, **kwargs)

        response.status_code = 200
        response.add_header("Content-Type", "application/json")
        response.body = json.dumps({"data": exists})

        return response
Esempio n. 4
0
	def is_authenticated(self, env):
		response = Response()
		response.add_header("Content-Type", "application/json")
		if self.provider.is_authorized(env.get("HTTP_AUTHORIZATION"), "authentication"):
			response.status_code = 200
			response.body = json.dumps({
				"status": "success",
			})
			return response
		else:
			response.status_code = 403
			response.body = json.dumps({
				"status": "denied",
			})
			return response
Esempio n. 5
0
    def save_cred_card(self, address, card, *args, **kwargs):
        """
		Save the user's exported Hyperledger Composer business card.
		This card is saved into the credential field.
		Later, this card can be imported back.

		The creation of the credentials card invalidates the temporary card.
		Therefore this card is set to empty.

		:param address: The participant's unique UUID.
		:type address: str
		:param card: The card to save.
		:type card: str

		:return: A response containing the participant's credential-ready card.
			This is not stored as a JSON string since it is a `bytes` string.
			Any errors that may arise are included.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        address = address.decode(
        )  # the username is decoded since it is coming from a binary multi-part form.

        self._connector.execute("""
			UPDATE
				participant_identities
			SET
				temp_card = null,
				cred_card = %s
			WHERE
				address = '%s';
		""" % (self.to_binary(card), address))

        response.status_code = 200
        return response
Esempio n. 6
0
    def get_participant(self, username=None, *args, **kwargs):
        """
		Filter participants using the given arguments.
		If no arguments are given, all participants are returned.

		:param username: The user's username.
		:type username: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        username = self._sanitize(
            username) if username is not None else username

        if username is None:
            rows = self._connector.select("""
				SELECT
					*
				FROM
					participants
			""")

            total = self._connector.count("""
				SELECT
					COUNT(*)
				FROM
					participants
			""")
        else:
            rows = self._connector.select("""
				SELECT
					*
				FROM
					participants
				WHERE
					user_id = '%s'
			""" % username)

            total = self._connector.count("""
				SELECT
					COUNT(*)
				FROM
					participants
				WHERE
					user_id = '%s'
			""")

        decrypted_data = [self._decrypt_participant(row) for row in rows]

        response = Response()
        response.status_code = 200
        response.add_header("Content-Type", "application/json")
        response.body = json.dumps({"data": decrypted_data, "total": total})
        return response
Esempio n. 7
0
    def _404_page_not_found(self, arguments):
        """
		Page not found error.

		:param arguments: The environment arguments.
		:type arguments: list

		:return: A 404 response with an error.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()
        response.add_header("Content-Type", "application/json")
        response.status_code = 404
        response.body = json.dumps({"error": "Page Not Found"})
        return response
Esempio n. 8
0
    def get_researchers(self, *args, **kwargs):
        """
		Retrieve a list of all researchers.

		:return: A response containing a list of researcher objects and any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        rows = self._connector.select("""
			SELECT *
			FROM researchers
		""")

        total = self._connector.count("""
			SELECT COUNT(*)
			FROM researchers
		""")

        response = Response()
        response.status_code = 200
        response.add_header("Content-Type", "application/json")
        response.body = json.dumps({"data": rows, "total": total})
        return response
Esempio n. 9
0
    def create_email(self,
                     subject,
                     body,
                     recipients=None,
                     recipient_group=None,
                     *args,
                     **kwargs):
        """
		Insert an email into the database.

		:param subject: The email's subject.
		:type subject: str
		:param body: The email's body.
		:type body: str
		:param recipients: The email's recipients.
						   If `None` is given, the recipients default to an empty list.
						   Otherwise, the list is considered as a list of emails.

		:type recipients: None or list
		:param recipient_group: A recipient group that should receive the email.
								The group is represented as a string, or nothing at all.
								The conversion from the group to the actual recipients is handled by the function.
								Accepted strings:

								- 'None' - no recipient should be added
								- 'Subscribed' - only subscribed users should receive the email
								- 'All' - everyone, including unsubscribed users, should receive the email.

								**This should be used sparingly and only when absolutely needed to respect user decisions.**

		:type recipient_group: None or str

		:return: A response with any errors that may arise.
			The response contains the new email's attributes, including its ID.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            subject = self._sanitize(subject)
            body = self._sanitize(body)

            cursor = self._connector.execute("""
				INSERT INTO
					emails (subject, body)
				VALUES
					('%s', '%s')
				RETURNING
					id, subject, body
				""" % (subject, body),
                                             with_cursor=True)
            email = cursor.fetchone()
            cursor.close()

            if recipient_group is None or recipient_group.lower() == "none":
                recipient_list = []
            elif recipient_group.lower() == "subscribed":
                rows = self._connector.select("""
					SELECT
						participants.email
					FROM
						participants JOIN participant_subscriptions
							ON participants.user_id = participant_subscriptions.participant_id
					WHERE
						participant_subscriptions.any_email = TRUE
				""")
                recipient_list = [self._decrypt(row['email']) for row in rows]
            elif recipient_group.lower() == "all":
                rows = self._connector.select("""
					SELECT
						email
					FROM
						participants
				""")
                recipient_list = [self._decrypt(row['email']) for row in rows]
            else:
                raise email_exceptions.UnknownRecipientGroupException(
                    recipient_group)
            """
			Add the given list of recipients to the email's recipients.
			"""
            if (recipients is not None and type(recipients) is list):
                recipient_list += recipients

            if len(recipient_list):
                self._connector.bulk_execute(
                    """
					INSERT INTO
						email_recipients (email_id, recipient)
					VALUES
						%s
					""", [(email['id'], recipient) for recipient in recipient_list],
                    "(%s, %s)")

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({"data": dict(email)})
        except (email_exceptions.UnknownRecipientGroupException,
                email_exceptions.UnsupportedRecipientGroupException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 10
0
    def get_studies(self,
                    number=10,
                    page=1,
                    search="",
                    case_sensitive=False,
                    active_only=False,
                    *args,
                    **kwargs):
        """
		Retrieve a list of studies.

		:param number: The number of studies to retrieve.
			If a negative number is provided, all matching studies are retrieved.
		:type number: str
		:param page: The page number, used to aid in pagination.
		:type page: str
		:param search: A search string used to look up studies using their name and description.
		:type search: str
		:param case_sensitive: A boolean indicating whether the search should be case sensitive.
		:type case_sensitive: str
		:param active_only: A boolean indicating whether only active studies should be fetched.
			By default, all studies are fetched.
			In the current implementation, the parameter has no effect.
		:type active_only: bool

		:return: A response containing a list of study objects and any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            number = int(number)
            page = max(int(page), 1)
            case_sensitive = case_sensitive == "True"

            query = """
				SELECT *
				FROM studies
				WHERE
					(studies."name" %s '%%%s%%' OR
					studies."description" %s '%%%s%%')
			""" % (
                "LIKE" if case_sensitive else "ILIKE",
                search,
                "LIKE" if case_sensitive else "ILIKE",
                search,
            )

            if number >= 0:
                query += """
				LIMIT %d OFFSET %d""" % (number, number * (page - 1))

            rows = self._connector.select(query)

            total = self._connector.count("""
				SELECT COUNT(*)
				FROM studies
				WHERE
					(studies."name" %s '%%%s%%' OR
					studies."description" %s '%%%s%%')
			""" % (
                "LIKE" if case_sensitive else "ILIKE",
                search,
                "LIKE" if case_sensitive else "ILIKE",
                search,
            ))

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "data": {
                    study['study_id']: {
                        "study":
                        study,
                        "researchers":
                        self._get_study_researchers(study["study_id"]),
                    }
                    for study in rows
                },
                "total": total,
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 11
0
    def get_study_by_id(self, study_id, *args, **kwargs):
        """
		Retrieve a single study and all associated researchers and attributes.

		:param study_id: The study's unique ID.
		:type study_id: str

		:return: A response containing a list of study objects and any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()
        try:

            if not self._study_exists(study_id):
                raise study_exceptions.StudyDoesNotExistException()

            study = self._connector.select_one("""
				SELECT *
				FROM studies
				WHERE
					"study_id" = '%s'
			""" % (study_id))

            researchers = self._get_study_researchers(study_id)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "study": study,
                "researchers": researchers,
            })
            return response
        except (general_exceptions.InputException,
                study_exceptions.StudyDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 12
0
    def create_study(self,
                     study_id,
                     name,
                     description,
                     homepage,
                     attachment=None,
                     recruiting=True,
                     researchers=None,
                     *args,
                     **kwargs):
        """
		Insert a study into the database.

		:param study_id: The study's unique ID.
		:type study_id: str
		:param name: The study's name.
		:type name: str
		:param description: A short description of the study.
		:type description: str
		:param homepage: A link to the study's homepage.
		:type homepage: str
		:param attachment: The path to the attachment, if there is one.
		:type: None or str
		:param recruiting: A boolean indicating whether the study is recruiting.
		:type recruiting: bool
		:param researchers: A list of researchers that are participating in the study.
		:type researchers: list

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`

		:raises: :class:`handlers.exceptions.study_exceptions.StudyExistsException`
		:raises: :class:`handlers.exceptions.user_exceptions.ResearcherDoesNotExistException`
		"""

        response = Response()

        try:
            """
			Load, parse and sanitize the study arguments.
			"""
            name, description, homepage = self._sanitize_list(
                [name, description, homepage])

            researchers = [] if researchers is None else researchers
            researchers = self._sanitize_list(researchers)
            """
			Validate the data.
			"""
            if self._study_exists(study_id):
                raise study_exceptions.StudyExistsException()

            for researcher in researchers:
                if not self._researcher_exists(researcher):
                    raise user_exceptions.ResearcherDoesNotExistException()

            self._blockchain_connector.create_study(study_id)
            """
			Create the study.
			"""
            """
			Add the study.
			"""
            self._connector.execute([
                """
				INSERT INTO studies (
					study_id, name, description, homepage, attachment, recruiting)
				VALUES ('%s', '%s', '%s', '%s', '%s', '%r');""" %
                (study_id, name, description, homepage, attachment
                 or '', recruiting),
            ])
            """
			Add the researchers.
			"""
            self._link_researchers(study_id, researchers)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (general_exceptions.InputException,
                study_exceptions.AttributeExistsException,
                study_exceptions.StudyExistsException,
                user_exceptions.ResearcherDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 13
0
    def get_studies_by_researcher(self,
                                  researcher,
                                  number=10,
                                  page=1,
                                  search="",
                                  case_sensitive=False,
                                  *args,
                                  **kwargs):
        """
		Retrieve a list of studies.

		:param researcher: The unique ID of the researcher, only used if it is not empty.
			The studies are filtered such that only those studies in which the researcher is participating are retrieved.
		:type researcher: str
		:param number: The number of studies to retrieve.
			If a negative number is provided, all matching studies are retrieved.
		:type number: str
		:param page: The page number, used to aid in pagination.
		:type page: str
		:param search: A search string used to look up studies using their name and description.
		:type search: str
		:param case_sensitive: A boolean indicating whether the search should be case sensitive.
		:type case_sensitive: str

		:return: A response containing a list of study objects and any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            number = int(number)
            page = max(int(page), 1)
            case_sensitive = case_sensitive == "True"

            if not self._researcher_exists(researcher):
                raise user_exceptions.ResearcherDoesNotExistException()

            query = """
				SELECT *
				FROM studies, studies_researchers
				WHERE
					(studies."name" %s '%%%s%%' OR
					studies."description" %s '%%%s%%') AND
					studies.study_id = studies_researchers.study_id AND
					studies_researchers.researcher_id = '%s'
			""" % (
                "LIKE" if case_sensitive else "ILIKE",
                search,
                "LIKE" if case_sensitive else "ILIKE",
                search,
                researcher,
            )

            if number >= 0:
                query += """
				LIMIT %d OFFSET %d""" % (number, number * (page - 1))

            rows = self._connector.select(query)
            for row in rows:
                if psycopg2.extras.RealDictRow in row:
                    del row[psycopg2.extras.RealDictRow]

            total = self._connector.count("""
				SELECT COUNT(*)
				FROM studies, studies_researchers
				WHERE
					(studies."name" %s '%%%s%%' OR
					studies."description" %s '%%%s%%') AND
					studies.study_id = studies_researchers.study_id AND
					studies_researchers.researcher_id = '%s'
			""" % (
                "LIKE" if case_sensitive else "ILIKE",
                search,
                "LIKE" if case_sensitive else "ILIKE",
                search,
                researcher,
            ))

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "data": [{
                    "study":
                    study,
                    "researchers":
                    self._get_study_researchers(study["study_id"]),
                } for study in rows],
                "total":
                total,
            })
        except (user_exceptions.ResearcherDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 14
0
    def update_study(self,
                     study_id,
                     name,
                     description,
                     homepage,
                     recruiting,
                     attachment=None,
                     researchers=None,
                     *args,
                     **kwargs):
        """
		Update an existing study.

		:param study_id: The study's unique ID.
		:type study_id: str
		:param name: The study's name.
		:type name: str
		:param description: A short description of the study.
		:type description: str
		:param homepage: A link to the study's homepage.
		:type homepage: str
		:param recruiting: A boolean indicating whether the study is recruiting.
		:type recruiting: bool
		:param attachment: The path to the attachment, if there is one.
		:type: None or str
		:param researchers: A list of researchers that are participating in the study.
		:type researchers: list

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            """
			Load, parse and sanitize the study arguments.
			"""
            name, description, homepage = self._sanitize_list(
                [name, description, homepage])

            researchers = [] if researchers is None else researchers
            researchers = self._sanitize_list(researchers)
            """
			Validate the data.
			"""
            if not self._study_exists(study_id):
                raise study_exceptions.StudyDoesNotExistException()

            for researcher in researchers:
                if not self._researcher_exists(researcher):
                    raise user_exceptions.ResearcherDoesNotExistException()
            """
			Update the study.
			"""
            self._connector.execute([
                """
				UPDATE studies
				SET
					"name" = '%s',
					"description" = '%s',
					"homepage" = '%s',
					"recruiting" = '%r'
				WHERE
					"study_id" = '%s';""" %
                (name, description, homepage, recruiting, study_id),
            ])

            if attachment:
                self._connector.execute([
                    """
					UPDATE studies
					SET
						"attachment" = '%s'
					WHERE
						"study_id" = '%s';""" % (attachment, study_id),
                ])
            """
			Remove all linked researchers.
			Then add the new ones.
			"""
            self._unlink_researchers(study_id)
            self._link_researchers(study_id, researchers)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (general_exceptions.InputException,
                study_exceptions.AttributeExistsException,
                study_exceptions.AttributeDoesNotExistException,
                study_exceptions.StudyDoesNotExistException,
                user_exceptions.ResearcherDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 15
0
    def remove_study(self, study_id, *args, **kwargs):
        """
		Remove an existing study.

		:param study_id: The study's unique ID.
		:type study_id: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            """
			Validate the data.
			"""
            if not self._study_exists(study_id):
                raise study_exceptions.StudyDoesNotExistException()
            """
			Remove the study.
			"""
            self._connector.execute([
                """
				DELETE FROM studies
				WHERE
					"study_id" = '%s';""" % (study_id, ),
            ])

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (general_exceptions.InputException,
                study_exceptions.StudyDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 16
0
    def get_consent_trail(self, username, *args, **kwargs):
        """
		Get a user's consent trail.
		This trail shows how the participant's consent for studies changed over time.

		:param username: The unique username of the participant.
		:type username: str

		:return: A response with any errors that may arise.
			The body contains the studies and the timelines.
			The two are separated from each other.
			The timeline is made up of the timestamp, and a list of consent changes separated by study IDs.
			The studies are separated by IDs, and therefore the timeline can use it as a look-up table.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        timeline = {}

        try:
            username = self._sanitize(username)

            if not self._participant_exists(username):
                raise user_exceptions.ParticipantDoesNotExistException()
            """
			The command must always check for the participant's consent status.
			"""
            command = """
				SELECT *
				FROM
					studies
				"""

            rows = self._connector.select(command)
            studies = {study["study_id"]: study for study in rows}
            """
			For each study get the user's consent changes, if any.
			"""
            for row in rows:
                study_id = row["study_id"]
                """
				Construct the timeline, one timestamp at a time, from the current study.
				"""
                consent_trail = self._blockchain_connector.get_consent_trail(
                    study_id, username, *args, **kwargs)

                for (timestamp, consent) in consent_trail.items():
                    timeline[timestamp] = timeline.get(timestamp, {})
                    timeline[timestamp][study_id] = consent

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps(
                {"data": {
                    "studies": studies,
                    "timeline": timeline
                }})
        except (user_exceptions.ParticipantDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 17
0
    def update_subscription(self, username, subscription, subscribed, *args,
                            **kwargs):
        """
		Update the subscription of a participant.

		:param username: The username of the participant whose subscription will be updated.
		:type username: str
		:param subscription: The subscription to update.
							 The only accepted subscription type at present is 'any_email'.
		:type subscription: str
		:param subscribed: A boolean indicating whether the participant is subscribed.
		:typr subscribed: bool

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            username = self._sanitize(username)
            if not self._participant_exists(username):
                raise user_exceptions.ParticipantDoesNotExistException()

            if subscription not in self._get_subscription_types():
                raise email_exceptions.UnknownSubscriptionTypeException(
                    subscription)
            """
			Update the subscription.
			"""
            row = self._connector.execute("""
				UPDATE
					participant_subscriptions
				SET
					%s = %s
				WHERE
					participant_id = '%s'
			""" % (subscription, subscribed, username))

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (email_exceptions.UnknownSubscriptionTypeException,
                user_exceptions.ParticipantDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 18
0
    def update_participant(self,
                           username,
                           first_name=None,
                           last_name=None,
                           email=None,
                           *args,
                           **kwargs):
        """
		Update a participant.

		:param username: The participant's new username.
		:type username: str
		:param first_name: The participant's new first name.
						   If it is not given, then it is not updated.

		:type first_name: str or None
		:param last_name: The participant's new last name.
						   If it is not given, then it is not updated.

		:type last_name: str or None
		:param email: The participant's new email.
					  If it is not given, then it is not updated.

		:type email: str or None

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            username = self._sanitize(username)

            if not self._participant_exists(username):
                raise user_exceptions.ParticipantDoesNotExistException()

            attributes = self._encrypt_participant({
                'username': username,
                'first_name': first_name,
                'last_name': last_name,
                'email': email,
            })

            sql = """
				UPDATE
					participants
				SET
					%s
				WHERE
					user_id = '%s'
			"""

            update_strings = []
            if first_name is not None:
                update_strings.append(
                    f"first_name = '{attributes['first_name']}'")

            if last_name is not None:
                update_strings.append(
                    f"last_name = '{attributes['last_name']}'")

            if email is not None:
                update_strings.append(f"email = '{attributes['email']}'")

            if len(update_strings):
                sql = sql % (', '.join(update_strings), username)
                self._connector.execute(sql)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (user_exceptions.ParticipantDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 19
0
    def give_consent(self, study_id, address, *args, **kwargs):
        """
		Set the consent of a participant.

		:param study_id: The unique ID of the study.
		:type study_id: str
		:param address: The unique address of the participant on the blockchain.
		:type address: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            if not self._study_exists(study_id):
                raise study_exceptions.StudyDoesNotExistException()

            if not self._participant_address_exists(address):
                raise user_exceptions.ParticipantAddressDoesNotExistException()

            thread = threading.Thread(target=self._set_consent,
                                      args=(study_id, address, True, *args),
                                      kwargs=kwargs)
            thread.start()
            self._threads.append(thread)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (study_exceptions.AttributeNotLinkedException,
                study_exceptions.MissingAttributesException,
                study_exceptions.StudyDoesNotExistException,
                study_exceptions.StudyExpiredException,
                user_exceptions.ParticipantAddressDoesNotExistException,
                user_exceptions.ParticipantDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            traceback.print_exc()
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 20
0
    def create_researcher(self, username, *args, **kwargs):
        """
		Insert a researcher into the database.

		:param username: The user's username.
		:type username: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            username = self._sanitize(username)
            if self._researcher_exists(username):
                raise user_exceptions.ResearcherExistsException()
            elif self._user_exists(username):
                raise user_exceptions.UserExistsException()

            self._connector.execute([
                """
				INSERT INTO users (
					user_id, role)
				VALUES ('%s', '%s');""" % (username, "RESEARCHER"),
                """
				INSERT INTO researchers (
					user_id)
				VALUES ('%s');""" % (username),
            ])
            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (general_exceptions.InputException,
                user_exceptions.ResearcherExistsException,
                user_exceptions.UserExistsException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 21
0
    def get_email(self,
                  id=None,
                  recipients=False,
                  search="",
                  case_sensitive=False,
                  number=-1,
                  page=1,
                  *args,
                  **kwargs):
        """
		Get the email with the given ID.
		If no ID is given, all emails are fetched.

		Associated recipients can also be fetched.

		:param id: The email's unique id.
		:type id: str
		:param recipients: A parameter that specifies whether the email's recipients should be returned.
		:type recipients: bool
		:param search: A search string used to look up emails using their subject and body.
		:type search: str
		:param case_sensitive: A boolean indicating whether the search should be case sensitive.
		:type case_sensitive: str
		:param number: The number of emails to retrieve.
			If a negative number is provided, all matching emails are retrieved.
		:type number: str
		:param page: The page number, used to aid in pagination.
		:type page: str

		:return: A response with any errors that may arise.
				 If an ID is provided, a single email is returned if found.
				 Otherwise, a list of emails is returned.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            if id is not None:
                id = int(id)

                if not self._email_exists(id):
                    raise email_exceptions.EmailDoesNotExistException(id)

            number = int(number)
            page = max(int(page), 1)
            case_sensitive = case_sensitive == 'True'
            """
			The base SQL string returns every email.
			The placeholder allows modifications to what is returned.
			"""
            sql = """
				SELECT
					emails.* %s
				FROM
					emails %s
				WHERE
					TRUE %s
			"""
            """
			Complete the SELECT and FROM fields.
			"""
            """
			If the recipients are requested, return them as well.
			"""
            if recipients:
                sql = sql % (", ARRAY_AGG(recipient) AS recipients",
                             """LEFT JOIN
						email_recipients
					ON
						emails.id = email_recipients.email_id""", '%s')
            else:
                sql = sql % ('', '', '%s')
            """
			Complete the WHERE field.
			"""
            filters = []
            """
			Filter the emails if an ID is given.
			"""
            if id is not None:
                filters.append(f"id = {id}")
            """
			Perform a search if a string is given.
			"""
            if search:
                filters.append(
                    f"(emails.subject %s '%%{search}%%') OR (emails.body %s '%%{search}%%')"
                    % ("LIKE" if case_sensitive else "ILIKE",
                       "LIKE" if case_sensitive else "ILIKE"))

            if filters:
                sql = sql % ('AND ' + ' AND '.join(filters))
            else:
                sql = sql % ''
            """
			Add grouping if recipients were requested.
			"""
            if recipients:
                sql += """
					GROUP BY
						emails.id
				"""
            """
			Limit the results if a non-negative number is given.
			"""
            if number >= 0:
                sql += """
				LIMIT %d OFFSET %d""" % (number, number * (page - 1))
            """
			Get the response.
			"""
            emails = self._connector.select(sql)
            for i, email in enumerate(emails):
                emails[i]['created_at'] = emails[i]['created_at'].timestamp()
            """
			Calculate the total number of results.
			"""
            sql = """
				SELECT
					COUNT(*) AS total
				FROM
					emails
				WHERE
					TRUE %s
			"""
            if filters:
                sql = sql % ('AND ' + ' AND '.join(filters))
            else:
                sql = sql % ''
            summary = self._connector.select_one(sql)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "total":
                summary['total'],
                "data":
                emails[0] if id is not None else emails
            })
        except (email_exceptions.EmailDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 22
0
    def remove_email(self, id, *args, **kwargs):
        """
		Remove the email that has the given ID.

		:param id: The email's unique ID.
		:type id: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            id = int(id)

            if not self._email_exists(id):
                raise email_exceptions.EmailDoesNotExistException(id)
            """
			Delete the email and associated recipients.
			"""
            self._connector.execute("""
				DELETE FROM
					emails
				WHERE
					id = %d
			""" % id)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (email_exceptions.EmailDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 23
0
 def authenticate(self):
     response = Response()
     grant_type = self._determine_grant_type(self.request)
     grant_type.read_validate_params(self.request)
     grant_type.process(self.request, response, {})
     return PyramidResponse(body=response.body, status=response.status_code, content_type="application/json")
Esempio n. 24
0
    def get_card(self, username, temp, study_id, *args, **kwargs):
        """
		Get the given participant's network business card.

		:param username: The participant's unique username.
		:type username: str
		:param temp: A boolean indicating whether the query should look at the temporary card.
			If it is set to false, the credential-ready card is queried instead.

			The boolean is actually provided as a string and converted.
		:type temp: str

		:return: A response containing the participant's credential-ready card.
			This is not stored as a JSON string since it is a `bytes` string.
			Any errors that may arise are included.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        temp = temp.lower() == "true"
        card_name = "temp" if temp else "cred"
        card_name = "%s_card" % card_name
        query = """
			SELECT
				%s, address
			FROM
				participant_identities
			WHERE
				participant_id = '%s' AND
				%s IS NOT NULL
		""" % (card_name, username, card_name)

        rows = self._connector.select(query)

        study_participants = self.get_all_study_participants(study_id)
        participating_rows = [
            row for row in rows if row['address'] in study_participants
        ]

        if len(participating_rows):
            """
			Check if the participant is already participating in a study.
			This happens regardless if the server is running in single- or multi-card mode.
			If they are, re-use that same card so the consent trail remains in the same place.
			"""

            row = participating_rows[0]
        elif not blockchain.multi_card:
            if len(rows):
                """
				If the participant never consented to participate in this study, fetch the first identity if there is one.
				"""
                row = rows[0]
            else:
                """
				If the research partner has no cards whatsoever, then create an identity for them.
				Then, return the newly-created card.
				"""
                self.create_participant(username)
                rows = self._connector.select(query)
                row = rows[0]
        else:
            """
			If the research partner has never consented to the study, issue a new identity.
			Then, return the newly-created card.
			"""

            address = self.create_participant(username)
            query = """
				SELECT
					temp_card, address
				FROM
					participant_identities
				WHERE
					address = '%s'
			""" % (address)
            rows = self._connector.select(query)
            row = rows[0]
            """
			Update the card name since the temp card will be returned now.
			"""
            card_name = 'temp_card'
        """
		The response is a :class:`memoryview` object.
		Therefore before it is returned, it is converted into a `bytes` string.
		"""

        card_data = row[card_name]
        card_data = bytes(card_data)

        if temp:
            """
			The temporary card can only be used once.
			Therefore it should be cleared once requested.
			"""

            self._connector.execute("""
				UPDATE
					participant_identities
				SET
					temp_card = null
				WHERE
					address = '%s';
			""" % (row['address']))

        response.status_code = 200
        response.add_header("Content-Type", "application/octet-stream")
        response.body = card_data

        return response
Esempio n. 25
0
    def remove_researcher_by_username(self, username, *args, **kwargs):
        """
		Remove a researcher that has the given username.

		:param username: The user's username.
		:type username: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            username = self._sanitize(username)
            if not self._researcher_exists(username):
                raise user_exceptions.ResearcherDoesNotExistException()

            self._connector.execute([
                """
				DELETE FROM users
				WHERE
					user_id = '%s'
					AND role = 'RESEARCHER';""" % (username),
            ])
            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (general_exceptions.InputException,
                user_exceptions.ResearcherDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 26
0
    def deliver(self, simulated=False, *args, **kwargs):
        """
		Send the next unsent email.
		The `max_recipients` parameter can be used to limit the number of recipients to deliver to.

		:param simulated: A boolean to simulate the email delivery.
						 If the delivery is simulated, the email is not actually delivered, but marked as such.
		:type simulated: boolean

		:return: A response with any errors that may arise.
				 The email and the recipients to whom the email was sent are returned.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            """
			Get the next email and its recipients.
			The results are returned in the same row.
			The recipients are returned as an array.
			"""

            email = self._get_next_email(*args, **kwargs)
            if email:
                recipients = email['recipients']
                email['created_at'] = email['created_at'].timestamp()
                del email['recipients']
                """
				If there is an email to be sent, set up the SMTP connection.
				Then, set up the email itself and deliver it.
				The email is always sent to the sender, with the recipients being `Bcc` receivers.
				"""

                if not simulated:
                    smtpserver = smtplib.SMTP(smtp.smtp_host, smtp.smtp_port)
                    if smtp.smtp_secure == 'tls':
                        smtpserver.set_debuglevel(0)
                        smtpserver.ehlo()
                        smtpserver.starttls()
                        smtpserver.ehlo()
                    elif smtp.smtp_secure == 'ssl':
                        smtpserver.set_debuglevel(0)
                        smtpserver.ehlo()
                    """
					Authenticate if need be.
					"""
                    if smtp.smtp_auth:
                        smtpserver.login(smtp.smtp_user, smtp.smtp_pass)
                    """
					Construct the email.
					"""
                    message = MIMEText(email_partials.email() % email['body'],
                                       'html')
                    message['Subject'] = email['subject']
                    message['From'] = f"{smtp.smtp_name} <{smtp.smtp_from}>"
                    message['Bcc'] = ','.join(recipients)

                    smtpserver.sendmail(smtp.smtp_from,
                                        [smtp.smtp_from] + recipients,
                                        message.as_string())
                    smtpserver.close()
                """
				Mark the emails as sent.
				"""

                self._connector.execute("""
					UPDATE
						email_recipients
					SET
						sent = True
					WHERE
						email_id = %d AND
						recipient IN ('%s')
				""" % (email['id'], "', '".join(recipients)))

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            if email:
                response.body = json.dumps(
                    {'data': {
                        'email': email,
                        'recipients': recipients
                    }})
            else:
                response.body = json.dumps({'data': {}})
        except (email_exceptions.EmailDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            traceback.print_exc()
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 27
0
    def remove_participant_by_username(self, username, *args, **kwargs):
        """
		Remove a participant that has the given username.

		:param username: The user's username.
		:type username: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            username = self._sanitize(username)
            if not self._participant_exists(username):
                raise user_exceptions.ParticipantDoesNotExistException()

            self._connector.execute([
                """
				DELETE FROM
					users
				WHERE
					user_id = '%s' AND
					role = 'PARTICIPANT';""" % (username),
            ])
            """
			Remove the participant from the backups.
			"""
            if erasure.script and erasure.backups:
                bashCommand = f"{erasure.script} -p {erasure.backups} {username}"
                print(f"Running: {bashCommand}", file=sys.stderr)
                process = subprocess.call(bashCommand,
                                          shell=True,
                                          stdout=subprocess.PIPE)

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (general_exceptions.InputException,
                user_exceptions.ParticipantDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 28
0
    def get_next_email(self, *args, **kwargs):
        """
		Get the next unsent email.
		The `max_recipients` parameter can be used to limit the number of recipients to deliver to.

		:return: A response with any errors that may arise.
				 The email and the recipients to whom the email should be sent are returned.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            """
			Get the next email and its recipients.
			The results are returned in the same row.
			The recipients are returned as an array.
			"""

            email = self._get_next_email(*args, **kwargs)
            if email:
                recipients = email['recipients']
                email['created_at'] = email['created_at'].timestamp()
                del email['recipients']

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            if email:
                response.body = json.dumps(
                    {'data': {
                        'email': email,
                        'recipients': recipients
                    }})
            else:
                response.body = json.dumps({'data': {}})
        except (email_exceptions.EmailDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 29
0
    def create_participant(self,
                           username,
                           first_name="",
                           last_name="",
                           email="",
                           *args,
                           **kwargs):
        """
		Insert a participant into the database.

		:param username: The participant's username.
		:type username: str
		:param first_name: The participant's first name.
		:type first_name: str
		:param first_name: The participant's last name.
		:type first_name: str
		:param email: The participant's email.
		:type email: str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            username = self._sanitize(username)

            if self._participant_exists(username):
                raise user_exceptions.ParticipantExistsException()
            elif self._user_exists(username):
                raise user_exceptions.UserExistsException()

            attributes = self._encrypt_participant({
                'username': username,
                'first_name': first_name,
                'last_name': last_name,
                'email': email,
            })

            self._connector.execute([
                """
				INSERT INTO
					users (user_id, role)
				VALUES
					('%s', '%s');
				""" % (username, "PARTICIPANT"),
                """
				INSERT INTO
					participants (user_id, first_name, last_name, email)
				VALUES
					('%s', '%s', '%s', '%s');
				""" % (username, attributes['first_name'], attributes['last_name'],
            attributes['email']),
                """
				INSERT INTO
					participant_subscriptions (participant_id)
				VALUES
					('%s');
				""" % (username),
            ])

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({})
        except (general_exceptions.InputException,
                user_exceptions.ParticipantExistsException,
                user_exceptions.UserExistsException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response
Esempio n. 30
0
    def get_subscription(self, username, subscription=None, *args, **kwargs):
        """
		Get a participant's subscription status.
		If no subscription type is provided, all subscription types are returned.

		:param username: The username of the participant whose subscriptions will be retrieved.
		:type username: str
		:param subscription: The subscription to retrieve.
			If `None` is given, all subscriptions are returned.
		:type subscription: None or str

		:return: A response with any errors that may arise.
		:rtype: :class:`oauth2.web.Response`
		"""

        response = Response()

        try:
            username = self._sanitize(username)
            if not self._participant_exists(username):
                raise user_exceptions.ParticipantDoesNotExistException()

            if subscription is not None and subscription not in self._get_subscription_types(
            ):
                raise email_exceptions.UnknownSubscriptionTypeException(
                    subscription)
            """
			Retrieve the subscription according to whether one or all subscriptions are requested.
			"""
            row = self._connector.select_one("""
				SELECT
					%s
				FROM
					participant_subscriptions
				WHERE
					participant_id = '%s'
			""" % ('*' if subscription is None else f"participant_id, {subscription}",
            username))

            response.status_code = 200
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({'data': dict(row)})
        except (email_exceptions.UnknownSubscriptionTypeException,
                user_exceptions.ParticipantDoesNotExistException) as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error": str(e),
                "exception": e.__class__.__name__
            })
        except Exception as e:
            response.status_code = 500
            response.add_header("Content-Type", "application/json")
            response.body = json.dumps({
                "error":
                "Internal Server Error: %s" % str(e),
                "exception":
                e.__class__.__name__
            })

        return response