Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example with versioning #194

Closed
valb3r opened this issue Jun 2, 2020 · 5 comments · Fixed by #353
Closed

Example with versioning #194

valb3r opened this issue Jun 2, 2020 · 5 comments · Fixed by #353
Assignees

Comments

@valb3r
Copy link
Contributor

valb3r commented Jun 2, 2020

package de.adorsys.datasafe.examples.business.filesystem;

import de.adorsys.datasafe.business.impl.service.DaggerVersionedDatasafeServices;
import de.adorsys.datasafe.business.impl.service.VersionedDatasafeServices;
import de.adorsys.datasafe.directory.impl.profile.config.DefaultDFSConfig;
import de.adorsys.datasafe.encrypiton.api.types.UserID;
import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth;
import de.adorsys.datasafe.metainfo.version.impl.version.types.DFSVersion;
import de.adorsys.datasafe.storage.impl.fs.FileSystemStorageService;
import de.adorsys.datasafe.types.api.actions.ListRequest;
import de.adorsys.datasafe.types.api.actions.ReadRequest;
import de.adorsys.datasafe.types.api.actions.WriteRequest;
import de.adorsys.datasafe.types.api.resource.AbsoluteLocation;
import de.adorsys.datasafe.types.api.resource.PrivateResource;
import de.adorsys.datasafe.types.api.resource.ResolvedResource;
import de.adorsys.datasafe.types.api.resource.Versioned;
import de.adorsys.datasafe.types.api.utils.ReadKeyPasswordTestFactory;
import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

/**
 * This test shows simplistic usage of Datasafe versioned services that reside on filesystem.
 */
class BaseUserOperationsTestWithVersionedDatasafeTest {

    private VersionedDatasafeServices versionedServices;

    /**
     * This shows how you build Software-versioned Datasafe services. Note that you can override any class/module you
     * want by providing your own interface using {@link VersionedDatasafeServices} as a template.
     */
    @BeforeEach
    void createServices() {
        Path root = Paths.get("/home/valb3r/temp/tests/");
        // BEGIN_SNIPPET:Create versioned Datasafe services
        // this will create all Datasafe files and user documents under <temp dir path>
        versionedServices = DaggerVersionedDatasafeServices.builder()
                .config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray))
                .storage(new FileSystemStorageService(root))
                .build();
        // END_SNIPPET
    }

    /**
     * Creating new user is same
     */
    void registerUser() {
        // BEGIN_SNIPPET:Creating user for versioned services looks same
        // Creating new user:
        /*
        IMPORTANT: For cases when user profile is stored on S3 without object locks, this requires some global
        synchronization due to eventual consistency or you need to supply globally unique username on registration
        */
        versionedServices.userProfile().registerUsingDefaults(new UserIDAuth("user", "passwrd"::toCharArray));
        // END_SNIPPET

        assertThat(versionedServices.userProfile().userExists(new UserID("user")));
    }


    /**
     * Writing file - you can write it to versioned private space multiple times and you will see only latest
     */
    @Test
    @SneakyThrows
    void writeFileToVersionedPrivateSpace() {
        // BEGIN_SNIPPET:Saving file couple of times - versioned
        // creating new user
        UserIDAuth user = registerUser("john");

        // writing string "Hello " + index to my/own/file.txt 3 times:
        // note that both resulting file content and its path are encrypted:
        for (int i = 1; i <= 3; ++i) {
            try (OutputStream os = versionedServices.latestPrivate()
                    .write(WriteRequest.forDefaultPrivate(user, "my/own/file.txt"))) {
                os.write(("Hello " + i).getBytes(StandardCharsets.UTF_8));
                Thread.sleep(1000L); // this will change file modified dates
            }
        }
    }

    @Test
    @SneakyThrows
    void readFileFromVersionedPrivateSpace() {

        UserIDAuth user = new UserIDAuth("john", ReadKeyPasswordTestFactory.getForString("passwrd" + "john"));
        // and still we read only latest file
        assertThat(versionedServices.latestPrivate()
                .read(ReadRequest.forDefaultPrivate(user, "my/own/file.txt"))
        ).hasContent("Hello 3");
        // but there are 3 versions of file stored physically in users' privatespace:
        assertThat(versionedServices.privateService().list(
                ListRequest.forDefaultPrivate(user, "my/own/file.txt"))
        ).hasSize(3);
        // and still only one file visible on latest view
        assertThat(versionedServices.latestPrivate().list(ListRequest.forDefaultPrivate(user, ""))).hasSize(1);
        // END_SNIPPET

        // BEGIN_SNIPPET:Lets check how to read oldest file version
        // so lets collect all versions
        List<Versioned<AbsoluteLocation<ResolvedResource>, PrivateResource, DFSVersion>> withVersions =
                versionedServices.versionInfo().versionsOf(
                        ListRequest.forDefaultPrivate(user, "my/own/file.txt")
                ).collect(Collectors.toList());
        // so that we can find oldest
        Versioned<AbsoluteLocation<ResolvedResource>, PrivateResource, DFSVersion> oldest =
                withVersions.stream()
                        .sorted(Comparator.comparing(it -> it.absolute().getResource().getModifiedAt()))
                        .collect(Collectors.toList())
                        .get(0);
        // and read oldest content
        assertThat(versionedServices.privateService()
                .read(ReadRequest.forPrivate(user, oldest.absolute().getResource().asPrivate()))
        ).hasContent("Hello 1");
        // END_SNIPPET
    }


    private UserIDAuth registerUser(String username) {
        UserIDAuth creds = new UserIDAuth(username, ReadKeyPasswordTestFactory.getForString("passwrd" + username));
        versionedServices.userProfile().registerUsingDefaults(creds);
        return creds;
    }
}

