Пример #1
0
def test_created_at_defaults_to_datetime(superuser, user, collection):
    """Test creation date."""
    permission = Permission(user=user, collection=collection)
    permission.save_as(superuser)

    assert bool(permission.created_at)
    assert isinstance(permission.created_at, datetime)
Пример #2
0
def test_get_by_id(superuser, user, collection):
    """Get permission by ID."""
    permission = Permission(user=user, collection=collection)
    permission.save_as(superuser)

    retrieved = Permission.get_by_id(permission.id)
    assert retrieved == permission
Пример #3
0
def test_adding_permissions(collection):
    """Grant a permission to a user."""
    user = UserFactory()
    user.save()
    permission = Permission(user=user, collection=collection)
    permission.save_as(user)

    assert permission in user.permissions
Пример #4
0
def test_adding_permissions(superuser, user):
    """Add a permission on the collection."""
    collection = CollectionFactory()
    collection.save()
    permission = Permission(user=user, collection=collection)
    permission.save_as(superuser)

    assert permission in collection.permissions
Пример #5
0
def test_has_any_permission_for(user, collection, superuser):
    """Test has_any_permission_for return value."""
    other_collection = CollectionFactory()
    regular_permission = Permission(user=user, collection=other_collection,
                                    cataloging_admin=False).save_as(superuser)
    assert user.has_any_permission_for(collection) is False
    regular_permission.collection = collection
    regular_permission.save()
    assert user.has_any_permission_for(collection) is True
Пример #6
0
def forget_user(email, dry_run):
    """Remove all traces of a user from the system."""
    user = User.get_by_email(email)
    if user:
        if user.is_admin:
            click.echo('User "{}" is a sysadmin, refusing to delete.'.format(user))
            sys.exit(1)

        if len(User.get_modified_and_created_by_user(user)) > 0:
            click.echo('User "{}" has created or modified users, refusing to delete.'.format(user))
            sys.exit(1)

        if len(Collection.get_modified_and_created_by_user(user)) > 0:
            click.echo('User "{}" has created or modified collections, '
                       'refusing to delete.'.format(user))
            sys.exit(1)

        if len(Permission.get_modified_and_created_by_user(user)) > 0:
            click.echo('User "{}" has created or modified permissions, '
                       'refusing to delete.'.format(user))
            sys.exit(1)

        if dry_run:
            tokens = Token.get_all_by_user(user)
            grants = Grant.get_all_by_user(user)
            failed_login_attempts = FailedLoginAttempt.get_all_by_user(user)
            permissions = user.permissions
            password_resets = user.password_resets
            click.echo('These tokens would be deleted: {}'.format(tokens))
            click.echo('These grants would be deleted: {}'.format(grants))
            click.echo('These failed login attempts would be deleted: {}'.format(
                failed_login_attempts))
            click.echo('These permissions would be deleted: {}'.format(permissions))
            click.echo('These password_resets would be deleted: {}'.format(password_resets))
        else:
            if click.confirm('Are you sure you want to delete all information '
                             'related to user "{}"?'.format(user)):
                Token.delete_all_by_user(user)
                Grant.delete_all_by_user(user)
                Permission.delete_all_by_user(user)
                PasswordReset.delete_all_by_user(user)
                FailedLoginAttempt.delete_all_by_user(user)
                user.delete()

    else:
        click.echo('User "{}" not found. Aborting...'.format(email))
        sys.exit(1)
Пример #7
0
def test_get_permissions_as_seen_by_other_user(user, superuser):
    """Test getting permissions for someone as regular user."""
    collection1, collection2 = CollectionFactory(), CollectionFactory()
    Permission(user=superuser, collection=collection1, cataloging_admin=True).save_as(superuser)
    col2_permission = Permission(user=superuser, collection=collection2,
                                 cataloging_admin=False).save_as(superuser)

    # If we have no permissions, we see no permissions
    assert superuser.get_permissions_as_seen_by(user) == []

    # If we only have non-cataloging admin permissions, we see no permissions
    Permission(user=user, collection=collection1, cataloging_admin=False).save_as(superuser)
    assert superuser.get_permissions_as_seen_by(user) == []

    # If we have cataloging admin permissions, we see permissions for that collection
    Permission(user=user, collection=collection2, cataloging_admin=True).save_as(superuser)
    assert superuser.get_permissions_as_seen_by(user) == [col2_permission]
