diff --git a/pom.xml b/pom.xml index 5a25d71..1c60f85 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 2.22.1 2.0.1-alpha 1.4.0 - 1.1.1 + 1.2.0 1.1.1 1.4.2 diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index 84938f4..cffe27c 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -7,18 +7,21 @@ import org.cryptomator.integrations.keychain.KeychainAccessProvider; import org.freedesktop.dbus.DBusPath; import org.purejava.secret.api.Collection; +import org.purejava.secret.api.DBusMessageHandler; import org.purejava.secret.api.EncryptedSession; import org.purejava.secret.api.Item; +import org.purejava.secret.api.Pair; import org.purejava.secret.api.Static; import org.purejava.secret.api.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import static org.purejava.secret.api.DBusMessageHandler.DBusResult.*; + @Priority(1100) @OperatingSystem(OperatingSystem.Value.LINUX) @DisplayName("Secret Service") @@ -36,9 +39,15 @@ public SecretServiceKeychainAccess() { session.getService().addCollectionCreatedHandler(collection -> LOG.debug("Collection {} created", collection.getPath())); session.getService().addCollectionDeletedHandler(collection -> LOG.debug("Collection {} deleted", collection.getPath())); var getAlias = session.getService().readAlias("default"); - if (getAlias.isSuccess() && "/".equals(getAlias.value().getPath())) { - // default alias is not set; set it to the login keyring - session.getService().setAlias("default", new DBusPath(Static.DBusPath.LOGIN_COLLECTION)); + switch (getAlias) { + case Success success-> { + if ("/".equals(success.value().getPath())) { + // default alias is not set; set it to the login keyring + session.getService().setAlias("default", new DBusPath(Static.DBusPath.LOGIN_COLLECTION)); + } + } + case Failure failure + -> LOG.warn("Getting the collection with the \"default\" alias failed with: {}", failure.error().getMessage()); } collection.addItemChangedHandler(item -> LOG.debug("Item {} changed", item.getPath())); collection.addItemCreatedHandler(item -> LOG.debug("Item {} created", item.getPath())); @@ -50,28 +59,66 @@ public SecretServiceKeychainAccess() { public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (call.value().isEmpty()) { - List lockable = new ArrayList<>(); - lockable.add(new DBusPath(collection.getDBusPath())); - var promptNeededToUnlock = session.getService().unlock(lockable); - if (promptNeededToUnlock.isSuccess() && !"/".equals(promptNeededToUnlock.value().b.getPath())) { - Util.promptAndGetResultAsArrayList(promptNeededToUnlock.value().b); + switch (call) { + case DBusMessageHandler.DBusResult.Success> success + when success.value().isEmpty() -> { + List lockable = List.of(new DBusPath(collection.getDBusPath())); + var unlockResult = session.getService().unlock(lockable); + + switch (unlockResult) { + case Success, DBusPath>> unlockSuccess -> { + /* The unlockResult variable may contain a prompt: when you call unlock for the collection + in order to be able to store the secret within the collection, depending on whether + a prompt is needed to unlock the collection or not, a prompt needs to be handled or not.*/ + var prompt = unlockSuccess.value().b; + if (!"/".equals(prompt.getPath())) { + Util.promptAndGetResultAsArrayList(prompt); + } + } + case Failure, DBusPath>> unlockFailure -> + LOG.warn("Failed to unlock collection {}", + collection.getDBusPath(), + unlockFailure.error()); } - var itemProps = Item.createProperties(LABEL_FOR_SECRET_IN_KEYRING, withKeyAndName(key, displayName)); + + var itemProps = Item.createProperties( + LABEL_FOR_SECRET_IN_KEYRING, + withKeyAndName(key, displayName) + ); var secret = session.encrypt(passphrase); var created = collection.createItem(itemProps, secret, false); - if (!created.isSuccess()) { - throw new KeychainAccessException("Storing password failed", created.error()); + + switch (created) { + case Success> successful -> + LOG.debug("Created item {} on collection {}", + successful.value().a.getPath(), + collection.getDBusPath()); + case Failure> failure -> + throw new KeychainAccessException( + "Storing password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } - } else { - changePassphrase(key, displayName, passphrase); } - } else { - throw new KeychainAccessException("Storing password failed", call.error()); + case DBusMessageHandler.DBusResult.Success> _ -> + changePassphrase(key, displayName, passphrase); + + case DBusMessageHandler.DBusResult.Failure> failure -> + throw new KeychainAccessException( + "Storing password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { - throw new KeychainAccessException("Storing password failed.", e); + throw new KeychainAccessException( + "Storing password failed for collection " + + collection.getDBusPath(), + e + ); } } @@ -79,20 +126,45 @@ public void storePassphrase(String key, String displayName, CharSequence passphr public char[] loadPassphrase(String key) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (!call.value().isEmpty()) { - var path = call.value().getFirst(); + + return switch (call) { + + case Success> success + when success.value().isEmpty() -> + null; + + case Success> success + when success.value().size() > 1 -> + throw new KeychainAccessException( + "Expected exactly one item, but found " + + success.value().size() + ); + + case Success> success -> { + var path = success.value().getFirst(); + session.getService().ensureUnlocked(path); + var secret = new Item(path).getSecret(session.getSession()); - return session.decrypt(secret); - } else { - return null; + yield session.decrypt(secret); } - } else { - throw new KeychainAccessException("Loading password failed", call.error()); - } + + case Failure> failure -> + throw new KeychainAccessException( + "Loading password failed for collection " + + collection.getDBusPath(), + failure.error() + ); + }; + + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { - throw new KeychainAccessException("Loading password failed.", e); + throw new KeychainAccessException( + "Loading password failed for collection " + + collection.getDBusPath(), + e + ); } } @@ -100,48 +172,135 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { public void deletePassphrase(String key) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (!call.value().isEmpty()) { - var path = call.value().getFirst(); + + switch (call) { + + case Success> success + when success.value().isEmpty() -> + LOG.debug("Deleting entry with {}={} failed: No such item found", + ID_KEY, + key); + + case Success> success + when success.value().size() > 1 -> + throw new KeychainAccessException( + "Expected exactly one item, but found " + + success.value().size() + ); + + case Success> success -> { + var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var item = new Item(path); - var deleted = item.delete(); - if (!deleted.isSuccess()) { - throw new KeychainAccessException("Deleting password failed", deleted.error()); + + switch (item.delete()) { + case Success _ -> + LOG.debug("Deleted item {} from collection {}", + path.getPath(), + collection.getDBusPath()); + + case Failure failure -> { + LOG.warn("Failed to delete item {} from collection {}", + path.getPath(), + collection.getDBusPath(), + failure.error()); + + throw new KeychainAccessException( + "Deleting password failed for collection " + + collection.getDBusPath(), + failure.error() + ); + } } - } else { - LOG.debug("Deleting entry with {}={} failed: No such item found", ID_KEY, key); } - } else { - throw new KeychainAccessException("Deleting password failed", call.error()); + + case Failure> failure -> + throw new KeychainAccessException( + "Deleting password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } + + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { - throw new KeychainAccessException("Deleting password failed", e); + throw new KeychainAccessException( + "Deleting password failed for collection " + + collection.getDBusPath(), + e + ); } } @Override - public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { + public void changePassphrase(String key, String displayName, CharSequence passphrase) + throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (!call.value().isEmpty()) { - session.getService().ensureUnlocked(call.value().getFirst()); + + switch (call) { + + case Success> success + when success.value().isEmpty() -> + throw new KeychainAccessException( + "Vault " + key + " not found, updating failed" + ); + + case Success> success + when success.value().size() > 1 -> + throw new KeychainAccessException( + "Expected exactly one item, but found " + + success.value().size() + ); + + case Success> success -> { + var path = success.value().getFirst(); + session.getService().ensureUnlocked(path); var secret = session.encrypt(passphrase); - var itemProps = Item.createProperties(LABEL_FOR_SECRET_IN_KEYRING, withKeyAndName(key, displayName)); + var itemProps = Item.createProperties( + LABEL_FOR_SECRET_IN_KEYRING, + withKeyAndName(key, displayName) + ); var updated = collection.createItem(itemProps, secret, true); - if (!updated.isSuccess()) { - throw new KeychainAccessException("Updating password failed", updated.error()); + + switch (updated) { + case Success> _ -> + LOG.debug("Updated item {} in collection {}", + path.getPath(), + collection.getDBusPath()); + + case Failure> failure -> { + LOG.warn("Failed to update item {} in collection {}", + path.getPath(), + collection.getDBusPath(), + failure.error()); + + throw new KeychainAccessException( + "Updating password failed for collection " + + collection.getDBusPath(), + failure.error() + ); + } } - } else { - var msg = "Vault " + key + " not found, updating failed"; - throw new KeychainAccessException(msg); } - } else { - throw new KeychainAccessException("Updating password failed", call.error()); + + case Failure> failure -> + throw new KeychainAccessException( + "Updating password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } + + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { - throw new KeychainAccessException("Updating password failed", e); + throw new KeychainAccessException( + "Updating password failed for collection " + + collection.getDBusPath(), + e + ); } } @@ -158,8 +317,19 @@ public boolean isSupported() { @Override public boolean isLocked() { - var call = collection.isLocked(); - return !call.isSuccess() || call.value(); + return switch (collection.isLocked()) { + case Success success -> + success.value(); // yields the value + + case Failure failure -> { + LOG.warn( + "Failed to determine lock state of collection {}", + collection.getDBusPath(), + failure.error() + ); + yield true; + } + }; } private Map withKey(String key) {