示例#1
0
def test_user_key_not_found_error_for_project(client):
    project_without_keys = models.Project(
        public_id="random_project_id",
        title="random project_title",
        description="This is a random project. ",
        pi="PI",
        bucket=
        f"publicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}",
    )

    # Somehow the key pair for the project is not created or persisted to the database

    invite1 = models.Invite(email="*****@*****.**",
                            role="Unit Personnel")
    unituser = models.User.query.filter_by(username="******").first()
    unituser.unit.projects.append(project_without_keys)
    dds_web.db.session.commit()
    unituser_token = encrypted_jwt_token(
        username=unituser.username,
        sensitive_content="password",
    )
    with pytest.raises(KeyNotFoundError) as error:
        share_project_private_key(
            from_user=unituser,
            to_another=invite1,
            from_user_token=unituser_token,
            project=project_without_keys,
        )

    assert "Unrecoverable key error. Aborting." in str(error.value)
示例#2
0
def __setup_invite(unit_name, invite_email):
    unit = models.Unit.query.filter_by(name=unit_name).first()
    invite = models.Invite(email=invite_email, role="Researcher")

    unit.invites.append(invite)

    db.session.add(invite)
    db.session.commit()

    invite = models.Invite.query.filter_by(email=invite_email).first()
    assert invite is not None
    assert invite.unit is not None

    return invite
示例#3
0
def test_user_key_operation_error_with_decrypt_user_private_key(client):
    invite1 = models.Invite(email="*****@*****.**",
                            role="Unit Personnel")
    unituser = models.User.query.filter_by(username="******").first()

    # Somehow a wrong password has ended up in the encrypted token
    unituser_token = encrypted_jwt_token(
        username=unituser.username,
        sensitive_content="passwor",
    )
    with pytest.raises(KeyOperationError) as error:
        share_project_private_key(
            from_user=unituser,
            to_another=invite1,
            from_user_token=unituser_token,
            project=unituser.unit.projects[0],
        )

    assert "User private key could not be decrypted!" in str(error.value)
示例#4
0
def test_user_key_setup_error_with_public_key(client):
    invite1 = models.Invite(email="*****@*****.**",
                            role="Unit Personnel")

    # Somehow the key pair for the invite has not taken place or disappeared

    unituser = models.User.query.filter_by(username="******").first()
    unituser_token = encrypted_jwt_token(
        username=unituser.username,
        sensitive_content="password",
    )
    with pytest.raises(KeySetupError) as error:
        share_project_private_key(
            from_user=unituser,
            to_another=invite1,
            from_user_token=unituser_token,
            project=unituser.unit.projects[0],
        )

    assert "User keys are not properly setup!" in str(error.value)
示例#5
0
def test_sensitive_content_missing_error(client):
    invite1 = models.Invite(email="*****@*****.**",
                            role="Unit Personnel")
    unituser = models.User.query.filter_by(username="******").first()

    # Somehow the password is missing in the encrypted token
    unituser_token = encrypted_jwt_token(
        username=unituser.username,
        sensitive_content=None,
    )
    with pytest.raises(SensitiveContentMissingError) as error:
        share_project_private_key(
            from_user=unituser,
            to_another=invite1,
            from_user_token=unituser_token,
            project=unituser.unit.projects[0],
        )

    assert "Sensitive content is missing in the encrypted token!" in str(
        error.value)
示例#6
0
def test_user_key_operation_error_with_load_user_public_key(client):
    invite1 = models.Invite(email="*****@*****.**",
                            role="Unit Personnel")
    generate_invite_key_pair(invite1)
    unituser = models.User.query.filter_by(username="******").first()
    unituser_token = encrypted_jwt_token(
        username=unituser.username,
        sensitive_content="password",
    )

    # Somehow the public key of the invite is not the expected public key
    invite1.public_key = b"useless_bytes"

    with pytest.raises(KeyOperationError) as error:
        share_project_private_key(
            from_user=unituser,
            to_another=invite1,
            from_user_token=unituser_token,
            project=unituser.unit.projects[0],
        )

    assert "User public key could not be loaded!" in str(error.value)
