├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── codeql-analysis.init.gradle │ ├── codeql-analysis.yml │ └── release_to_maven.yml ├── .gitignore ├── .idea ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── icon.svg ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── KWallet.png ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main └── java │ ├── module-info.java │ └── org │ └── purejava │ └── kwallet │ ├── KDEWallet.java │ ├── KWallet.java │ ├── MapEntries.java │ ├── Static.java │ └── freedesktop │ └── dbus │ └── handlers │ ├── MessageHandler.java │ ├── Messaging.java │ └── SignalHandler.java └── test ├── java └── org │ └── purejava │ ├── Context.java │ ├── KDEWalletTest.java │ ├── KDEWalletTest1.java │ ├── KDEWalletTest2.java │ ├── KDEWalletTest3.java │ └── KDEWalletTest4.java └── resources ├── org.freedesktop.DBus.Introspectable.xml ├── org.freedesktop.DBus.Peer.xml ├── org.freedesktop.DBus.Properties.xml ├── org.kde.KWallet.nodeprecated.xml ├── org.kde.KWallet.xml └── simplelogger.properties /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: purejava 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "saturday" 8 | time: "06:00" 9 | timezone: "Etc/UTC" 10 | groups: 11 | java-bundles: 12 | patterns: 13 | - "org.junit.jupiter:*" 14 | - "org.slf4j:*" 15 | 16 | - package-ecosystem: "github-actions" 17 | directory: "/" # even for `.github/workflows` 18 | schedule: 19 | interval: "monthly" 20 | groups: 21 | github-actions: 22 | patterns: 23 | - "*" 24 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.init.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | allprojects { 18 | tasks.withType(JavaCompile).configureEach { 19 | outputs.doNotCacheIf("CodeQL scanning", { true }) 20 | } 21 | } -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [develop, main] 6 | pull_request: 7 | branches: [develop] 8 | schedule: 9 | - cron: '34 10 * * 4' 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | CodeQL-Build: 15 | permissions: 16 | actions: read # for github/codeql-action/init to get workflow details 17 | contents: read # for actions/checkout to fetch code 18 | security-events: write # for github/codeql-action/analyze to upload SARIF results 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | # Override automatic language detection by changing the below list 25 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 26 | language: ['java', 'javascript'] 27 | # Learn more... 28 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v4 33 | # Checkout must run before the caching key is computed using the `hashFiles` method 34 | 35 | - name: Cache Gradle Modules 36 | uses: actions/cache@v4 37 | with: 38 | path: | 39 | ~/.gradle/caches/modules-2/ 40 | ~/.gradle/caches/build-cache-1/ 41 | ~/.gradle/caches/signatures/ 42 | ~/.gradle/caches/keyrings/ 43 | key: ${{ runner.os }}-gradle-cache-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} 44 | if: ${{ matrix.language == 'java' }} 45 | 46 | - name: Disable checksum offloading 47 | # See: https://github.com/actions/virtual-environments/issues/1187#issuecomment-686735760 48 | run: sudo ethtool -K eth0 tx off rx off 49 | 50 | # Install and setup JDK 19 51 | - name: Setup JDK 19 52 | uses: actions/setup-java@v4 53 | with: 54 | distribution: temurin 55 | java-version: 19 56 | 57 | # Initializes the CodeQL tools for scanning. 58 | - name: Initialize CodeQL 59 | uses: github/codeql-action/init@v3 60 | with: 61 | languages: ${{ matrix.language }} 62 | tools: latest 63 | # If you wish to specify custom queries, you can do so here or in a config file. 64 | # By default, queries listed here will override any specified in a config file. 65 | # Prefix the list here with "+" to use these queries and those in the config file. 66 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 67 | 68 | - name: Compile with Gradle with Build Scan 69 | if: ${{ matrix.language == 'java' && github.repository_owner == 'gradle' }} 70 | run: ./gradlew --init-script .github/workflows/codeql-analysis.init.gradle -DcacheNode=us -S testClasses -Dhttp.keepAlive=false 71 | env: 72 | # Set the DEVELOCITY_ACCESS_KEY so that Gradle Build Scans are generated 73 | DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} 74 | # Potential stop-gap solution for ReadTimeout issues with the Gradle Build Cache 75 | # https://gradle.slack.com/archives/CHDLT99C6/p1636477584059200 76 | GRADLE_OPTS: -Dhttp.keepAlive=false 77 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }} 78 | ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.GPG_PRIVATE_KEY_ID }} 79 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }} 80 | 81 | - name: Compile with Gradle without Build Scan 82 | if: ${{ matrix.language == 'java' && github.repository_owner != 'gradle' }} 83 | run: ./gradlew --init-script .github/workflows/codeql-analysis.init.gradle -S testClasses 84 | 85 | - name: Cleanup Gradle Daemons 86 | run: ./gradlew --stop 87 | if: ${{ matrix.language == 'java' }} 88 | 89 | # ℹ️ Command-line programs to run using the OS shell. 90 | # 📚 https://git.io/JvXDl 91 | 92 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 93 | # and modify them (or add more) to build your code if your project 94 | # uses a compiled language 95 | 96 | #- run: | 97 | # make bootstrap 98 | # make release 99 | 100 | - name: Perform CodeQL Analysis 101 | uses: github/codeql-action/analyze@v3 102 | with: 103 | config-file: ./.github/codeql/codeql-config.yml 104 | 105 | - name: Cleanup Gradle Cache 106 | # Cleans up the Gradle caches before being cached 107 | run: | 108 | rm -f ~/.gradle/caches/modules-2/modules-2.lock 109 | rm -f ~/.gradle/caches/modules-2/gc.properties 110 | if: ${{ matrix.language == 'java' }} 111 | -------------------------------------------------------------------------------- /.github/workflows/release_to_maven.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Maven Central 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: 'Tag' 8 | required: true 9 | default: '0.0.0' 10 | 11 | jobs: 12 | publish: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Java 19 | uses: actions/setup-java@v4 20 | with: 21 | java-version: '19' 22 | distribution: 'temurin' 23 | 24 | - name: Setup Gradle 25 | uses: gradle/actions/setup-gradle@v4 26 | 27 | - name: Import GPG key 28 | run: | 29 | echo "$GPG_SIGNING_KEY_PW" | gpg --batch --import --yes --passphrase-fd 0 <(echo -n "$GPG_SIGNING_KEY_B64" | base64 --decode) 30 | env: 31 | GPG_SIGNING_KEY_B64: ${{ secrets.GPG_PRIVATE_KEY_B64 }} 32 | GPG_SIGNING_KEY_PW: ${{ secrets.GPG_PASSPHRASE }} 33 | 34 | - name: Setup GPG key information 35 | run: | 36 | mkdir -p ~/.gradle 37 | echo "signing.gnupg.passphrase=${GPG_SIGNING_KEY_PW}" >> ~/.gradle/gradle.properties 38 | env: 39 | GPG_SIGNING_KEY_PW: ${{ secrets.GPG_PASSPHRASE }} 40 | 41 | - name: Build package 42 | run: ./gradlew clean build -x test 43 | 44 | - name: Publish package 45 | run: ./gradlew publishToCentralPortal 46 | env: 47 | SONATYPE_USERNAME: ${{ secrets.NEXUS_USERNAME }} 48 | SONATYPE_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # Eclipse Settings Files # 9 | .settings 10 | .project 11 | .classpath 12 | 13 | # Gradle 14 | .gradle 15 | build/ 16 | !gradle/wrapper/gradle-wrapper.jar 17 | !**/src/main/**/build/ 18 | !**/src/test/**/build/ 19 | 20 | # IntelliJ Settings Files (https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems) # 21 | .idea/**/workspace.xml 22 | .idea/**/uiDesigner.xml 23 | .idea/**/tasks.xml 24 | .idea/dictionaries 25 | .idea/**/libraries/ 26 | *.iml -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /KWallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purejava/kdewallet/9ee340014252ca5089ea9cc5ec9405db34c271db/KWallet.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 Ralph Plawetzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kdewallet 2 | ![KWallet](KWallet.png) 3 | 4 | [![Publish to Maven Central](https://github.com/purejava/kdewallet/workflows/Publish%20to%20Maven%20Central/badge.svg)](https://github.com/purejava/kdewallet/actions?query=workflow%3A%22Publish+to+Maven+Central%22) 5 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/b1379afd2db3447abfbdca82fbdc2b7a)](https://app.codacy.com/gh/purejava/kdewallet/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) 6 | [![Maven Central](https://img.shields.io/maven-central/v/org.purejava/kdewallet.svg?label=Maven%20Central)](https://central.sonatype.com/search?q=kdewallet&smo=true&namespace=org.purejava) 7 | [![License](https://img.shields.io/github/license/purejava/kdewallet.svg)](https://github.com/purejava/kdewallet/blob/master/LICENSE) 8 | 9 | A Java library for storing secrets on linux in a KDE wallet over D-Bus. 10 | 11 | The KDE wallet functionality itself is provided by the kwallet daemon [kwalletd](https://github.com/KDE/kwallet/tree/master/src/runtime/kwalletd). 12 | 13 | ## Usage 14 | The library provides an API, which sends secrets over D-Bus and has D-Bus signaling enabled. 15 | 16 | ### Dependency 17 | Add `kdewallet` as a dependency to your project. 18 | ### Gradle 19 | ```groovy 20 | implementation group: 'org.purejava', name: 'kdewallet', version: '1.6.0' 21 | ``` 22 | ### Maven 23 | ```maven 24 | 25 | org.purejava 26 | kdewallet 27 | 1.6.0 28 | 29 | ``` 30 | 31 | ### Accessing the kwallet daemon 32 | Creating a folder in a wallet can be done like this: 33 | ```java 34 | package org.example; 35 | 36 | import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; 37 | import org.freedesktop.dbus.exceptions.DBusException; 38 | import org.kde.KWallet; 39 | 40 | import java.io.IOException; 41 | 42 | public class App { 43 | public static void main(String[] args) { 44 | DBusConnection connection = null; 45 | 46 | try { 47 | connection = DBusConnectionBuilder.forSessionBus().withShared(false).build(); 48 | 49 | var service = connection.getRemoteObject("org.kde.kwalletd6", 50 | "/modules/kwalletd6", KWallet.class); 51 | 52 | var wallet = "kdewallet"; 53 | var wId = 0; 54 | var appid = "Tester"; 55 | var handle = service.open(wallet, wId, appid); 56 | var folder = "Test-Folder"; 57 | var created = service.createFolder(handle, folder, appid); 58 | service.close(handle, false, appid); 59 | } catch (DBusException e) { 60 | System.out.println(e.toString() + e.getCause()); 61 | } 62 | try { 63 | connection.close(); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ### Signal handling 72 | D-Bus signals are emitted on every change to a wallet, e.g. when it's opened asynchronously, closed etc. 73 | Listening to these signals can be done asynchronously with few effort. 74 | 75 | For the complete API and examples see the [Wiki](https://github.com/purejava/kdewallet/wiki/Home). 76 | 77 | ## Thank you 78 | Thanks to [David M.](https://github.com/hypfvieh), who wrote an improved version of [Java DBus](https://github.com/hypfvieh/dbus-java) library provided by freedesktop.org. 79 | Thanks to [Sebastian Wiesendahl](https://github.com/swiesend), who implemented the original core messaging interface to DBus in his [secret-service](https://github.com/swiesend/secret-service) library. 80 | 81 | ## Copyright 82 | Copyright (C) 2020-2025 Ralph Plawetzki 83 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import net.thebugmc.gradle.sonatypepublisher.PublishingType.* 2 | 3 | plugins { 4 | id("java-library") 5 | id("net.thebugmc.gradle.sonatype-central-portal-publisher") version "1.2.4" 6 | id("maven-publish") 7 | id("signing") 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | api(libs.com.github.hypfvieh.dbus.java.core) 16 | api(libs.com.github.hypfvieh.dbus.java.transport.native.unixsocket) 17 | api(libs.org.slf4j.slf4j.api) 18 | testImplementation(libs.org.junit.jupiter.junit.jupiter.api) 19 | testImplementation(libs.org.junit.jupiter.junit.jupiter.engine) 20 | testImplementation(libs.org.junit.jupiter.junit.jupiter) 21 | testImplementation(libs.org.slf4j.slf4j.simple) 22 | testRuntimeOnly(libs.org.junit.platform.junit.platform.launcher) 23 | } 24 | 25 | group = "org.purejava" 26 | version = "1.6.1-SNAPSHOT" 27 | description = "A Java library for storing secrets on linux in a KDE wallet over D-Bus, implements kwallet." 28 | 29 | val sonatypeUsername: String = System.getenv("SONATYPE_USERNAME") ?: "" 30 | val sonatypePassword: String = System.getenv("SONATYPE_PASSWORD") ?: "" 31 | 32 | java { 33 | java.sourceCompatibility = JavaVersion.VERSION_19 34 | withSourcesJar() 35 | withJavadocJar() 36 | } 37 | 38 | tasks.test { 39 | useJUnitPlatform() 40 | filter { 41 | includeTestsMatching("KDEWalletTest") 42 | } 43 | } 44 | 45 | publishing { 46 | publications { 47 | create("mavenJava") { 48 | from(components["java"]) 49 | 50 | pom { 51 | name.set("kdewallet") 52 | description.set("A Java library for storing secrets on linux in a KDE wallet over D-Bus, implements kwallet.") 53 | url.set("https://github.com/purejava/kdewallet") 54 | 55 | licenses { 56 | license { 57 | name.set("MIT License") 58 | url.set("https://opensource.org/licenses/MIT") 59 | } 60 | } 61 | 62 | developers { 63 | developer { 64 | id.set("purejava") 65 | name.set("Ralph Plawetzki") 66 | email.set("ralph@purejava.org") 67 | } 68 | } 69 | 70 | scm { 71 | connection.set("scm:git:git://github.com/purejava/kdewallet.git") 72 | developerConnection.set("scm:git:ssh://github.com/purejava/kdewallet.git") 73 | url.set("https://github.com/purejava/kdewallet/tree/main") 74 | } 75 | 76 | issueManagement { 77 | system.set("GitHub Issues") 78 | url.set("https://github.com/purejava/kdewallet/issues") 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | centralPortal { 86 | publishingType.set(USER_MANAGED) 87 | 88 | username.set(sonatypeUsername) 89 | password.set(sonatypePassword) 90 | 91 | // Configure POM metadata 92 | pom { 93 | name.set("kdewallet") 94 | description.set("A Java library for storing secrets on linux in a KDE wallet over D-Bus, implements kwallet.") 95 | url.set("https://github.com/purejava/kdewallet") 96 | licenses { 97 | license { 98 | name.set("MIT License") 99 | url.set("https://opensource.org/licenses/MIT") 100 | } 101 | } 102 | developers { 103 | developer { 104 | id.set("purejava") 105 | name.set("Ralph Plawetzki") 106 | email.set("ralph@purejava.org") 107 | } 108 | } 109 | scm { 110 | connection.set("scm:git:git://github.com/purejava/kdewallet.git") 111 | developerConnection.set("scm:git:ssh://github.com/purejava/kdewallet.git") 112 | url.set("https://github.com/purejava/kdewallet/tree/main") 113 | } 114 | issueManagement { 115 | system.set("GitHub Issues") 116 | url.set("https://github.com/kdewallet/issues") 117 | } 118 | } 119 | } 120 | 121 | if (!version.toString().endsWith("-SNAPSHOT")) { 122 | signing { 123 | useGpgCmd() 124 | sign(configurations.runtimeElements.get()) 125 | sign(publishing.publications["mavenJava"]) 126 | } 127 | } 128 | 129 | tasks.withType { 130 | options.encoding = "UTF-8" 131 | } 132 | 133 | tasks.withType { 134 | isFailOnError = false 135 | if (JavaVersion.current().isJava9Compatible) { 136 | (options as? StandardJavadocDocletOptions)?.addBooleanOption("html5", true) 137 | } 138 | (options as? StandardJavadocDocletOptions)?.encoding = "UTF-8" 139 | } 140 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # This file was generated by the Gradle 'init' task. 2 | # https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties 3 | 4 | org.gradle.configuration-cache=false 5 | org.gradle.parallel=true 6 | org.gradle.caching=true 7 | 8 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by the Gradle 'init' task. 2 | # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format 3 | 4 | [versions] 5 | com-github-hypfvieh-dbus-java-core = "5.1.1" 6 | com-github-hypfvieh-dbus-java-transport-native-unixsocket = "5.1.1" 7 | org-junit-jupiter-junit-jupiter = "5.13.0" 8 | org-junit-jupiter-junit-jupiter-api = "5.13.0" 9 | org-junit-jupiter-junit-jupiter-engine = "5.13.0" 10 | org-slf4j-slf4j-api = "2.0.17" 11 | org-slf4j-slf4j-simple = "2.0.17" 12 | 13 | [libraries] 14 | com-github-hypfvieh-dbus-java-core = { module = "com.github.hypfvieh:dbus-java-core", version.ref = "com-github-hypfvieh-dbus-java-core" } 15 | com-github-hypfvieh-dbus-java-transport-native-unixsocket = { module = "com.github.hypfvieh:dbus-java-transport-native-unixsocket", version.ref = "com-github-hypfvieh-dbus-java-transport-native-unixsocket" } 16 | org-junit-jupiter-junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "org-junit-jupiter-junit-jupiter" } 17 | org-junit-jupiter-junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "org-junit-jupiter-junit-jupiter-api" } 18 | org-junit-jupiter-junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "org-junit-jupiter-junit-jupiter-engine" } 19 | org-junit-platform-junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } 20 | org-slf4j-slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "org-slf4j-slf4j-api" } 21 | org-slf4j-slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "org-slf4j-slf4j-simple" } 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purejava/kdewallet/9ee340014252ca5089ea9cc5ec9405db34c271db/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | org.gradle.wrapper.GradleWrapperMain \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * This project uses @Incubating APIs which are subject to change. 5 | */ 6 | 7 | rootProject.name = "kdewallet" 8 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.purejava.kwallet { 2 | requires java.desktop; 3 | requires org.freedesktop.dbus; 4 | requires org.slf4j; 5 | 6 | exports org.purejava.kwallet; 7 | exports org.purejava.kwallet.freedesktop.dbus.handlers; 8 | } -------------------------------------------------------------------------------- /src/main/java/org/purejava/kwallet/KDEWallet.java: -------------------------------------------------------------------------------- 1 | package org.purejava.kwallet; 2 | 3 | import org.freedesktop.dbus.connections.impl.DBusConnection; 4 | import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; 5 | import org.freedesktop.dbus.exceptions.DBusException; 6 | import org.purejava.kwallet.freedesktop.dbus.handlers.Messaging; 7 | import org.freedesktop.dbus.interfaces.DBus; 8 | import org.freedesktop.dbus.messages.DBusSignal; 9 | import org.freedesktop.dbus.types.Variant; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.IOException; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | import java.util.stream.IntStream; 19 | 20 | public class KDEWallet extends Messaging implements KWallet, AutoCloseable { 21 | 22 | private static final Logger LOG = LoggerFactory.getLogger(KDEWallet.class); 23 | private static String SERVICE; 24 | private static String OBJECT_PATHS; 25 | private final DBusConnection connection; 26 | 27 | public static final List> SIGNALS = Arrays.asList( 28 | applicationDisconnected.class, 29 | folderUpdated.class, 30 | folderListUpdated.class, 31 | allWalletsClosed.class, 32 | walletClosedId.class, 33 | walletClosed.class, 34 | walletDeleted.class, 35 | walletAsyncOpened.class, 36 | walletOpened.class, 37 | walletCreated.class, 38 | walletListDirty.class); 39 | 40 | public KDEWallet(DBusConnection connection) { 41 | super(connection, 42 | SIGNALS, 43 | SERVICE, 44 | OBJECT_PATHS, 45 | Static.Interfaces.KWALLET); 46 | this.connection = connection; 47 | } 48 | 49 | static { 50 | try (var connection = DBusConnectionBuilder.forSessionBus().withShared(false).build()) { 51 | try { 52 | var bus = connection.getRemoteObject("org.freedesktop.DBus", 53 | "/org/freedesktop/DBus", DBus.class); 54 | if (Arrays.asList(bus.ListActivatableNames()).contains(Static.Service.KWALLETD6)) { 55 | SERVICE = Static.Service.KWALLETD6; 56 | OBJECT_PATHS = Static.ObjectPaths.KWALLETD6; 57 | LOG.debug("Kwallet daemon v6 initialized"); 58 | } else if (Arrays.asList(bus.ListActivatableNames()).contains(Static.Service.KWALLETD5)) { 59 | SERVICE = Static.Service.KWALLETD5; 60 | OBJECT_PATHS = Static.ObjectPaths.KWALLETD5; 61 | LOG.debug("Kwallet daemon v5 initialized"); 62 | } 63 | } catch (DBusException e) { 64 | LOG.error(e.toString(), e.getCause()); 65 | } 66 | } catch (IOException | DBusException e) { 67 | LOG.error(e.toString(), e.getCause()); 68 | } 69 | } 70 | 71 | @Override 72 | public boolean isEnabled() { 73 | if (null == connection) { 74 | LOG.debug("No d-bus connection available"); 75 | return false; 76 | } 77 | try { 78 | var bus = connection.getRemoteObject("org.freedesktop.DBus", 79 | "/org/freedesktop/DBus", DBus.class); 80 | if (Arrays.asList(bus.ListActivatableNames()).contains(Static.Service.KWALLETD6)) { 81 | LOG.debug("Kwallet daemon v6 is available"); 82 | return true; 83 | } else if (Arrays.asList(bus.ListActivatableNames()).contains(Static.Service.KWALLETD5)) { 84 | LOG.debug("Kwallet daemon v5 is available"); 85 | return true; 86 | } else { 87 | return false; 88 | } 89 | } catch (DBusException e) { 90 | LOG.error(e.toString(), e.getCause()); 91 | return false; 92 | } 93 | } 94 | 95 | @Override 96 | public int open(String wallet, long wId, String appid) { 97 | var response = send("open", "sxs", wallet, wId, appid); 98 | return null == response ? -1 : (int) response[0]; 99 | } 100 | 101 | @Override 102 | public int openPath(String path, long wId, String appid) { 103 | var response = send("openPath", "sxs", path, wId, appid); 104 | return null == response ? 0 : (int) response[0]; 105 | } 106 | 107 | @Override 108 | public int openAsync(String wallet, long wId, String appid, boolean handleSession) { 109 | var response = send("openAsync", "sxsb", wallet, wId, appid, handleSession); 110 | return null == response ? -1 : (int) response[0]; 111 | } 112 | 113 | @Override 114 | public int openPathAsync(String path, long wId, String appid, boolean handleSession) { 115 | var response = send("openPathAsync", "sxsb", path, wId, appid, handleSession); 116 | return null == response ? -1 : (int) response[0]; 117 | } 118 | 119 | @Override 120 | public int close(String wallet, boolean force) { 121 | var response = send("close", "sb", wallet, force); 122 | return null == response ? -1 : (int) response[0]; 123 | } 124 | 125 | @Override 126 | public int close(int handle, boolean force, String appid) { 127 | var response = send("close", "ibs", handle, force, appid); 128 | return null == response ? -1 : (int) response[0]; 129 | } 130 | 131 | @Override 132 | public void sync(int handle, String appid) { 133 | send("sync", "is", handle, appid); 134 | } 135 | 136 | @Override 137 | public int deleteWallet(String wallet) { 138 | var response = send("deleteWallet", "s", wallet); 139 | return null == response ? -1 : (int) response[0]; 140 | } 141 | 142 | @Override 143 | public boolean isOpen(String wallet) { 144 | var response = send("isOpen", "s", wallet); 145 | return null != response && (boolean) response[0]; 146 | } 147 | 148 | @Override 149 | public boolean isOpen(int handle) { 150 | var response = send("isOpen", "i", handle); 151 | return null != response && (boolean) response[0]; 152 | } 153 | 154 | @Override 155 | public List users(String wallet) { 156 | return contentOrEmptyList(send("users", "s", wallet)); 157 | } 158 | 159 | @Override 160 | public void changePassword(String wallet, long wId, String appid) { 161 | send("changePassword", "sxs", wallet, wId, appid); 162 | } 163 | 164 | @Override 165 | public List wallets() { 166 | return contentOrEmptyList(send("wallets")); 167 | } 168 | 169 | @Override 170 | public List folderList(int handle, String appid) { 171 | return contentOrEmptyList(send("folderList", "is", handle, appid)); 172 | } 173 | 174 | @Override 175 | public boolean hasFolder(int handle, String folder, String appid) { 176 | var response = send("hasFolder", "iss", handle, folder, appid); 177 | return null != response && (boolean) response[0]; 178 | } 179 | 180 | @Override 181 | public boolean createFolder(int handle, String folder, String appid) { 182 | var response = send("createFolder", "iss", handle, folder, appid); 183 | return null != response && (boolean) response[0]; 184 | } 185 | 186 | @Override 187 | public boolean removeFolder(int handle, String folder, String appid) { 188 | var response = send("removeFolder", "iss", handle, folder, appid); 189 | return null != response && (boolean) response[0]; 190 | } 191 | 192 | @Override 193 | public List entryList(int handle, String folder, String appid) { 194 | return contentOrEmptyList(send("entryList", "iss", handle, folder, appid)); 195 | } 196 | 197 | @Override 198 | public byte[] readEntry(int handle, String folder, String key, String appid) { 199 | var response = send("readEntry", "isss", handle, folder, key, appid); 200 | return getBytes(response); 201 | } 202 | 203 | @Override 204 | public byte[] readMap(int handle, String folder, String key, String appid) { 205 | var response = send("readMap", "isss", handle, folder, key, appid); 206 | return getBytes(response); 207 | } 208 | 209 | /** 210 | * Helper method to convert d-bus arg type "ay" (array of byte) into a byte[] 211 | * 212 | * @param response The b-bus reposnse to process 213 | * @return The data part of the response converted 214 | */ 215 | private byte[] getBytes(Object[] response) { 216 | if (null == response) { 217 | return new byte[0]; 218 | } else { 219 | var objectList = (List) response[0]; 220 | var entry = new byte[objectList.size()]; 221 | IntStream.range(0, objectList.size()).forEach(i -> entry[i] = (Byte) objectList.get(i)); 222 | return entry; 223 | } 224 | } 225 | 226 | @Override 227 | public String readPassword(int handle, String folder, String key, String appid) { 228 | var response = send("readPassword", "isss", handle, folder, key, appid); 229 | return null == response ? "" : (String) response[0]; 230 | } 231 | 232 | @Override 233 | public Map entriesList(int handle, String folder, String appid) { 234 | var map = contentOrEmptyMap(send("entriesList", "iss", handle, folder, appid)); 235 | return map.entrySet().stream() 236 | .collect(Collectors.toMap(Map.Entry::getKey, e -> { 237 | var objectList = (List) e.getValue().getValue(); 238 | var innerMap = new byte[objectList.size()]; 239 | IntStream.range(0, objectList.size()).forEach(i -> innerMap[i] = (Byte) objectList.get(i)); 240 | return innerMap; 241 | })); 242 | } 243 | 244 | @Override 245 | public Map mapList(int handle, String folder, String appid) { 246 | var map = contentOrEmptyMap(send("mapList", "iss", handle, folder, appid)); 247 | return map.entrySet().stream() 248 | .collect(Collectors.toMap(Map.Entry::getKey, e -> { 249 | var objectList = (List) e.getValue().getValue(); 250 | var innerMap = new byte[objectList.size()]; 251 | IntStream.range(0, objectList.size()).forEach(i -> innerMap[i] = (Byte) objectList.get(i)); 252 | return innerMap; 253 | })); 254 | } 255 | 256 | @Override 257 | public Map passwordList(int handle, String folder, String appid) { 258 | var map = contentOrEmptyMap(send("passwordList", "iss", handle, folder, appid)); 259 | return map.entrySet().stream() 260 | .collect(Collectors.toMap(Map.Entry::getKey, e -> (String) e.getValue().getValue())); 261 | } 262 | 263 | @Override 264 | public int renameEntry(int handle, String folder, String oldName, String newName, String appid) { 265 | var response = send("renameEntry", "issss", handle, folder, oldName, newName, appid); 266 | return null == response ? -1 : (int) response[0]; 267 | } 268 | 269 | @Override 270 | public int writeEntry(int handle, String folder, String key, byte[] value, int entryType, String appid) { 271 | var response = send("writeEntry", "issayis", handle, folder, key, value, entryType, appid); 272 | return null == response ? -1 : (int) response[0]; 273 | } 274 | 275 | @Override 276 | public int writeEntry(int handle, String folder, String key, byte[] value, String appid) { 277 | var response = send("writeEntry", "issays", handle, folder, key, value, appid); 278 | return null == response ? -1 : (int) response[0]; 279 | } 280 | 281 | @Override 282 | public int writeMap(int handle, String folder, String key, byte[] value, String appid) { 283 | var response = send("writeMap", "issays", handle, folder, key, value, appid); 284 | return null == response ? -1 : (int) response[0]; 285 | } 286 | 287 | @Override 288 | public int writePassword(int handle, String folder, String key, String value, String appid) { 289 | var response = send("writePassword", "issss", handle, folder, key, value, appid); 290 | return null == response ? -1 : (int) response[0]; 291 | } 292 | 293 | @Override 294 | public boolean hasEntry(int handle, String folder, String key, String appid) { 295 | var response = send("hasEntry", "isss", handle, folder, key, appid); 296 | return null != response && (boolean) response[0]; 297 | } 298 | 299 | @Override 300 | public int entryType(int handle, String folder, String key, String appid) { 301 | var response = send("entryType", "isss", handle, folder, key, appid); 302 | return null == response ? 0 : (int) response[0]; 303 | } 304 | 305 | @Override 306 | public int removeEntry(int handle, String folder, String key, String appid) { 307 | var response = send("removeEntry", "isss", handle, folder, key, appid); 308 | return null == response ? -1 : (int) response[0]; 309 | } 310 | 311 | @Override 312 | public boolean disconnectApplication(String wallet, String application) { 313 | var response = send("disconnectApplication", "ss", wallet, application); 314 | return null != response && (boolean) response[0]; 315 | } 316 | 317 | @Override 318 | public void reconfigure() { 319 | send("reconfigure"); 320 | } 321 | 322 | @Override 323 | public boolean folderDoesNotExist(String wallet, String folder) { 324 | var response = send("folderDoesNotExist", "ss", wallet, folder); 325 | return null != response && (boolean) response[0]; 326 | } 327 | 328 | @Override 329 | public boolean keyDoesNotExist(String wallet, String folder, String key) { 330 | var response = send("keyDoesNotExist", "sss", wallet, folder, key); 331 | return null != response && (boolean) response[0]; 332 | } 333 | 334 | @Override 335 | public void closeAllWallets() { 336 | send("closeAllWallets"); 337 | } 338 | 339 | @Override 340 | public String networkWallet() { 341 | var response = send("networkWallet"); 342 | return null == response ? "" : (String) response[0]; 343 | } 344 | 345 | @Override 346 | public String localWallet() { 347 | var response = send("localWallet"); 348 | return null == response ? "" : (String) response[0]; 349 | } 350 | 351 | @Override 352 | public void pamOpen(String wallet, byte[] passwordHash, int sessionTimeout) { 353 | send("pamOpen", "sayi", wallet, passwordHash, sessionTimeout); 354 | } 355 | 356 | @Override 357 | public boolean isRemote() { 358 | return false; 359 | } 360 | 361 | @Override 362 | public String getObjectPath() { 363 | return super.getObjectPath(); 364 | } 365 | 366 | public void close() { 367 | try { 368 | if (null != connection && connection.isConnected()) connection.disconnect(); 369 | } catch (Exception e) { 370 | LOG.error(e.toString(), e.getCause()); 371 | } 372 | } 373 | 374 | private List contentOrEmptyList(Object[] o) { 375 | return null == o ? List.of() : (List) o[0]; 376 | } 377 | 378 | private Map contentOrEmptyMap(Object[] o) { 379 | return null == o ? Map.of() : (Map) o[0]; 380 | } 381 | } -------------------------------------------------------------------------------- /src/main/java/org/purejava/kwallet/KWallet.java: -------------------------------------------------------------------------------- 1 | package org.purejava.kwallet; 2 | 3 | import org.freedesktop.dbus.annotations.DBusInterfaceName; 4 | import org.freedesktop.dbus.annotations.MethodNoReply; 5 | import org.freedesktop.dbus.exceptions.DBusException; 6 | import org.freedesktop.dbus.interfaces.DBusInterface; 7 | import org.freedesktop.dbus.messages.DBusSignal; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | @DBusInterfaceName("org.kde.KWallet") 13 | public interface KWallet extends DBusInterface { 14 | 15 | public static class walletListDirty extends DBusSignal { 16 | /** 17 | * A wallet was modified, but not yet saved to disc. 18 | * 19 | * @param path The path to the object this is emitted from. 20 | * @throws DBusException Could not communicate properly with the D-Bus. 21 | */ 22 | public walletListDirty(String path) throws DBusException { 23 | super(path); 24 | } 25 | } 26 | 27 | public static class walletCreated extends DBusSignal { 28 | public final String wallet; 29 | 30 | /** 31 | * A new wallet was created. 32 | * 33 | * @param path The path to the object this is emitted from. 34 | * @param wallet The wallet that has been created. 35 | * @throws DBusException Could not communicate properly with the D-Bus. 36 | */ 37 | public walletCreated(String path, String wallet) throws DBusException { 38 | super(path, wallet); 39 | this.wallet = wallet; 40 | } 41 | } 42 | 43 | public static class walletOpened extends DBusSignal { 44 | public final String wallet; 45 | 46 | /** 47 | * A wallet was opened. 48 | * 49 | * @param path The path to the object this is emitted from. 50 | * @param wallet The wallet that has been opened. 51 | * @throws DBusException Could not communicate properly with the D-Bus. 52 | */ 53 | public walletOpened(String path, String wallet) throws DBusException { 54 | super(path, wallet); 55 | this.wallet = wallet; 56 | } 57 | } 58 | 59 | public static class walletAsyncOpened extends DBusSignal { 60 | public final int tId; 61 | public final int handle; 62 | 63 | /** 64 | * A wallet was opened asynchronously. 65 | * 66 | * @param path The path to the object this is emitted from. 67 | * @param tId Sequential TransactionID. 68 | * @param handle Handle to the wallet, -1 in case the unlock wallet dialog was dismissed. 69 | * @throws DBusException Could not communicate properly with the D-Bus. 70 | */ 71 | public walletAsyncOpened(String path, int tId, int handle) throws DBusException { 72 | super(path, tId, handle); 73 | this.tId = tId; 74 | this.handle = handle; 75 | } 76 | } 77 | 78 | public static class walletDeleted extends DBusSignal { 79 | public final String wallet; 80 | 81 | /** 82 | * A wallet was deleted. 83 | * 84 | * @param path The path to the object this is emitted from. 85 | * @param wallet The wallet that has been deleted. 86 | * @throws DBusException Could not communicate properly with the D-Bus. 87 | */ 88 | public walletDeleted(String path, String wallet) throws DBusException { 89 | super(path, wallet); 90 | this.wallet = wallet; 91 | } 92 | } 93 | 94 | public static class walletClosed extends DBusSignal { 95 | public final String wallet; 96 | 97 | /** 98 | * A wallet was closed. 99 | * 100 | * @param path The path to the object this is emitted from. 101 | * @param wallet The wallet that has been closed. 102 | * @throws DBusException Could not communicate properly with the D-Bus. 103 | */ 104 | public walletClosed(String path, String wallet) throws DBusException { 105 | super(path, wallet); 106 | this.wallet = wallet; 107 | } 108 | } 109 | 110 | public static class walletClosedId extends DBusSignal { 111 | public final int handle; 112 | 113 | /** 114 | * A wallet was closed. 115 | * 116 | * @param path The path to the object this is emitted from. 117 | * @param handle Handle of the wallet. 118 | * @throws DBusException Could not communicate properly with the D-Bus. 119 | */ 120 | public walletClosedId(String path, int handle) throws DBusException { 121 | super(path, handle); 122 | this.handle = handle; 123 | } 124 | } 125 | 126 | public static class allWalletsClosed extends DBusSignal { 127 | /** 128 | * All wallets were closed. 129 | * 130 | * @param path The path to the object this is emitted from. 131 | * @throws DBusException Could not communicate properly with the D-Bus. 132 | */ 133 | public allWalletsClosed(String path) throws DBusException { 134 | super(path); 135 | } 136 | } 137 | 138 | public static class folderListUpdated extends DBusSignal { 139 | public final String wallet; 140 | 141 | /** 142 | * The list of folders contained in a wallet was updated. 143 | * 144 | * @param path The path to the object this is emitted from. 145 | * @param wallet The wallet in which the list of folders has been updated. 146 | * @throws DBusException Could not communicate properly with the D-Bus. 147 | */ 148 | public folderListUpdated(String path, String wallet) throws DBusException { 149 | super(path, wallet); 150 | this.wallet = wallet; 151 | } 152 | } 153 | 154 | public static class folderUpdated extends DBusSignal { 155 | public final String a; 156 | public final String b; 157 | 158 | /** 159 | * The content of a folder in a wallet was updated. 160 | * 161 | * @param path The path to the object this is emitted from. 162 | * @param a The wallet that contains the folder. 163 | * @param b The folder whose content has been updated. 164 | * @throws DBusException Could not communicate properly with the D-Bus. 165 | */ 166 | public folderUpdated(String path, String a, String b) throws DBusException { 167 | super(path, a, b); 168 | this.a = a; 169 | this.b = b; 170 | } 171 | } 172 | 173 | public static class applicationDisconnected extends DBusSignal { 174 | public final String wallet; 175 | public final String application; 176 | 177 | /** 178 | * An application was disconnected from a wallet. 179 | * 180 | * @param path The path to the object this is emitted from. 181 | * @param wallet The wallet the application has been disconnected from. 182 | * @param application The application that has been disconnected. 183 | * @throws DBusException Could not communicate properly with the D-Bus. 184 | */ 185 | public applicationDisconnected(String path, String wallet, String application) throws DBusException { 186 | super(path, wallet, application); 187 | this.wallet = wallet; 188 | this.application = application; 189 | } 190 | } 191 | 192 | /** 193 | * Is kwallet installed? 194 | * 195 | * @return Indicator, if the kwallet daemon is up and running or not. 196 | */ 197 | abstract public boolean isEnabled(); 198 | 199 | /** 200 | * Open and unlock the wallet. 201 | * 202 | * @param wallet The wallet to be opened and unlocked. 203 | * @param wId The window id to associate any dialogs with. You can pass 0 if you don't have a window the password dialog should associate with. 204 | * @param appid The application that accesses the wallet. 205 | * @return Handle to the wallet or -1, if opening fails. 206 | */ 207 | abstract public int open(String wallet, long wId, String appid); 208 | 209 | /** 210 | * Open and unlock the wallet with this path. 211 | * 212 | * @param path Path to the wallet. 213 | * @param wId The window id to associate any dialogs with. You can pass 0 if you don't have a window the password dialog should associate with. 214 | * @param appid The application that accesses the wallet. 215 | * @return Always 0. 216 | */ 217 | abstract public int openPath(String path, long wId, String appid); 218 | 219 | /** 220 | * Open the wallet asynchronously. 221 | * 222 | * @param wallet The wallet to be opened. 223 | * @param wId The window id to associate any dialogs with. You can pass 0 if you don't have a window the password dialog should associate with. 224 | * @param appid The application that accesses the wallet. 225 | * @param handleSession Handle session with kwalletsessionstore or not. 226 | * @return Sequential TransactionID. 227 | */ 228 | abstract public int openAsync(String wallet, long wId, String appid, boolean handleSession); 229 | 230 | /** 231 | * Open and unlock the wallet with this path asynchronously. 232 | * 233 | * @param path Path to the wallet. 234 | * @param wId The window id to associate any dialogs with. You can pass 0 if you don't have a window the password dialog should associate with. 235 | * @param appid The application that accesses the wallet. 236 | * @param handleSession Handle session with kwalletsessionstore or not. 237 | * @return Sequential TransactionID. 238 | */ 239 | abstract public int openPathAsync(String path, long wId, String appid, boolean handleSession); 240 | 241 | /** 242 | * Close and lock the wallet. The wallet will only be closed if it is open but not in use (rare), or if it is forced closed. 243 | *

