Search in sources :

Example 36 with AsciiRandomStringService

use of ch.cyberduck.core.AsciiRandomStringService in project cyberduck by iterate-ch.

the class SwiftAccountLoader method operate.

@Override
protected Map<Region, AccountInfo> operate(final PasswordCallback callback, final Path file) throws BackgroundException {
    final Map<Region, AccountInfo> accounts = new ConcurrentHashMap<>();
    for (Region region : session.getClient().getRegions()) {
        try {
            final AccountInfo info = session.getClient().getAccountInfo(region);
            if (log.isInfoEnabled()) {
                log.info(String.format("Signing key is %s", info.getTempUrlKey()));
            }
            if (StringUtils.isBlank(info.getTempUrlKey())) {
                // Update account info setting temporary URL key
                try {
                    final String key = new AsciiRandomStringService().random();
                    if (log.isDebugEnabled()) {
                        log.debug(String.format("Set acccount temp URL key to %s", key));
                    }
                    session.getClient().updateAccountMetadata(region, Collections.singletonMap("X-Account-Meta-Temp-URL-Key", key));
                    info.setTempUrlKey(key);
                } catch (GenericException e) {
                    log.warn(String.format("Ignore failure %s updating account metadata", e));
                }
            }
            accounts.put(region, info);
        } catch (GenericException e) {
            if (e.getHttpStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE) {
                log.warn(String.format("Ignore failure %s for region %s", e, region));
                continue;
            }
            throw new SwiftExceptionMappingService().map(e);
        } catch (IOException e) {
            throw new DefaultIOExceptionMappingService().map(e);
        }
    }
    return accounts;
}
Also used : AsciiRandomStringService(ch.cyberduck.core.AsciiRandomStringService) Region(ch.iterate.openstack.swift.model.Region) IOException(java.io.IOException) DefaultIOExceptionMappingService(ch.cyberduck.core.DefaultIOExceptionMappingService) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) GenericException(ch.iterate.openstack.swift.exception.GenericException) AccountInfo(ch.iterate.openstack.swift.model.AccountInfo)

Example 37 with AsciiRandomStringService

use of ch.cyberduck.core.AsciiRandomStringService in project cyberduck by iterate-ch.

the class STSCredentialsConfigurator method configure.