示例#7
0
    def invite_user(email, new_user_role, project=None, unit=None):
        """Invite a new user"""

        current_user_role = get_user_roles_common(user=auth.current_user())

        if not project:
            if current_user_role == "Project Owner":
                return {
                    "status": ddserr.InviteError.code.value,
                    "message":
                    "Project ID required to invite users to projects.",
                }
            if new_user_role == "Project Owner":
                return {
                    "status": ddserr.InviteError.code.value,
                    "message":
                    "Project ID required to invite a 'Project Owner'.",
                }

        # Verify role or current and new user
        if current_user_role == "Super Admin" and project:
            return {
                "status":
                ddserr.InviteError.code.value,
                "message":
                ("Super Admins do not have project data access and can therefore "
                 "not invite users to specific projects."),
            }
        elif current_user_role == "Unit Admin" and new_user_role == "Super Admin":
            return {
                "status": ddserr.AccessDeniedError.code.value,
                "message": ddserr.AccessDeniedError.description,
            }
        elif current_user_role == "Unit Personnel" and new_user_role in [
                "Super Admin",
                "Unit Admin",
        ]:
            return {
                "status": ddserr.AccessDeniedError.code.value,
                "message": ddserr.AccessDeniedError.description,
            }
        elif current_user_role == "Project Owner" and new_user_role in [
                "Super Admin",
                "Unit Admin",
                "Unit Personnel",
        ]:
            return {
                "status": ddserr.AccessDeniedError.code.value,
                "message": ddserr.AccessDeniedError.description,
            }
        elif current_user_role == "Researcher":
            return {
                "status": ddserr.AccessDeniedError.code.value,
                "message": ddserr.AccessDeniedError.description,
            }

        # Create invite row
        new_invite = models.Invite(
            email=email,
            role=("Researcher"
                  if new_user_role == "Project Owner" else new_user_role),
        )

        # Create URL safe token for invitation link
        token = encrypted_jwt_token(
            username="",
            sensitive_content=generate_invite_key_pair(
                invite=new_invite).hex(),
            expires_in=datetime.timedelta(
                hours=flask.current_app.config["INVITATION_EXPIRES_IN_HOURS"]),
            additional_claims={"inv": new_invite.email},
        )

        # Create link for invitation email
        link = flask.url_for("auth_blueprint.confirm_invite",
                             token=token,
                             _external=True)

        # Quick search gave this as the URL length limit.
        if len(link) >= 2048:
            flask.current_app.logger.error(
                "Invitation link was not possible to create due to length.")
            return {
                "message": "Invite failed due to server error",
                "status": http.HTTPStatus.INTERNAL_SERVER_ERROR,
            }

        projects_not_shared = {}
        goahead = False
        # Append invite to unit if applicable
        if new_invite.role in ["Unit Admin", "Unit Personnel"]:
            # TODO Change / move this later. This is just so that we can add an initial Unit Admin.
            if auth.current_user().role == "Super Admin":
                if unit:
                    unit_row = models.Unit.query.filter_by(
                        public_id=unit).one_or_none()
                    if not unit_row:
                        raise ddserr.DDSArgumentError(
                            message="Invalid unit publid id.")

                    unit_row.invites.append(new_invite)
                    goahead = True
                else:
                    raise ddserr.DDSArgumentError(
                        message=
                        "You need to specify a unit to invite a Unit Personnel or Unit Admin."
                    )

            if "Unit" in auth.current_user().role:
                # Give new unit user access to all projects of the unit
                auth.current_user().unit.invites.append(new_invite)
                if auth.current_user().unit.projects:
                    for unit_project in auth.current_user().unit.projects:
                        if unit_project.is_active:
                            try:
                                share_project_private_key(
                                    from_user=auth.current_user(),
                                    to_another=new_invite,
                                    from_user_token=dds_web.security.auth.
                                    obtain_current_encrypted_token(),
                                    project=unit_project,
                                )
                            except ddserr.KeyNotFoundError as keyerr:
                                projects_not_shared[
                                    unit_project.
                                    public_id] = "You do not have access to the project(s)"
                            else:
                                goahead = True
                else:
                    goahead = True

                if not project:  # specified project is disregarded for unituser invites
                    msg = f"{str(new_invite)} was successful."
                else:
                    msg = f"{str(new_invite)} was successful, but specification for {str(project)} dropped. Unit Users have automatic access to projects of their unit."

        else:
            db.session.add(new_invite)
            if project:
                try:
                    share_project_private_key(
                        from_user=auth.current_user(),
                        to_another=new_invite,
                        project=project,
                        from_user_token=dds_web.security.auth.
                        obtain_current_encrypted_token(),
                        is_project_owner=new_user_role == "Project Owner",
                    )
                except ddserr.KeyNotFoundError as keyerr:
                    projects_not_shared[
                        project.
                        public_id] = "You do not have access to the specified project."
                else:
                    goahead = True
            else:
                goahead = True

        # Compose and send email
        status_code = http.HTTPStatus.OK
        if goahead:
            try:
                db.session.commit()
            except (sqlalchemy.exc.SQLAlchemyError,
                    sqlalchemy.exc.OperationalError) as sqlerr:
                db.session.rollback()
                raise ddserr.DatabaseError(
                    message=str(sqlerr),
                    alt_message=f"Invitation failed" +
                    (": Database malfunction." if isinstance(
                        sqlerr, sqlalchemy.exc.OperationalError) else "."),
                ) from sqlerr

            AddUser.compose_and_send_email_to_user(userobj=new_invite,
                                                   mail_type="invite",
                                                   link=link)
            msg = f"{str(new_invite)} was successful."
        else:
            msg = (f"The user could not be added to the project(s)."
                   if projects_not_shared else
                   "Unknown error!") + " The invite did not succeed."
            status_code = ddserr.InviteError.code.value

        return {
            "email": new_invite.email,
            "message": msg,
            "status": status_code,
            "errors": projects_not_shared,
        }