244 | * If force = true, will close it for all users. Behave. This 245 | * can break applications, and is generally intended for use by 246 | * the wallet manager app only. 247 | * 248 | * @param wallet The wallet to be closed and locked. 249 | * @param force Forced closing or not. 250 | * @return -1 if wallet does not exist, 0 if all references fom applications to the wallet have been removed. 251 | */ 252 | abstract public int close(String wallet, boolean force); 253 | 254 | /** 255 | * Close and lock the wallet. 256 | *

257 | * If force = true, will close it for all users. Behave. This 258 | * can break applications, and is generally intended for use by 259 | * the wallet manager app only. 260 | * 261 | * @param handle Handle to the wallet to be closed and locked. 262 | * @param force Forced closing or not. 263 | * @param appid AppID of the app to access the wallet. 264 | * @return -1 if wallet does not exist, amount of references to the wallet fom other applications. 265 | */ 266 | abstract public int close(int handle, boolean force, String appid); 267 | 268 | /** 269 | * Save to disk but leave open. 270 | * 271 | * @param handle Valid handle to the wallet to be saved. 272 | * @param appid AppID of the app to access the wallet. 273 | */ 274 | @MethodNoReply 275 | abstract public void sync(int handle, String appid); 276 | 277 | /** 278 | * Physically deletes the wallet from disk. 279 | * 280 | * @param wallet The wallet to be deleted. 281 | * @return -1 if something went wrong, 0 if the wallet could be deleted. 282 | */ 283 | abstract public int deleteWallet(String wallet); 284 | 285 | /** 286 | * Is the wallet open and unlocked by any application? 287 | * 288 | * @param wallet The wallet to be tested. 289 | * @return True, if the wallet is open and unlocked, false otherwise. 290 | */ 291 | abstract public boolean isOpen(String wallet); 292 | 293 | /** 294 | * Is the wallet open, unlocked and has a valid handle? 295 | * 296 | * @param handle The handle to the wallet to be tested. 297 | * @return True, if the wallet is open, unlocked and the handle is valid, false otherwise. 298 | */ 299 | abstract public boolean isOpen(int handle); 300 | 301 | /** 302 | * List the applications that are using the wallet. 303 | * 304 | * @param wallet The wallet to query. 305 | * @return A list of all application IDs using the wallet. 306 | */ 307 | abstract public List users(String wallet); 308 | 309 | /** 310 | * Request to the wallet service to change the password of the wallet. 311 | * 312 | * @param wallet The wallet to change the password of. 313 | * @param wId The window id to associate any dialogs with. You can pass 0 if you don't have a window the password dialog should associate with. 314 | * @param appid AppID of the app to access the wallet. 315 | */ 316 | abstract public void changePassword(String wallet, long wId, String appid); 317 | 318 | /** 319 | * List of all wallets. 320 | * 321 | * @return List of wallets available. 322 | */ 323 | abstract public List wallets(); 324 | 325 | /** 326 | * Obtain the list of all folders contained in the wallet. 327 | * 328 | * @param handle Valid handle to the wallet to be read from. 329 | * @param appid AppID of the app to access the wallet. 330 | * @return List of folders in that wallet. 331 | */ 332 | abstract public List folderList(int handle, String appid); 333 | 334 | /** 335 | * Determine, if the folder exists in the wallet. 336 | * 337 | * @param handle Valid handle to the wallet to be read from. 338 | * @param folder Name of the folder. 339 | * @param appid AppID of the app to access the wallet. 340 | * @return True if the folder exists, false otherwise. 341 | */ 342 | abstract public boolean hasFolder(int handle, String folder, String appid); 343 | 344 | /** 345 | * Create a folder. 346 | * 347 | * @param handle Valid handle to the wallet to write to. 348 | * @param folder Name of the folder. 349 | * @param appid AppID of the app to access the wallet. 350 | * @return True on success, false on error or in case the folder already exists. 351 | */ 352 | abstract public boolean createFolder(int handle, String folder, String appid); 353 | 354 | /** 355 | * Delete a folder. 356 | * 357 | * @param handle Valid handle to the wallet to write to. 358 | * @param folder Name of the folder. 359 | * @param appid AppID of the app to access the wallet. 360 | * @return True on success, false on error. 361 | */ 362 | abstract public boolean removeFolder(int handle, String folder, String appid); 363 | 364 | /** 365 | * Get a list of all the entries (keys) in the given folder. 366 | * 367 | * @param handle Valid handle to the wallet to read from. 368 | * @param folder Name of the folder. 369 | * @param appid AppID of the app to access the wallet. 370 | * @return List of entries (keys) in the folder. 371 | */ 372 | abstract public List entryList(int handle, String folder, String appid); 373 | 374 | /** 375 | * Read a secret from the wallet. 376 | * 377 | * @param handle Valid handle to the wallet to read from. 378 | * @param folder Folder that contains the secret. 379 | * @param key Identifier for the secret. 380 | * @param appid AppID of the app to access the wallet. 381 | * @return The secret or an array bytes with length 0, in case there is no secret stored for that key. 382 | */ 383 | abstract public byte[] readEntry(int handle, String folder, String key, String appid); 384 | 385 | /** 386 | * Read a secret of type map from the wallet. 387 | * 388 | * @param handle Valid handle to the wallet to read from. 389 | * @param folder Folder that contains the secret. 390 | * @param key Identifier for the secret. 391 | * @param appid AppID of the app to access the wallet. 392 | * @return The secret or an array bytes with length 0, in case there is no secret stored for that key. 393 | */ 394 | abstract public byte[] readMap(int handle, String folder, String key, String appid); 395 | 396 | /** 397 | * Read a secret of type password from the wallet. 398 | * 399 | * @param handle Valid handle to the wallet to read from. 400 | * @param folder Folder that contains the secret. 401 | * @param key Identifier for the secret. 402 | * @param appid AppID of the app to access the wallet. 403 | * @return The secret or an empty String, in case there is no secret stored for that key. 404 | */ 405 | abstract public String readPassword(int handle, String folder, String key, String appid); 406 | 407 | /* 408 | abstract public Map readEntryList(int handle, String folder, String key, String appid); 409 | 410 | abstract public Map readMapList(int handle, String folder, String key, String appid); 411 | 412 | abstract public Map readPasswordList(int handle, String folder, String key, String appid); 413 | */ 414 | 415 | /** 416 | * Get a list of all the secrets in the given folder. 417 | * 418 | * @param handle Valid handle to the wallet to read from. 419 | * @param folder Folder that contains the secret(s). 420 | * @param appid AppID of the app to access the wallet. 421 | * @return Map of secrets of all types in the folder. 422 | */ 423 | abstract public Map entriesList(int handle, String folder, String appid); 424 | 425 | /** 426 | * Get a list of all the secrets of type map in the given folder. 427 | * 428 | * @param handle Valid handle to the wallet to read from. 429 | * @param folder Folder that contains the secret(s). 430 | * @param appid AppID of the app to access the wallet. 431 | * @return Map of maps in the folder. 432 | */ 433 | abstract public Map mapList(int handle, String folder, String appid); 434 | 435 | /** 436 | * Get a list of all the secrets of type password in the given folder. 437 | * 438 | * @param handle Valid handle to the wallet to read from. 439 | * @param folder Folder that contains the secret(s). 440 | * @param appid AppID of the app to access the wallet. 441 | * @return Map of passwords in the folder. 442 | */ 443 | abstract public Map passwordList(int handle, String folder, String appid); 444 | 445 | /** 446 | * Rename an entry that contains a secret within a folder. 447 | * 448 | * @param handle Valid handle to the wallet to write to. 449 | * @param folder Folder that contains the secret. 450 | * @param oldName Old name of the entry to be changed. 451 | * @param newName New name. 452 | * @param appid AppID of the app to access the wallet. 453 | * @return 0 if renaming the entry was successful, -1 otherwise. 454 | */ 455 | abstract public int renameEntry(int handle, String folder, String oldName, String newName, String appid); 456 | 457 | /** 458 | * Store a secret in the wallet. An existing secret gets overwritten. 459 | * 460 | * @param handle Valid handle to the wallet to write to. 461 | * @param folder Folder to store the secret in. 462 | * @param key Identifier for the secret. 463 | * @param value The secret itself. 464 | * @param entryType An enumerated type representing the type of the entry, e.g. 1 for password, 2 for stream, 3 for map 465 | * @param appid AppID of the app to access the wallet. 466 | * @return 0 if storing the secret was successful, -1 otherwise. 467 | */ 468 | abstract public int writeEntry(int handle, String folder, String key, byte[] value, int entryType, String appid); 469 | 470 | /** 471 | * Store a secret of type stream in the wallet. An existing secret gets overwritten. 472 | * 473 | * @param handle Valid handle to the wallet to write to. 474 | * @param folder Folder to store the secret in. 475 | * @param key Identifier for the secret. 476 | * @param value The secret itself. 477 | * @param appid AppID of the app to access the wallet. 478 | * @return 0 if storing the secret was successful, -1 otherwise. 479 | */ 480 | abstract public int writeEntry(int handle, String folder, String key, byte[] value, String appid); 481 | 482 | /** 483 | * Store a secret of type map in the wallet. An existing secret gets overwritten. 484 | * 485 | * @param handle Vaild handle to the wallet to write to. 486 | * @param folder Folder to store the secret in. 487 | * @param key Identifier for the secret. 488 | * @param value The secret itself. 489 | * @param appid AppID of the app to access the wallet. 490 | * @return 0 if storing the secret was successful, -1 otherwise. 491 | */ 492 | abstract public int writeMap(int handle, String folder, String key, byte[] value, String appid); 493 | 494 | /** 495 | * Store a secret of type password in the wallet. An existing secret gets overwritten. 496 | * 497 | * @param handle Valid handle to the wallet to write to. 498 | * @param folder Folder to store the secret in. 499 | * @param key Identifier for the secret. 500 | * @param value The secret itself. 501 | * @param appid AppID of the app to access the wallet. 502 | * @return 0 if storing the secret was successful, -1 otherwise. 503 | */ 504 | abstract public int writePassword(int handle, String folder, String key, String value, String appid); 505 | 506 | /** 507 | * Check whether a folder in a wallet contains an identifier for a secret. 508 | * 509 | * @param handle Valid handle to the wallet to read from. 510 | * @param folder Folder to search. 511 | * @param key Identifier for the secret. 512 | * @param appid AppID of the app to access the wallet. 513 | * @return True if the folder contains the key, false otherwise. 514 | */ 515 | abstract public boolean hasEntry(int handle, String folder, String key, String appid); 516 | 517 | /** 518 | * Determine the type of the entry key in this folder. 519 | * 520 | * @param handle Valid handle to the wallet to read from. 521 | * @param folder Name of the folder. 522 | * @param key Identifier for the secret. 523 | * @param appid AppID of the app to access the wallet. 524 | * @return An enumerated type representing the type of the entry on creation, e.g. 1 for password, 2 for stream, 3 for map, 0 if the key was not found. 525 | */ 526 | abstract public int entryType(int handle, String folder, String key, String appid); 527 | 528 | /** 529 | * Delete an identifier for a secret from the folder. 530 | * 531 | * @param handle Valid handle to the wallet to write to. 532 | * @param folder Folder to delete the key from. 533 | * @param key Identifier for the secret. 534 | * @param appid AppID of the app to access the wallet. 535 | * @return 0 if deleting the key was successful, -1 in case the wallet does not exist, -3 in case the key does not exist. 536 | */ 537 | abstract public int removeEntry(int handle, String folder, String key, String appid); 538 | 539 | /** 540 | * Disconnect the application from wallet. 541 | * 542 | * @param wallet The name of the wallet to disconnect from. 543 | * @param application The name of the application to disconnect. 544 | * @return True on success, false on error. 545 | */ 546 | abstract public boolean disconnectApplication(String wallet, String application); 547 | 548 | /** 549 | * Read kwalletd configuration and configure daemon accordingly. 550 | * 551 | */ 552 | abstract public void reconfigure(); 553 | 554 | /** 555 | * Determine, if the folder does not exist in the wallet. 556 | * 557 | * @param wallet The wallet to look into. 558 | * @param folder The folder to look for. 559 | * @return True, if the folder does not exist, false otherwise. 560 | */ 561 | abstract public boolean folderDoesNotExist(String wallet, String folder); 562 | 563 | /** 564 | * Determine, if the identifier for a secret does not exist in the wallet. 565 | * 566 | * @param wallet The wallet to look into. 567 | * @param folder The folder to look into. 568 | * @param key The identifier to look for. 569 | * @return True, if the identifier does not exist, false otherwise. 570 | */ 571 | abstract public boolean keyDoesNotExist(String wallet, String folder, String key); 572 | 573 | /** 574 | * Close all wallets. 575 | * 576 | */ 577 | abstract public void closeAllWallets(); 578 | 579 | /** 580 | * The name of the wallet used to store network passwords. 581 | * 582 | * @return Name of the wallet. 583 | */ 584 | abstract public String networkWallet(); 585 | 586 | /** 587 | * The name of the wallet used to store local passwords. 588 | * 589 | * @return Name of the wallet. 590 | */ 591 | abstract public String localWallet(); 592 | 593 | /** 594 | * Open a wallet using a pre-hashed password. This is only useful in cooperation 595 | * with the kwallet PAM module. It's also less secure than manually entering the 596 | * password as the password hash is transmitted using D-Bus. 597 | * 598 | * @param wallet The wallet to be opened. 599 | * @param passwordHash The pre-hashed password. 600 | * @param sessionTimeout Timeout after which the wallet gets closed. 601 | */ 602 | @MethodNoReply 603 | abstract public void pamOpen(String wallet, byte[] passwordHash, int sessionTimeout); 604 | } -------------------------------------------------------------------------------- /src/main/java/org/purejava/kwallet/MapEntries.java: -------------------------------------------------------------------------------- 1 | package org.purejava.kwallet; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.*; 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | 12 | /** 13 | * KWallet allows to store secrets as byte fields, that contain maps. 14 | * This is a helper class to manage these maps. 15 | */ 16 | public class MapEntries { 17 | 18 | private final Logger log = LoggerFactory.getLogger(MapEntries.class); 19 | private Map map = new HashMap<>(); 20 | private final byte[] EMPTY_ENTRY = new byte[]{-1, -1, -1, -1}; 21 | private final byte[] EMPTY_VALUE = new byte[]{0, 0, 0, 0}; 22 | 23 | /** 24 | * Add or replace an entry to the map. 25 | * 26 | * @param key The key for the entry in the map. 27 | * @param value The secret to be stored with the key. 28 | */ 29 | public void storeEntry(String key, String value) { 30 | map.put(Objects.toString(key, ""), Objects.toString(value, "")); 31 | } 32 | 33 | /** 34 | * Delete an entry from the map. 35 | * 36 | * @param key The key for the entry in the map. 37 | * @param value The secret to be deleted in conjunction with the key. 38 | */ 39 | public void removeEntry(String key, String value) { 40 | map.remove(key, value); 41 | } 42 | 43 | /** 44 | * Check, whether the map contains an entry with the given key. 45 | * 46 | * @param key The key to search for. 47 | * @return True if the key was found, false otherwise. 48 | */ 49 | public boolean hasKey(String key) { 50 | return map.containsKey(key); 51 | } 52 | 53 | /** 54 | * Check, whether the map contains an entry with the given key and value. 55 | * 56 | * @param key The key to search for. 57 | * @param value The value that is stored with that key. 58 | * @return True if the according entry was found, false otherwise. 59 | */ 60 | public boolean hasValue(String key, String value) { 61 | return hasKey(key) && map.get(key).equals(value); 62 | } 63 | 64 | /** 65 | * Get the value of an entry for a given key. 66 | * 67 | * @param key The key to search for. 68 | * @return The value if set, an empty String otherwise. 69 | */ 70 | public String getValue(String key) { 71 | return hasKey(key) && null != map.get(key) ? map.get(key) : ""; 72 | } 73 | 74 | /** 75 | * Change the value of an entry in the map. 76 | * 77 | * @param key The key on which the value should be changed. 78 | * @param value The new value. 79 | * @return True, if the key exists. The value was changed then. False otherwise. 80 | */ 81 | public boolean changeValue(String key, String value) { 82 | if (hasKey(key)) { 83 | storeEntry(key, value); 84 | return true; 85 | } 86 | return false; 87 | } 88 | 89 | /** 90 | * Count number of entries in the map. 91 | * 92 | * @return Number or 0 if map is empty. 93 | */ 94 | public int count() { 95 | return null != map && !map.isEmpty() ? map.size() : 0; 96 | } 97 | 98 | /** 99 | * Take the intern representation of the map entries and convert it to a KWallet map compatible byte field. 100 | * @see MapEntries#setByteField(byte[]) 101 | * 102 | * @return The byte field or byte[0] in case something went wrong. 103 | */ 104 | public byte[] getByteField() { 105 | if (null == map || map.isEmpty()) return new byte[0]; 106 | try (var b = new ByteArrayOutputStream(); 107 | var s = new DataOutputStream(b)) { 108 | 109 | s.writeInt(map.size()); 110 | 111 | for (var entry : map.entrySet()) { 112 | if (null == entry.getKey() || entry.getKey().isEmpty()) { 113 | s.write(EMPTY_ENTRY); 114 | s.write(EMPTY_ENTRY); 115 | 116 | } else if (null == entry.getValue() || entry.getValue().isEmpty()) { 117 | s.writeInt(entry.getKey().length() * 2); 118 | s.writeChars(entry.getKey()); 119 | s.write(EMPTY_VALUE); 120 | 121 | } else { 122 | s.writeInt(entry.getKey().length() * 2); 123 | s.writeChars(entry.getKey()); 124 | s.writeInt(entry.getValue().length() * 2); 125 | s.writeChars(entry.getValue()); 126 | } 127 | } 128 | return b.toByteArray(); 129 | 130 | } catch (IOException e) { 131 | log.error(e.toString(), e.getCause()); 132 | return new byte[0]; 133 | } 134 | } 135 | 136 | /** 137 | * Take a KWallet map compatible byte field and store it as a HashMap. 138 | * 139 | * @param s The byte field. Format is as follows: 140 | * {0,0,0,2, // always starts with the number of entries (int) - key/value-combinations 141 | * // followed by the entries themselves, which are _either_ 142 | * 0,0,0,2, // the number of bytes for the key 143 | * 0,65, // the key as bytes 144 | * 0,0,0,2, // the number of bytes for the value 145 | * 0,66, // the value as bytes 146 | * / note: empty values are 0,0,0,0 instead of number of bytes for the key and value bytes 147 | * -1,-1,-1,-1,-1,-1,-1,-1} // _or_ an empty entry 148 | * @return True if converting the byte field succeeded without errors, false otherwise. 149 | */ 150 | public boolean setByteField(byte[] s) { 151 | map = new HashMap<>(); 152 | if (null == s || s.length == 0) return true; 153 | 154 | try (var b = new ByteArrayInputStream(s); 155 | var x = new DataInputStream(b)) { 156 | 157 | var mapSize = x.readInt() & 0xFF; 158 | 159 | for (var i = 0; i < mapSize; i++) { 160 | // check if the mext part is a number or an EMPTY_ENTRY 161 | var nextPart = new byte[4]; 162 | for (var k = 0; k < nextPart.length; k++) { 163 | nextPart[k] = x.readByte(); 164 | } 165 | if (Arrays.equals(nextPart, EMPTY_ENTRY)) { 166 | map.put("", ""); 167 | x.skipBytes(4); 168 | continue; 169 | } 170 | // we have a number 171 | var keySize = fourBytesToInt(nextPart) / 2; 172 | var k = new StringBuilder(); 173 | 174 | for (var j = 0; j < keySize; j++) { 175 | k.append(x.readChar()); 176 | } 177 | 178 | // check if the next part is a number or an EMPTY_VALUE 179 | nextPart = new byte[4]; 180 | for (var l = 0; l < nextPart.length; l++) { 181 | nextPart[l] = x.readByte(); 182 | } 183 | if (Arrays.equals(nextPart, EMPTY_VALUE)) { 184 | map.put(k.toString(), ""); 185 | continue; 186 | } 187 | var valueSize = fourBytesToInt(nextPart) / 2; 188 | var v = new StringBuilder(); 189 | for (var j = 0; j < valueSize; j++) { 190 | v.append(x.readChar()); 191 | } 192 | map.put(k.toString(), v.toString()); 193 | } 194 | return true; 195 | 196 | } catch (IOException e) { 197 | log.error(e.toString(), e.getCause()); 198 | return false; 199 | } 200 | } 201 | 202 | private int fourBytesToInt(byte[] b) { 203 | return ((b[0] << 24) + (b[1] << 16) + (b[2] << 8) + (b[3] << 0)) & 0xFF; 204 | } 205 | 206 | @Override 207 | public String toString() { 208 | var sout = new StringBuilder(); 209 | var i = 1; 210 | for (Map.Entry entry : map.entrySet()) { 211 | var key = entry.getKey(); 212 | if (key.isEmpty()) key = "''"; 213 | var value = entry.getValue(); 214 | if (value.isEmpty()) value = "''"; 215 | sout.append("MapEntries (").append(i).append(") {key: ").append(key).append(", value: ").append(value).append("}\n"); 216 | i++; 217 | } 218 | return sout.substring(0, sout.toString().length()-1); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/org/purejava/kwallet/Static.java: -------------------------------------------------------------------------------- 1 | package org.purejava.kwallet; 2 | 3 | import org.freedesktop.dbus.DBusPath; 4 | import org.freedesktop.dbus.ObjectPath; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class Static { 11 | 12 | public static final String DEFAULT_WALLET = "kdewallet"; 13 | 14 | public static class DBus { 15 | public static class Interfaces { 16 | public static final String DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"; 17 | } 18 | } 19 | 20 | public static class Service { 21 | public static final String KWALLETD5 = "org.kde.kwalletd5"; 22 | public static final String KWALLETD6 = "org.kde.kwalletd6"; 23 | } 24 | 25 | public static class ObjectPaths { 26 | public static final String KWALLETD5 = "/modules/kwalletd5"; 27 | public static final String KWALLETD6 = "/modules/kwalletd6"; 28 | } 29 | 30 | public static class Interfaces { 31 | public static final String KWALLET = "org.kde.KWallet"; 32 | } 33 | 34 | public static class Convert { 35 | 36 | public static byte[] toByteArray(List list) { 37 | var result = new byte[list.size()]; 38 | for (var i = 0; i < list.size(); i++) { 39 | result[i] = list.get(i); 40 | } 41 | return result; 42 | } 43 | 44 | public static String toString(byte[] bytes) { 45 | return new String(bytes, StandardCharsets.UTF_8); 46 | } 47 | 48 | public static ObjectPath toObjectPath(String path) { 49 | return new ObjectPath("", path); 50 | } 51 | 52 | public static List toStrings(List paths) { 53 | ArrayList ps = new ArrayList(); 54 | for (ObjectPath p : paths) { 55 | ps.add(p.getPath()); 56 | } 57 | return ps; 58 | } 59 | 60 | public static List toDBusPaths(List paths) { 61 | ArrayList ps = new ArrayList(); 62 | for (ObjectPath p : paths) { 63 | ps.add(p); 64 | } 65 | return ps; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/org/purejava/kwallet/freedesktop/dbus/handlers/MessageHandler.java: -------------------------------------------------------------------------------- 1 | package org.purejava.kwallet.freedesktop.dbus.handlers; 2 | 3 | import org.freedesktop.dbus.connections.impl.DBusConnection; 4 | import org.freedesktop.dbus.exceptions.DBusException; 5 | import org.freedesktop.dbus.types.Variant; 6 | import org.purejava.kwallet.Static; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.Arrays; 11 | 12 | public class MessageHandler { 13 | 14 | private static final Logger LOG = LoggerFactory.getLogger(MessageHandler.class); 15 | 16 | private DBusConnection connection; 17 | 18 | public MessageHandler(DBusConnection connection) { 19 | this.connection = connection; 20 | 21 | if (this.connection != null) { 22 | this.connection.setWeakReferences(true); 23 | Runtime.getRuntime().addShutdownHook(new Thread(() -> 24 | this.connection.disconnect() 25 | )); 26 | } 27 | } 28 | 29 | public Object[] send(String service, String path, String iface, String method, String signature, Object... args) { 30 | try { 31 | var msgFactory = connection.getMessageFactory(); 32 | var message = msgFactory.createMethodCall(service, path, iface, method, (byte) 0, signature, args); 33 | 34 | if (LOG.isTraceEnabled()) LOG.trace(String.valueOf(message)); 35 | connection.sendMessage(message); 36 | 37 | var response = message.getReply(2000L); 38 | if (LOG.isTraceEnabled()) LOG.trace(String.valueOf(response)); 39 | 40 | Object[] parameters = null; 41 | if (response != null) { 42 | parameters = response.getParameters(); 43 | LOG.debug(Arrays.deepToString(parameters)); 44 | } 45 | 46 | if (response instanceof org.freedesktop.dbus.messages.Error) { 47 | var error = response.getName(); 48 | switch (error) { 49 | case "org.freedesktop.DBus.Error.NoReply", 50 | "org.freedesktop.DBus.Error.UnknownMethod", 51 | "org.freedesktop.dbus.exceptions.NotConnected" -> { 52 | LOG.debug(error); 53 | return null; 54 | } 55 | default -> throw new DBusException(error); 56 | } 57 | } 58 | return parameters; 59 | 60 | } catch (DBusException e) { 61 | LOG.error("Unexpected D-Bus response:", e); 62 | } 63 | 64 | return null; 65 | } 66 | 67 | public Variant getProperty(String service, String path, String iface, String property) { 68 | var response = send(service, path, Static.DBus.Interfaces.DBUS_PROPERTIES, 69 | "Get", "ss", iface, property); 70 | return response == null ? null : (Variant) response[0]; 71 | } 72 | 73 | public Variant getAllProperties(String service, String path, String iface) { 74 | var response = send(service, path, Static.DBus.Interfaces.DBUS_PROPERTIES, 75 | "GetAll", "ss", iface); 76 | return response == null ? null : (Variant) response[0]; 77 | } 78 | 79 | public void setProperty(String service, String path, String iface, String property, Variant value) { 80 | send(service, path, Static.DBus.Interfaces.DBUS_PROPERTIES, 81 | "Set", "ssv", iface, property, value); 82 | } 83 | 84 | public DBusConnection getConnection() { 85 | return connection; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/purejava/kwallet/freedesktop/dbus/handlers/Messaging.java: -------------------------------------------------------------------------------- 1 | package org.purejava.kwallet.freedesktop.dbus.handlers; 2 | 3 | import org.freedesktop.dbus.ObjectPath; 4 | import org.freedesktop.dbus.connections.impl.DBusConnection; 5 | import org.freedesktop.dbus.messages.DBusSignal; 6 | import org.freedesktop.dbus.types.Variant; 7 | import org.purejava.kwallet.Static; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.List; 12 | 13 | abstract public class Messaging { 14 | 15 | private static final Logger LOG = LoggerFactory.getLogger(Messaging.class); 16 | private DBusConnection connection; 17 | private MessageHandler msg; 18 | private SignalHandler sh = SignalHandler.getInstance(); 19 | private String serviceName; 20 | private String objectPath; 21 | private String interfaceName; 22 | 23 | public Messaging(DBusConnection connection, List> signals, 24 | String serviceName, String objectPath, String interfaceName) { 25 | this.connection = connection; 26 | this.msg = new MessageHandler(connection); 27 | if (null != signals) { 28 | this.sh.connect(connection, signals); 29 | } 30 | if (null == serviceName || null == objectPath) { 31 | LOG.error("Kwallet daemon not initialized properly"); 32 | } 33 | this.serviceName = serviceName; 34 | this.objectPath = objectPath; 35 | this.interfaceName = interfaceName; 36 | } 37 | 38 | public Object[] send(String method) { 39 | return msg.send(serviceName, objectPath, interfaceName, method, ""); 40 | } 41 | 42 | public Object[] send(String method, String signature, Object... arguments) { 43 | return msg.send(serviceName, objectPath, interfaceName, method, signature, arguments); 44 | } 45 | 46 | protected Variant getProperty(String property) { 47 | return msg.getProperty(serviceName, objectPath, interfaceName, property); 48 | } 49 | 50 | protected Variant getAllProperties() { 51 | return msg.getAllProperties(serviceName, objectPath, interfaceName); 52 | } 53 | 54 | protected void setProperty(String property, Variant value) { 55 | msg.setProperty(serviceName, objectPath, interfaceName, property, value); 56 | } 57 | 58 | public String getServiceName() { 59 | return serviceName; 60 | } 61 | 62 | public String getObjectPath() { 63 | return objectPath; 64 | } 65 | 66 | public ObjectPath getPath() { 67 | return Static.Convert.toObjectPath(objectPath); 68 | } 69 | 70 | public String getInterfaceName() { 71 | return interfaceName; 72 | } 73 | 74 | public MessageHandler getMessageHandler() { 75 | return msg; 76 | } 77 | 78 | public SignalHandler getSignalHandler() { 79 | return sh; 80 | } 81 | 82 | public DBusConnection getConnection() { 83 | return connection; 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/main/java/org/purejava/kwallet/freedesktop/dbus/handlers/SignalHandler.java: -------------------------------------------------------------------------------- 1 | package org.purejava.kwallet.freedesktop.dbus.handlers; 2 | 3 | import org.freedesktop.dbus.connections.impl.DBusConnection; 4 | import org.freedesktop.dbus.exceptions.DBusException; 5 | import org.freedesktop.dbus.interfaces.DBusSigHandler; 6 | import org.freedesktop.dbus.messages.DBusSignal; 7 | import org.purejava.kwallet.KWallet; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.beans.PropertyChangeListener; 12 | import java.beans.PropertyChangeSupport; 13 | import java.time.Duration; 14 | import java.util.*; 15 | import java.util.concurrent.*; 16 | import java.util.stream.Collectors; 17 | 18 | public class SignalHandler implements DBusSigHandler { 19 | 20 | private static SignalHandler instance = new SignalHandler(); 21 | 22 | private static final Logger LOG = LoggerFactory.getLogger(SignalHandler.class); 23 | 24 | private PropertyChangeSupport support; 25 | private DBusConnection connection = null; 26 | private List> registered = new ArrayList(); 27 | private DBusSignal[] handled = new DBusSignal[250]; 28 | private int count = 0; 29 | 30 | private SignalHandler() { 31 | support = new PropertyChangeSupport(this); 32 | } 33 | 34 | public static SignalHandler getInstance() { 35 | return instance; 36 | } 37 | 38 | public void addPropertyChangeListener(PropertyChangeListener pcl) { 39 | support.addPropertyChangeListener(pcl); 40 | } 41 | 42 | public void removePropertyChangeListener(PropertyChangeListener pcl) { 43 | support.removePropertyChangeListener(pcl); 44 | } 45 | 46 | public void connect(DBusConnection connection, List> signals) { 47 | if (null == this.connection) { 48 | this.connection = connection; 49 | } 50 | if (signals != null) { 51 | try { 52 | for (Class sc : signals) { 53 | if (!registered.contains(sc)) { 54 | connection.addSigHandler(sc, this); 55 | registered.add(sc); 56 | } 57 | } 58 | } catch (DBusException e) { 59 | LOG.error(e.toString(), e.getCause()); 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | public void handle(DBusSignal s) { 66 | 67 | Collections.rotate(Arrays.asList(handled), 1); 68 | handled[0] = s; 69 | count += 1; 70 | 71 | if (s instanceof KWallet.walletOpened wo) { 72 | support.firePropertyChange("KWallet.walletOpened", null, wo.wallet); 73 | LOG.info("Received signal KWallet.walletOpened: {}", wo.wallet); 74 | } else if (s instanceof KWallet.walletAsyncOpened wo) { 75 | support.firePropertyChange("KWallet.walletAsyncOpened", null, wo.handle); 76 | LOG.info("Received signal KWallet.walletAsyncOpened: {TransactionID: {}, handle: {}}", wo.tId, wo.handle); 77 | } else if (s instanceof KWallet.walletDeleted wd) { 78 | support.firePropertyChange("KWallet.walletDeleted", null, wd.wallet); 79 | LOG.info("Received signal KWallet.walletDeleted: {}", wd.wallet); 80 | } else if (s instanceof KWallet.walletClosedId wc) { 81 | support.firePropertyChange("KWallet.walletClosedId", null, wc.handle); 82 | LOG.info("Received signal KWallet.walletClosedId: {}", wc.handle); 83 | } else if (s instanceof KWallet.walletClosed wc) { 84 | support.firePropertyChange("KWallet.walletClosed", null, wc.wallet); 85 | LOG.info("Received signal KWallet.walletClosed: {}", wc.wallet); 86 | } else if (s instanceof KWallet.allWalletsClosed) { 87 | support.firePropertyChange("KWallet.allWalletsClosed", null, s.getPath()); 88 | LOG.info("Received signal KWallet.allWalletsClosed: {}", s.getPath()); 89 | } else if (s instanceof KWallet.folderListUpdated flu) { 90 | support.firePropertyChange("KWallet.folderListUpdated", null, flu.wallet); 91 | LOG.info("Received signal KWallet.folderListUpdated: {}", flu.wallet); 92 | } else if (s instanceof KWallet.folderUpdated fu) { 93 | support.firePropertyChange("KWallet.folderUpdated", null, fu.a + "/" + fu.b); 94 | LOG.info("Received signal KWallet.folderUpdated: {wallet: {}, folder: {}}", fu.a, fu.b); 95 | } else if (s instanceof KWallet.applicationDisconnected ad) { 96 | support.firePropertyChange("KWallet.applicationDisconnected", null, ad.application + "/" + ad.wallet); 97 | LOG.info("Received signal KWallet.applicationDisconnected: {application: {}, wallet: {}}", ad.application, ad.wallet); 98 | } else if (s instanceof KWallet.walletListDirty) { 99 | support.firePropertyChange("KWallet.walletListDirty", null, s.getPath()); 100 | LOG.debug("Received signal KWallet.walletListDirty: {}", s.getPath()); 101 | } else if (s instanceof KWallet.walletCreated wc) { 102 | support.firePropertyChange("KWallet.walletCreated", null, wc.wallet); 103 | LOG.info("Received signal KWallet.walletCreated: {}", wc.wallet); 104 | } else { 105 | LOG.warn("Received unknown signal: {} {{}}", s.getClass(), s); 106 | } 107 | } 108 | 109 | public DBusSignal[] getHandledSignals() { 110 | return handled; 111 | } 112 | 113 | public List getHandledSignals(Class s) { 114 | return Arrays.stream(handled) 115 | .filter(Objects::nonNull) 116 | .filter(signal -> signal.getClass().equals(s)) 117 | .map(signal -> (S) signal) 118 | .collect(Collectors.toList()); 119 | } 120 | 121 | public List getHandledSignals(Class s, String path) { 122 | return Arrays.stream(handled) 123 | .filter(Objects::nonNull) 124 | .filter(signal -> signal.getClass().equals(s)) 125 | .filter(signal -> signal.getPath().equals(path)) 126 | .map(signal -> (S) signal) 127 | .collect(Collectors.toList()); 128 | } 129 | 130 | public int getCount() { 131 | return count; 132 | } 133 | 134 | public DBusSignal getLastHandledSignal() { 135 | return handled.length > 0 ? handled[0] : null; 136 | } 137 | 138 | public S getLastHandledSignal(Class s) { 139 | return getHandledSignals(s).isEmpty() ? null : getHandledSignals(s).get(0); 140 | } 141 | 142 | public S getLastHandledSignal(Class s, String path) { 143 | return getHandledSignals(s, path).isEmpty() ? null : getHandledSignals(s, path).get(0); 144 | } 145 | 146 | /** 147 | * @deprecated Please use {@link #addPropertyChangeListener(PropertyChangeListener)} instead 148 | */ 149 | @Deprecated 150 | public S await(Class s, String path, Callable action) { 151 | final Duration timeout = Duration.ofSeconds(120); 152 | return await(s, path, action, timeout); 153 | } 154 | 155 | /** 156 | * @deprecated Please use {@link #addPropertyChangeListener(PropertyChangeListener)} instead 157 | */ 158 | @Deprecated 159 | public S await(Class s, String path, Callable action, Duration timeout) { 160 | final int init = getCount(); 161 | 162 | try { 163 | action.call(); 164 | } catch (Exception e) { 165 | LOG.error(e.toString(), e.getCause()); 166 | } 167 | 168 | var executor = Executors.newSingleThreadExecutor(); 169 | 170 | LOG.info("Await signal {}" + "({}) within {} seconds.", s.getName(), path, timeout.getSeconds()); 171 | 172 | final Future handler = executor.submit((Callable) () -> { 173 | var await = init; 174 | List signals = null; 175 | while (await == init) { 176 | if (Thread.currentThread().isInterrupted()) return null; 177 | Thread.currentThread().sleep(50L); 178 | signals = getHandledSignals(s, path); 179 | await = getCount(); 180 | } 181 | if (!signals.isEmpty()) { 182 | return signals.get(0); 183 | } else { 184 | return null; 185 | } 186 | }); 187 | 188 | try { 189 | return handler.get(timeout.toMillis(), TimeUnit.MILLISECONDS); 190 | } catch (TimeoutException | InterruptedException | ExecutionException e) { 191 | handler.cancel(true); 192 | LOG.warn(e.toString(), e.getCause()); 193 | } finally { 194 | executor.shutdownNow(); 195 | } 196 | 197 | return null; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/test/java/org/purejava/Context.java: -------------------------------------------------------------------------------- 1 | package org.purejava; 2 | 3 | import org.freedesktop.dbus.connections.impl.DBusConnection; 4 | import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; 5 | import org.freedesktop.dbus.exceptions.DBusException; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class Context { 10 | 11 | private static final Logger LOG = LoggerFactory.getLogger(Context.class); 12 | 13 | public DBusConnection connection = null; 14 | 15 | public void ensureService() { 16 | try { 17 | connection = DBusConnectionBuilder.forSessionBus().withShared(false).build(); 18 | } catch (DBusException e) { 19 | LOG.error(e.toString(), e.getCause()); 20 | } 21 | } 22 | 23 | public void after() { 24 | try { 25 | connection.disconnect(); 26 | Thread.sleep(150L); 27 | } catch (InterruptedException e) { 28 | LOG.error(e.toString(), e.getCause()); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/java/org/purejava/KDEWalletTest.java: -------------------------------------------------------------------------------- 1 | package org.purejava; 2 | 3 | import org.freedesktop.dbus.exceptions.DBusException; 4 | import org.freedesktop.dbus.interfaces.DBus; 5 | import org.junit.jupiter.api.*; 6 | import org.purejava.kwallet.Static; 7 | import org.purejava.kwallet.KDEWallet; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.beans.PropertyChangeEvent; 12 | import java.beans.PropertyChangeListener; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertTrue; 18 | 19 | public class KDEWalletTest implements PropertyChangeListener { 20 | private static final Logger LOG = LoggerFactory.getLogger(KDEWalletTest.class); 21 | private Context context; 22 | private int handle = -1; 23 | 24 | @BeforeEach 25 | public void beforeEach(TestInfo info) { 26 | context = new Context(); 27 | context.ensureService(); 28 | } 29 | 30 | @AfterEach 31 | public void afterEach() { 32 | context.after(); 33 | } 34 | 35 | @Test 36 | @Order(1) 37 | @DisplayName("Checking availability of kwallet daemon...") 38 | public void isEnabled() { 39 | try { 40 | var bus = context.connection.getRemoteObject("org.freedesktop.DBus", 41 | "/org/freedesktop/DBus", DBus.class); 42 | assertTrue (Arrays.asList(bus.ListActivatableNames()).contains("org.kde.kwalletd5") 43 | || Arrays.asList(bus.ListActivatableNames()).contains("org.kde.kwalletd6")); 44 | } catch (DBusException e) { 45 | LOG.error(e.toString(), e.getCause()); 46 | } 47 | } 48 | 49 | @Test 50 | @Order(2) 51 | @DisplayName("Checking availability of wallets...") 52 | public void wallets() { 53 | var kwallet = new KDEWallet(context.connection); 54 | var response = kwallet.send("wallets"); 55 | var walletNames = (List) response[0]; 56 | assertTrue(walletNames.size() > 0); 57 | assertEquals(walletNames.get(0), Static.DEFAULT_WALLET); 58 | LOG.info("Found '{}' as first wallet.", walletNames.get(0)); 59 | } 60 | 61 | @Test 62 | @Order(3) 63 | @DisplayName("Testing create folder functionality in locked kdewallet...") 64 | void testCreateFolder() throws InterruptedException { 65 | var kwallet = new KDEWallet(context.connection); 66 | var wallet = Static.DEFAULT_WALLET; 67 | var wId = 0; 68 | var appid = "Tester"; 69 | kwallet.getSignalHandler().addPropertyChangeListener(this); 70 | kwallet.openAsync(wallet, wId, appid, false); 71 | LOG.info("You have 10 seconds to enter the password :)"); 72 | Thread.sleep(10000L); // give me 10 seconds to enter the password 73 | assertTrue(handle > 0); 74 | if (handle > 0) LOG.info("Wallet '{}' successfully opened.", Static.DEFAULT_WALLET); 75 | if (handle > 0) LOG.info("Received handle: {}.", handle); 76 | var folder = "Test-Folder"; 77 | var created = kwallet.createFolder(handle, folder, appid); 78 | assertTrue(created); 79 | LOG.info("Folder '{}' successfully created.", folder); 80 | var removed = kwallet.removeFolder(handle, folder, appid); 81 | assertTrue(removed); 82 | LOG.info("Folder '{}' successfully deleted.", folder); 83 | } 84 | 85 | @Override 86 | public void propertyChange(PropertyChangeEvent event) { 87 | if (event.getPropertyName().equals("KWallet.walletAsyncOpened")) handle = (int) event.getNewValue(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/org/purejava/KDEWalletTest1.java: -------------------------------------------------------------------------------- 1 | package org.purejava; 2 | 3 | import org.junit.jupiter.api.*; 4 | import org.purejava.kwallet.Static; 5 | import org.purejava.kwallet.KDEWallet; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | public class KDEWalletTest1 { 13 | private static final Logger LOG = LoggerFactory.getLogger(KDEWalletTest1.class); 14 | private Context context; 15 | 16 | @BeforeEach 17 | public void beforeEach(TestInfo info) { 18 | context = new Context(); 19 | context.ensureService(); 20 | } 21 | 22 | @AfterEach 23 | public void afterEach() { 24 | context.after(); 25 | } 26 | 27 | @Test 28 | @Order(4) 29 | @DisplayName("Testing password functionality in unlocked kdewallet...") 30 | void testPassword() { 31 | var kwallet = new KDEWallet(context.connection); 32 | var wallet = Static.DEFAULT_WALLET; 33 | var wId = 0; 34 | var appid = "Tester"; 35 | var response = kwallet.send("open", "sxs", wallet, wId, appid); 36 | assertTrue(kwallet.isOpen(wallet)); 37 | var handle = (int) response[0]; 38 | assertTrue(handle > 0); 39 | LOG.info("Wallet '{}' successfully opened.", Static.DEFAULT_WALLET); 40 | LOG.info("Received handle: {}.", handle); 41 | var folder = "Test-Folder"; 42 | response = kwallet.send("createFolder", "iss", handle, folder, appid); 43 | var folderCreated = (boolean) response[0]; 44 | assertTrue(folderCreated); 45 | LOG.info("Folder '{}' successfully created.", folder); 46 | var key = "PW1"; 47 | var value = "secret1"; 48 | response = kwallet.send("writePassword", "issss", handle, folder, key, value, appid); 49 | var secretStored = (int) response[0]; 50 | assertEquals(secretStored, 0); 51 | if (secretStored == 0) LOG.info("Secret '{}' successfully stored.", value); 52 | response = kwallet.send("readPassword", "isss", handle, folder, key, appid); 53 | var secretRead = (String) response[0]; 54 | assertEquals(secretRead, value); 55 | if (secretRead.equals(value)) LOG.info("Secret '{}' successfully retrieved.", value); 56 | response = kwallet.send("hasEntry", "isss", handle, folder, key, appid); 57 | var keyFound = (boolean) response[0]; 58 | assertTrue(keyFound); 59 | LOG.info("Folder '{}' has key '{}'.", folder, key); 60 | response = kwallet.send("removeEntry", "isss", handle, folder, key, appid); 61 | var keyRemoved = (int) response[0]; 62 | assertEquals(keyRemoved, 0); 63 | if (keyRemoved == 0) LOG.info("Key '{}' successfully removed from folder '{}'.", key, folder); 64 | response = kwallet.send("removeFolder", "iss", handle, folder, appid); 65 | var folderRemoved = (boolean) response[0]; 66 | assertTrue(folderRemoved); 67 | LOG.info("Folder '{}' successfully deleted.", folder); 68 | response = kwallet.send("close", "ibs", handle, false, appid); 69 | var walletClosedId = (int) response[0]; 70 | assertTrue(walletClosedId != -1); 71 | LOG.info("Wallet '{}' with handle '{}' successfully closed.", Static.DEFAULT_WALLET, handle); 72 | } 73 | } -------------------------------------------------------------------------------- /src/test/java/org/purejava/KDEWalletTest2.java: -------------------------------------------------------------------------------- 1 | package org.purejava; 2 | 3 | import org.junit.jupiter.api.*; 4 | import org.purejava.kwallet.Static; 5 | import org.purejava.kwallet.KDEWallet; 6 | import org.purejava.kwallet.MapEntries; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.IntStream; 13 | import java.util.stream.Stream; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | 17 | public class KDEWalletTest2 { 18 | private static final Logger LOG = LoggerFactory.getLogger(KDEWalletTest2.class); 19 | private Context context; 20 | 21 | @BeforeEach 22 | public void beforeEach(TestInfo info) { 23 | context = new Context(); 24 | context.ensureService(); 25 | } 26 | 27 | @AfterEach 28 | public void afterEach() { 29 | context.after(); 30 | } 31 | 32 | @Test 33 | @Order(5) 34 | @DisplayName("Testing write entry functionality in unlocked kdewallet...") 35 | void testWriteEntry() { 36 | var kwallet = new KDEWallet(context.connection); 37 | var wallet = Static.DEFAULT_WALLET; 38 | var wId = 0; 39 | var appid = "Tester"; 40 | var response = kwallet.send("open", "sxs", wallet, wId, appid); 41 | var handle = (int) response[0]; 42 | assertTrue(handle > 0); 43 | LOG.info("Wallet '{}' successfully opened.", Static.DEFAULT_WALLET); 44 | LOG.info("Received handle: {}.", handle); 45 | var folder = "Test-Folder"; 46 | response = kwallet.send("createFolder", "iss", handle, folder, appid); 47 | var folderCreated = (boolean) response[0]; 48 | assertTrue(folderCreated); 49 | LOG.info("Folder '{}' successfully created.", folder); 50 | var key1 = "PW1"; 51 | var password = "password".getBytes(); 52 | response = kwallet.send("writeEntry", "issays", handle, folder, key1, password, appid); 53 | var result = (int) response[0]; 54 | assertEquals(result, 0); 55 | if (result == 0) LOG.info("Secret '{}' with with key '{}' successfully stored in folder '{}'.", new String(password), key1, folder); 56 | var key2 = "Map"; 57 | var map = new byte[]{0,0,0,2,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,2,0,65,0,0,0,2,0,66}; 58 | var me = new MapEntries(); 59 | me.setByteField(map); 60 | response = kwallet.send("writeEntry", "issayis", handle, folder, key2, map, 3, appid); 61 | result = (int) response[0]; 62 | assertEquals(result, 0); 63 | if (result == 0) LOG.info("Map with with key '{}' successfully stored in folder '{}'.", key2, folder); 64 | response = kwallet.send("entryList", "iss", handle, folder, appid); 65 | var listOfEntries = (List) response[0]; 66 | assertEquals(listOfEntries.size(), 2); 67 | assertEquals(listOfEntries.get(0), "Map"); 68 | assertEquals(listOfEntries.get(1), "PW1"); 69 | LOG.info("Found this list of entries: {}.", listOfEntries); 70 | response = kwallet.send("entryType", "isss", handle, folder, key2, appid); 71 | var entryType = (int) response[0]; 72 | assertEquals(entryType, 3); 73 | LOG.info("Type of entry for map is: {}.", entryType); 74 | response = kwallet.send("readEntry", "isss", handle, folder, key1, appid); 75 | var objectList = (List) response[0]; 76 | var entry = new byte[objectList.size()]; 77 | IntStream.range(0, objectList.size()).forEach(i -> entry[i] = (Byte) objectList.get(i)); 78 | assertEquals(new String(entry), "password"); 79 | LOG.info("Raw data from secret successfully retrieved: {}.", new String(entry)); 80 | LOG.info(me.toString()); 81 | assertEquals(me.count(), 2); 82 | assertTrue(me.hasKey("A")); 83 | assertTrue(me.hasValue("A","B")); 84 | me.changeValue("A","C"); 85 | assertTrue(me.hasValue("A","C")); 86 | LOG.info("Value successfully changed for key 'A' from 'B' to '{}'.", me.getValue("A")); 87 | me.storeEntry("D",null); 88 | assertTrue(me.hasKey("D")); 89 | assertTrue(me.hasValue("D","")); 90 | LOG.info("Entry with key 'D' and value '' successfully stored."); 91 | LOG.info(me.toString()); 92 | assertEquals(me.count(),3); 93 | me.removeEntry("A","C"); 94 | me.removeEntry("D",""); 95 | assertFalse(me.hasKey("D")); 96 | assertFalse(me.hasValue("D", "")); 97 | assertEquals(me.count(), 1); 98 | LOG.info("Entries with keys 'A' and 'D' successfully removed."); 99 | response = kwallet.send("writeEntry", "issayis", handle, folder, key2, me.getByteField(), 3, appid); 100 | result = (int) response[0]; 101 | assertEquals(result, 0); 102 | if (result == 0) LOG.info("Map with with key '{}' successfully updated in folder '{}'.", key2, folder); 103 | var longString = Stream.iterate("A", s -> s).limit(64).collect(Collectors.joining()); 104 | me.storeEntry("E", longString); 105 | response = kwallet.send("writeEntry", "issayis", handle, folder, key2, me.getByteField(), 3, appid); 106 | result = (int) response[0]; 107 | assertEquals(result, 0); 108 | response = kwallet.send("readMap", "isss", handle, folder, key2, appid); 109 | var objectListFromResponse = (List) response[0]; 110 | var rm = new byte[objectListFromResponse.size()]; 111 | IntStream.range(0, objectListFromResponse.size()).forEach(i -> rm[i] = (Byte) objectListFromResponse.get(i)); 112 | me.setByteField(rm); 113 | assertTrue(me.hasValue("E", longString)); 114 | LOG.info("Successfully stored and retrieved value '{}' with a length gt 63.", me.getValue("E")); 115 | var newName = "newName"; 116 | response = kwallet.send("renameEntry", "issss", handle, folder, key1, newName, appid); 117 | var renaming = (int) response[0]; 118 | assertTrue(renaming != -1); 119 | LOG.info("Entry successfully renamed to: '{}'.", newName); 120 | response = kwallet.send("removeFolder", "iss", handle, folder, appid); 121 | var folderRemoved = (boolean) response[0]; 122 | assertTrue(folderRemoved); 123 | LOG.info("Folder '{}' successfully deleted.", folder); 124 | response = kwallet.send("close", "ibs", handle, false, appid); 125 | var walletClosedId = (int) response[0]; 126 | assertTrue(walletClosedId != -1); 127 | LOG.info("Wallet '{}' with handle '{}' successfully closed.", Static.DEFAULT_WALLET, handle); 128 | } 129 | } -------------------------------------------------------------------------------- /src/test/java/org/purejava/KDEWalletTest3.java: -------------------------------------------------------------------------------- 1 | package org.purejava; 2 | 3 | import org.junit.jupiter.api.*; 4 | import org.purejava.kwallet.Static; 5 | import org.purejava.kwallet.KDEWallet; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | public class KDEWalletTest3 { 13 | private static final Logger LOG = LoggerFactory.getLogger(KDEWalletTest3.class); 14 | private Context context; 15 | 16 | @BeforeEach 17 | public void beforeEach(TestInfo info) { 18 | context = new Context(); 19 | context.ensureService(); 20 | } 21 | 22 | @AfterEach 23 | public void afterEach() { 24 | context.after(); 25 | } 26 | 27 | @Test 28 | @Order(6) 29 | @DisplayName("Testing entries list functionalities in unlocked kdewallet...") 30 | void testWriteEntry() { 31 | var kwallet = new KDEWallet(context.connection); 32 | var wallet = Static.DEFAULT_WALLET; 33 | var wId = 0; 34 | var appid = "Tester"; 35 | var handle = kwallet.open(wallet, wId, appid); 36 | assertTrue(handle > 0); 37 | LOG.info("Wallet '{}' successfully opened.", Static.DEFAULT_WALLET); 38 | LOG.info("Received handle: {}.", handle); 39 | var folder = "Test-Folder"; 40 | var folderCreated = kwallet.createFolder(handle, folder, appid); 41 | assertTrue(folderCreated); 42 | LOG.info("Folder '{}' successfully created.", folder); 43 | var key1 = "PW1"; 44 | var password = "password"; 45 | var result = kwallet.writePassword(handle, folder, key1, password, appid); 46 | assertEquals(result, 0); 47 | if (result == 0) LOG.info("Secret '{}' with with key '{}' successfully stored in folder '{}'.", password, key1, folder); 48 | var key2 = "PW2"; 49 | var password2 = "password2"; 50 | result = kwallet.writePassword(handle, folder, key2, password2, appid); 51 | assertEquals(result, 0); 52 | if (result == 0) LOG.info("Secret '{}' with with key '{}' successfully stored in folder '{}'.", password2, key2, folder); 53 | var key3 = "Map1"; 54 | var map = new byte[]{0,0,0,2,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,2,0,65,0,0,0,2,0,66}; 55 | result = kwallet.writeMap(handle, folder, key3, map, appid); 56 | assertEquals(result, 0); 57 | if (result == 0) LOG.info("Secret as map with key '{}' successfully stored in folder '{}'.", key3, folder); 58 | var key4 = "Stream1"; 59 | var stream = "password3".getBytes(); 60 | result = kwallet.writeEntry(handle, folder, key4, stream, appid); 61 | assertEquals(result, 0); 62 | if (result == 0) LOG.info("Secret '{}' with with key '{}' successfully stored in folder '{}'.", new String(stream), key4, folder); 63 | var el = kwallet.entriesList(handle, folder, appid); 64 | assertEquals(el.size(),4); 65 | var ml = kwallet.mapList(handle, folder, appid); 66 | assertEquals(ml.size(),1); 67 | var pl = kwallet.passwordList(handle, folder, appid); 68 | assertEquals(pl.size(),2); 69 | var p2 = pl.get("PW2"); 70 | assertEquals(p2,"password2"); 71 | LOG.info("Password list {} contains secret '{}' with key '{}'.", pl, password2, key2); 72 | result = kwallet.removeEntry(handle, folder, key2, appid); 73 | assertEquals(result, 0); 74 | result = kwallet.removeEntry(handle, folder, key1, appid); 75 | assertEquals(result, 0); 76 | pl = kwallet.passwordList(handle, folder, appid); 77 | assertEquals(pl.size(), 0); 78 | var folderRemoved = kwallet.removeFolder(handle, folder, appid); 79 | assertTrue(folderRemoved); 80 | LOG.info("Folder '{}' successfully deleted.", folder); 81 | var walletClosedId = kwallet.close(handle, false, appid); 82 | assertTrue(walletClosedId != -1); 83 | LOG.info("Wallet '{}' with handle '{}' successfully closed.", Static.DEFAULT_WALLET, handle); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/org/purejava/KDEWalletTest4.java: -------------------------------------------------------------------------------- 1 | package org.purejava; 2 | 3 | import org.junit.jupiter.api.*; 4 | import org.purejava.kwallet.Static; 5 | import org.purejava.kwallet.KDEWallet; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.beans.PropertyChangeEvent; 10 | import java.beans.PropertyChangeListener; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class KDEWalletTest4 implements PropertyChangeListener { 15 | private static final Logger LOG = LoggerFactory.getLogger(KDEWalletTest4.class); 16 | private Context context; 17 | private int handle = -1; 18 | 19 | @BeforeEach 20 | public void beforeEach(TestInfo info) { 21 | context = new Context(); 22 | context.ensureService(); 23 | } 24 | 25 | @AfterEach 26 | public void afterEach() { 27 | context.after(); 28 | } 29 | 30 | @Test 31 | @Order(7) 32 | @DisplayName("Checking availability of signals for locked wallet ...") 33 | public void testSignals() throws InterruptedException { 34 | var kwallet = new KDEWallet(context.connection); 35 | var wallet = Static.DEFAULT_WALLET; 36 | var wId = 0; 37 | var appid = "Tester"; 38 | kwallet.getSignalHandler().addPropertyChangeListener(this); 39 | assertFalse(kwallet.isOpen(wallet)); 40 | kwallet.openAsync(wallet, wId, appid, false); 41 | LOG.info("You have 10 seconds to enter the password :)"); 42 | Thread.sleep(10000L); 43 | assertTrue(handle > 0); 44 | LOG.info("Wallet '{}' successfully opened.", Static.DEFAULT_WALLET); 45 | var walletClosedId = kwallet.close(handle, true, appid); 46 | assertTrue(walletClosedId != -1); 47 | LOG.info("Wallet '{}' with handle '{}' successfully closed.", Static.DEFAULT_WALLET, handle); 48 | } 49 | 50 | @Override 51 | public void propertyChange(PropertyChangeEvent event) { 52 | switch (event.getPropertyName()) { 53 | case "KWallet.walletAsyncOpened": 54 | handle = (int) event.getNewValue(); 55 | break; 56 | case "KWallet.walletClosedId": 57 | var closeHandle = (int) event.getNewValue(); 58 | assertEquals(closeHandle, handle); 59 | LOG.info("Received signal 'KWallet.walletClosedId' with handle '{}'.", closeHandle); 60 | break; 61 | case "KWallet.walletClosed": 62 | try { 63 | var wallet = (String) event.getNewValue(); 64 | assertEquals(wallet, Static.DEFAULT_WALLET); 65 | LOG.info("Received signal 'KWallet.walletClosed' for wallet '{}'.", wallet); 66 | } catch (ClassCastException ex) { 67 | // ToDo handling overloaded signal here. Remove, when walletClosed(String path, int handle) got removed from kwallet. 68 | } 69 | break; 70 | default: 71 | break; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/test/resources/org.freedesktop.DBus.Introspectable.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/org.freedesktop.DBus.Peer.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/org.freedesktop.DBus.Properties.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/resources/org.kde.KWallet.nodeprecated.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /src/test/resources/org.kde.KWallet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.defaultLogLevel=info --------------------------------------------------------------------------------