public Credentials configure(final Host host) throws LoginFailureException, LoginCanceledException {
    final Credentials credentials = new Credentials(host.getCredentials());
    // See https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html for configuration behavior
    final Local awsDirectory = LocalFactory.get(LocalFactory.get(), ".aws");
    final Local configFile = LocalFactory.get(awsDirectory, "config");
    final Local credentialsFile = LocalFactory.get(awsDirectory, "credentials");
    // Profile can be null. The default profile from the configuration will be loaded
    final String profile = host.getCredentials().getUsername();
    if (log.isDebugEnabled()) {
        log.debug(String.format("Look for profile name %s in %s and %s", profile, configFile, credentialsFile));
    }
    // Iterating all profiles on our own because AWSProfileCredentialsConfigurator does not support MFA tokens
    final Map<String, Map<String, String>> allProfileProperties = new HashMap<>();
    try {
        final Map<String, Map<String, String>> credentialsFileProfileProperties = new ProfilesConfigFileLoaderHelper().parseProfileProperties(credentialsFile);
        allProfileProperties.putAll(credentialsFileProfileProperties);
        final Map<String, Map<String, String>> configFileProfileProperties = new ProfilesConfigFileLoaderHelper().parseProfileProperties(configFile);
        for (Map.Entry<String, Map<String, String>> entry : configFileProfileProperties.entrySet()) {
            final String profileName = entry.getKey();
            final Map<String, String> configFileProperties = entry.getValue();
            final Map<String, String> credentialsFileProperties = allProfileProperties.get(profileName);
            // If the credentials file had properties, then merge them in
            if (credentialsFileProperties != null) {
                configFileProperties.putAll(credentialsFileProperties);
            }
            allProfileProperties.put(profileName, configFileProperties);
        }
    } catch (AccessDeniedException | IllegalArgumentException | IOException e) {
        log.warn(String.format("Failure reading %s and %s", configFile, credentialsFile), e);
        return credentials;
    }
    if (allProfileProperties.isEmpty()) {
        log.warn("Missing configuration file ~/.aws/credentials or ~/.aws/config. Skip auto configuration");
        return host.getCredentials();
    }
    // Convert the loaded property map to credential objects
    final Map<String, BasicProfile> profilesByName = new LinkedHashMap<>();
    for (Map.Entry<String, Map<String, String>> entry : allProfileProperties.entrySet()) {
        String profileName = entry.getKey();
        Map<String, String> properties = entry.getValue();
        profilesByName.put(profileName, new BasicProfile(profileName, properties));
    }
    final Map<String, BasicProfile> profiles = new AllProfiles(profilesByName).getProfiles();
    final Optional<Map.Entry<String, BasicProfile>> optional = profiles.entrySet().stream().filter(new Predicate<Map.Entry<String, BasicProfile>>() {

        @Override
        public boolean test(final Map.Entry<String, BasicProfile> entry) {
            final String profileName = entry.getKey();
            final BasicProfile basicProfile = entry.getValue();
            final String awsAccessIdKey = basicProfile.getAwsAccessIdKey();
            // Matching access key or profile name
            if (StringUtils.equals(profileName, profile) || StringUtils.equals(awsAccessIdKey, profile)) {
                if (log.isDebugEnabled()) {
                    log.debug(String.format("Found matching profile %s", profile));
                }
                return true;
            }
            return false;
        }
    }).findFirst();
    if (optional.isPresent()) {
        final Map.Entry<String, BasicProfile> entry = optional.get();
        final BasicProfile basicProfile = entry.getValue();
        if (basicProfile.isRoleBasedProfile()) {
            if (log.isDebugEnabled()) {
                log.debug(String.format("Configure credentials from role based profile %s", basicProfile.getProfileName()));
            }
            if (StringUtils.isBlank(basicProfile.getRoleSourceProfile())) {
                throw new LoginFailureException(String.format("Missing source profile reference in profile %s", basicProfile.getProfileName()));
            } else if (!profiles.containsKey(basicProfile.getRoleSourceProfile())) {
                throw new LoginFailureException(String.format("Missing source profile with name %s", basicProfile.getRoleSourceProfile()));
            } else {
                final BasicProfile sourceProfile = profiles.get(basicProfile.getRoleSourceProfile());
                // If a profile defines the role_arn property then the profile is treated as an assume role profile
                final AWSSecurityTokenService service = this.getTokenService(host, host.getRegion(), sourceProfile.getAwsAccessIdKey(), sourceProfile.getAwsSecretAccessKey(), sourceProfile.getAwsSessionToken());
                final String tokenCode;
                if (basicProfile.getProperties().containsKey("mfa_serial")) {
                    tokenCode = prompt.prompt(host, LocaleFactory.localizedString("Provide additional login credentials", "Credentials"), String.format("%s %s", LocaleFactory.localizedString("Multi-Factor Authentication", "S3"), basicProfile.getPropertyValue("mfa_serial")), new LoginOptions(host.getProtocol()).password(true).passwordPlaceholder(LocaleFactory.localizedString("MFA Authentication Code", "S3")).keychain(false)).getPassword();
                } else {
                    tokenCode = null;
                }
                final Integer durationSeconds;
                if (basicProfile.getProperties().containsKey("duration_seconds")) {
                    durationSeconds = Integer.valueOf(basicProfile.getPropertyValue("duration_seconds"));
                } else {
                    durationSeconds = null;
                }
                // Starts a new session by sending a request to the AWS Security Token Service (STS) to assume a
                // Role using the long lived AWS credentials
                final AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest().withExternalId(basicProfile.getRoleExternalId()).withRoleArn(basicProfile.getRoleArn()).withSerialNumber(basicProfile.getPropertyValue("mfa_serial")).withTokenCode(tokenCode).withRoleSessionName(new AsciiRandomStringService().random()).withDurationSeconds(durationSeconds);
                if (log.isDebugEnabled()) {
                    log.debug(String.format("Request %s from %s", assumeRoleRequest, service));
                }
                try {
                    final AssumeRoleResult assumeRoleResult = service.assumeRole(assumeRoleRequest);
                    if (log.isDebugEnabled()) {
                        log.debug(String.format("Set credentials from %s", assumeRoleResult));
                    }
                    credentials.setUsername(assumeRoleResult.getCredentials().getAccessKeyId());
                    credentials.setPassword(assumeRoleResult.getCredentials().getSecretAccessKey());
                    credentials.setToken(assumeRoleResult.getCredentials().getSessionToken());
                } catch (AWSSecurityTokenServiceException e) {
                    throw new LoginFailureException(e.getErrorMessage(), e);
                }
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug(String.format("Configure credentials from basic profile %s", basicProfile.getProfileName()));
            }
            final Map<String, String> profileProperties = basicProfile.getProperties();
            if (profileProperties.containsKey("sso_start_url")) {
                // Read cached SSO credentials
                final CachedCredential cached = this.fetchSsoCredentials(credentials, profileProperties, awsDirectory);
                credentials.setUsername(cached.accessKey);
                credentials.setPassword(cached.secretKey);
                credentials.setToken(cached.sessionToken);
            } else if (StringUtils.isNotBlank(basicProfile.getAwsSessionToken())) {
                // No need to obtain session token if preconfigured in profile
                if (log.isDebugEnabled()) {
                    log.debug(String.format("Set session token credentials from profile %s", profile));
                }
                credentials.setUsername(basicProfile.getAwsAccessIdKey());
                credentials.setPassword(basicProfile.getAwsSecretAccessKey());
                credentials.setToken(basicProfile.getAwsSessionToken());
            } else {
                if (host.getProtocol().isTokenConfigurable()) {
                    // Obtain session token
                    if (log.isDebugEnabled()) {
                        log.debug(String.format("Get session token from credentials in profile %s", basicProfile.getProfileName()));
                    }
                    final AWSSecurityTokenService service = this.getTokenService(host, host.getRegion(), basicProfile.getAwsAccessIdKey(), basicProfile.getAwsSecretAccessKey(), basicProfile.getAwsSessionToken());
                    final GetSessionTokenRequest sessionTokenRequest = new GetSessionTokenRequest();
                    if (log.isDebugEnabled()) {
                        log.debug(String.format("Request %s from %s", sessionTokenRequest, service));
                    }
                    try {
                        final GetSessionTokenResult sessionTokenResult = service.getSessionToken(sessionTokenRequest);
                        if (log.isDebugEnabled()) {
                            log.debug(String.format("Set credentials from %s", sessionTokenResult));
                        }
                        credentials.setUsername(sessionTokenResult.getCredentials().getAccessKeyId());
                        credentials.setPassword(sessionTokenResult.getCredentials().getSecretAccessKey());
                        credentials.setToken(sessionTokenResult.getCredentials().getSessionToken());
                    } catch (AWSSecurityTokenServiceException e) {
                        throw new LoginFailureException(e.getErrorMessage(), e);
                    }
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug(String.format("Set static credentials from profile %s", basicProfile.getProfileName()));
                    }
                    credentials.setUsername(basicProfile.getAwsAccessIdKey());
                    credentials.setPassword(basicProfile.getAwsSecretAccessKey());
                }
            }
        }
    }
    return credentials;
}
Also used : AssumeRoleRequest(com.amazonaws.services.securitytoken.model.AssumeRoleRequest) AccessDeniedException(ch.cyberduck.core.exception.AccessDeniedException) AllProfiles(com.amazonaws.auth.profile.internal.AllProfiles) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) LinkedHashMap(java.util.LinkedHashMap) Predicate(java.util.function.Predicate) LoginOptions(ch.cyberduck.core.LoginOptions) AWSSecurityTokenServiceException(com.amazonaws.services.securitytoken.model.AWSSecurityTokenServiceException) BasicProfile(com.amazonaws.auth.profile.internal.BasicProfile) AsciiRandomStringService(ch.cyberduck.core.AsciiRandomStringService) GetSessionTokenResult(com.amazonaws.services.securitytoken.model.GetSessionTokenResult) Local(ch.cyberduck.core.Local) IOException(java.io.IOException) AssumeRoleResult(com.amazonaws.services.securitytoken.model.AssumeRoleResult) LoginFailureException(ch.cyberduck.core.exception.LoginFailureException) GetSessionTokenRequest(com.amazonaws.services.securitytoken.model.GetSessionTokenRequest) Map(java.util.Map) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) AWSSecurityTokenService(com.amazonaws.services.securitytoken.AWSSecurityTokenService) AWSCredentials(com.amazonaws.auth.AWSCredentials) AWSSessionCredentials(com.amazonaws.auth.AWSSessionCredentials) Credentials(ch.cyberduck.core.Credentials)

