Beispiel #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)
Beispiel #2
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)
Beispiel #3
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)
Beispiel #4
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)
Beispiel #5
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)
Beispiel #6
0
    def give_project_access(project_list, current_user, user):
        """Give specific user project access."""
        # Loop through and check that the project(s) is(are) active
        fix_errors = {}
        for proj in project_list:
            try:
                if proj.is_active:
                    project_keys_row = models.ProjectUserKeys.query.filter_by(
                        project_id=proj.id, user_id=user.username
                    ).one_or_none()
                    if not project_keys_row:
                        share_project_private_key(
                            from_user=current_user,
                            to_another=user,
                            project=proj,
                            from_user_token=dds_web.security.auth.obtain_current_encrypted_token(),
                        )
            except KeyNotFoundError as keyerr:
                fix_errors[
                    proj.public_id
                ] = "You do not have access to this project. Please contact the responsible unit."

        return fix_errors
Beispiel #7
0
def fill_basic_db(db):
    """
    Fill the database with basic data.
    """

    units, users, projects = add_data_to_db()
    db.session.add_all(units)
    db.session.add_all(users)

    db.session.commit()

    generate_project_key_pair(users[2], units[0].projects[0])
    generate_project_key_pair(users[2], units[0].projects[2])
    generate_project_key_pair(users[2], units[0].projects[4])

    generate_project_key_pair(users[3], units[0].projects[1])
    generate_project_key_pair(users[3], units[0].projects[3])

    db.session.commit()

    user2_token = encrypted_jwt_token(
        username=users[2].username,
        sensitive_content="password",
    )
    share_project_private_key(
        from_user=users[2],
        to_another=users[0],
        from_user_token=user2_token,
        project=projects[0],
    )
    share_project_private_key(
        from_user=users[2],
        to_another=users[1],
        from_user_token=user2_token,
        project=projects[0],
    )

    user3_token = encrypted_jwt_token(
        username=users[3].username,
        sensitive_content="password",
    )
    share_project_private_key(
        from_user=users[3],
        to_another=users[6],
        from_user_token=user3_token,
        project=projects[3],
    )

    db.session.commit()
Beispiel #8
0
    def add_to_project(whom, project, role, send_email=True):
        """Add existing user or invite to a project"""

        allowed_roles = ["Project Owner", "Researcher"]

        if role not in allowed_roles:
            return {
                "status":
                ddserr.AccessDeniedError.code.value,
                "message": ("User Role should be either 'Project Owner' or "
                            "'Researcher' to be added to a project"),
            }

        if whom.role not in allowed_roles:
            return {
                "status":
                ddserr.AccessDeniedError.code.value,
                "message":
                ("Users affiliated with units can not be added to projects individually."
                 ),
            }

        is_owner = role == "Project Owner"
        ownership_change = False

        if isinstance(whom, models.ResearchUser):
            project_user_row = models.ProjectUsers.query.filter_by(
                project_id=project.id, user_id=whom.username).one_or_none()
        else:
            project_user_row = models.ProjectInviteKeys.query.filter_by(
                project_id=project.id, invite_id=whom.id).one_or_none()

        if project_user_row:
            send_email = False
            if project_user_row.owner == is_owner:
                return {
                    "status":
                    ddserr.RoleException.code.value,
                    "message":
                    f"{str(whom)} is already associated with the {str(project)} in this capacity. ",
                }
            ownership_change = True
            project_user_row.owner = is_owner

        if not ownership_change:
            if isinstance(whom, models.ResearchUser):
                project.researchusers.append(
                    models.ProjectUsers(
                        project_id=project.id,
                        user_id=whom.username,
                        owner=is_owner,
                    ))

            try:
                share_project_private_key(
                    from_user=auth.current_user(),
                    to_another=whom,
                    from_user_token=dds_web.security.auth.
                    obtain_current_encrypted_token(),
                    project=project,
                    is_project_owner=is_owner,
                )
            except ddserr.KeyNotFoundError as keyerr:
                return {
                    "message":
                    ("You do not have access to the current project. To get access, "
                     "ask the a user within the responsible unit to grant you access."
                     ),
                    "status":
                    ddserr.AccessDeniedError.code.value,
                }

        try:
            db.session.commit()
        except (
                sqlalchemy.exc.SQLAlchemyError,
                sqlalchemy.exc.IntegrityError,
                sqlalchemy.exc.OperationalError,
        ) as err:
            flask.current_app.logger.exception(err)
            db.session.rollback()
            raise ddserr.DatabaseError(
                message=str(err),
                alt_message=
                f"Server Error: User was not associated with the project" +
                (": Database malfunction." if isinstance(
                    err, sqlalchemy.exc.OperationalError) else "."),
            ) from err

        # If project is already released and not expired, send mail to user
        send_email = send_email and project.current_status == "Available"
        if send_email:
            AddUser.compose_and_send_email_to_user(whom,
                                                   "project_release",
                                                   project=project)

        flask.current_app.logger.debug(
            f"{str(whom)} was given access to the {str(project)} as a {'Project Owner' if is_owner else 'Researcher'}."
        )

        return {
            "status":
            http.HTTPStatus.OK,
            "message":
            (f"{str(whom)} was given access to the "
             f"{str(project)} as a {'Project Owner' if is_owner else 'Researcher'}. An e-mail notification has{' not ' if not send_email else ' '}been sent."
             ),
        }