Пример #8
0
def test_is_cataloging_admin_for(user, collection, superuser):
    """Test is_cataloging_admin_for return value."""
    other_collection = CollectionFactory()
    not_admin_permission = Permission(user=user, collection=collection,
                                      cataloging_admin=False).save_as(superuser)
    admin_permission = Permission(user=user, collection=other_collection,
                                  cataloging_admin=True).save_as(superuser)

    assert user.is_cataloging_admin_for(not_admin_permission.collection) is False
    assert user.is_cataloging_admin_for(admin_permission.collection) is True

    assert user.is_cataloging_admin_for(admin_permission.collection,
                                        not_admin_permission.collection) is False
    not_admin_permission.cataloging_admin = True
    now_also_admin_permission = not_admin_permission.save()
    assert user.is_cataloging_admin_for(admin_permission.collection,
                                        now_also_admin_permission.collection) is True
Пример #9
0
def test_get_permissions_as_seen_by(collection, user, superuser):
    """Test getting viewable permissions as 'user'."""
    # If there are no permissions, we se no permissions.
    assert collection.get_permissions_as_seen_by(user) == []

    # If 'user' is not cataloging admin, they don't see regular user permissions on a collection.
    other_user = UserFactory()
    others_non_cataloging_admin_permission = Permission(user=other_user, collection=collection,
                                                        cataloging_admin=False).save_as(superuser)
    assert others_non_cataloging_admin_permission not in \
        collection.get_permissions_as_seen_by(user)

    # 'user' sees own and cataloging admin permissions on a collection.
    users_own_permission = Permission(user=user, collection=collection,
                                      cataloging_admin=False).save_as(superuser)
    third_user = UserFactory()
    thirds_cataloging_admin_permission = Permission(user=third_user, collection=collection,
                                                    cataloging_admin=True).save_as(superuser)
    assert len(collection.get_permissions_as_seen_by(user)) == 2
    assert users_own_permission in collection.get_permissions_as_seen_by(user)
    assert thirds_cataloging_admin_permission in collection.get_permissions_as_seen_by(user)

    # When 'user' becomes a cataloging admin on a collection, they sees all permissions.
    users_own_permission.cataloging_admin = True
    users_own_permission.save()
    assert len(collection.get_permissions_as_seen_by(user)) == 3
    assert others_non_cataloging_admin_permission in collection.get_permissions_as_seen_by(user)
    assert users_own_permission in collection.get_permissions_as_seen_by(user)
    assert thirds_cataloging_admin_permission in collection.get_permissions_as_seen_by(user)

    # As a system admin, you see all permissions on a collection.
    assert len(superuser.permissions) == 0
    assert len(collection.get_permissions_as_seen_by(superuser)) == 3
Пример #10
0
def test_modified_at_defaults_to_current_datetime(superuser, user, collection):
    """Test modified date."""
    permission = Permission(user=user, collection=collection)
    permission.save_as(superuser)
    first_modified_at = permission.modified_at

    assert abs((first_modified_at - permission.created_at).total_seconds()) < 10

    permission.registrant = not permission.registrant
    permission.save()

    assert first_modified_at != permission.modified_at
Пример #11
0
def test_removing_permissions(collection):
    """Withdraw permission from a user."""
    user = UserFactory()
    user.save()
    permission = Permission(user=user, collection=collection)
    permission.save_as(user)
    permission.delete()

    assert permission not in user.permissions
Пример #12
0
def test_removing_permissions(superuser, user):
    """Remove the permissions an a collection."""
    collection = CollectionFactory()
    collection.save()
    permission = Permission(user=user, collection=collection)
    permission.save_as(superuser)
    permission.delete()

    assert permission not in collection.permissions
Пример #13
0
def test_is_cataloging_admin(superuser, user):
    """Test is_cataloging_admin return value."""
    collection1, collection2 = CollectionFactory(), CollectionFactory()
    not_admin_permission = Permission(user=user, collection=collection1,
                                      cataloging_admin=False).save_as(superuser)
    admin_permission = Permission(user=user, collection=collection2,
                                  cataloging_admin=True).save_as(superuser)

    assert not_admin_permission in user.permissions and admin_permission in user.permissions
    assert user.is_cataloging_admin is True

    admin_permission.delete()
    assert admin_permission not in user.permissions
    assert user.is_cataloging_admin is False

    not_admin_permission.delete()
    assert user.permissions == []
    assert user.is_cataloging_admin is False
Пример #14
0
def test_delete_all_by_user(db, user, superuser, collection):
    """Delete all permissions for a specific user."""
    permission = Permission(user=user, collection=collection)
    permission.save_as(superuser)

    other_permission = PermissionFactory()
    db.session.commit()

    Permission.delete_all_by_user(user)
    permissions = Permission.query.all()
    assert permission not in permissions
    assert [other_permission] == permissions