Example 38 with AsciiRandomStringService

use of ch.cyberduck.core.AsciiRandomStringService in project cyberduck by iterate-ch.

the class S3VersionedObjectListServiceTest method testEnableVersioningExistingFiles.

@Test
public void testEnableVersioningExistingFiles() throws Exception {
    final Path bucket = new S3DirectoryFeature(session, new S3WriteFeature(session)).mkdir(new Path(new AsciiRandomStringService().random(), EnumSet.of(Path.Type.directory, Path.Type.volume)), new TransferStatus());
    assertTrue(new S3FindFeature(session).find(bucket));
    final Path file = new S3TouchFeature(session).touch(new Path(bucket, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
    final S3WriteFeature feature = new S3WriteFeature(session);
    {
        final byte[] content = RandomUtils.nextBytes(1024);
        final TransferStatus status = new TransferStatus();
        status.setLength(content.length);
        status.setChecksum(new SHA256ChecksumCompute().compute(new ByteArrayInputStream(content), status));
        final HttpResponseOutputStream<StorageObject> out = feature.write(file, status, new DisabledConnectionCallback());
        new StreamCopier(status, status).transfer(new ByteArrayInputStream(content), out);
        out.close();
        assertEquals(content.length, new DefaultAttributesFinderFeature(session).find(file).getSize());
        final PathAttributes attr = new DefaultAttributesFinderFeature(session).find(file);
        assertEquals(content.length, attr.getSize());
        assertNull(new DefaultAttributesFinderFeature(session).find(file).getVersionId());
    }
    session.getFeature(Versioning.class).setConfiguration(bucket, new DisabledPasswordCallback(), new VersioningConfiguration(true));
    assertNull(new DefaultAttributesFinderFeature(session).find(file).getVersionId());
    {
        final byte[] content = RandomUtils.nextBytes(1024);
        final TransferStatus status = new TransferStatus();
        status.setLength(content.length);
        status.setChecksum(new SHA256ChecksumCompute().compute(new ByteArrayInputStream(content), status));
        final HttpResponseOutputStream<StorageObject> out = feature.write(file, status, new DisabledConnectionCallback());
        new StreamCopier(status, status).transfer(new ByteArrayInputStream(content), out);
        out.close();
        assertEquals(content.length, new DefaultAttributesFinderFeature(session).find(file).getSize());
        final PathAttributes attr = new DefaultAttributesFinderFeature(session).find(file);
        assertEquals(content.length, attr.getSize());
        assertNotNull(attr.getVersionId());
    }
    final AttributedList<Path> list = new S3VersionedObjectListService(session, 1, true).list(bucket, new DisabledListProgressListener()).filter(new Filter<Path>() {

        @Override
        public boolean accept(final Path f) {
            return new SimplePathPredicate(file).test(f);
        }

        @Override
        public Pattern toPattern() {
            return null;
        }
    });
    assertEquals(2, list.size());
    assertEquals(1, list.get(0).attributes().getVersions().size());
    assertEquals(0, list.get(1).attributes().getVersions().size());
    assertSame(list.get(0).attributes().getVersions().get(0), list.get(1));
    new S3DefaultDeleteFeature(session).delete(Arrays.asList(new Path(file).withAttributes(new PathAttributes().withVersionId("null")), new Path(file).withAttributes(new DefaultAttributesFinderFeature(session).find(file)), bucket), new DisabledLoginCallback(), new Delete.DisabledCallback());
}
Also used : Delete(ch.cyberduck.core.features.Delete) DisabledListProgressListener(ch.cyberduck.core.DisabledListProgressListener) TransferStatus(ch.cyberduck.core.transfer.TransferStatus) Path(ch.cyberduck.core.Path) Pattern(java.util.regex.Pattern) AsciiRandomStringService(ch.cyberduck.core.AsciiRandomStringService) PathAttributes(ch.cyberduck.core.PathAttributes) VersioningConfiguration(ch.cyberduck.core.VersioningConfiguration) Versioning(ch.cyberduck.core.features.Versioning) DefaultAttributesFinderFeature(ch.cyberduck.core.shared.DefaultAttributesFinderFeature) ByteArrayInputStream(java.io.ByteArrayInputStream) DisabledLoginCallback(ch.cyberduck.core.DisabledLoginCallback) SimplePathPredicate(ch.cyberduck.core.SimplePathPredicate) HttpResponseOutputStream(ch.cyberduck.core.http.HttpResponseOutputStream) DisabledPasswordCallback(ch.cyberduck.core.DisabledPasswordCallback) SHA256ChecksumCompute(ch.cyberduck.core.io.SHA256ChecksumCompute) DisabledConnectionCallback(ch.cyberduck.core.DisabledConnectionCallback) StreamCopier(ch.cyberduck.core.io.StreamCopier) Test(org.junit.Test) IntegrationTest(ch.cyberduck.test.IntegrationTest)

Example 39 with AsciiRandomStringService

use of ch.cyberduck.core.AsciiRandomStringService in project cyberduck by iterate-ch.

the class S3MoveFeatureTest method testMoveWithServerSideEncryptionBucketPolicy.

@Test
public void testMoveWithServerSideEncryptionBucketPolicy() throws Exception {
    final Path container = new Path("sse-test-us-east-1-cyberduck", EnumSet.of(Path.Type.directory, Path.Type.volume));
    final Path test = new Path(container, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.file));
    final S3TouchFeature touch = new S3TouchFeature(session);
    final TransferStatus status = new TransferStatus();
    status.setEncryption(S3EncryptionFeature.SSE_AES256);
    touch.touch(test, status);
    assertTrue(new S3FindFeature(session).find(test));
    final Path renamed = new Path(container, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.file));
    new S3MoveFeature(session).move(test, renamed, new TransferStatus(), new Delete.DisabledCallback(), new DisabledConnectionCallback());
    assertFalse(new S3FindFeature(session).find(test));
    assertTrue(new S3FindFeature(session).find(renamed));
    new S3DefaultDeleteFeature(session).delete(Collections.singletonList(renamed), new DisabledLoginCallback(), new Delete.DisabledCallback());
}
Also used : Path(ch.cyberduck.core.Path) Delete(ch.cyberduck.core.features.Delete) AsciiRandomStringService(ch.cyberduck.core.AsciiRandomStringService) DisabledLoginCallback(ch.cyberduck.core.DisabledLoginCallback) TransferStatus(ch.cyberduck.core.transfer.TransferStatus) DisabledConnectionCallback(ch.cyberduck.core.DisabledConnectionCallback) Test(org.junit.Test) IntegrationTest(ch.cyberduck.test.IntegrationTest)