@valb3r
Copy link
Contributor Author

valb3r commented Jun 3, 2020

With unencrypted path

package de.adorsys.datasafe.examples.business.filesystem;

import de.adorsys.datasafe.business.impl.service.DaggerVersionedDatasafeServices;
import de.adorsys.datasafe.business.impl.service.VersionedDatasafeServices;
import de.adorsys.datasafe.directory.impl.profile.config.DefaultDFSConfig;
import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth;
import de.adorsys.datasafe.encrypiton.impl.pathencryption.PathEncryptionImpl;
import de.adorsys.datasafe.encrypiton.impl.pathencryption.PathEncryptionImplRuntimeDelegatable;
import de.adorsys.datasafe.metainfo.version.impl.version.types.DFSVersion;
import de.adorsys.datasafe.storage.impl.fs.FileSystemStorageService;
import de.adorsys.datasafe.types.api.actions.ListRequest;
import de.adorsys.datasafe.types.api.actions.ReadRequest;
import de.adorsys.datasafe.types.api.actions.WriteRequest;
import de.adorsys.datasafe.types.api.context.BaseOverridesRegistry;
import de.adorsys.datasafe.types.api.context.overrides.OverridesRegistry;
import de.adorsys.datasafe.types.api.resource.AbsoluteLocation;
import de.adorsys.datasafe.types.api.resource.PrivateResource;
import de.adorsys.datasafe.types.api.resource.ResolvedResource;
import de.adorsys.datasafe.types.api.resource.Uri;
import de.adorsys.datasafe.types.api.resource.Versioned;
import de.adorsys.datasafe.types.api.utils.ReadKeyPasswordTestFactory;
import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

/**
 * This test shows simplistic usage of Datasafe versioned services that reside on filesystem.
 */
class BaseUserOperationsTestWithVersionedDatasafeTest {

    private VersionedDatasafeServices versionedServices;