Beispiel #9
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,
        }
Beispiel #10
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
Beispiel #11
0
def fill_db():
    """Fills the database with initial entries used for development."""

    # Foreign key/relationship updates:
    # The model with the row db.relationship should append the row of the model with foreign key

    password = "******"

    # Super Admin
    superadmin = models.SuperAdmin(username="******",
                                   password=password,
                                   name="Super Admin")
    superadmin_email = models.Email(email="*****@*****.**",
                                    primary=True)
    superadmin_email.user = superadmin
    db.session.add(superadmin_email)

    # Create first unit user
    unituser_1 = models.UnitUser(
        username="******",
        password=password,
        name="First Unit User",
    )

    # Create second unit user
    unituser_2 = models.UnitUser(
        username="******",
        password=password,
        name="Second Unit User",
    )

    # create a few e-mail addresses
    email_unituser_1 = models.Email(email="*****@*****.**",
                                    primary=True)
    email_unituser_1b = models.Email(email="*****@*****.**",
                                     primary=False)
    email_unituser_2 = models.Email(email="*****@*****.**",
                                    primary=True)
    email_unituser_1.user = unituser_1
    email_unituser_1b.user = unituser_1
    email_unituser_2.user = unituser_2
    unituser_1.active = True
    unituser_2.active = True

    # Create first unit
    unit_1 = models.Unit(
        public_id="unit_1",
        name="Unit 1",
        external_display_name="Unit 1 external",
        contact_email="*****@*****.**",
        internal_ref="someunit",
        safespring_endpoint=current_app.config.get("SAFESPRING_URL"),
        safespring_name=current_app.config.get("DDS_SAFESPRING_PROJECT"),
        safespring_access=current_app.config.get("DDS_SAFESPRING_ACCESS"),
        safespring_secret=current_app.config.get("DDS_SAFESPRING_SECRET"),
    )

    unit_1.users.extend([unituser_1, unituser_2])

    # Create first project - leave out foreign key
    project_1 = models.Project(
        public_id="project_1",
        title="First Project",
        description=
        "This is a test project. You will be able to upload to but NOT download "
        "from this project. Create a new project to test the entire system. ",
        pi="*****@*****.**",
        bucket="testbucket",
    )

    project_1.project_statuses.append(
        models.ProjectStatuses(**{
            "status": "In Progress",
            "date_created": dds_web.utils.current_time()
        }))

    unituser_1.created_projects.append(project_1)

    generate_project_key_pair(unituser_1, project_1)

    # Create second project - leave out foreign key
    project_2 = models.Project(
        public_id="project_2",
        title="Second Project",
        description=
        "This is a test project. You will be able to upload to but NOT download "
        "from this project. Create a new project to test the entire system. ",
        pi="*****@*****.**",
        bucket=
        f"secondproject-{str(dds_web.utils.timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}",
    )

    project_2.project_statuses.append(
        models.ProjectStatuses(**{
            "status": "In Progress",
            "date_created": dds_web.utils.current_time()
        }))

    unituser_2.created_projects.append(project_2)

    generate_project_key_pair(unituser_2, project_2)

    # Connect project to unit. append (not =) due to many projects per unit
    unit_1.projects.extend([project_1, project_2])

    # Create an email
    email_researchuser_1 = models.Email(email="*****@*****.**",
                                        primary=True)
    # Create first research user
    researchuser_1 = models.ResearchUser(
        username="******",
        password=password,
        name="First Research User",
    )
    email_researchuser_1.user = researchuser_1
    # Create association with user - not owner of project
    project_1_user_1_association = models.ProjectUsers(owner=False)
    # Connect research user to association row. = (not append) due to one user per ass. row
    project_1_user_1_association.researchuser = researchuser_1
    # Connect project to association row. = (not append) due to one project per ass. row
    project_1_user_1_association.project = project_1

    researchuser_1.active = True

    email_researchuser_2 = models.Email(email="*****@*****.**",
                                        primary=True)
    # Create second research user
    researchuser_2 = models.ResearchUser(
        username="******",
        password=password,
        name="Second Research User",
    )
    email_researchuser_2.user = researchuser_2
    # Create association with user - is owner of project
    project_1_user_2_association = models.ProjectUsers(owner=True)
    # Connect research user to association row. = (not append) due to one user per ass. row
    project_1_user_2_association.researchuser = researchuser_2
    # Connect project to association row. = (not append) due to one project per ass. row
    project_1_user_2_association.project = project_1

    researchuser_2.active = True

    # Add unit to database - relationship will add the rest because of foreign key constraints
    db.session.add(unit_1)

    db.session.commit()

    unituser_1_token = encrypted_jwt_token(
        username=unituser_1.username,
        sensitive_content=password,
    )

    share_project_private_key(
        from_user=unituser_1,
        to_another=researchuser_1,
        from_user_token=unituser_1_token,
        project=project_1,
    )

    share_project_private_key(
        from_user=unituser_1,
        to_another=researchuser_2,
        from_user_token=unituser_1_token,
        project=project_1,
    )

    db.session.commit()