Example 40 with AsciiRandomStringService

use of ch.cyberduck.core.AsciiRandomStringService in project cyberduck by iterate-ch.

the class S3MoveFeatureTest method testMoveVersioned.

@Test
public void testMoveVersioned() throws Exception {
    final Path container = new Path("versioning-test-us-east-1-cyberduck", EnumSet.of(Path.Type.directory, Path.Type.volume));
    Path test = new Path(container, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.file));
    assertNotNull(new S3TouchFeature(session).touch(test, new TransferStatus().withMime("text/plain")).attributes().getVersionId());
    assertTrue(new S3FindFeature(session).find(test));
    // Write some data to add a new version
    final S3WriteFeature feature = new S3WriteFeature(session);
    final byte[] content = RandomUtils.nextBytes(10);
    final TransferStatus status = new TransferStatus().withMime("text/plain");
    status.setLength(content.length);
    status.setChecksum(new SHA256ChecksumCompute().compute(new ByteArrayInputStream(content), status));
    final HttpResponseOutputStream<StorageObject> out = feature.write(test, status, new DisabledConnectionCallback());
    new StreamCopier(status, status).transfer(new ByteArrayInputStream(content), out);
    out.close();
    // Get new path with updated version id
    final AttributedList<Path> list = new S3ListService(session).list(container, new DisabledListProgressListener());
    for (Path path : list) {
        if (new SimplePathPredicate(test).test(path)) {
            test = path;
            break;
        }
    }
    final Path renamed = new Path(container, String.format("%s-renamed", test.getName()), EnumSet.of(Path.Type.file));
    new S3MoveFeature(session).move(test, renamed, new TransferStatus(), new Delete.DisabledCallback(), new DisabledConnectionCallback());
    assertTrue(new S3FindFeature(session).find(test));
    assertTrue(new S3FindFeature(session).find(renamed));
    // Ensure that the latest version of the source file is a delete marker
    for (Path path : new S3ListService(session).list(container, new DisabledListProgressListener())) {
        if (new SimplePathPredicate(test).test(path)) {
            assertTrue(path.attributes().isDuplicate());
            assertTrue(new S3AttributesFinderFeature(session, true).find(path).isDuplicate());
            assertTrue(new S3AttributesFinderFeature(session, false).find(path).isDuplicate());
            break;
        }
    }
    final Map<String, String> metadata = new S3MetadataFeature(session, new S3AccessControlListFeature(session)).getMetadata(renamed);
    assertFalse(metadata.isEmpty());
    assertEquals("text/plain", metadata.get("Content-Type"));
    new S3DefaultDeleteFeature(session).delete(Collections.singletonList(renamed), new DisabledLoginCallback(), new Delete.DisabledCallback());
}
Also used : Delete(ch.cyberduck.core.features.Delete) DisabledListProgressListener(ch.cyberduck.core.DisabledListProgressListener) TransferStatus(ch.cyberduck.core.transfer.TransferStatus) Path(ch.cyberduck.core.Path) StorageObject(org.jets3t.service.model.StorageObject) AsciiRandomStringService(ch.cyberduck.core.AsciiRandomStringService) ByteArrayInputStream(java.io.ByteArrayInputStream) DisabledLoginCallback(ch.cyberduck.core.DisabledLoginCallback) SimplePathPredicate(ch.cyberduck.core.SimplePathPredicate) SHA256ChecksumCompute(ch.cyberduck.core.io.SHA256ChecksumCompute) DisabledConnectionCallback(ch.cyberduck.core.DisabledConnectionCallback) StreamCopier(ch.cyberduck.core.io.StreamCopier) Test(org.junit.Test) IntegrationTest(ch.cyberduck.test.IntegrationTest)