Пример #15
0
def test_created_by_and_modified_by_is_updated(superuser, user, collection):
    """Test created/modified by."""
    permission = Permission(user=user, collection=collection)
    permission.save_as(superuser)
    assert permission.created_by_id == superuser.id
    assert permission.created_by == superuser
    assert permission.modified_by_id == superuser.id
    assert permission.modified_by == superuser

    # Another superuser updates something in the permission.
    another_superuser = SuperUserFactory()
    permission.update_as(another_superuser, commit=True, cataloger=not permission.cataloger)
    assert permission.created_by == superuser
    assert permission.modified_by == another_superuser
Пример #16
0
def import_data(verbose, admin_email, wipe_permissions, send_password_resets):
    """Read data from Voyager dump and BibDB API to create DB entities.

    Creates:
        - collections
        - user accounts for collection managers
        - permissions between the two

    """
    import requests
    from flask_babel import gettext

    from .collection.forms import RegisterForm as CollectionRegisterForm
    from .collection.models import Collection
    from .permission.models import Permission
    from .user.forms import RegisterForm as UserRegisterForm
    from .user.models import PasswordReset, User

    def _get_collection_details_from_bibdb(code):
        raw_bibdb_api_data = json.loads(requests.get(
            'https://bibdb.libris.kb.se/api/lib?level=brief&sigel={}'
            .format(code)).content.decode('utf-8'))
        if raw_bibdb_api_data['query']['operation'] == 'sigel {}'.format(code):
            if verbose:
                print('Fetched details for sigel %r' % code)
            else:
                click.echo('.', nl=False)
        else:
            if not verbose:
                click.echo('x', nl=False)
            raise AssertionError('Lookup failed for sigel %r' % code)

        bibdb_api_data = None
        for chunk in raw_bibdb_api_data['libraries']:
            if chunk['sigel'] == code:
                if bibdb_api_data is not None:
                    raise AssertionError('Duplicate results for sigel %r' % code)
                bibdb_api_data = chunk
        if not bibdb_api_data:
            raise AssertionError('Zero results for sigel %r' % code)

        if bibdb_api_data['type'] in {'library', 'bibliography'}:
            category = bibdb_api_data['type']
        else:
            category = 'uncategorized'

        if bibdb_api_data['dept']:
            friendly_name = '%s, %s' % (bibdb_api_data['name'], bibdb_api_data['dept'])
        else:
            friendly_name = bibdb_api_data['name']

        assert bibdb_api_data['alive'] in {True, False}

        collection = {
            'friendly_name': friendly_name,
            'code': bibdb_api_data['sigel'],
            'category': category,
            'is_active': bibdb_api_data['alive'],
            'replaces': bibdb_api_data['sigel_old'],
            'replaced_by': bibdb_api_data['sigel_new']
        }

        if bibdb_api_data['date_created']:
            collection['created_at'] = \
                dt.datetime.strptime(bibdb_api_data['date_created'], '%Y-%m-%dT%H:%M:%S')

        return collection

    def _get_voyager_data():
        raw_voyager_sigels_and_locations = requests.get(
            'https://github.com/libris/xl_auth/files/1513982/171129_KB--sigel_locations.txt'
        ).content.decode('latin-1').splitlines()
        voyager_sigels_and_collections = dict()
        voyager_main_sigels, voyager_location_sigels = set(), set()
        for voyager_row in raw_voyager_sigels_and_locations:
            voyager_sigel, voyager_location = voyager_row.split(',')
            assert voyager_sigel and voyager_location
            voyager_main_sigels.add(voyager_sigel)
            voyager_location_sigels.add(voyager_location)
            if voyager_sigel == 'SEK' and voyager_location != 'SEK':
                continue  # Don't add all the collections under SEK super cataloger.
            if voyager_sigel in voyager_sigels_and_collections:
                voyager_sigels_and_collections[voyager_sigel].add(voyager_location)
            else:
                voyager_sigels_and_collections[voyager_sigel] = {voyager_location}
        print('voyager_main_sigels:', len(voyager_main_sigels), '/',
              'voyager_location_sigels:', len(voyager_location_sigels))
        print('(voyager_main_sigels | voyager_location_sigels):',
              len(voyager_main_sigels | voyager_location_sigels))

        return {
            'sigel_to_collections': voyager_sigels_and_collections,
            'sigels': voyager_main_sigels,
            'collections': voyager_location_sigels
        }

    def _get_bibdb_cataloging_admins():
        raw_bibdb_sigels_and_cataloging_admins = requests.get(
            'https://libris.kb.se/libinfo/library_konreg.jsp').content.decode('utf-8').splitlines()

        registering_bibdb_sigels, bibdb_cataloging_admins = set(), set()
        bibdb_sigels_and_cataloging_admins = dict()
        bibdb_cataloging_admins_and_sigels = dict()
        bibdb_cataloging_admin_emails_and_names = dict()
        for bibdb_row in raw_bibdb_sigels_and_cataloging_admins:
            try:
                bibdb_sigel, cataloging_admin_name, cataloging_admin_email = bibdb_row.split(',')
            except ValueError as err:
                print('ValueError: %s / bibdb_row: %r' % (err, bibdb_row))
                continue
            cataloging_admin_email = cataloging_admin_email.lower()
            bibdb_cataloging_admin_emails_and_names[cataloging_admin_email] = cataloging_admin_name
            assert bibdb_sigel != ''
            registering_bibdb_sigels.add(bibdb_sigel)
            if not cataloging_admin_email:
                continue

            bibdb_cataloging_admins.add(cataloging_admin_email)
            bibdb_sigels_and_cataloging_admins[bibdb_sigel] = cataloging_admin_email

            if cataloging_admin_email in bibdb_cataloging_admins_and_sigels:
                bibdb_cataloging_admins_and_sigels[cataloging_admin_email].add(bibdb_sigel)
            else:
                bibdb_cataloging_admins_and_sigels[cataloging_admin_email] = {bibdb_sigel}

        print('registering_bibdb_sigels:', len(registering_bibdb_sigels), '/',
              'bibdb_cataloging_admins:', len(bibdb_cataloging_admins))

        return {
            'sigel_to_cataloging_admins': bibdb_sigels_and_cataloging_admins,
            'cataloging_admin_to_sigels': bibdb_cataloging_admins_and_sigels,
            'cataloging_admin_emails_to_names': bibdb_cataloging_admin_emails_and_names,
            'sigels': registering_bibdb_sigels,
            'cataloging_admins': bibdb_cataloging_admins,
        }

    def _get_bibdb_sigels_not_in_voyager(bibdb_sigels, voyager_sigels):
        unknown_sigels = set()
        for bibdb_sigel in bibdb_sigels:
            if bibdb_sigel not in voyager_sigels:
                unknown_sigels.add(bibdb_sigel)
        return unknown_sigels

    def _generate_xl_auth_cataloging_admins_and_collections(bibdb_cataloging_admin_to_sigels,
                                                            voyager_sigel_to_collections):
        pre_total, post_total = 0, 0
        voyager_sigels_unknown_in_bibdb = set()
        xl_auth_cataloging_admins = dict()
        xl_auth_collections = dict()
        # Prepare permissions for cataloging admins.
        for cataloging_admin, sigels in bibdb_cataloging_admin_to_sigels.items():
            pre_total += len(sigels)
            xl_auth_cataloging_admins[cataloging_admin] = set()
            for sigel in sigels:
                # Fetch details if necessary.
                if sigel not in xl_auth_collections:
                    xl_auth_collections[sigel] = _get_collection_details_from_bibdb(sigel)

                xl_auth_cataloging_admins[cataloging_admin].add(sigel)

                if sigel in bibdb_sigels_unknown_in_voyager:
                    continue  # Don't attempt resolving sigels that does not exist in Voyager.

                # Add additional sigels from Voyager sigel-to-"sub-sigel" mapping.
                for voyager_collection in voyager_sigel_to_collections[sigel]:
                    if voyager_collection not in xl_auth_collections:
                        try:
                            # Fetch details if necessary.
                            xl_auth_collections[voyager_collection] = \
                                _get_collection_details_from_bibdb(voyager_collection)
                        except AssertionError as err:
                            voyager_sigels_unknown_in_bibdb.add(voyager_collection)
                            if verbose:
                                print(err)
                            continue
                    xl_auth_cataloging_admins[cataloging_admin].add(voyager_collection)

            post_total += len(xl_auth_cataloging_admins[cataloging_admin])

        print('\npre_total:', pre_total, '/ post_total:', post_total)
        print('voyager_sigels_unknown_in_bibdb:', voyager_sigels_unknown_in_bibdb)

        resolved_bibdb_refs = set()
        unresolved_bibdb_refs = set()
        print('before-replaces-lookups:', len(xl_auth_collections))
        for _ in range(10):
            for _, details in deepcopy(xl_auth_collections).items():
                for old_new_ref in {'replaces', 'replaced_by'}:
                    if details[old_new_ref] and details[old_new_ref] not in xl_auth_collections:
                        try:
                            xl_auth_collections[details[old_new_ref]] = \
                                _get_collection_details_from_bibdb(details[old_new_ref])
                            resolved_bibdb_refs.add(details[old_new_ref])
                        except AssertionError as err:
                            unresolved_bibdb_refs.add(details[old_new_ref])
                            print(err)
        print('after-replaces-lookups:', len(xl_auth_collections))

        print('resolved_bibdb_refs:', resolved_bibdb_refs)
        print('unresolved_bibdb_refs:', unresolved_bibdb_refs)

        return {
            'collections': xl_auth_collections,
            'cataloging_admins': xl_auth_cataloging_admins
        }

    def _get_manually_added_permissions():
        emails_and_collection_codes = requests.get(
            'https://docs.google.com/spreadsheets/d/e/2PACX-1vT2TjS_L9_J5LJztfKWo0UxQD-RCZo3bheFIH'
            'Ouz2Gu-aGcd7IrlDzHDmQ2yL726z0BnSc47vasL0l3/pub?gid=0&single=true&output=tsv'
        ).content.decode('utf-8').splitlines()

        manual_additions = []
        for add_row in emails_and_collection_codes[1:]:
            add_email, add_code, _ = add_row.split('\t')
            manual_additions.append((add_email.strip(), add_code.strip()))

        return manual_additions

    def _get_manually_deleted_permissions():
        emails_and_collection_codes = requests.get(
            'https://docs.google.com/spreadsheets/d/e/2PACX-1vT2TjS_L9_J5LJztfKWo0UxQD-RCZo3bheFIH'
            'Ouz2Gu-aGcd7IrlDzHDmQ2yL726z0BnSc47vasL0l3/pub?gid=518641812&single=true&output=tsv'
        ).content.decode('utf-8').splitlines()

        manual_deletions = []
        for del_row in emails_and_collection_codes[1:]:
            del_email, del_code, _ = del_row.split('\t')
            manual_deletions.append((del_email.strip(), del_code.strip()))

        return manual_deletions

    # Get admin user
    admin = User.get_by_email(admin_email)

    # Gather data.
    voyager = _get_voyager_data()
    bibdb = _get_bibdb_cataloging_admins()

    bibdb_sigels_unknown_in_voyager = \
        _get_bibdb_sigels_not_in_voyager(bibdb['sigels'], voyager['sigels'])
    print('bibdb_sigels_unknown_in_voyager:', bibdb_sigels_unknown_in_voyager)

    # Compile it into xl_auth-compatible model.
    xl_auth = _generate_xl_auth_cataloging_admins_and_collections(
        bibdb['cataloging_admin_to_sigels'], voyager['sigel_to_collections'])

    # Store collections.
    for collection, details in deepcopy(xl_auth['collections']).items():
        with current_app.test_request_context():
            collection_form = CollectionRegisterForm(admin, code=details['code'],
                                                     friendly_name=details['friendly_name'])
            collection_form.validate()
        if collection_form.code.errors or collection_form.friendly_name.errors:
            for code_error in collection_form.code.errors:
                print('collection %r: %s' % (collection, code_error))
            for friendly_name_error in collection_form.friendly_name.errors:
                print('friendly_name %r: %s' % (details['friendly_name'], friendly_name_error))
            del xl_auth['collections'][collection]
            continue

        collection = Collection.get_by_code(code=details['code'])
        if collection:
            if collection.is_active != details['is_active']:
                collection.is_active = details['is_active']
                collection.save_as(admin)
                print('corrected collection %r: is_active=%s'
                      % (collection.code, collection.is_active))
        else:
            collection = Collection(**details)
            if collection.created_at:
                collection.modified_at = collection.created_at
                collection.modified_by = collection.created_by = admin
                collection.save(preserve_modified=True)
            else:
                collection.save_as(admin)

    # Store users.
    for email, full_name in deepcopy(bibdb['cataloging_admin_emails_to_names']).items():
        if email not in bibdb['cataloging_admins']:
            del bibdb['cataloging_admin_emails_to_names'][email]
            continue

        with current_app.test_request_context():
            user_form = UserRegisterForm(None, username=email, full_name=full_name)
            user_form.validate()
        if gettext('Email already registered') in user_form.username.errors:
            pass
        elif user_form.username.errors or user_form.full_name.errors:
            print('validation failed for %s <%s>' % (full_name, email))
            for username_error in user_form.username.errors:
                print('email %r: %s' % (email, username_error))
            for full_name_error in user_form.full_name.errors:
                print('full_name %r: %s' % (full_name, full_name_error))
            del bibdb['cataloging_admin_emails_to_names'][email]
            continue

        user = User.get_by_email(email)
        if not user:
            user = User(email=email, full_name=full_name, is_active=False)
            if send_password_resets:  # Requires SERVER_NAME and PREFERRED_URL_SCHEME env vars.
                with current_app.test_request_context():
                    password_reset = PasswordReset(user)
                    password_reset.send_email(account_registration_from_user=admin)
                    user.save_as(admin)
                    password_reset.save()
                print('Added inactive user %r (password reset email sent).' % email)
            else:
                user.save_as(admin)
                print('Added inactive user %r (no password reset).' % email)

    old_permissions = Permission.query.all()
    current_permissions, new_permissions, removed_permissions = [], [], []

    # Store permissions.
    for email, collections in xl_auth['cataloging_admins'].items():
        user = User.get_by_email(email)
        if not user:
            continue

        for code in collections:
            collection = Collection.get_by_code(code)
            if not collection:
                print('Collection %r does not exist' % code)
                continue
            permission = Permission.query.filter_by(user_id=user.id,
                                                    collection_id=collection.id).first()
            if permission:
                current_permissions.append(permission)
            elif collection.is_active:  # No creating permissions on inactive collections.
                if user.email == '*****@*****.**':
                    permission = Permission.create_as(admin, user=user, collection=collection,
                                                      registrant=True,
                                                      cataloger=collection.code == 'Utb2',
                                                      cataloging_admin=False)
                else:
                    permission = Permission.create_as(admin, user=user, collection=collection,
                                                      registrant=True, cataloger=True,
                                                      cataloging_admin=True)
                new_permissions.append(permission)

    # Apply manual additions.
    for email, code in _get_manually_added_permissions():
        user = User.get_by_email(email)
        if not user:
            print('Cannot add permission manually; user %r does not exist' % email)
            continue

        collection = Collection.get_by_code(code)
        if not collection:
            print('Cannot add permission manually, collection %r does not exist' % code)
            continue

        permission = Permission.query.filter_by(user_id=user.id,
                                                collection_id=collection.id).first()
        if permission:
            current_permissions.append(permission)
            if verbose:
                print('Manual permission for %r on %r already exists.' % (email, code))
        else:
            permission = Permission.create_as(admin, user=user, collection=collection,
                                              registrant=True, cataloger=True,
                                              cataloging_admin=True)
            new_permissions.append(permission)
            if verbose:
                print('Manually added permissions for %r on %r.' % (email, code))

    # Apply manual deletions.
    for email, code in _get_manually_deleted_permissions():
        user = User.get_by_email(email)
        if not user:
            print('Cannot delete permission manually; user %r does not exist' % email)
            continue

        collection = Collection.get_by_code(code)
        if not collection:
            print('Cannot delete permission manually, collection %r does not exist' % code)
            continue

        permission = Permission.query.filter_by(user_id=user.id,
                                                collection_id=collection.id).first()
        if permission:
            permission.delete()
            removed_permissions.append(permission)
            if verbose:
                print('Manually deleted permissions for %r on %r.' % (email, code))
        else:
            current_permissions.append(permission)
            if verbose:
                print('Cannot manually deleted permissions for %r on %r; does not exist.'
                      % (email, code))

    # Optionally wipe permissions not deduced from controlled sources (BibDB, Voyager, manual),
    # but only if created by admin account. And also existing permissions on inactive collections.
    for permission in old_permissions:
        if not permission.collection.is_active:
            print('Existing permission for %r on inactive collection %r (deleting=%s).'
                  % (permission.user.email, permission.collection.code, wipe_permissions))
            if wipe_permissions:
                permission.delete()
            continue
        elif permission in current_permissions and permission not in removed_permissions:
            continue
        else:
            if permission.created_by != admin:
                print('Unknown permission for %r on %r, created by %r (deleting=False).'
                      % (permission.user.email, permission.collection.code,
                         permission.created_by.email))
                continue

            print('Permission for %r on %r not found during import (deleting=%s).'
                  % (permission.user.email, permission.collection.code, wipe_permissions))
            if wipe_permissions:
                permission.delete()