@Override
    public List<UserFileKeySetRequest> operate(final Session<?> client, final PasswordCallback callback, final Path file) throws BackgroundException {
        final SDSSession session = (SDSSession) client;
        final SDSNodeIdProvider nodeid = (SDSNodeIdProvider) session._getFeature(VersionIdProvider.class);
        try {
            final UserAccountWrapper account = session.userAccount();
            if(!account.isEncryptionEnabled()) {
                log.warn(String.format("No key pair found in user account %s", account));
                return Collections.emptyList();
            }
            final List<UserFileKeySetRequest> processed = new ArrayList<>();
            // Null when operating from scheduler. File reference is set for post upload.
            final Long fileId = file != null ? Long.parseLong(nodeid.getVersionId(file)) : null;
            UserFileKeySetBatchRequest request;
            do {
                if(log.isDebugEnabled()) {
                    log.debug(String.format("Request a list of missing file keys for file %s", file));
                }
                request = new UserFileKeySetBatchRequest();
                final MissingKeysResponse missingKeys = new NodesApi(session.getClient()).requestMissingFileKeys(
                        null, null, null, fileId, null, null, null);
                final Map<Long, List<UserUserPublicKey>> userPublicKeys = missingKeys.getUsers().stream().collect(groupingBy(UserUserPublicKey::getId));
                final Map<Long, List<FileFileKeys>> files = missingKeys.getFiles().stream().collect(groupingBy(FileFileKeys::getId));
                for(UserIdFileIdItem item : missingKeys.getItems()) {
                    final HashMap<UserKeyPairContainer, Credentials> passphrases = new HashMap<>();
                    for(FileFileKeys fileKey : files.get(item.getFileId())) {
                        final EncryptedFileKey encryptedFileKey = TripleCryptConverter.toCryptoEncryptedFileKey(fileKey.getFileKeyContainer());
                        final UserKeyPairContainer keyPairForDecryption = session.getKeyPairForFileKey(encryptedFileKey.getVersion());
                        if(!passphrases.containsKey(keyPairForDecryption)) {
                            passphrases.put(keyPairForDecryption,
                                    new TripleCryptKeyPair().unlock(callback, session.getHost(), TripleCryptConverter.toCryptoUserKeyPair(keyPairForDecryption)));
                        }
                        final Credentials passphrase = passphrases.get(keyPairForDecryption);
                        for(UserUserPublicKey userPublicKey : userPublicKeys.get(item.getUserId())) {
                            final EncryptedFileKey fk = this.encryptFileKey(
                                    TripleCryptConverter.toCryptoUserPrivateKey(keyPairForDecryption.getPrivateKeyContainer()),
                                    passphrase, userPublicKey, fileKey);
                            final UserFileKeySetRequest keySetRequest = new UserFileKeySetRequest()
                                    .fileId(item.getFileId())
                                    .userId(item.getUserId())
                                    .fileKey(TripleCryptConverter.toSwaggerFileKey(fk));
                            if(log.isDebugEnabled()) {
                                log.debug(String.format("Missing file key processed for file %d and user %d", item.getFileId(), item.getUserId()));
                            }
                            request.addItemsItem(keySetRequest);
                        }
                    }
                }
                if(!request.getItems().isEmpty()) {
                    if(log.isDebugEnabled()) {
                        log.debug(String.format("Set file keys with %s", request));
                    }
                    new NodesApi(session.getClient()).setUserFileKeys(request, StringUtils.EMPTY);
                    processed.addAll(request.getItems());
                }
            }
            while(!request.getItems().isEmpty());
            this.deleteDeprecatedKeyPair(session);
            return processed;
        }
        catch(ApiException e) {
            throw new SDSExceptionMappingService(nodeid).map(e);
        }
        catch(CryptoException e) {
            throw new TripleCryptExceptionMappingService().map(e);
        }
    }
@Test(expected = LoginCanceledException.class)
    public void testWrongPassword() throws Exception {
        final SDSNodeIdProvider nodeid = new SDSNodeIdProvider(session);
        final Path room = new SDSDirectoryFeature(session, nodeid).mkdir(new Path(
                new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory, Path.Type.volume)), new TransferStatus());
        final EncryptRoomRequest encrypt = new EncryptRoomRequest().isEncrypted(true);
        final Node node = new NodesApi(session.getClient()).encryptRoom(encrypt, Long.parseLong(new SDSNodeIdProvider(session).getVersionId(room)), StringUtils.EMPTY, null);
        new NodesApi(session.getClient()).updateRoomUsers(new RoomUsersAddBatchRequest().
                addItemsItem(new RoomUsersAddBatchRequestItem().id(757L).permissions(new NodePermissions().read(true))), node.getId(), StringUtils.EMPTY);
        room.attributes().withCustom(KEY_ENCRYPTED, String.valueOf(true));
        final byte[] content = RandomUtils.nextBytes(32769);
        final TransferStatus status = new TransferStatus();
        status.setLength(content.length);
        final Path test = new Path(room, UUID.randomUUID().toString(), EnumSet.of(Path.Type.file));
        final SDSEncryptionBulkFeature bulk = new SDSEncryptionBulkFeature(session, nodeid);
        bulk.pre(Transfer.Type.upload, Collections.singletonMap(new TransferItem(test), status), new DisabledConnectionCallback());
        final TripleCryptWriteFeature writer = new TripleCryptWriteFeature(session, nodeid, new SDSDirectS3MultipartWriteFeature(session, nodeid));
        final StatusOutputStream<Node> out = writer.write(test, status, new DisabledConnectionCallback());
        new StreamCopier(status, status).transfer(new ByteArrayInputStream(content), out);
        assertTrue(new DefaultFindFeature(session).find(test));
        assertEquals(content.length, new SDSAttributesFinderFeature(session, nodeid).find(test).getSize());
        final SDSMissingFileKeysSchedulerFeature background = new SDSMissingFileKeysSchedulerFeature();
        final AtomicBoolean prompt = new AtomicBoolean();
        final List<UserFileKeySetRequest> processed = background.operate(session, new DisabledPasswordCallback() {
            @Override
            public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) throws LoginCanceledException {
                if(prompt.get()) {
                    throw new LoginCanceledException();
                }
                prompt.set(true);
                return new VaultCredentials("n");
            }
        }, null);
        assertTrue(prompt.get());
    }