    /**
     * This shows how you build Software-versioned Datasafe services. Note that you can override any class/module you
     * want by providing your own interface using {@link VersionedDatasafeServices} as a template.
     */
    @BeforeEach
    void createServices() {
        Path root = Paths.get("/home/valb3r/temp/tests/");

        OverridesRegistry registry = new BaseOverridesRegistry();

        // PathEncryptionImpl now will have completely different functionality
        // instead of calling PathEncryptionImpl methods we will call PathEncryptionImplOverridden methods
        PathEncryptionImplRuntimeDelegatable.overrideWith(registry, PathEncryptionImplOverridden::new);

        // BEGIN_SNIPPET:Create versioned Datasafe services
        // this will create all Datasafe files and user documents under <temp dir path>
        versionedServices = DaggerVersionedDatasafeServices.builder()
                .config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray))
                .storage(new FileSystemStorageService(root))
                .overridesRegistry(registry)
                .build();
        // END_SNIPPET
    }


    /**
     * Writing file - you can write it to versioned private space multiple times and you will see only latest
     */
    @Test
    @SneakyThrows
    void writeFileToVersionedPrivateSpace() {
        // BEGIN_SNIPPET:Saving file couple of times - versioned
        // creating new user
        UserIDAuth user = registerUser("john");

        // writing string "Hello " + index to my/own/file.txt 3 times:
        // note that both resulting file content and its path are encrypted:
        for (int i = 1; i <= 3; ++i) {
            try (OutputStream os = versionedServices.latestPrivate()
                    .write(WriteRequest.forDefaultPrivate(user, "my/own/file.txt"))) {
                os.write(("Hello " + i).getBytes(StandardCharsets.UTF_8));
                Thread.sleep(1000L); // this will change file modified dates
            }
        }
    }

    @Test
    @SneakyThrows
    void readFileFromVersionedPrivateSpace() {
        UserIDAuth user = new UserIDAuth("john", ReadKeyPasswordTestFactory.getForString("passwrd" + "john"));
        // and still we read only latest file
        assertThat(versionedServices.latestPrivate()
                .read(ReadRequest.forDefaultPrivate(user, "my/own/file.txt"))
        ).hasContent("Hello 3");
        // but there are 3 versions of file stored physically in users' privatespace:
        assertThat(versionedServices.privateService().list(
                ListRequest.forDefaultPrivate(user, "my/own/file.txt"))
        ).hasSize(3);

        // and we know 3 versions of the file
        assertThat(versionedServices.versionInfo().versionsOf(
                ListRequest.forDefaultPrivate(user, "my/own/file.txt"))
        ).hasSize(3);

        // BEGIN_SNIPPET:Lets check how to read oldest file version
        // so lets collect all versions
        List<Versioned<AbsoluteLocation<ResolvedResource>, PrivateResource, DFSVersion>> withVersions =
                versionedServices.versionInfo().versionsOf(
                        ListRequest.forDefaultPrivate(user, "my/own/file.txt")
                ).collect(Collectors.toList());
        // so that we can find oldest
        Versioned<AbsoluteLocation<ResolvedResource>, PrivateResource, DFSVersion> oldest =
                withVersions.stream()
                        .sorted(Comparator.comparing(it -> it.absolute().getResource().getModifiedAt()))
                        .collect(Collectors.toList())
                        .get(0);
        // and read oldest content
        assertThat(versionedServices.privateService()
                .read(ReadRequest.forPrivate(user, oldest.absolute().getResource().asPrivate()))
        ).hasContent("Hello 1");
        // END_SNIPPET
    }


    private UserIDAuth registerUser(String username) {
        UserIDAuth creds = new UserIDAuth(username, ReadKeyPasswordTestFactory.getForString("passwrd" + username));
        versionedServices.userProfile().registerUsingDefaults(creds);
        return creds;
    }

    class PathEncryptionImplOverridden extends PathEncryptionImpl {

        PathEncryptionImplOverridden(PathEncryptionImplRuntimeDelegatable.ArgumentsCaptor captor) {
            super(captor.getSymmetricPathEncryptionService(), captor.getPrivateKeyService());
        }

        @Override
        public Uri encrypt(UserIDAuth forUser, Uri path) {
            if (path.asString().contains("/")) {
                String[] rootAndInRoot = path.asString().split("/", 2);
                return new Uri(URI.create(rootAndInRoot[0] + "/" + super.encrypt(forUser, new Uri(rootAndInRoot[1])).asString()));
            }
            // encryption disabled for root folder:
            return path;
        }

        @Override
        public Function<Uri, Uri> decryptor(UserIDAuth forUser) {
            return rootWithEncrypted -> {
                if (rootWithEncrypted.asString().contains("/")) {
                    String[] rootAndInRoot = rootWithEncrypted.asString().split("/", 2);
                    return new Uri(rootAndInRoot[0] + "/" + super.decryptor(forUser).apply(new Uri(URI.create(rootAndInRoot[1]))).asString());
                }
                // encryption disabled for root folder:
                return rootWithEncrypted;
            };
        }
    }
}

@Victoire-Motouom
Copy link

Please i would like to have more information about this ticket.

@AssahBismarkabah
Copy link
Collaborator

The proposed feature @Victoire-Motouom pertains to the administration of file versions within a user's private space. The implementation has already been addressed in the code base. For the unencrypted file segment, it enables specific paths (such as the root folder) to remain unencrypted while still managing file versions. The inclusion of the second example is necessary if it hasn't already been implemented.

@Victoire-Motouom
Copy link

Hello @max402 and @francis-pouatcha,

I was assigned to work on this ticket, but from what I've seen, it has already been implemented. I would like us to review it in the next daily datasafe meeting to close it or refine it and work on it if necessary.

File: https://github.com/Victoire-Motouom/datasafe/blob/develop/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/BaseUserOperationsTestWithVersionedDatasafeTest.java

@francis-pouatcha
Copy link
Member

Please check how versioning is working with different storages.

  1. Learn how to use it
  2. Add documentation show how to do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging a pull request may close this issue.

6 participants