|
4 | 4 |
|
5 | 5 | package cloud.katta.protocols.hub; |
6 | 6 |
|
7 | | -import ch.cyberduck.core.AbstractPath; |
| 7 | +import ch.cyberduck.core.Credentials; |
8 | 8 | import ch.cyberduck.core.DisabledCancelCallback; |
9 | 9 | import ch.cyberduck.core.DisabledHostKeyCallback; |
10 | | -import ch.cyberduck.core.DisabledListProgressListener; |
11 | 10 | import ch.cyberduck.core.DisabledLoginCallback; |
12 | | -import ch.cyberduck.core.ListService; |
| 11 | +import ch.cyberduck.core.Host; |
13 | 12 | import ch.cyberduck.core.PasswordCallback; |
14 | 13 | import ch.cyberduck.core.Path; |
| 14 | +import ch.cyberduck.core.PathAttributes; |
| 15 | +import ch.cyberduck.core.Protocol; |
| 16 | +import ch.cyberduck.core.ProtocolFactory; |
15 | 17 | import ch.cyberduck.core.Session; |
16 | | -import ch.cyberduck.core.cryptomator.ContentWriter; |
| 18 | +import ch.cyberduck.core.SessionFactory; |
17 | 19 | import ch.cyberduck.core.cryptomator.UVFVault; |
18 | 20 | import ch.cyberduck.core.exception.BackgroundException; |
| 21 | +import ch.cyberduck.core.exception.InteroperabilityException; |
19 | 22 | import ch.cyberduck.core.exception.UnsupportedException; |
20 | | -import ch.cyberduck.core.features.Directory; |
21 | | -import ch.cyberduck.core.features.Write; |
22 | | -import ch.cyberduck.core.preferences.PreferencesFactory; |
| 23 | +import ch.cyberduck.core.features.AttributesFinder; |
23 | 24 | import ch.cyberduck.core.proxy.ProxyFactory; |
24 | | -import ch.cyberduck.core.transfer.TransferStatus; |
| 25 | +import ch.cyberduck.core.ssl.X509KeyManager; |
| 26 | +import ch.cyberduck.core.ssl.X509TrustManager; |
| 27 | +import ch.cyberduck.core.vault.VaultUnlockCancelException; |
25 | 28 |
|
| 29 | +import org.apache.http.HttpStatus; |
26 | 30 | import org.apache.logging.log4j.LogManager; |
27 | 31 | import org.apache.logging.log4j.Logger; |
28 | 32 |
|
29 | | -import java.nio.charset.StandardCharsets; |
30 | 33 | import java.util.EnumSet; |
| 34 | +import java.util.UUID; |
| 35 | + |
| 36 | +import cloud.katta.client.ApiException; |
| 37 | +import cloud.katta.crypto.uvf.UvfMetadataPayload; |
| 38 | +import cloud.katta.crypto.uvf.UvfMetadataPayloadPasswordCallback; |
| 39 | +import cloud.katta.crypto.uvf.VaultMetadataJWEBackendDto; |
| 40 | +import cloud.katta.protocols.hub.exceptions.HubExceptionMappingService; |
| 41 | +import cloud.katta.workflows.VaultServiceImpl; |
| 42 | +import cloud.katta.workflows.exceptions.AccessException; |
| 43 | +import cloud.katta.workflows.exceptions.SecurityFailure; |
| 44 | +import com.fasterxml.jackson.core.JsonProcessingException; |
| 45 | + |
| 46 | +import static cloud.katta.protocols.s3.S3AssumeRoleProtocol.OAUTH_TOKENEXCHANGE_VAULT; |
31 | 47 |
|
32 | 48 | /** |
33 | 49 | * Unified vault format (UVF) implementation for Katta |
34 | 50 | */ |
35 | 51 | public class HubUVFVault extends UVFVault { |
36 | 52 | private static final Logger log = LogManager.getLogger(HubUVFVault.class); |
37 | 53 |
|
38 | | - private final Session<?> storage; |
39 | | - private final Path home; |
| 54 | + private final UUID vaultId; |
| 55 | + |
| 56 | + /** |
| 57 | + * Storage connection only available after loading vault |
| 58 | + */ |
| 59 | + private Session<?> storage; |
40 | 60 |
|
41 | | - public HubUVFVault(final Session<?> storage, final Path home) { |
| 61 | + /** |
| 62 | + * Constructor for factory creating new vault |
| 63 | + * |
| 64 | + * @param home Bucket |
| 65 | + */ |
| 66 | + public HubUVFVault(final Path home) { |
42 | 67 | super(home); |
43 | | - this.storage = storage; |
44 | | - this.home = home; |
| 68 | + this.vaultId = UUID.fromString(home.getName()); |
| 69 | + } |
| 70 | + |
| 71 | + /** |
| 72 | + * Open from existing metadata |
| 73 | + * |
| 74 | + * @param vaultId Vault ID Used to lookup profile |
| 75 | + * @param bucket Bucket name |
| 76 | + */ |
| 77 | + public HubUVFVault(final UUID vaultId, final String bucket) { |
| 78 | + super(new Path(bucket, EnumSet.of(Path.Type.directory, Path.Type.volume))); |
| 79 | + this.vaultId = vaultId; |
45 | 80 | } |
46 | 81 |
|
47 | 82 | public Session<?> getStorage() { |
@@ -73,46 +108,66 @@ public synchronized void close() { |
73 | 108 | } |
74 | 109 |
|
75 | 110 | /** |
76 | | - * Upload vault template into existing bucket (permanent credentials) |
| 111 | + * |
| 112 | + * @param session Hub Connection |
| 113 | + * @param prompt Return user keys |
| 114 | + * @return Vault configuration with storage connection |
77 | 115 | */ |
78 | | - // TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 review @dko check method signature? |
79 | | - public synchronized Path create(final Session<?> session, final String region, final String metadata, final String hashedRootDirId, final byte[] rootDirUvf) throws BackgroundException { |
80 | | - log.debug("Uploading vault template {} in {} ", home, session.getHost()); |
81 | | - |
82 | | - // N.B. there seems to be no API to check write permissions without actually writing. |
83 | | - if(!session.getFeature(ListService.class).list(home, new DisabledListProgressListener()).isEmpty()) { |
84 | | - throw new BackgroundException("Bucket not empty", String.format("Cannot upload bucket %s in %s is not empty.", home, session.getHost())); |
| 116 | + @Override |
| 117 | + public HubUVFVault load(final Session<?> session, final PasswordCallback prompt) throws BackgroundException { |
| 118 | + try { |
| 119 | + final HubSession hub = HubSession.coerce(session); |
| 120 | + // Find storage configuration in vault metadata |
| 121 | + final VaultServiceImpl vaultService = new VaultServiceImpl(hub); |
| 122 | + final UvfMetadataPayload vaultMetadata = vaultService.getVaultMetadataJWE(vaultId, hub.getUserKeys()); |
| 123 | + final Protocol profile = ProtocolFactory.get().forName(vaultId.toString()); |
| 124 | + log.debug("Loaded profile {} for vault {}", profile, this); |
| 125 | + final Credentials credentials = new Credentials(hub.getHost().getCredentials()); |
| 126 | + log.debug("Copy credentials {}", credentials); |
| 127 | + final VaultMetadataJWEBackendDto vaultStorageMetadata = vaultMetadata.storage(); |
| 128 | + if(vaultStorageMetadata.getUsername() != null) { |
| 129 | + credentials.setUsername(vaultStorageMetadata.getUsername()); |
| 130 | + } |
| 131 | + if(vaultStorageMetadata.getPassword() != null) { |
| 132 | + credentials.setPassword(vaultStorageMetadata.getPassword()); |
| 133 | + } |
| 134 | + final Host bookmark = new Host(profile, credentials); |
| 135 | + log.debug("Configure bookmark for vault {}", vaultStorageMetadata); |
| 136 | + bookmark.setNickname(vaultStorageMetadata.getNickname()); |
| 137 | + bookmark.setDefaultPath(vaultStorageMetadata.getDefaultPath()); |
| 138 | + bookmark.setProperty(OAUTH_TOKENEXCHANGE_VAULT, vaultId.toString()); |
| 139 | + // region as chosen by user upon vault creation (STS) or as retrieved from bucket (permanent) |
| 140 | + bookmark.setRegion(vaultStorageMetadata.getRegion()); |
| 141 | + log.debug("Configured {} for vault {}", bookmark, this); |
| 142 | + storage = SessionFactory.create(bookmark, session.getFeature(X509TrustManager.class), session.getFeature(X509KeyManager.class)); |
| 143 | + log.debug("Connect to {}", storage); |
| 144 | + storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()); |
| 145 | + storage.login(new DisabledLoginCallback(), new DisabledCancelCallback()); |
| 146 | + final PathAttributes attr = storage.getFeature(AttributesFinder.class).find(home); |
| 147 | + attr.setDisplayname(vaultMetadata.storage().getNickname()); |
| 148 | + home.setAttributes(attr); |
| 149 | + log.debug("Initialize vault {} with metadata {}", this, vaultMetadata); |
| 150 | + // Initialize cryptors |
| 151 | + super.load(storage, new UvfMetadataPayloadPasswordCallback(vaultMetadata.toJSON())); |
| 152 | + return this; |
| 153 | + } |
| 154 | + catch(ApiException e) { |
| 155 | + if(HttpStatus.SC_FORBIDDEN == e.getCode()) { |
| 156 | + throw new VaultUnlockCancelException(this, e); |
| 157 | + } |
| 158 | + throw new HubExceptionMappingService().map(e); |
| 159 | + } |
| 160 | + catch(JsonProcessingException | SecurityFailure | AccessException e) { |
| 161 | + throw new InteroperabilityException(e.getMessage(), e); |
85 | 162 | } |
86 | | - |
87 | | - // See https://github.com/cryptomator/hub/blob/develop/frontend/src/common/vaultconfig.ts |
88 | | - // zip.file('vault.cryptomator', this.vaultConfigToken); |
89 | | - // zip.folder('d')?.folder(this.rootDirHash.substring(0, 2))?.folder(this.rootDirHash.substring(2)); |
90 | | - |
91 | | - // /vault.uvf |
92 | | - new ContentWriter(session).write(new Path(home, PreferencesFactory.get().getProperty("cryptomator.vault.config.filename"), EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.vault)), metadata.getBytes(StandardCharsets.US_ASCII)); |
93 | | - Directory<?> directory = (Directory<?>) session._getFeature(Directory.class); |
94 | | - |
95 | | - // TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 implement CryptoDirectory for uvf |
96 | | - // Path secondLevel = this.directoryProvider.toEncrypted(session, this.home.attributes().getDirectoryId(), this.home); |
97 | | - final Path secondLevel = new Path(String.format("/%s/d/%s/%s/", session.getHost().getDefaultPath(), hashedRootDirId.substring(0, 2), hashedRootDirId.substring(2)), EnumSet.of(AbstractPath.Type.directory)); |
98 | | - final Path firstLevel = secondLevel.getParent(); |
99 | | - final Path dataDir = firstLevel.getParent(); |
100 | | - log.debug("Create vault root directory at {}", secondLevel); |
101 | | - final TransferStatus status = (new TransferStatus()).setRegion(region); |
102 | | - |
103 | | - directory.mkdir(session._getFeature(Write.class), dataDir, status); |
104 | | - directory.mkdir(session._getFeature(Write.class), firstLevel, status); |
105 | | - directory.mkdir(session._getFeature(Write.class), secondLevel, status); |
106 | | - new ContentWriter(session).write(new Path(secondLevel, "dir.uvf", EnumSet.of(AbstractPath.Type.file)), rootDirUvf); |
107 | | - return home; |
108 | 163 | } |
109 | 164 |
|
110 | 165 | @Override |
111 | | - public HubUVFVault load(final Session<?> ignore, final PasswordCallback prompt) throws BackgroundException { |
112 | | - log.debug("Connect to {}", storage); |
113 | | - storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()); |
114 | | - storage.login(new DisabledLoginCallback(), new DisabledCancelCallback()); |
115 | | - super.load(storage, prompt); |
116 | | - return this; |
| 166 | + public String toString() { |
| 167 | + final StringBuilder sb = new StringBuilder("HubUVFVault{"); |
| 168 | + sb.append("storage=").append(storage); |
| 169 | + sb.append(", vaultId=").append(vaultId); |
| 170 | + sb.append('}'); |
| 171 | + return sb.toString(); |
117 | 172 | } |
118 | 173 | } |
0 commit comments