Aggregations

AsciiRandomStringService (ch.cyberduck.core.AsciiRandomStringService)66 Path (ch.cyberduck.core.Path)64 Test (org.junit.Test)64 TransferStatus (ch.cyberduck.core.transfer.TransferStatus)61 IntegrationTest (ch.cyberduck.test.IntegrationTest)56 Delete (ch.cyberduck.core.features.Delete)52 DisabledLoginCallback (ch.cyberduck.core.DisabledLoginCallback)51 DisabledConnectionCallback (ch.cyberduck.core.DisabledConnectionCallback)22 DisabledListProgressListener (ch.cyberduck.core.DisabledListProgressListener)18 AlphanumericRandomStringService (ch.cyberduck.core.AlphanumericRandomStringService)12 Host (ch.cyberduck.core.Host)8 ByteArrayInputStream (java.io.ByteArrayInputStream)8 StreamCopier (ch.cyberduck.core.io.StreamCopier)7 DisabledProgressListener (ch.cyberduck.core.DisabledProgressListener)6 NullLocal (ch.cyberduck.core.NullLocal)5 NullTransferSession (ch.cyberduck.core.NullTransferSession)5 SimplePathPredicate (ch.cyberduck.core.SimplePathPredicate)5 TestProtocol (ch.cyberduck.core.TestProtocol)5 DisabledStreamListener (ch.cyberduck.core.io.DisabledStreamListener)5 DefaultHomeFinderService (ch.cyberduck.core.shared.DefaultHomeFinderService)5