示例#8
0
def test_share_project_keys_via_two_invites(client):
    # this test focuses only on the secure parts related to the following scenario

    # unituser invites a new Unit Personnel
    invite1 = models.Invite(email="*****@*****.**",
                            role="Unit Personnel")
    temporary_key = generate_invite_key_pair(invite1)
    invite_token1 = encrypted_jwt_token(
        username="",
        sensitive_content=temporary_key.hex(),
        additional_claims={"inv": invite1.email},
    )
    unituser = models.User.query.filter_by(username="******").first()
    unituser.unit.invites.append(invite1)
    unituser_token = encrypted_jwt_token(
        username=unituser.username,
        sensitive_content="password",
    )
    for project in unituser.unit.projects:
        share_project_private_key(
            from_user=unituser,
            to_another=invite1,
            from_user_token=unituser_token,
            project=project,
        )
    dds_web.db.session.commit()

    # ************************************

    # invited Unit Personnel follows the link and registers itself
    common_user_fields = {
        "username": "******",
        "password": "******",
        "name": "Test User",
    }
    new_user = models.UnitUser(**common_user_fields)
    invite1.unit.users.append(new_user)
    new_email = models.Email(email=invite1.email, primary=True)
    new_user.emails.append(new_email)
    new_user.active = True
    dds_web.db.session.add(new_user)
    verify_and_transfer_invite_to_user(invite_token1, new_user,
                                       common_user_fields["password"])
    for project_invite_key in invite1.project_invite_keys:
        project_user_key = models.ProjectUserKeys(
            project_id=project_invite_key.project_id,
            user_id=new_user.username,
            key=project_invite_key.key,
        )
        dds_web.db.session.add(project_user_key)
        dds_web.db.session.delete(project_invite_key)

    assert invite1.nonce != new_user.nonce
    assert invite1.public_key == new_user.public_key
    assert invite1.private_key != new_user.private_key

    dds_web.db.session.delete(invite1)
    dds_web.db.session.commit()

    # ************************************

    # new Unit Personnel invites another new Unit Personnel
    invite2 = models.Invite(email="*****@*****.**",
                            role="Unit Personnel")
    invite_token2 = encrypted_jwt_token(
        username="",
        sensitive_content=generate_invite_key_pair(invite2).hex(),
        additional_claims={"inv": invite2.email},
    )
    unituser = models.User.query.filter_by(
        username="******").first()
    unituser.unit.invites.append(invite2)
    unituser_token = encrypted_jwt_token(
        username=unituser.username,
        sensitive_content=common_user_fields["password"],
    )
    for project in unituser.unit.projects:
        share_project_private_key(
            from_user=unituser,
            to_another=invite2,
            from_user_token=unituser_token,
            project=project,
        )
    dds_web.db.session.commit()

    project_invite_keys = invite2.project_invite_keys
    number_of_asserted_projects = 0
    for project_invite_key in project_invite_keys:
        if (project_invite_key.project.public_id == "public_project_id"
                or project_invite_key.project.public_id == "unused_project_id"
                or project_invite_key.project.public_id
                == "restricted_project_id"
                or project_invite_key.project.public_id
                == "second_public_project_id"
                or project_invite_key.project.public_id
                == "file_testing_project"):
            number_of_asserted_projects += 1
    assert len(project_invite_keys) == number_of_asserted_projects
    assert len(project_invite_keys) == 5