├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── merge-dependabot.yml │ └── pull-request-ci.yaml ├── .gitignore ├── LICENSE.md ├── README.md ├── checkstyle.xml ├── local-connect └── docker-compose.yaml ├── pom.xml └── src ├── main └── java │ └── com │ └── sanctionco │ └── opconnect │ ├── OPConnectClient.java │ ├── OPConnectClientBuilder.java │ ├── OPConnectVaultClient.java │ ├── RetrofitOPConnectClient.java │ └── model │ ├── Category.java │ ├── CharacterSet.java │ ├── Field.java │ ├── File.java │ ├── Filter.java │ ├── GeneratorRecipe.java │ ├── Id.java │ ├── Item.java │ ├── PasswordDetails.java │ ├── Patch.java │ ├── PatchOperation.java │ ├── Purpose.java │ ├── Section.java │ ├── Type.java │ ├── URL.java │ ├── Vault.java │ ├── apiactivity │ ├── APIRequest.java │ ├── APIRequestAction.java │ ├── APIRequestResult.java │ ├── Actor.java │ ├── Resource.java │ └── ResourceType.java │ └── health │ ├── ConnectServer.java │ └── Dependency.java └── test ├── java └── com │ └── sanctionco │ └── opconnect │ ├── IntegrationTest.java │ ├── OPConnectClientBuilderTest.java │ ├── OPConnectVaultClientTest.java │ └── model │ ├── CharacterSetTest.java │ ├── FieldTest.java │ ├── FilterTest.java │ ├── GeneratorRecipeTest.java │ ├── URLTest.java │ └── apiactivity │ └── ActorTest.java └── resources └── fixtures └── apirequest └── actor.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "09:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: "org.mockito:mockito-core" 11 | update-types: [ "version-update:semver-major" ] 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: daily 16 | time: "09:00" 17 | open-pull-requests-limit: 10 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | concurrency: integration_test 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | tags: '*' 8 | schedule: 9 | - cron: '0 10 * * *' # Run every day at 10:00 UTC 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up JDK 18 | uses: actions/setup-java@v4 19 | with: 20 | distribution: 'adopt' 21 | java-version: 11 22 | cache: 'maven' 23 | 24 | - name: Set up 1Password Credentials 25 | run: echo $OP_CREDENTIALS > local-connect/1password-credentials.json 26 | env: 27 | OP_CREDENTIALS: ${{ secrets.OP_CREDENTIALS }} 28 | 29 | # First we test with the latest OPConnect Docker images 30 | - name: Set up env file (latest) 31 | run: echo OP_IMAGE_VER=$OP_IMAGE_VER > local-connect/.env 32 | env: 33 | OP_IMAGE_VER: 'latest' 34 | 35 | - name: Start OPConnect Server 36 | run: sudo docker compose -f local-connect/docker-compose.yaml up -d 37 | 38 | - name: Build with Maven 39 | run: mvn package jacoco:report 40 | env: 41 | OP_ACCESS_TOKEN: ${{ secrets.OP_ACCESS_TOKEN }} 42 | 43 | - name: Upload Codecov report 44 | uses: codecov/codecov-action@v5 45 | with: 46 | directory: . 47 | 48 | - name: Get OPConnect Server logs 49 | run: sudo docker compose -f local-connect/docker-compose.yaml logs 50 | 51 | - name: Stop OPConnect Server 52 | run: sudo docker compose -f local-connect/docker-compose.yaml down 53 | 54 | # Then test with older versions 55 | # TODO: use reusable workflows https://docs.github.com/en/actions/using-workflows/reusing-workflows 56 | -------------------------------------------------------------------------------- /.github/workflows/merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Automerge Dependabot 2 | 3 | on: 4 | pull_request_target: 5 | branches: [ master ] 6 | 7 | jobs: 8 | merge: 9 | if: github.actor == 'dependabot[bot]' 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Enable Automerge 14 | run: gh pr merge --auto --squash "$PR_URL" 15 | env: 16 | PR_URL: ${{ github.event.pull_request.html_url }} 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | - name: Approve 20 | uses: hmarr/auto-approve-action@v4.0.0 21 | with: 22 | github-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-ci.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request CI 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up JDK 14 | uses: actions/setup-java@v4 15 | with: 16 | distribution: 'adopt' 17 | java-version: 11 18 | cache: 'maven' 19 | 20 | - name: Build with Maven 21 | run: mvn package jacoco:report 22 | 23 | - name: Upload Codecov report 24 | uses: codecov/codecov-action@v5 25 | with: 26 | directory: . 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ Files 2 | .idea/ 3 | *.ipr 4 | *.iws 5 | *.iml 6 | 7 | # Eclipse Files 8 | .classpath 9 | .project 10 | .settings/ 11 | 12 | # Build outputs 13 | target/ 14 | _build/ 15 | 16 | # Vagrant outputs 17 | .vagrant 18 | 19 | # OS hidden files 20 | .DS_Store 21 | 22 | # Temporary Maven files 23 | dependency-reduced-pom.xml 24 | release.properties 25 | pom.xml.releaseBackup 26 | 27 | # Log files 28 | *.log 29 | 30 | # Node.js dependency directories 31 | node_modules/ 32 | out/ 33 | 34 | # Credentials 35 | *credentials.json 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Rohan Nagar 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 | # 1Password Connect Java SDK 2 | 3 | 4 | Maven Central 5 | 6 | 7 | Javadoc 8 | 9 | 10 | An unofficial SDK for interacting with the 11 | [1Password Connect API](https://support.1password.com/connect-api-reference). 12 | Tested with the latest version of the 1Password Connect Server (v1.6). 13 | 14 | **NOTE: Until version 1.0.0, any new release may contain breaking changes!** 15 | 16 | Still a work in progress, feel free to submit pull requests or issues if you encounter 17 | bugs or need additional enhancements. 18 | 19 | ## Installation 20 | 21 | Add this library as a dependency in your `pom.xml`: 22 | 23 | ```xml 24 | 25 | com.sanctionco.opconnect 26 | opconnect-java 27 | 0.5.3 28 | 29 | ``` 30 | 31 | ## Setup 32 | 33 | Before you can use the 1Password Connect API, make sure that you follow 34 | step 1 and 2 of the 35 | [1Password Secrets Automation Setup Guide](https://support.1password.com/secrets-automation/) 36 | in order to set up a local 1Password Connect server with Docker or Kubernetes. 37 | 38 | ## Usage 39 | 40 | Create a client: 41 | 42 | ```java 43 | OPConnectClient client = OPConnectClient.builder() 44 | .withEndpoint("http://localhost:8080") 45 | .withAccessToken("OP_ACCESS_TOKEN") 46 | .build(); 47 | ``` 48 | 49 | Below are examples of how to call each of the API methods, 50 | in both blocking and non-blocking form. 51 | 52 | ### List vaults 53 | 54 | ```java 55 | List vaults = client.listVaults().join(); 56 | ``` 57 | 58 | ```java 59 | client.listVaults().whenComplete((vaults, throwable) -> { 60 | // vaults variable contains the list of vaults 61 | }); 62 | ``` 63 | 64 | #### With a Filter 65 | 66 | ```java 67 | List vaults = client 68 | .listVaults(Filter.name().contains("My Vault")) 69 | .join(); 70 | ``` 71 | 72 | ```java 73 | client.listVaults(Filter.name().contains("My Vault")) 74 | .whenComplete((vaults, throwable) -> { 75 | // vaults variable contains the list of vaults 76 | }); 77 | ``` 78 | 79 | ### Get vault details 80 | 81 | ```java 82 | Vault vault = client.getVault("VAULTID").join(); 83 | ``` 84 | 85 | ```java 86 | client.getVault("VAULTID").whenComplete((vault, throwable) -> { 87 | // vault variable contains the vault 88 | }); 89 | ``` 90 | 91 | ### List items 92 | 93 | ```java 94 | List items = client.listItems("VAULTID").join(); 95 | ``` 96 | 97 | ```java 98 | client.listItems("VAULTID").whenComplete((items, throwable) -> { 99 | // items variable contains the list of items 100 | }); 101 | ``` 102 | 103 | #### With a Filter 104 | 105 | ```java 106 | Filter filter = Filter.title().contains("Login"); 107 | 108 | List loginItems = client.listItems("VAULTID", filter).join(); 109 | ``` 110 | 111 | ```java 112 | Filter filter = Filter.title() 113 | .contains("Test") 114 | .or(Filter.title().equals("Test2")) 115 | 116 | client.listItems("VAULTID", filter).whenComplete((testItems, throwable) -> { 117 | // testItems variable contains the list of items 118 | }); 119 | ``` 120 | 121 | ### Add an item 122 | 123 | ```java 124 | Item itemToCreate = Item.builder() 125 | .withTitle("My Login Item") 126 | .withCategory(Category.LOGIN) 127 | .withVaultId("VAULTID") 128 | .withField(Field.username("myemail@test.com").build()) 129 | .withField(Field.generatedPassword( 130 | GeneratorRecipe.letters().ofLength(30)).build()) 131 | .withUrl(URL.primary("https://www.test.com")) 132 | .build(); 133 | 134 | Item createdItem = client.createItem("VAULTID", itemToCreate).join(); 135 | ``` 136 | 137 | ```java 138 | Item itemToCreate = Item.builder() 139 | .withTitle("My Login Item") 140 | .withCategory(Category.LOGIN) 141 | .withVaultId("VAULTID") 142 | .withField(Field.username("myemail@test.com").build()) 143 | .withField(Field.generatedPassword( 144 | GeneratorRecipe.letters().ofLength(30)).build()) 145 | .withUrl(URL.primary("https://www.test.com")) 146 | .build(); 147 | 148 | client.createItem("VAULTID", itemToCreate).whenComplete((item, throwable) -> { 149 | // item variable contains the created item 150 | }); 151 | ``` 152 | 153 | ### Get item details 154 | 155 | ```java 156 | Item item = client.getItem("VAULTID", "ITEMID").join(); 157 | ``` 158 | 159 | ```java 160 | client.getItem("VAULTID", "ITEMID").whenComplete((item, throwable) -> { 161 | // item variable contains the item 162 | }); 163 | ``` 164 | 165 | ### Replace an item 166 | 167 | ```java 168 | Item existing = client.getItem("VAULTID", "ITEMID").join(); 169 | 170 | Item replacement = Item.builder() 171 | .fromItem(existing) 172 | .withTitle("New title") 173 | .build(); 174 | 175 | Item replaced = client.replaceItem("VAULTID", "ITEMID", replacement).join(); 176 | ``` 177 | 178 | ```java 179 | client.getItem("VAULTID", "ITEMID").thenCompose(item -> { 180 | Item replacement = Item.builder() 181 | .fromItem(item) 182 | .withTitle("New title") 183 | .build(); 184 | 185 | return client.replaceItem("VAULTID", "ITEMID", replacement); 186 | }).whenComplete((item, throwable) -> { 187 | // item variable contains the replaced item 188 | }); 189 | ``` 190 | 191 | ### Move an item to the trash 192 | 193 | ```java 194 | client.deleteItem("VAULTID", "ITEMID").join(); 195 | ``` 196 | 197 | ```java 198 | client.deleteItem("VAULTID", "ITEMID").whenComplete((unused, throwable) -> { 199 | // delete does not return the item 200 | }); 201 | ``` 202 | 203 | ### Change item details 204 | 205 | ```java 206 | Item patched = client 207 | .patchItem("VAULTID", "ITEMID", Patch.remove().withPath("/title").build()) 208 | .join(); 209 | ``` 210 | 211 | ```java 212 | client.patchItem("VAULTID", "ITEMID", Patch.remove().withPath("/title").build()) 213 | .whenComplete((item, throwable) -> { 214 | // item variable contains the patched item 215 | }); 216 | ``` 217 | 218 | ### List Files 219 | 220 | ```java 221 | List files = client 222 | .listFiles("VAULTID", "ITEMID") 223 | .join(); 224 | ``` 225 | 226 | ```java 227 | client.listFiles("VAULTID", "ITEMID") 228 | .whenComplete((files, throwable) -> { 229 | // files variable contains the list of files 230 | }); 231 | ``` 232 | 233 | #### With Inline Content 234 | 235 | ```java 236 | List files = client 237 | .listFiles("VAULTID", "ITEMID", true) 238 | .join(); 239 | ``` 240 | 241 | ```java 242 | client.listFiles("VAULTID", "ITEMID", true) 243 | .whenComplete((files, throwable) -> { 244 | // files variable contains the list of files 245 | }); 246 | ``` 247 | 248 | ### Get a File 249 | 250 | ```java 251 | File file = client.getFile("VAULTID", "ITEMID", "FILEID").join(); 252 | ``` 253 | 254 | ```java 255 | client.getFile("VAULTID", "ITEMID", "FILEID") 256 | .whenComplete((file, throwable) -> { 257 | // file variable contains the file details 258 | }); 259 | ``` 260 | 261 | #### With Inline Content 262 | 263 | ```java 264 | File file = client.getFile("VAULTID", "ITEMID", "FILEID", true).join(); 265 | ``` 266 | 267 | ```java 268 | client.getFile("VAULTID", "ITEMID", "FILEID", true) 269 | .whenComplete((file, throwable) -> { 270 | // file variable contains the file details 271 | }); 272 | ``` 273 | 274 | ### Get the Content of a File 275 | 276 | ```java 277 | String content = client 278 | .getFileContent("VAULTID", "ITEMID", "FILEID") 279 | .join(); 280 | ``` 281 | 282 | ```java 283 | client.getFileContent("VAULTID", "ITEMID", "FILEID") 284 | .whenComplete((content, throwable) -> { 285 | // content variable contains the file content 286 | }); 287 | ``` 288 | 289 | ### List API activity 290 | 291 | ```java 292 | List requests = client.listAPIActivity().join(); 293 | 294 | // Limit to 5 requests 295 | List limitedRequests = client.listAPIActivity(5).join(); 296 | 297 | // Get 10 requests, starting at index 2 298 | List limitedAndOffsetRequests = client.listAPIActivity(10, 2).join(); 299 | ``` 300 | 301 | ```java 302 | client.listAPIActivity().whenComplete((requests, throwable) -> { 303 | // requests variable contains the list of requests 304 | }); 305 | ``` 306 | 307 | ### Get Health Details 308 | 309 | ```java 310 | ConnectServer serverHealth = client.health().join(); 311 | ``` 312 | 313 | ```java 314 | client.health().whenComplete((serverHealth, throwable) -> { 315 | // serverHealth variable contains the server health details 316 | }); 317 | ``` 318 | 319 | ### Heartbeat 320 | 321 | ```java 322 | client.heartbeat().join(); // throws exception if heartbeat fails 323 | ``` 324 | 325 | ```java 326 | client.heartbeat().exceptionally(throwable -> { 327 | // execution reaches here if heartbeat failed 328 | }); 329 | ``` 330 | 331 | ### Get Connect Server Metrics 332 | 333 | ```java 334 | String metrics = client.metrics().join(); 335 | ``` 336 | 337 | ```java 338 | client.metrics().whenComplete((metrics, throwable) -> { 339 | // metrics variable contains the metrics in text form 340 | }); 341 | ``` 342 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 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 | 82 | 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 | 126 | 127 | 128 | 130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 140 | 141 | 142 | 143 | 145 | 146 | 147 | 148 | 149 | 151 | 152 | 153 | 154 | 156 | 157 | 158 | 159 | 161 | 162 | 163 | 164 | 166 | 167 | 168 | 169 | 171 | 173 | 175 | 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 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /local-connect/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | services: 4 | op-connect-api: 5 | image: 1password/connect-api:${OP_IMAGE_VER} 6 | ports: 7 | - "8080:8080" 8 | volumes: 9 | - "./1password-credentials.json:/home/opuser/.op/1password-credentials.json" 10 | - "data:/home/opuser/.op/data" 11 | op-connect-sync: 12 | image: 1password/connect-sync:${OP_IMAGE_VER} 13 | ports: 14 | - "8081:8080" 15 | volumes: 16 | - "./1password-credentials.json:/home/opuser/.op/1password-credentials.json" 17 | - "data:/home/opuser/.op/data" 18 | 19 | volumes: 20 | data: 21 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.sanctionco.opconnect 6 | opconnect-java 7 | 0.6.3-SNAPSHOT 8 | jar 9 | 10 | opconnect-java 11 | An unofficial Java SDK for 1Password Connect 12 | https://github.com/RohanNagar/op-connect-sdk-java 13 | 14 | 15 | https://github.com/RohanNagar/op-connect-sdk-java 16 | scm:git:git@github.com:RohanNagar/op-connect-sdk-java.git 17 | scm:git:git@github.com:RohanNagar/op-connect-sdk-java.git 18 | HEAD 19 | 20 | 21 | 22 | 23 | MIT License 24 | https://opensource.org/licenses/mit-license.php 25 | repo 26 | 27 | 28 | 29 | 30 | 31 | Rohan Nagar 32 | rohannagar11@gmail.com 33 | 34 | 35 | 36 | 37 | UTF-8 38 | UTF-8 39 | 40 | 1.8 41 | 8 42 | 43 | 44 | 0.7.0 45 | 10.25.0 46 | 3.6.0 47 | 3.14.0 48 | 3.2.7 49 | 0.8.13 50 | 3.11.2 51 | 3.1.1 52 | 3.3.1 53 | 3.5.3 54 | 55 | 56 | 2.19.0 57 | 5.13.1 58 | 1.9.2 59 | 5.18.0 60 | 3.0.0 61 | 62 | 63 | 64 | 65 | 66 | central 67 | Maven Central 68 | https://central.sonatype.com 69 | 70 | 71 | 72 | 73 | 74 | 75 | release-sign-artifacts 76 | 77 | 78 | performRelease 79 | true 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-gpg-plugin 88 | ${gpg-plugin.version} 89 | 90 | 91 | 92 | --pinentry-mode 93 | loopback 94 | 95 | 96 | 97 | 98 | sign-artifacts 99 | verify 100 | 101 | sign 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-source-plugin 111 | ${source-plugin.version} 112 | 113 | 114 | attach-sources 115 | 116 | jar-no-fork 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-javadoc-plugin 126 | ${javadoc-plugin.version} 127 | 128 | ${java.minimum.version} 129 | none 130 | true 131 | 132 | 133 | 134 | attach-javadocs 135 | 136 | jar 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | org.sonatype.central 145 | central-publishing-maven-plugin 146 | ${central-publishing-plugin.version} 147 | true 148 | 149 | central 150 | true 151 | published 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-compiler-plugin 164 | ${compiler-plugin.version} 165 | 166 | -Xlint:all 167 | true 168 | true 169 | ${java.release.version} 170 | ${java.minimum.version} 171 | ${java.minimum.version} 172 | 173 | 174 | 175 | 176 | 177 | org.apache.maven.plugins 178 | maven-release-plugin 179 | ${release-plugin.version} 180 | 181 | release-sign-artifacts 182 | v@{project.version} 183 | 184 | 185 | 186 | 187 | 188 | org.apache.maven.plugins 189 | maven-checkstyle-plugin 190 | ${checkstyle-plugin.version} 191 | 192 | 193 | ${project.build.sourceDirectory} 194 | 195 | true 196 | 197 | 198 | 199 | compile 200 | 201 | check 202 | 203 | 204 | checkstyle.xml 205 | warning 206 | 207 | 208 | 209 | 210 | 211 | com.puppycrawl.tools 212 | checkstyle 213 | ${checkstyle.version} 214 | 215 | 216 | 217 | 218 | 219 | 220 | org.jacoco 221 | jacoco-maven-plugin 222 | ${jacoco.version} 223 | 224 | 225 | prepare-agent 226 | 227 | prepare-agent 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | org.apache.maven.plugins 236 | maven-surefire-plugin 237 | ${surefire.version} 238 | 239 | 240 | 241 | 242 | 243 | 244 | com.fasterxml.jackson.datatype 245 | jackson-datatype-jsr310 246 | ${jackson.version} 247 | 248 | 249 | com.squareup.retrofit2 250 | converter-jackson 251 | ${retrofit.version} 252 | 253 | 254 | com.squareup.retrofit2 255 | converter-scalars 256 | ${retrofit.version} 257 | 258 | 259 | com.squareup.retrofit2 260 | retrofit 261 | ${retrofit.version} 262 | 263 | 264 | org.junit.jupiter 265 | junit-jupiter-engine 266 | ${junit.version} 267 | test 268 | 269 | 270 | org.junit.jupiter 271 | junit-jupiter-params 272 | ${junit.version} 273 | test 274 | 275 | 276 | org.mockito 277 | mockito-core 278 | ${mockito.version} 279 | test 280 | 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/OPConnectClient.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect; 2 | 3 | import com.sanctionco.opconnect.model.File; 4 | import com.sanctionco.opconnect.model.Filter; 5 | import com.sanctionco.opconnect.model.Item; 6 | import com.sanctionco.opconnect.model.Patch; 7 | import com.sanctionco.opconnect.model.Vault; 8 | import com.sanctionco.opconnect.model.apiactivity.APIRequest; 9 | import com.sanctionco.opconnect.model.health.ConnectServer; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.concurrent.CompletableFuture; 14 | 15 | import okhttp3.OkHttpClient; 16 | 17 | /** 18 | * The {@code OPConnectClient} provides access to the 1Password Connect API methods. 19 | */ 20 | public class OPConnectClient { 21 | private final RetrofitOPConnectClient client; 22 | private final OkHttpClient httpClient; 23 | 24 | OPConnectClient(RetrofitOPConnectClient client, OkHttpClient httpClient) { 25 | this.client = client; 26 | this.httpClient = httpClient; 27 | } 28 | 29 | /** 30 | * List the available vaults in 1Password. 31 | * 32 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 33 | * the list of available vault objects 34 | */ 35 | public CompletableFuture> listVaults() { 36 | return client.listVaults(); 37 | } 38 | 39 | /** 40 | * List the available vaults in 1Password, filtering based on the filter. 41 | * 42 | * @param filter an SCM-style filter to filter the results server-side 43 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 44 | * the list of available vault objects 45 | */ 46 | public CompletableFuture> listVaults(String filter) { 47 | return client.listVaults(filter); 48 | } 49 | 50 | /** 51 | * List the available vaults in 1Password, filtering based on the filter. 52 | * 53 | * @param filter the {@link Filter} to filter the results server-side 54 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 55 | * the list of available vault objects 56 | */ 57 | public CompletableFuture> listVaults(Filter filter) { 58 | return listVaults(filter.getFilter()); 59 | } 60 | 61 | /** 62 | * Get the details of a specific vault. 63 | * 64 | * @param vaultUUID the id of the vault to retrieve 65 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 66 | * the vault object 67 | */ 68 | public CompletableFuture getVault(String vaultUUID) { 69 | return client.getVault(vaultUUID); 70 | } 71 | 72 | /** 73 | * List the items from the given vault. 74 | * 75 | * @param vaultUUID the id of the vault 76 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 77 | * a list of items that exist in the vault, without sections or fields 78 | */ 79 | public CompletableFuture> listItems(String vaultUUID) { 80 | return client.listItems(vaultUUID); 81 | } 82 | 83 | /** 84 | * List the items from the given vault, filtering based on the filter. 85 | * 86 | * @param vaultUUID the id of the vault 87 | * @param filter an SCM-style filter to filter the results server-side 88 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with a 89 | * list of items that exist in the vault and match the filter, without sections or fields 90 | */ 91 | public CompletableFuture> listItems(String vaultUUID, String filter) { 92 | return client.listItems(vaultUUID, filter); 93 | } 94 | 95 | 96 | /** 97 | * List the items from the given vault, filtering based on the filter. 98 | * 99 | * @param vaultUUID the id of the vault 100 | * @param filter the {@link Filter} to filter the results server-side 101 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with a 102 | * list of items that exist in the vault and match the filter, without sections or fields 103 | */ 104 | public CompletableFuture> listItems(String vaultUUID, Filter filter) { 105 | return listItems(vaultUUID, filter.getFilter()); 106 | } 107 | 108 | /** 109 | * Get a full item from the given vault. 110 | * 111 | * @param vaultUUID the id of the vault 112 | * @param itemUUID the id of the item 113 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 114 | * the item 115 | */ 116 | public CompletableFuture getItem(String vaultUUID, String itemUUID) { 117 | return client.getItem(vaultUUID, itemUUID); 118 | } 119 | 120 | /** 121 | * Create a new item in the given vault. 122 | * 123 | * @param vaultUUID the id of the vault 124 | * @param item the full item object to create 125 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 126 | * the newly created item 127 | */ 128 | public CompletableFuture createItem(String vaultUUID, Item item) { 129 | return client.createItem(vaultUUID, item); 130 | } 131 | 132 | /** 133 | * Replace an entire item in the given vault. 134 | * 135 | * @param vaultUUID the id of the vault 136 | * @param itemUUID the id of the item to replace 137 | * @param item the full item object that will replace the old item 138 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 139 | * the newly replaced item 140 | */ 141 | public CompletableFuture replaceItem(String vaultUUID, String itemUUID, Item item) { 142 | return client.replaceItem(vaultUUID, itemUUID, item); 143 | } 144 | 145 | /** 146 | * Applies a list of add, remove, or replace operations on an item or the fields of an item. 147 | * Uses the RFC6902 JSON Patch 148 | * document standard. 149 | * 150 | * @param vaultUUID the id of the vault 151 | * @param itemUUID the id of the item to patch 152 | * @param patches a list of patches to apply to the item in order 153 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 154 | * the updated item 155 | */ 156 | public CompletableFuture patchItem(String vaultUUID, String itemUUID, List patches) { 157 | return client.patchItem(vaultUUID, itemUUID, patches); 158 | } 159 | 160 | /** 161 | * Applies one or more of add, remove, or replace operation on an item or the fields of an item. 162 | * Uses the RFC6902 JSON Patch 163 | * document standard. 164 | * 165 | * @param vaultUUID the id of the vault 166 | * @param itemUUID the id of the item to patch 167 | * @param patches one or more patches to apply to the item 168 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 169 | * the updated item 170 | */ 171 | public CompletableFuture patchItem(String vaultUUID, String itemUUID, Patch... patches) { 172 | return patchItem(vaultUUID, itemUUID, Arrays.asList(patches)); 173 | } 174 | 175 | /** 176 | * Moves an item to the trash in the given vault. 177 | * 178 | * @param vaultUUID the id of the vault 179 | * @param itemUUID the id of the item to move to the trash 180 | * @return a {@link CompletableFuture} is returned immediately and eventually completed when the 181 | * operation is complete 182 | */ 183 | public CompletableFuture deleteItem(String vaultUUID, String itemUUID) { 184 | return client.deleteItem(vaultUUID, itemUUID); 185 | } 186 | 187 | 188 | /** 189 | * List the files attached to the given item. 190 | * 191 | * @param vaultUUID the id of the vault 192 | * @param itemUUID the id of the item to get files for 193 | * @param inlineContent whether to include the base64 encoded file contents. The file size must 194 | * be less than OP_MAX_INLINE_FILE_SIZE_KB, or 100 kilobytes if the file 195 | * size isn't defined. 196 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 197 | * list of {@link File} objects 198 | */ 199 | public CompletableFuture> listFiles(String vaultUUID, 200 | String itemUUID, 201 | boolean inlineContent) { 202 | return client.listFiles(vaultUUID, itemUUID, inlineContent); 203 | } 204 | 205 | /** 206 | * List the files attached to the given item. 207 | * 208 | * @param vaultUUID the id of the vault 209 | * @param itemUUID the id of the item to get files for 210 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 211 | * list of {@link File} objects 212 | */ 213 | public CompletableFuture> listFiles(String vaultUUID, String itemUUID) { 214 | return client.listFiles(vaultUUID, itemUUID); 215 | } 216 | 217 | /** 218 | * Get the details of a file from the given item. 219 | * 220 | * @param vaultUUID the id of the vault 221 | * @param itemUUID the id of the item that the file is attached to 222 | * @param fileUUID the id of the file 223 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 224 | * {@link File} details 225 | */ 226 | public CompletableFuture getFile(String vaultUUID, String itemUUID, String fileUUID) { 227 | return client.getFile(vaultUUID, itemUUID, fileUUID); 228 | } 229 | 230 | /** 231 | * Get the details of a file from the given item. 232 | * 233 | * @param vaultUUID the id of the vault 234 | * @param itemUUID the id of the item that the file is attached to 235 | * @param fileUUID the id of the file 236 | * @param inlineContent whether to include the base64 encoded file contents. The file size must 237 | * be less than OP_MAX_INLINE_FILE_SIZE_KB, or 100 kilobytes if the file 238 | * size isn't defined. 239 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 240 | * {@link File} details 241 | */ 242 | public CompletableFuture getFile(String vaultUUID, 243 | String itemUUID, 244 | String fileUUID, 245 | boolean inlineContent) { 246 | return client.getFile(vaultUUID, itemUUID, fileUUID, inlineContent); 247 | } 248 | 249 | /** 250 | * Get the content of a file. 251 | * 252 | * @param vaultUUID the id of the vault 253 | * @param itemUUID the id of the item that the file is attached to 254 | * @param fileUUID the id of the file 255 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 256 | * file contents 257 | */ 258 | public CompletableFuture getFileContent(String vaultUUID, 259 | String itemUUID, 260 | String fileUUID) { 261 | return client.getFileContent(vaultUUID, itemUUID, fileUUID); 262 | } 263 | 264 | /** 265 | * Provides a list of recent API activity. 266 | * 267 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 268 | * a list of {@link APIRequest} objects that describe activity 269 | */ 270 | public CompletableFuture> listAPIActivity() { 271 | return client.listAPIActivity(); 272 | } 273 | 274 | /** 275 | * Provides a list of recent API activity, limiting the results based on the given limit. 276 | * 277 | * @param limit the maximum number of activity instances to retrieve 278 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 279 | * a list of {@link APIRequest} objects that describe activity 280 | */ 281 | public CompletableFuture> listAPIActivity(Integer limit) { 282 | return client.listAPIActivity(limit); 283 | } 284 | 285 | /** 286 | * Provides a list of recent API activity, starting at the given offset and limiting the results 287 | * based on the given limit. 288 | * 289 | * @param limit the maximum number of activity instances to retrieve 290 | * @param offset how far into the collection of API events the response should start 291 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 292 | * a list of {@link APIRequest} objects that describe activity 293 | */ 294 | public CompletableFuture> listAPIActivity(Integer limit, Integer offset) { 295 | return client.listAPIActivity(limit, offset); 296 | } 297 | 298 | /** 299 | * Retrieves the health of the 1Password connect server. 300 | * 301 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 302 | * a {@link ConnectServer} object that describes the server and its health 303 | */ 304 | public CompletableFuture health() { 305 | return client.health(); 306 | } 307 | 308 | /** 309 | * Checks the heartbeat of the 1Password connect server, completing exceptionally 310 | * if the heartbeat fails. 311 | * 312 | * @return a {@link CompletableFuture} is returned immediately and eventually completed 313 | * successfully if the server has a healthy heartbeat, or completed exceptionally 314 | * otherwise 315 | */ 316 | public CompletableFuture heartbeat() { 317 | return client.heartbeat(); 318 | } 319 | 320 | /** 321 | * Retrieves Prometheus metrics collected by the server. 322 | * 323 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 324 | * a plaintext list of Prometheus metrics. See the 325 | * Prometheus documentation for specifics. 327 | */ 328 | public CompletableFuture metrics() { 329 | return client.metrics(); 330 | } 331 | 332 | /** 333 | * Provides a convenient wrapper client that interacts with a specific vault. 334 | * 335 | * @param vaultUUID the id of the vault to interact with 336 | * @return a new {@link OPConnectVaultClient} instance 337 | */ 338 | public OPConnectVaultClient getVaultClient(String vaultUUID) { 339 | return new OPConnectVaultClient(this, vaultUUID); 340 | } 341 | 342 | /** 343 | * Provides a convenient wrapper client that interacts with a specific vault. 344 | * 345 | * @param vault the vault to interact with 346 | * @return a new {@link OPConnectVaultClient} instance 347 | */ 348 | public OPConnectVaultClient getVaultClient(Vault vault) { 349 | return getVaultClient(vault.getId()); 350 | } 351 | 352 | /** 353 | * Cleanly close the client and any open connections. 354 | */ 355 | public void close() { 356 | httpClient.dispatcher().executorService().shutdown(); 357 | httpClient.connectionPool().evictAll(); 358 | } 359 | 360 | /** 361 | * Creates a new {@link OPConnectClientBuilder} instance to build a client. 362 | * 363 | * @return the new builder instance 364 | */ 365 | public static OPConnectClientBuilder builder() { 366 | return OPConnectClientBuilder.builder(); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/OPConnectClientBuilder.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSetter; 4 | import com.fasterxml.jackson.annotation.Nulls; 5 | import com.fasterxml.jackson.databind.DeserializationFeature; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 8 | 9 | import java.util.Objects; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import okhttp3.OkHttpClient; 13 | import okhttp3.Request; 14 | 15 | import retrofit2.Retrofit; 16 | import retrofit2.converter.jackson.JacksonConverterFactory; 17 | import retrofit2.converter.scalars.ScalarsConverterFactory; 18 | 19 | /** 20 | * A builder class for {@link OPConnectClient}. 21 | */ 22 | public class OPConnectClientBuilder { 23 | private String endpoint = null; 24 | private String accessToken = null; 25 | private Long timeout = null; 26 | 27 | private OPConnectClientBuilder() { 28 | } 29 | 30 | /** 31 | * Creates a new {@code OPConnectClientBuilder} instance. 32 | * 33 | * @return the new {@code OPConnectClientBuilder} instance 34 | */ 35 | public static OPConnectClientBuilder builder() { 36 | return new OPConnectClientBuilder(); 37 | } 38 | 39 | /** 40 | * Set the endpoint to use when connecting to 1Password Connect. 41 | * 42 | * @param endpoint the endpoint in URL format 43 | * @return this 44 | */ 45 | public OPConnectClientBuilder withEndpoint(String endpoint) { 46 | Objects.requireNonNull(endpoint, "The endpoint must not be null."); 47 | 48 | this.endpoint = ensureTrailingSlashExists(endpoint); 49 | 50 | return this; 51 | } 52 | 53 | /** 54 | * Set the access token to use when making requests to 1Password Connect. 55 | * 56 | * @param accessToken the access token 57 | * @return this 58 | */ 59 | public OPConnectClientBuilder withAccessToken(String accessToken) { 60 | this.accessToken = Objects.requireNonNull(accessToken, "The access token must not be null."); 61 | 62 | return this; 63 | } 64 | 65 | /** 66 | * Set the timeout in milliseconds. Default timeout is 10000 milliseconds (10 seconds). 67 | * 68 | * @param timeout the timeout in milliseconds 69 | * @return this 70 | */ 71 | public OPConnectClientBuilder withTimeoutInMilliseconds(long timeout) { 72 | this.timeout = timeout; 73 | 74 | return this; 75 | } 76 | 77 | /** 78 | * Builds a new instance of {@link OPConnectClient}. 79 | * 80 | * @return the built client 81 | */ 82 | public OPConnectClient build() { 83 | Objects.requireNonNull(endpoint, 84 | "You must provide an endpoint with the builder.withEndpoint() method" 85 | + " in order to build an OPConnectClient."); 86 | Objects.requireNonNull(accessToken, 87 | "You must provide an access token with the" 88 | + " builder.withAccessToken() method in order to build a OPConnectClient."); 89 | 90 | ObjectMapper mapper = new ObjectMapper(); 91 | mapper.registerModule(new JavaTimeModule()); 92 | mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE); 93 | 94 | mapper.configOverride(String.class) 95 | .setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); 96 | 97 | OkHttpClient httpClient = buildHttpClient(this.accessToken); 98 | 99 | Retrofit retrofit = new Retrofit.Builder() 100 | .baseUrl(this.endpoint) 101 | .addConverterFactory(ScalarsConverterFactory.create()) 102 | .addConverterFactory(JacksonConverterFactory.create(mapper)) 103 | .client(httpClient) 104 | .build(); 105 | 106 | RetrofitOPConnectClient retrofitClient = retrofit.create(RetrofitOPConnectClient.class); 107 | 108 | return new OPConnectClient(retrofitClient, httpClient); 109 | } 110 | 111 | private OkHttpClient buildHttpClient(String accessToken) { 112 | String authorization = "Bearer " + accessToken; 113 | 114 | OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); 115 | httpClient.addInterceptor((chain) -> { 116 | Request original = chain.request(); 117 | 118 | // Add the authorization header 119 | Request request = original.newBuilder() 120 | .header("Authorization", authorization) 121 | .header("Content-type", "application/json") 122 | .method(original.method(), original.body()) 123 | .build(); 124 | 125 | return chain.proceed(request); 126 | }); 127 | 128 | if (timeout != null) { 129 | httpClient 130 | .connectTimeout(timeout, TimeUnit.MILLISECONDS) 131 | .readTimeout(timeout, TimeUnit.MILLISECONDS) 132 | .writeTimeout(timeout, TimeUnit.MILLISECONDS); 133 | } 134 | 135 | return httpClient.build(); 136 | } 137 | 138 | /** 139 | * Ensures that the given URL ends with a trailing slash ('/'). 140 | * 141 | * @param url the url to verify contains a trailing slash 142 | * @return the original url with a trailing slash added if necessary 143 | */ 144 | static String ensureTrailingSlashExists(String url) { 145 | return url.endsWith("/") 146 | ? url 147 | : url + "/"; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/OPConnectVaultClient.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect; 2 | 3 | import com.sanctionco.opconnect.model.File; 4 | import com.sanctionco.opconnect.model.Filter; 5 | import com.sanctionco.opconnect.model.Item; 6 | import com.sanctionco.opconnect.model.Patch; 7 | import com.sanctionco.opconnect.model.Vault; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | /** 13 | * The {@code OPConnectVaultClient} is a convenient wrapper that provides access to 14 | * 1Password Connect API methods that interact with a specific vault. 15 | */ 16 | public class OPConnectVaultClient { 17 | private final OPConnectClient client; 18 | private final String vaultUUID; 19 | 20 | /** 21 | * Creates a new instance of {@code OPConnectVaultClient}. 22 | * 23 | * @param client the underlying {@link OPConnectClient} to use for making requests 24 | * @param vaultUUID the id of the vault this client uses when making requests 25 | */ 26 | public OPConnectVaultClient(OPConnectClient client, String vaultUUID) { 27 | this.client = client; 28 | this.vaultUUID = vaultUUID; 29 | } 30 | 31 | /** 32 | * Get the details of this vault. 33 | * 34 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 35 | * the vault object 36 | */ 37 | public CompletableFuture getVault() { 38 | return client.getVault(vaultUUID); 39 | } 40 | 41 | /** 42 | * List the items from the vault. 43 | * 44 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 45 | * a list of items that exist in the vault, without sections or fields 46 | */ 47 | public CompletableFuture> listItems() { 48 | return client.listItems(vaultUUID); 49 | } 50 | 51 | /** 52 | * List the items from the vault, filtering based on the filter. 53 | * 54 | * @param filter an SCM-style filter to filter the results server-side 55 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with a 56 | * list of items that exist in the vault and match the filter, without sections or fields 57 | */ 58 | public CompletableFuture> listItems(String filter) { 59 | return client.listItems(vaultUUID, filter); 60 | } 61 | 62 | /** 63 | * List the items from the given vault, filtering based on the filter. 64 | * 65 | * @param filter the {@link Filter} to filter the results server-side 66 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with a 67 | * list of items that exist in the vault and match the filter, without sections or fields 68 | */ 69 | public CompletableFuture> listItems(Filter filter) { 70 | return client.listItems(vaultUUID, filter.getFilter()); 71 | } 72 | 73 | /** 74 | * Get a full item from the vault. 75 | * 76 | * @param itemUUID the id of the item 77 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 78 | * the item 79 | */ 80 | public CompletableFuture getItem(String itemUUID) { 81 | return client.getItem(vaultUUID, itemUUID); 82 | } 83 | 84 | /** 85 | * Create a new item in the vault. 86 | * 87 | * @param item the full item object to create 88 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 89 | * the newly created item 90 | */ 91 | public CompletableFuture createItem(Item item) { 92 | return client.createItem(vaultUUID, item); 93 | } 94 | 95 | /** 96 | * Replace an entire item in the vault. 97 | * 98 | * @param itemUUID the id of the item to replace 99 | * @param item the full item object that will replace the old item 100 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 101 | * the newly replaced item 102 | */ 103 | public CompletableFuture replaceItem(String itemUUID, Item item) { 104 | return client.replaceItem(vaultUUID, itemUUID, item); 105 | } 106 | 107 | /** 108 | * Applies a list of add, remove, or replace operations on an item or the fields of an item. 109 | * Uses the RFC6902 JSON Patch 110 | * document standard. 111 | * 112 | * @param itemUUID the id of the item to patch 113 | * @param patches a list of patches to apply to the item in order 114 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 115 | * the updated item 116 | */ 117 | public CompletableFuture patchItem(String itemUUID, List patches) { 118 | return client.patchItem(vaultUUID, itemUUID, patches); 119 | } 120 | 121 | /** 122 | * Applies an add, remove, or replace operation on an item or the fields of an item. 123 | * Uses the RFC6902 JSON Patch 124 | * document standard. 125 | * 126 | * @param itemUUID the id of the item to patch 127 | * @param patch a patch to apply to the item 128 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 129 | * the updated item 130 | */ 131 | public CompletableFuture patchItem(String itemUUID, Patch patch) { 132 | return client.patchItem(vaultUUID, itemUUID, patch); 133 | } 134 | 135 | /** 136 | * Moves an item to the trash in the vault. 137 | * 138 | * @param itemUUID the id of the item to move to the trash 139 | * @return a {@link CompletableFuture} is returned immediately and eventually completed when the 140 | * operation is complete 141 | */ 142 | public CompletableFuture deleteItem(String itemUUID) { 143 | return client.deleteItem(vaultUUID, itemUUID); 144 | } 145 | 146 | /** 147 | * List the files attached to the given item. 148 | * 149 | * @param itemUUID the id of the item to get files for 150 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 151 | * list of {@link File} objects 152 | */ 153 | public CompletableFuture> listFiles(String itemUUID) { 154 | return client.listFiles(vaultUUID, itemUUID); 155 | } 156 | 157 | /** 158 | * List the files attached to the given item. 159 | * 160 | * @param itemUUID the id of the item to get files for 161 | * @param inlineContent whether to include the base64 encoded file contents. The file size must 162 | * be less than OP_MAX_INLINE_FILE_SIZE_KB, or 100 kilobytes if the file 163 | * size isn't defined. 164 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 165 | * list of {@link File} objects 166 | */ 167 | public CompletableFuture> listFiles(String itemUUID, boolean inlineContent) { 168 | return client.listFiles(vaultUUID, itemUUID, inlineContent); 169 | } 170 | 171 | 172 | /** 173 | * Get the details of a file from the given item. 174 | * 175 | * @param itemUUID the id of the item that the file is attached to 176 | * @param fileUUID the id of the file 177 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 178 | * {@link File} details 179 | */ 180 | public CompletableFuture getFile(String itemUUID, String fileUUID) { 181 | return client.getFile(vaultUUID, itemUUID, fileUUID); 182 | } 183 | 184 | /** 185 | * Get the details of a file from the given item. 186 | * 187 | * @param itemUUID the id of the item that the file is attached to 188 | * @param fileUUID the id of the file 189 | * @param inlineContent whether to include the base64 encoded file contents. The file size must 190 | * be less than OP_MAX_INLINE_FILE_SIZE_KB, or 100 kilobytes if the file 191 | * size isn't defined. 192 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 193 | * {@link File} details 194 | */ 195 | public CompletableFuture getFile(String itemUUID, String fileUUID, boolean inlineContent) { 196 | return client.getFile(vaultUUID, itemUUID, fileUUID, inlineContent); 197 | } 198 | 199 | /** 200 | * Get the content of a file. 201 | * 202 | * @param itemUUID the id of the item that the file is attached to 203 | * @param fileUUID the id of the file 204 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 205 | * file contents 206 | */ 207 | public CompletableFuture getFileContent(String itemUUID, String fileUUID) { 208 | return client.getFileContent(vaultUUID, itemUUID, fileUUID); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/RetrofitOPConnectClient.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect; 2 | 3 | import com.sanctionco.opconnect.model.File; 4 | import com.sanctionco.opconnect.model.Item; 5 | import com.sanctionco.opconnect.model.Patch; 6 | import com.sanctionco.opconnect.model.Vault; 7 | import com.sanctionco.opconnect.model.apiactivity.APIRequest; 8 | import com.sanctionco.opconnect.model.health.ConnectServer; 9 | 10 | import java.util.List; 11 | import java.util.concurrent.CompletableFuture; 12 | 13 | import retrofit2.http.Body; 14 | import retrofit2.http.DELETE; 15 | import retrofit2.http.GET; 16 | import retrofit2.http.PATCH; 17 | import retrofit2.http.POST; 18 | import retrofit2.http.PUT; 19 | import retrofit2.http.Path; 20 | import retrofit2.http.Query; 21 | 22 | interface RetrofitOPConnectClient { 23 | 24 | /** 25 | * List the available vaults in 1Password. 26 | * 27 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 28 | * the list of available vault objects 29 | */ 30 | @GET("v1/vaults") 31 | CompletableFuture> listVaults(); 32 | 33 | /** 34 | * List the available vaults in 1Password, filtering based on the filter. 35 | * 36 | * @param filter an SCM-style filter to filter the results server-side 37 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 38 | * the list of available vault objects 39 | */ 40 | @GET("v1/vaults") 41 | CompletableFuture> listVaults(@Query("filter") String filter); 42 | 43 | /** 44 | * Get the details of a specific vault. 45 | * 46 | * @param vaultUUID the id of the vault to retrieve 47 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 48 | * the vault object 49 | */ 50 | @GET("v1/vaults/{id}") 51 | CompletableFuture getVault(@Path("id") String vaultUUID); 52 | 53 | /** 54 | * List the items from the given vault. 55 | * 56 | * @param vaultUUID the id of the vault 57 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 58 | * a list of items that exist in the vault, without sections or fields 59 | */ 60 | @GET("v1/vaults/{id}/items") 61 | CompletableFuture> listItems(@Path("id") String vaultUUID); 62 | 63 | /** 64 | * List the items from the given vault, filtering based on the filter. 65 | * 66 | * @param vaultUUID the id of the vault 67 | * @param filter an SCM-style filter to filter the results server-side 68 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with a 69 | * list of items that exist in the vault and match the filter, without sections or fields 70 | */ 71 | @GET("v1/vaults/{id}/items") 72 | CompletableFuture> listItems(@Path("id") String vaultUUID, 73 | @Query("filter") String filter); 74 | 75 | /** 76 | * Get a full item from the given vault. 77 | * 78 | * @param vaultUUID the id of the vault 79 | * @param itemUUID the id of the item 80 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 81 | * the item 82 | */ 83 | @GET("v1/vaults/{vaultId}/items/{itemId}") 84 | CompletableFuture getItem(@Path("vaultId") String vaultUUID, 85 | @Path("itemId") String itemUUID); 86 | 87 | /** 88 | * Create a new item in the given vault. 89 | * 90 | * @param vaultUUID the id of the vault 91 | * @param item the full item object to create 92 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 93 | * the newly created item 94 | */ 95 | @POST("v1/vaults/{vaultId}/items") 96 | CompletableFuture createItem(@Path("vaultId") String vaultUUID, 97 | @Body Item item); 98 | 99 | /** 100 | * Replace an entire item in the given vault. 101 | * 102 | * @param vaultUUID the id of the vault 103 | * @param itemUUID the id of the item to replace 104 | * @param item the full item object that will replace the old item 105 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 106 | * the newly replaced item 107 | */ 108 | @PUT("v1/vaults/{vaultId}/items/{itemId}") 109 | CompletableFuture replaceItem(@Path("vaultId") String vaultUUID, 110 | @Path("itemId") String itemUUID, 111 | @Body Item item); 112 | 113 | /** 114 | * Applies a list of add, remove, or replace operations on an item or the fields of an item. 115 | * Uses the RFC6902 JSON Patch 116 | * document standard. 117 | * 118 | * @param vaultUUID the id of the vault 119 | * @param itemUUID the id of the item to patch 120 | * @param patches a list of patches to apply to the item in order 121 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 122 | * the updated item 123 | */ 124 | @PATCH("v1/vaults/{vaultId}/items/{itemId}") 125 | CompletableFuture patchItem(@Path("vaultId") String vaultUUID, 126 | @Path("itemId") String itemUUID, 127 | @Body List patches); 128 | 129 | /** 130 | * Moves an item to the trash in the given vault. 131 | * 132 | * @param vaultUUID the id of the vault 133 | * @param itemUUID the id of the item to move to the trash 134 | * @return a {@link CompletableFuture} is returned immediately and eventually completed when the 135 | * operation is complete 136 | */ 137 | @DELETE("v1/vaults/{vaultId}/items/{itemId}") 138 | CompletableFuture deleteItem(@Path("vaultId") String vaultUUID, 139 | @Path("itemId") String itemUUID); 140 | 141 | 142 | /** 143 | * List the files attached to the given item. 144 | * 145 | * @param vaultUUID the id of the vault 146 | * @param itemUUID the id of the item to get files for 147 | * @param inlineContent whether to include the base64 encoded file contents. The file size must 148 | * be less than OP_MAX_INLINE_FILE_SIZE_KB, or 100 kilobytes if the file 149 | * size isn't defined. 150 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 151 | * list of {@link File} objects 152 | */ 153 | @GET("/v1/vaults/{vaultId}/items/{itemId}/files") 154 | CompletableFuture> listFiles(@Path("vaultId") String vaultUUID, 155 | @Path("itemId") String itemUUID, 156 | @Query("inline_content") boolean inlineContent); 157 | 158 | /** 159 | * List the files attached to the given item. 160 | * 161 | * @param vaultUUID the id of the vault 162 | * @param itemUUID the id of the item to get files for 163 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 164 | * list of {@link File} objects 165 | */ 166 | @GET("/v1/vaults/{vaultId}/items/{itemId}/files") 167 | CompletableFuture> listFiles(@Path("vaultId") String vaultUUID, 168 | @Path("itemId") String itemUUID); 169 | 170 | /** 171 | * Get the details of a file from the given item. 172 | * 173 | * @param vaultUUID the id of the vault 174 | * @param itemUUID the id of the item that the file is attached to 175 | * @param fileUUID the id of the file 176 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 177 | * {@link File} details 178 | */ 179 | @GET("/v1/vaults/{vaultId}/items/{itemId}/files/{fileId}") 180 | CompletableFuture getFile(@Path("vaultId") String vaultUUID, 181 | @Path("itemId") String itemUUID, 182 | @Path("fileId") String fileUUID); 183 | 184 | /** 185 | * Get the details of a file from the given item. 186 | * 187 | * @param vaultUUID the id of the vault 188 | * @param itemUUID the id of the item that the file is attached to 189 | * @param fileUUID the id of the file 190 | * @param inlineContent whether to include the base64 encoded file contents. The file size must 191 | * be less than OP_MAX_INLINE_FILE_SIZE_KB, or 100 kilobytes if the file 192 | * size isn't defined. 193 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 194 | * {@link File} details 195 | */ 196 | @GET("/v1/vaults/{vaultId}/items/{itemId}/files/{fileId}") 197 | CompletableFuture getFile(@Path("vaultId") String vaultUUID, 198 | @Path("itemId") String itemUUID, 199 | @Path("fileId") String fileUUID, 200 | @Query("inline_content") boolean inlineContent); 201 | 202 | /** 203 | * Get the content of a file. 204 | * 205 | * @param vaultUUID the id of the vault 206 | * @param itemUUID the id of the item that the file is attached to 207 | * @param fileUUID the id of the file 208 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with the 209 | * file contents 210 | */ 211 | @GET("/v1/vaults/{vaultId}/items/{itemId}/files/{fileId}/content") 212 | CompletableFuture getFileContent(@Path("vaultId") String vaultUUID, 213 | @Path("itemId") String itemUUID, 214 | @Path("fileId") String fileUUID); 215 | 216 | /** 217 | * Provides a list of recent API activity. 218 | * 219 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 220 | * a list of {@link APIRequest} objects that describe activity 221 | */ 222 | @GET("v1/activity") 223 | CompletableFuture> listAPIActivity(); 224 | 225 | /** 226 | * Provides a list of recent API activity, limiting the results based on the given limit. 227 | * 228 | * @param limit the maximum number of activity instances to retrieve 229 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 230 | * a list of {@link APIRequest} objects that describe activity 231 | */ 232 | @GET("v1/activity") 233 | CompletableFuture> listAPIActivity(@Query("limit") Integer limit); 234 | 235 | /** 236 | * Provides a list of recent API activity, starting at the given offset and limiting the results 237 | * based on the given limit. 238 | * 239 | * @param limit the maximum number of activity instances to retrieve 240 | * @param offset how far into the collection of API events the response should start 241 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 242 | * a list of {@link APIRequest} objects that describe activity 243 | */ 244 | @GET("v1/activity") 245 | CompletableFuture> listAPIActivity(@Query("limit") Integer limit, 246 | @Query("offset") Integer offset); 247 | 248 | /** 249 | * Retrieves the health of the 1Password connect server. 250 | * 251 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 252 | * a {@link ConnectServer} object that describes the server and its health 253 | */ 254 | @GET("health") 255 | CompletableFuture health(); 256 | 257 | /** 258 | * Checks the heartbeat of the 1Password connect server, completing exceptionally 259 | * if the heartbeat fails. 260 | * 261 | * @return a {@link CompletableFuture} is returned immediately and eventually completed 262 | * successfully if the server has a healthy heartbeat, or completed exceptionally 263 | * otherwise 264 | */ 265 | @GET("heartbeat") 266 | CompletableFuture heartbeat(); 267 | 268 | /** 269 | * Retrieves Prometheus metrics collected by the server. 270 | * 271 | * @return a {@link CompletableFuture} is returned immediately and eventually completed with 272 | * a plaintext list of Prometheus metrics. See the 273 | * Prometheus documentation for specifics. 275 | */ 276 | @GET("metrics") 277 | CompletableFuture metrics(); 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Category.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; 4 | 5 | /** 6 | * Represents an item category. 7 | */ 8 | public enum Category { 9 | /** 10 | * The login category. 11 | */ 12 | LOGIN, 13 | 14 | /** 15 | * The password category. 16 | */ 17 | PASSWORD, 18 | 19 | /** 20 | * The server category. 21 | */ 22 | SERVER, 23 | 24 | /** 25 | * The database category. 26 | */ 27 | DATABASE, 28 | 29 | /** 30 | * The credit card category. 31 | */ 32 | CREDIT_CARD, 33 | 34 | /** 35 | * The membership category. 36 | */ 37 | MEMBERSHIP, 38 | 39 | /** 40 | * The passport category. 41 | */ 42 | PASSPORT, 43 | 44 | /** 45 | * The software license category. 46 | */ 47 | SOFTWARE_LICENSE, 48 | 49 | /** 50 | * The outdoor license category. 51 | */ 52 | OUTDOOR_LICENSE, 53 | 54 | /** 55 | * The secure note category. 56 | */ 57 | SECURE_NOTE, 58 | 59 | /** 60 | * The wireless router category. 61 | */ 62 | WIRELESS_ROUTER, 63 | 64 | /** 65 | * The bank account category. 66 | */ 67 | BANK_ACCOUNT, 68 | 69 | /** 70 | * The driver license category. 71 | */ 72 | DRIVER_LICENSE, 73 | 74 | /** 75 | * The identity category. 76 | */ 77 | IDENTITY, 78 | 79 | /** 80 | * The reward program category. 81 | */ 82 | REWARD_PROGRAM, 83 | 84 | /** 85 | * The document category. 86 | */ 87 | DOCUMENT, 88 | 89 | /** 90 | * The email account category. 91 | */ 92 | EMAIL_ACCOUNT, 93 | 94 | /** 95 | * The social security number category. 96 | */ 97 | SOCIAL_SECURITY_NUMBER, 98 | 99 | /** 100 | * The API credential category. 101 | */ 102 | API_CREDENTIAL, 103 | 104 | /** 105 | * The medical record category. 106 | */ 107 | MEDICAL_RECORD, 108 | 109 | /** 110 | * The SSH key category. 111 | */ 112 | SSH_KEY, 113 | 114 | /** 115 | * The custom category, used as the default for any unknown category. 116 | * 117 | *

Note that you cannot create an item using the {@code CUSTOM} category. 118 | */ 119 | @JsonEnumDefaultValue 120 | CUSTOM 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/CharacterSet.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | /** 8 | * Represents all allowed types of character sets. 9 | */ 10 | public enum CharacterSet { 11 | /** 12 | * The character set consisting of only letters. 13 | */ 14 | LETTERS, 15 | 16 | /** 17 | * The character set consisting of only numerical digits. 18 | */ 19 | DIGITS, 20 | 21 | /** 22 | * The character set consisting of only symbols. 23 | */ 24 | SYMBOLS; 25 | 26 | /** 27 | * Returns a list of {@code CharacterSet} containing only letters. 28 | * 29 | * @return the new {@link List} 30 | */ 31 | public static List letters() { 32 | return Collections.singletonList(LETTERS); 33 | } 34 | 35 | /** 36 | * Returns a list of {@code CharacterSet} containing only digits. 37 | * 38 | * @return the new {@link List} 39 | */ 40 | public static List digits() { 41 | return Collections.singletonList(DIGITS); 42 | } 43 | 44 | /** 45 | * Returns a list of {@code CharacterSet} containing only symbols. 46 | * 47 | * @return the new {@link List} 48 | */ 49 | public static List symbols() { 50 | return Collections.singletonList(SYMBOLS); 51 | } 52 | 53 | /** 54 | * Returns a list of {@code CharacterSet} containing letters and digits. 55 | * 56 | * @return the new {@link List} 57 | */ 58 | public static List lettersAndDigits() { 59 | return Arrays.asList(LETTERS, DIGITS); 60 | } 61 | 62 | /** 63 | * Returns a list of {@code CharacterSet} containing letters and symbols. 64 | * 65 | * @return the new {@link List} 66 | */ 67 | public static List lettersAndSymbols() { 68 | return Arrays.asList(LETTERS, SYMBOLS); 69 | } 70 | 71 | /** 72 | * Returns a list of {@code CharacterSet} containing digits and symbols. 73 | * 74 | * @return the new {@link List} 75 | */ 76 | public static List digitsAndSymbols() { 77 | return Arrays.asList(DIGITS, SYMBOLS); 78 | } 79 | 80 | /** 81 | * Returns a list of {@code CharacterSet} containing all characters. 82 | * 83 | * @return the new {@link List} 84 | */ 85 | public static List allCharacters() { 86 | return Arrays.asList(values()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Field.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAlias; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | 7 | import java.util.Objects; 8 | import java.util.StringJoiner; 9 | 10 | /** 11 | * Represents a field contained in an {@link Item}. 12 | */ 13 | @JsonDeserialize(builder = Field.Builder.class) 14 | public class Field { 15 | private final String id; 16 | private final Purpose purpose; 17 | private final Type type; 18 | private final String label; 19 | private final String value; 20 | private final Boolean generate; 21 | private final GeneratorRecipe recipe; 22 | private final Section section; 23 | private final Double entropy; 24 | private final PasswordDetails passwordDetails; 25 | private final String totp; 26 | 27 | private Field(Builder builder) { 28 | this.id = builder.id; 29 | this.purpose = builder.purpose; 30 | this.type = builder.type; 31 | this.label = builder.label; 32 | this.value = builder.value; 33 | this.generate = builder.generate; 34 | this.recipe = builder.recipe; 35 | this.section = builder.section; 36 | this.entropy = builder.entropy; 37 | this.passwordDetails = builder.passwordDetails; 38 | this.totp = builder.totp; 39 | } 40 | 41 | /** 42 | * Get the ID of this field. 43 | * 44 | * @return the ID of the field 45 | */ 46 | public String getId() { 47 | return id; 48 | } 49 | 50 | /** 51 | * Get the {@link Purpose} of this field. 52 | * 53 | * @return the purpose of the field 54 | */ 55 | public Purpose getPurpose() { 56 | return purpose; 57 | } 58 | 59 | /** 60 | * Get the {@link Type} of this field. 61 | * 62 | * @return the type of the field 63 | */ 64 | public Type getType() { 65 | return type; 66 | } 67 | 68 | /** 69 | * Get the label of this field. 70 | * 71 | * @return the label of this field 72 | */ 73 | public String getLabel() { 74 | return label; 75 | } 76 | 77 | /** 78 | * Get the value of this field. 79 | * 80 | * @return the value of this field 81 | */ 82 | public String getValue() { 83 | return value; 84 | } 85 | 86 | /** 87 | * Get whether this field should have a generated value. This will not 88 | * be set for fields that are returned from 1Password Connect, as the 89 | * {@code generate} option is only used when creating a field. 90 | * 91 | * @return true if the value should be generated, false otherwise 92 | */ 93 | public Boolean getGenerate() { 94 | return generate; 95 | } 96 | 97 | /** 98 | * Get the {@link GeneratorRecipe} to use when generating the value, if {@link #getGenerate()} 99 | * is set to {@code true}. This will not be set for fields that are returned from 1Password 100 | * Connect, as the {@code generate} option is only used when creating a field. 101 | * 102 | * @return the {@link GeneratorRecipe} to use when generating the value 103 | */ 104 | public GeneratorRecipe getRecipe() { 105 | return recipe; 106 | } 107 | 108 | /** 109 | * Get the {@link Section} that this field is contained in. 110 | * 111 | * @return the section this field is contained in 112 | */ 113 | public Section getSection() { 114 | return section; 115 | } 116 | 117 | /** 118 | * Get the entropy of the generated field value. 119 | * 120 | * @return the entropy of the generated field value 121 | */ 122 | public Double getEntropy() { 123 | return entropy; 124 | } 125 | 126 | /** 127 | * Get the password details for this field if it is a password field. 128 | * 129 | * @return the password details for this field if it is a password field, or null otherwise 130 | */ 131 | @JsonProperty("password_details") 132 | public PasswordDetails getPasswordDetails() { 133 | return passwordDetails; 134 | } 135 | 136 | /** 137 | * Get the TOTP value for this field if it is an OTP field. 138 | * 139 | * @return the current TOTP value for this field if it is an OTP field, or null otherwise 140 | */ 141 | public String getTotp() { 142 | return totp; 143 | } 144 | 145 | @Override 146 | public boolean equals(Object o) { 147 | if (this == o) return true; 148 | if (o == null || getClass() != o.getClass()) return false; 149 | Field field = (Field) o; 150 | return Objects.equals(id, field.id); 151 | } 152 | 153 | @Override 154 | public int hashCode() { 155 | return Objects.hash(id); 156 | } 157 | 158 | @Override 159 | public String toString() { 160 | return new StringJoiner(", ", Field.class.getSimpleName() + "[", "]") 161 | .add("id='" + id + "'") 162 | .add("purpose=" + purpose) 163 | .add("type=" + type) 164 | .add("label='" + label + "'") 165 | .add("value='" + value + "'") 166 | .add("generate=" + generate) 167 | .add("recipe=" + recipe) 168 | .add("section=" + section) 169 | .add("entropy=" + entropy) 170 | .add("passwordDetails=" + passwordDetails) 171 | .add("totp=" + totp) 172 | .toString(); 173 | } 174 | 175 | /** 176 | * Create a new {@link Field.Builder} for a username field. 177 | * 178 | * @param username the value of the username to set 179 | * @return a new builder instance used to build a {@link Field} 180 | */ 181 | public static Builder username(String username) { 182 | return builder().withPurpose(Purpose.USERNAME).withValue(username); 183 | } 184 | 185 | /** 186 | * Create a new {@link Field.Builder} for a password field. 187 | * 188 | * @param password the value of the password to set 189 | * @return a new builder instance used to build a {@link Field} 190 | */ 191 | public static Builder password(String password) { 192 | return builder().withPurpose(Purpose.PASSWORD).withValue(password); 193 | } 194 | 195 | /** 196 | * Create a new {@link Field.Builder} for a generated password field. 197 | * 198 | * @return a new builder instance used to build a {@link Field} 199 | */ 200 | public static Builder generatedPassword() { 201 | return builder().withPurpose(Purpose.PASSWORD).withGenerate(true); 202 | } 203 | 204 | /** 205 | * Create a new {@link Field.Builder} for a generated password field. 206 | * 207 | * @param recipe the {@link GeneratorRecipe} to use when generating the password value 208 | * @return a new builder instance used to build a {@link Field} 209 | */ 210 | public static Builder generatedPassword(GeneratorRecipe recipe) { 211 | return builder().withPurpose(Purpose.PASSWORD).withGenerate(true) 212 | .withRecipe(recipe); 213 | } 214 | 215 | /** 216 | * Create a new {@link Field.Builder} for a note field. 217 | * 218 | * @param note the value of the note to set 219 | * @return a new builder instance used to build a {@link Field} 220 | */ 221 | public static Builder note(String note) { 222 | return builder().withPurpose(Purpose.NOTES).withValue(note); 223 | } 224 | 225 | /** 226 | * Create a new {@link Field.Builder} for a field with the given label. 227 | * 228 | * @param label the label to use for the field 229 | * @return a new builder instance used to build a {@link Field} 230 | */ 231 | public static Builder labeled(String label) { 232 | return builder().withLabel(label); 233 | } 234 | 235 | /** 236 | * Create a new {@link Field.Builder} with no properties set. 237 | * 238 | * @return a new builder instance used to build a {@link Field} 239 | */ 240 | public static Builder builder() { 241 | return new Builder(); 242 | } 243 | 244 | /** 245 | * The builder class used to build a new {@link Field}. 246 | */ 247 | public static class Builder { 248 | private String id; 249 | private Purpose purpose; 250 | private Type type; 251 | private String label; 252 | private String value; 253 | private Boolean generate; 254 | private GeneratorRecipe recipe; 255 | private Section section; 256 | private Double entropy; 257 | private PasswordDetails passwordDetails; 258 | private String totp; 259 | 260 | /** 261 | * Set the ID of the field. 262 | * 263 | * @param id the ID of the field 264 | * @return this 265 | */ 266 | public Builder withId(String id) { 267 | this.id = id; 268 | return this; 269 | } 270 | 271 | /** 272 | * Set the {@link Purpose} of the field. 273 | * 274 | * @param purpose the purpose of the field 275 | * @return this 276 | */ 277 | public Builder withPurpose(Purpose purpose) { 278 | this.purpose = purpose; 279 | return this; 280 | } 281 | 282 | /** 283 | * Set the {@link Type} of the field. 284 | * 285 | * @param type the type of the field 286 | * @return this 287 | */ 288 | public Builder withType(Type type) { 289 | this.type = type; 290 | return this; 291 | } 292 | 293 | /** 294 | * Set the label for the field. 295 | * 296 | * @param label the label of the field 297 | * @return this 298 | */ 299 | public Builder withLabel(String label) { 300 | this.label = label; 301 | return this; 302 | } 303 | 304 | /** 305 | * Set the value of the field. 306 | * 307 | * @param value the value of the field 308 | * @return this 309 | */ 310 | public Builder withValue(String value) { 311 | this.value = value; 312 | return this; 313 | } 314 | 315 | /** 316 | * Set to true in order to generate the value of the field. 317 | * 318 | * @param generate true to generate the value, or false otherwise 319 | * @return this 320 | */ 321 | public Builder withGenerate(Boolean generate) { 322 | this.generate = generate; 323 | return this; 324 | } 325 | 326 | /** 327 | * Set that the value of the field should be generated. 328 | * 329 | * @return this 330 | */ 331 | public Builder generate() { 332 | return withGenerate(true); 333 | } 334 | 335 | /** 336 | * Set the {@link GeneratorRecipe} to use when generating the value of the field. 337 | * 338 | * @param recipe the recipe to use when generating the value 339 | * @return this 340 | */ 341 | public Builder withRecipe(GeneratorRecipe recipe) { 342 | this.recipe = recipe; 343 | return this; 344 | } 345 | 346 | /** 347 | * Set the {@link Section} that this field should be a part of. 348 | * 349 | * @param section the section that the field belongs to 350 | * @return this 351 | */ 352 | public Builder withSection(Section section) { 353 | this.section = section; 354 | return this; 355 | } 356 | 357 | /** 358 | * Set the entropy of the generated value. 359 | * 360 | * @param entropy the entropy of the generated value 361 | * @return this 362 | */ 363 | public Builder withEntropy(Double entropy) { 364 | this.entropy = entropy; 365 | return this; 366 | } 367 | 368 | /** 369 | * Set the {@link PasswordDetails} when this field is a password field. 370 | * 371 | * @param passwordDetails the password details 372 | * @return this 373 | */ 374 | @JsonProperty("password_details") 375 | @JsonAlias("passwordDetails") 376 | public Builder withPasswordDetails(PasswordDetails passwordDetails) { 377 | this.passwordDetails = passwordDetails; 378 | return this; 379 | } 380 | 381 | /** 382 | * Set the current TOTP value when this field is an OTP type field. 383 | * 384 | * @param totp the current TOTP value 385 | * @return this 386 | */ 387 | public Builder withTotp(String totp) { 388 | this.totp = totp; 389 | return this; 390 | } 391 | 392 | /** 393 | * Build a new {@link Field} instance based on the current configuration of 394 | * the builder. 395 | * 396 | * @return a new {@link Field} instance 397 | */ 398 | public Field build() { 399 | return new Field(this); 400 | } 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/File.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAlias; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.Base64; 9 | import java.util.Objects; 10 | import java.util.StringJoiner; 11 | 12 | /** 13 | * Represents a file that is stored alongside an {@link Item}. 14 | */ 15 | @JsonDeserialize(builder = File.Builder.class) 16 | public class File { 17 | private final String id; 18 | private final String name; 19 | private final int size; 20 | private final String contentPath; 21 | private final String content; 22 | private final Section section; 23 | 24 | private File(Builder builder) { 25 | this.id = builder.id; 26 | this.name = builder.name; 27 | this.size = builder.size; 28 | this.contentPath = builder.contentPath; 29 | this.content = builder.content; 30 | this.section = builder.section; 31 | } 32 | 33 | /** 34 | * Get the unique ID of the file. 35 | * 36 | * @return the id of this file 37 | */ 38 | public String getId() { 39 | return id; 40 | } 41 | 42 | /** 43 | * Get the name of the file. 44 | * 45 | * @return the name of this file 46 | */ 47 | public String getName() { 48 | return name; 49 | } 50 | 51 | /** 52 | * Get the size of the file in bytes. 53 | * 54 | * @return the size of the file in bytes 55 | */ 56 | public int getSize() { 57 | return size; 58 | } 59 | 60 | /** 61 | * Get the URL path that the content of the file can be accessed at. 62 | * 63 | * @return the path to get the content 64 | */ 65 | @JsonProperty("content_path") 66 | public String getContentPath() { 67 | return contentPath; 68 | } 69 | 70 | /** 71 | * Get the Base64-encoded content of this file, if {@code inlineContent} was set to true 72 | * when getting the file details. 73 | * 74 | * @return the base64-encoded content of the file if requested, or null if not 75 | */ 76 | public String getContent() { 77 | return content; 78 | } 79 | 80 | /** 81 | * Get the decoded plaintext content of this file, if {@code inlineContent} was set to true 82 | * when getting the file details. 83 | * 84 | * @return the plaintext content of the file if requested, or null if not 85 | */ 86 | public String getDecodedContent() { 87 | return content == null 88 | ? null 89 | : new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8); 90 | } 91 | 92 | /** 93 | * Get the {@link Section} that this file belongs to within its {@link Item}. 94 | * 95 | * @return the section that the file belongs to 96 | */ 97 | public Section getSection() { 98 | return section; 99 | } 100 | 101 | @Override 102 | public boolean equals(Object o) { 103 | if (this == o) return true; 104 | if (o == null || getClass() != o.getClass()) return false; 105 | File file = (File) o; 106 | return Objects.equals(id, file.id); 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | return Objects.hash(id); 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return new StringJoiner(", ", File.class.getSimpleName() + "[", "]") 117 | .add("id='" + id + "'") 118 | .add("name='" + name + "'") 119 | .add("size=" + size) 120 | .add("contentPath='" + contentPath + "'") 121 | .add("content='" + content + "'") 122 | .add("section=" + section) 123 | .toString(); 124 | } 125 | 126 | public static Builder builder() { 127 | return new Builder(); 128 | } 129 | 130 | public static class Builder { 131 | private String id; 132 | private String name; 133 | private int size; 134 | private String contentPath; 135 | private String content; 136 | private Section section; 137 | 138 | public Builder withId(String id) { 139 | this.id = id; 140 | return this; 141 | } 142 | 143 | public Builder withName(String name) { 144 | this.name = name; 145 | return this; 146 | } 147 | 148 | public Builder withSize(int size) { 149 | this.size = size; 150 | return this; 151 | } 152 | 153 | @JsonProperty("content_path") 154 | @JsonAlias("contentPath") 155 | public Builder withContentPath(String contentPath) { 156 | this.contentPath = contentPath; 157 | return this; 158 | } 159 | 160 | public Builder withContent(String content) { 161 | this.content = content; 162 | return this; 163 | } 164 | 165 | public Builder withSection(Section section) { 166 | this.section = section; 167 | return this; 168 | } 169 | 170 | public File build() { 171 | return new File(this); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Filter.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import java.util.Objects; 4 | import java.util.StringJoiner; 5 | 6 | /** 7 | * Represents SCIM-style filter that can be used to filter list requests server-side. 8 | */ 9 | public class Filter { 10 | private final String filter; 11 | 12 | private Filter(Builder builder) { 13 | if (builder.operator.equals(Operator.PRESENT)) { 14 | filter = String.format("%s pr", builder.property); 15 | } else { 16 | filter = String.format("%s %s \"%s\"", 17 | builder.property, builder.operator.getOpText(), builder.value); 18 | } 19 | } 20 | 21 | private Filter(Filter other, Filter and, Filter or) { 22 | if (and == null && or == null) { 23 | filter = other.filter; 24 | } else if (and != null) { 25 | filter = String.format("(%s and %s)", other.filter, and.filter); 26 | } else { 27 | filter = String.format("(%s or %s)", other.filter, or.filter); 28 | } 29 | } 30 | 31 | /** 32 | * Get the string value of the filter. 33 | * 34 | * @return the string value of the filter 35 | */ 36 | public String getFilter() { 37 | return filter; 38 | } 39 | 40 | /** 41 | * Create a new {@code Filter}, concatenating the provided 42 | * filter using the {@code and} operator. 43 | * 44 | * @param other the other filter to concatenate to this one 45 | * @return the new filter 46 | */ 47 | public Filter and(Filter other) { 48 | return new Filter(this, other, null); 49 | } 50 | 51 | /** 52 | * Create a new {@code Filter}, concatenating the provided 53 | * filter using the {@code or} operator. 54 | * 55 | * @param other the other filter to concatenate to this one 56 | * @return the new filter 57 | */ 58 | public Filter or(Filter other) { 59 | return new Filter(this, null, other); 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | Filter filter1 = (Filter) o; 67 | return Objects.equals(filter, filter1.filter); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return Objects.hash(filter); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return new StringJoiner(", ", Filter.class.getSimpleName() + "[", "]") 78 | .add("filter='" + filter + "'") 79 | .toString(); 80 | } 81 | 82 | /** 83 | * Create a new title Filter builder. 84 | * 85 | * @return a new {@code Filter.Builder} 86 | */ 87 | public static Builder title() { 88 | return new Builder("title"); 89 | } 90 | 91 | /** 92 | * Create a new name Filter builder. 93 | * 94 | * @return a new {@code Filter.Builder} 95 | */ 96 | public static Builder name() { 97 | return new Builder("name"); 98 | } 99 | 100 | /** 101 | * Create a new Filter builder on the given property. 102 | * 103 | * @param property the property to filter on 104 | * @return a new {@code Filter.Builder} 105 | */ 106 | public static Builder onProperty(String property) { 107 | return new Builder(property); 108 | } 109 | 110 | /** 111 | * The builder class used to build a new {@link Filter}. 112 | */ 113 | public static class Builder { 114 | private final String property; 115 | private Operator operator; 116 | private String value; 117 | 118 | private Builder(String property) { 119 | this.property = property; 120 | } 121 | 122 | private Filter withOpAndValue(Operator op, String value) { 123 | this.operator = op; 124 | this.value = value; 125 | return new Filter(this); 126 | } 127 | 128 | /** 129 | * Builds a new {@code Filter} with the 'eq' operator. 130 | * Filters based on the property exactly matching the given value. 131 | * 132 | * @param value the value that the property should be equal to 133 | * @return the new {@code Filter} 134 | */ 135 | public Filter equals(String value) { 136 | return withOpAndValue(Operator.EQUALS, value); 137 | } 138 | 139 | /** 140 | * Builds a new {@code Filter} with the 'co' operator. 141 | * Filters based on the property containing the given value. 142 | * 143 | * @param value the value that the property should contain 144 | * @return the new {@code Filter} 145 | */ 146 | public Filter contains(String value) { 147 | return withOpAndValue(Operator.CONTAINS, value); 148 | } 149 | 150 | /** 151 | * Builds a new {@code Filter} with the 'sw' operator. 152 | * Filters based on the property starting with the given value. 153 | * 154 | * @param value the value that the property should start with 155 | * @return the new {@code Filter} 156 | */ 157 | public Filter startsWith(String value) { 158 | return withOpAndValue(Operator.STARTS_WITH, value); 159 | } 160 | 161 | /** 162 | * Builds a new {@code Filter} with the 'pr' operator. 163 | * Filters based on the property being present. 164 | * 165 | * @return the new {@code Filter} 166 | */ 167 | public Filter present() { 168 | return withOpAndValue(Operator.PRESENT, ""); 169 | } 170 | 171 | /** 172 | * Builds a new {@code Filter} with the 'gt' operator. 173 | * Filters based on the property being alphanumerically greater than 174 | * the given value. 175 | * 176 | * @param value the value that the property should be greater than 177 | * @return the new {@code Filter} 178 | */ 179 | public Filter greaterThan(String value) { 180 | return withOpAndValue(Operator.GREATER_THAN, value); 181 | } 182 | 183 | /** 184 | * Builds a new {@code Filter} with the 'ge' operator. 185 | * Filters based on the property being alphanumerically greater than 186 | * or equal to the given value. 187 | * 188 | * @param value the value that the property should be greater than or equal to 189 | * @return the new {@code Filter} 190 | */ 191 | public Filter greaterThanOrEqual(String value) { 192 | return withOpAndValue(Operator.GREATER_THAN_OR_EQUAL, value); 193 | } 194 | 195 | /** 196 | * Builds a new {@code Filter} with the 'lt' operator. 197 | * Filters based on the property being alphanumerically less than 198 | * the given value. 199 | * 200 | * @param value the value that the property should be less than 201 | * @return the new {@code Filter} 202 | */ 203 | public Filter lessThan(String value) { 204 | return withOpAndValue(Operator.LESS_THAN, value); 205 | } 206 | 207 | /** 208 | * Builds a new {@code Filter} with the 'le' operator. 209 | * Filters based on the property being alphanumerically less than 210 | * or equal to the given value. 211 | * 212 | * @param value the value that the property should be less than or equal to 213 | * @return the new {@code Filter} 214 | */ 215 | public Filter lessThanOrEqual(String value) { 216 | return withOpAndValue(Operator.LESS_THAN_OR_EQUAL, value); 217 | } 218 | } 219 | 220 | private enum Operator { 221 | EQUALS("eq"), 222 | CONTAINS("co"), 223 | STARTS_WITH("sw"), 224 | PRESENT("pr"), 225 | GREATER_THAN("gt"), 226 | GREATER_THAN_OR_EQUAL("ge"), 227 | LESS_THAN("lt"), 228 | LESS_THAN_OR_EQUAL("le"); 229 | 230 | private final String opText; 231 | 232 | Operator(String opText) { 233 | this.opText = opText; 234 | } 235 | 236 | String getOpText() { 237 | return opText; 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/GeneratorRecipe.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import java.util.StringJoiner; 10 | 11 | /** 12 | * Represents a recipe for generating a password. 13 | */ 14 | public class GeneratorRecipe { 15 | private final Integer length; 16 | private final List characterSets; 17 | 18 | @JsonCreator 19 | GeneratorRecipe(@JsonProperty("length") Integer length, 20 | @JsonProperty("characterSets") List characterSets) { 21 | this.length = length; 22 | this.characterSets = Collections.unmodifiableList(characterSets); 23 | } 24 | 25 | /** 26 | * Get the length of the recipe. 27 | * 28 | * @return the length 29 | */ 30 | public Integer getLength() { 31 | return length; 32 | } 33 | 34 | /** 35 | * Get the list of allowed characters. 36 | * 37 | * @return the list of allowed characters 38 | */ 39 | public List getCharacterSets() { 40 | return characterSets; 41 | } 42 | 43 | /** 44 | * Create a new {@code GeneratorRecipe.Builder} with the given list of characters. 45 | * 46 | * @param characters the characters to include in generated passwords 47 | * @return the new Builder 48 | */ 49 | public static Builder withAllowedCharacters(List characters) { 50 | return new Builder(characters); 51 | } 52 | 53 | /** 54 | * Create a new {@code GeneratorRecipe.Builder} with only letter characters allowed. 55 | * 56 | * @return the new Builder 57 | */ 58 | public static Builder letters() { 59 | return new Builder(CharacterSet.letters()); 60 | } 61 | 62 | /** 63 | * Create a new {@code GeneratorRecipe.Builder} with only digit characters allowed. 64 | * 65 | * @return the new Builder 66 | */ 67 | public static Builder digits() { 68 | return new Builder(CharacterSet.digits()); 69 | } 70 | 71 | /** 72 | * Create a new {@code GeneratorRecipe.Builder} with only symbol characters allowed. 73 | * 74 | * @return the new Builder 75 | */ 76 | public static Builder symbols() { 77 | return new Builder(CharacterSet.symbols()); 78 | } 79 | 80 | /** 81 | * Create a new {@code GeneratorRecipe.Builder} with letters and digits allowed. 82 | * 83 | * @return the new Builder 84 | */ 85 | public static Builder lettersAndDigits() { 86 | return new Builder(CharacterSet.lettersAndDigits()); 87 | } 88 | 89 | /** 90 | * Create a new {@code GeneratorRecipe.Builder} with letters and symbols allowed. 91 | * 92 | * @return the new Builder 93 | */ 94 | public static Builder lettersAndSymbols() { 95 | return new Builder(CharacterSet.lettersAndSymbols()); 96 | } 97 | 98 | /** 99 | * Create a new {@code GeneratorRecipe.Builder} with digits and symbols allowed. 100 | * 101 | * @return the new Builder 102 | */ 103 | public static Builder digitsAndSymbols() { 104 | return new Builder(CharacterSet.digitsAndSymbols()); 105 | } 106 | 107 | /** 108 | * Create a new {@code GeneratorRecipe.Builder} with all characters allowed. 109 | * 110 | * @return the new Builder 111 | */ 112 | public static Builder allCharacters() { 113 | return new Builder(CharacterSet.allCharacters()); 114 | } 115 | 116 | @Override 117 | public boolean equals(Object o) { 118 | if (this == o) return true; 119 | if (o == null || getClass() != o.getClass()) return false; 120 | GeneratorRecipe that = (GeneratorRecipe) o; 121 | return Objects.equals(length, that.length) 122 | && Objects.equals(characterSets, that.characterSets); 123 | } 124 | 125 | @Override 126 | public int hashCode() { 127 | return Objects.hash(length, characterSets); 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | return new StringJoiner(", ", GeneratorRecipe.class.getSimpleName() + "[", "]") 133 | .add("length=" + length) 134 | .add("characterSets=" + characterSets) 135 | .toString(); 136 | } 137 | 138 | /** 139 | * The builder class used to build a new {@link GeneratorRecipe}. 140 | */ 141 | public static class Builder { 142 | private final List characters; 143 | 144 | Builder(List characters) { 145 | this.characters = characters; 146 | } 147 | 148 | /** 149 | * Create a new {@code GeneratorRecipe} with the given length. 150 | * 151 | * @param length the required length for generated passwords 152 | * @return a new {@code GeneratorRecipe} instance 153 | */ 154 | public GeneratorRecipe ofLength(int length) { 155 | return new GeneratorRecipe(length, characters); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Id.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Objects; 7 | import java.util.StringJoiner; 8 | 9 | public class Id { 10 | private final String id; 11 | private final String name; 12 | 13 | @JsonCreator 14 | public Id(@JsonProperty("id") String id, 15 | @JsonProperty("name") String name) { 16 | this.id = id; 17 | this.name = name; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | Id id = (Id) o; 29 | return Objects.equals(this.id, id.id) && Objects.equals(this.name, id.name); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(id, name); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return new StringJoiner(", ", Id.class.getSimpleName() + "[", "]") 40 | .add("id='" + id + "'") 41 | .add("name='" + name + "'") 42 | .toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Item.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAlias; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | 7 | import java.time.Instant; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.StringJoiner; 12 | 13 | /** 14 | * Represents an item that is stored in a {@link Vault}. 15 | */ 16 | @JsonDeserialize(builder = Item.Builder.class) 17 | public class Item { 18 | private final String id; 19 | private final String title; 20 | private final Id vaultId; 21 | private final Category category; 22 | private final List urls; 23 | private final Boolean favorite; 24 | private final List tags; 25 | private final Integer version; 26 | private final Boolean trashed; 27 | private final Instant createdAt; 28 | private final Instant updatedAt; 29 | private final String lastEditedBy; 30 | private final List

sections; 31 | private final List fields; 32 | private final List files; 33 | private final String additionalInformation; 34 | 35 | private Item(Builder builder) { 36 | this.id = builder.id; 37 | this.title = builder.title; 38 | this.vaultId = builder.vaultId; 39 | this.category = builder.category; 40 | this.urls = builder.urls; 41 | this.favorite = builder.favorite; 42 | this.tags = builder.tags; 43 | this.version = builder.version; 44 | this.trashed = builder.trashed; 45 | this.createdAt = builder.createdAt; 46 | this.updatedAt = builder.updatedAt; 47 | this.lastEditedBy = builder.lastEditedBy; 48 | this.sections = builder.sections; 49 | this.fields = builder.fields; 50 | this.files = builder.files; 51 | this.additionalInformation = builder.additionalInformation; 52 | } 53 | 54 | /** 55 | * Get the unique ID of the item. 56 | * 57 | * @return the id of this item 58 | */ 59 | public String getId() { 60 | return id; 61 | } 62 | 63 | /** 64 | * Get the title of the item. 65 | * 66 | * @return the title of this item 67 | */ 68 | public String getTitle() { 69 | return title; 70 | } 71 | 72 | /** 73 | * Get the vault {@link Id} indicating the vault that this item belongs to. 74 | * 75 | * @return the vault id that this item belongs to 76 | */ 77 | @JsonProperty("vault") 78 | public Id getVaultId() { 79 | return vaultId; 80 | } 81 | 82 | /** 83 | * Get the category of the item. 84 | * 85 | * @return the category of this item 86 | */ 87 | public Category getCategory() { 88 | return category; 89 | } 90 | 91 | /** 92 | * Get the list of URLs associated with the item. 93 | * 94 | * @return the list of URLs associated with this item 95 | */ 96 | public List getUrls() { 97 | return urls; 98 | } 99 | 100 | /** 101 | * Get whether or not the item has been marked as a favorite. 102 | * 103 | * @return true is this item is a favorite, false otherwise 104 | */ 105 | public Boolean getFavorite() { 106 | return favorite; 107 | } 108 | 109 | /** 110 | * Get the list of tags associated with the item. 111 | * 112 | * @return the list of tags associated with this item 113 | */ 114 | public List getTags() { 115 | return tags; 116 | } 117 | 118 | /** 119 | * Get the version of the item. 120 | * 121 | * @return the version of this item 122 | */ 123 | public Integer getVersion() { 124 | return version; 125 | } 126 | 127 | /** 128 | * Get whether this item is in the trash. 129 | * 130 | * @return true if this item is in the trash, false otherwise 131 | */ 132 | public Boolean getTrashed() { 133 | return trashed; 134 | } 135 | 136 | /** 137 | * Ge the {@link Instant} this item was created at. 138 | * 139 | * @return the time the item was created 140 | */ 141 | @JsonProperty("created_at") 142 | public Instant getCreatedAt() { 143 | return createdAt; 144 | } 145 | 146 | /** 147 | * Ge the {@link Instant} this item was last updated. 148 | * 149 | * @return the time the item was last updated 150 | */ 151 | @JsonProperty("updated_at") 152 | public Instant getUpdatedAt() { 153 | return updatedAt; 154 | } 155 | 156 | /** 157 | * Get the ID of the actor that this item was last edited by. 158 | * 159 | * @return the id of the actor this item was last editied by 160 | */ 161 | @JsonProperty("last_edited_by") 162 | public String getLastEditedBy() { 163 | return lastEditedBy; 164 | } 165 | 166 | /** 167 | * Get the list of sections contained in this item. 168 | * 169 | * @return the list of sections contained in this item 170 | */ 171 | public List
getSections() { 172 | return sections; 173 | } 174 | 175 | /** 176 | * Get the list of fields contained in this item. 177 | * 178 | * @return the list of fields contained in this item 179 | */ 180 | public List getFields() { 181 | return fields; 182 | } 183 | 184 | /** 185 | * Get the list of files attached to this item. 186 | * 187 | * @return the list of files attached to this item 188 | */ 189 | public List getFiles() { 190 | return files; 191 | } 192 | 193 | /** 194 | * Get any additional information for this item. 195 | * 196 | * @return the item's additional information 197 | */ 198 | @JsonProperty("additional_information") 199 | public String getAdditionalInformation() { 200 | return additionalInformation; 201 | } 202 | 203 | @Override 204 | public boolean equals(Object o) { 205 | if (this == o) return true; 206 | if (o == null || getClass() != o.getClass()) return false; 207 | Item item = (Item) o; 208 | return Objects.equals(id, item.id); 209 | } 210 | 211 | @Override 212 | public int hashCode() { 213 | return Objects.hash(id); 214 | } 215 | 216 | @Override 217 | public String toString() { 218 | return new StringJoiner(", ", Item.class.getSimpleName() + "[", "]") 219 | .add("id='" + id + "'") 220 | .add("title='" + title + "'") 221 | .add("vaultId=" + vaultId) 222 | .add("category=" + category) 223 | .add("urls=" + urls) 224 | .add("favorite=" + favorite) 225 | .add("tags=" + tags) 226 | .add("version=" + version) 227 | .add("trashed=" + trashed) 228 | .add("createdAt=" + createdAt) 229 | .add("updatedAt=" + updatedAt) 230 | .add("lastEditedBy='" + lastEditedBy + "'") 231 | .add("sections=" + sections) 232 | .add("fields=" + fields) 233 | .add("files=" + files) 234 | .add("additionalInformation='" + additionalInformation + "'") 235 | .toString(); 236 | } 237 | 238 | public static Builder builder() { 239 | return new Builder(); 240 | } 241 | 242 | public static Builder login() { 243 | return builder().withCategory(Category.LOGIN); 244 | } 245 | 246 | public static Builder password() { 247 | return builder().withCategory(Category.PASSWORD); 248 | } 249 | 250 | public static class Builder { 251 | private String id; 252 | private String title; 253 | private Id vaultId; 254 | private Category category; 255 | private List urls = new ArrayList<>(); 256 | private Boolean favorite = false; 257 | private List tags = new ArrayList<>(); 258 | private Integer version; 259 | private Boolean trashed = false; 260 | private Instant createdAt; 261 | private Instant updatedAt; 262 | private String lastEditedBy; 263 | private List
sections = new ArrayList<>(); 264 | private List fields = new ArrayList<>(); 265 | private List files = new ArrayList<>(); 266 | private String additionalInformation; 267 | 268 | public Builder fromItem(Item item) { 269 | this.id = item.id; 270 | this.title = item.title; 271 | this.vaultId = item.vaultId; 272 | this.category = item.category; 273 | this.urls = item.urls; 274 | this.favorite = item.favorite; 275 | this.tags = item.tags; 276 | this.version = item.version; 277 | this.trashed = item.trashed; 278 | this.createdAt = item.createdAt; 279 | this.updatedAt = item.updatedAt; 280 | this.lastEditedBy = item.lastEditedBy; 281 | this.sections = item.sections; 282 | this.fields = item.fields; 283 | this.files = item.files; 284 | this.additionalInformation = item.additionalInformation; 285 | 286 | return this; 287 | } 288 | 289 | public Builder withId(String id) { 290 | this.id = id; 291 | return this; 292 | } 293 | 294 | public Builder withTitle(String title) { 295 | this.title = title; 296 | return this; 297 | } 298 | 299 | public Builder withVault(Vault vault) { 300 | this.vaultId = new Id(vault.getId(), ""); 301 | return this; 302 | } 303 | 304 | @JsonProperty("vault") 305 | public Builder withVaultId(Id vaultId) { 306 | this.vaultId = vaultId; 307 | return this; 308 | } 309 | 310 | public Builder withVaultId(String id) { 311 | this.vaultId = new Id(id, ""); 312 | return this; 313 | } 314 | 315 | public Builder withCategory(Category category) { 316 | this.category = category; 317 | return this; 318 | } 319 | 320 | public Builder withUrls(List urls) { 321 | this.urls.addAll(urls); 322 | return this; 323 | } 324 | 325 | public Builder withUrl(URL url) { 326 | this.urls.add(url); 327 | return this; 328 | } 329 | 330 | public Builder withFavorite(Boolean favorite) { 331 | this.favorite = favorite; 332 | return this; 333 | } 334 | 335 | public Builder withTags(List tags) { 336 | this.tags.addAll(tags); 337 | return this; 338 | } 339 | 340 | public Builder withTag(String tag) { 341 | this.tags.add(tag); 342 | return this; 343 | } 344 | 345 | public Builder withVersion(Integer version) { 346 | this.version = version; 347 | return this; 348 | } 349 | 350 | public Builder withTrashed(Boolean trashed) { 351 | this.trashed = trashed; 352 | return this; 353 | } 354 | 355 | @JsonProperty("created_at") 356 | @JsonAlias("createdAt") 357 | public Builder withCreatedAt(Instant createdAt) { 358 | this.createdAt = createdAt; 359 | return this; 360 | } 361 | 362 | @JsonProperty("updated_at") 363 | @JsonAlias("updatedAt") 364 | public Builder withUpdatedAt(Instant updatedAt) { 365 | this.updatedAt = updatedAt; 366 | return this; 367 | } 368 | 369 | @JsonProperty("last_edited_by") 370 | @JsonAlias("lastEditedBy") 371 | public Builder withLastEditedBy(String lastEditedBy) { 372 | this.lastEditedBy = lastEditedBy; 373 | return this; 374 | } 375 | 376 | public Builder withSections(List
sections) { 377 | this.sections.addAll(sections); 378 | return this; 379 | } 380 | 381 | public Builder withSection(Section section) { 382 | this.sections.add(section); 383 | return this; 384 | } 385 | 386 | public Builder withFields(List fields) { 387 | this.fields.addAll(fields); 388 | return this; 389 | } 390 | 391 | public Builder withField(Field field) { 392 | this.fields.add(field); 393 | return this; 394 | } 395 | 396 | public Builder withFiles(List files) { 397 | this.files.addAll(files); 398 | return this; 399 | } 400 | 401 | public Builder withFile(File file) { 402 | this.files.add(file); 403 | return this; 404 | } 405 | 406 | @JsonProperty("additional_information") 407 | @JsonAlias("additionalInformation") 408 | public Builder withAdditionalInformation(String additionalInformation) { 409 | this.additionalInformation = additionalInformation; 410 | return this; 411 | } 412 | 413 | public Item build() { 414 | return new Item(this); 415 | } 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/PasswordDetails.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.StringJoiner; 9 | 10 | /** 11 | * Contains the password details for a password {@link Field}. 12 | */ 13 | public class PasswordDetails { 14 | private final Double entropy; 15 | private final Boolean generated; 16 | private final String strength; // TERRIBLE, WEAK, FAIR, GOOD, VERY_GOOD, EXCELLENT, FANTASTIC 17 | private final List history; 18 | 19 | @JsonCreator 20 | public PasswordDetails(@JsonProperty("entropy") Double entropy, 21 | @JsonProperty("generated") Boolean generated, 22 | @JsonProperty("strength") String strength, 23 | @JsonProperty("history") List history) { 24 | this.entropy = entropy; 25 | this.generated = generated; 26 | this.strength = strength; 27 | this.history = history; 28 | } 29 | 30 | /** 31 | * Get the entropy of the password if generated. 32 | * 33 | * @return the entropy of the password if generated 34 | */ 35 | public Double getEntropy() { 36 | return entropy; 37 | } 38 | 39 | /** 40 | * Get weather the password was generated or not. 41 | * 42 | * @return true if the password was generated, false otherwise 43 | */ 44 | public Boolean getGenerated() { 45 | return generated; 46 | } 47 | 48 | /** 49 | * Get the strength of the password. 50 | * 51 | * @return the strength of the password 52 | */ 53 | public String getStrength() { 54 | return strength; 55 | } 56 | 57 | /** 58 | * Get the historical values of the password. 59 | * 60 | * @return the historical values of the password 61 | */ 62 | public List getHistory() { 63 | return history; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) { 68 | if (this == o) return true; 69 | if (o == null || getClass() != o.getClass()) return false; 70 | PasswordDetails that = (PasswordDetails) o; 71 | return Objects.equals(strength, that.strength); 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return Objects.hash(strength); 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return new StringJoiner(", ", PasswordDetails.class.getSimpleName() + "[", "]") 82 | .add("entropy=" + entropy) 83 | .add("generated=" + generated) 84 | .add("strength='" + strength + "'") 85 | .add("history=" + history) 86 | .toString(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Patch.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | 5 | import java.util.Objects; 6 | import java.util.StringJoiner; 7 | 8 | @JsonDeserialize(builder = Patch.Builder.class) 9 | public class Patch { 10 | private final PatchOperation op; 11 | private final String path; 12 | private final Object value; 13 | 14 | private Patch(PatchOperation op, String path, Object value) { 15 | this.op = op; 16 | this.path = path; 17 | this.value = value; 18 | } 19 | 20 | public PatchOperation getOp() { 21 | return op; 22 | } 23 | 24 | public String getPath() { 25 | return path; 26 | } 27 | 28 | public Object getValue() { 29 | return value; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (o == null || getClass() != o.getClass()) return false; 36 | Patch patch = (Patch) o; 37 | return op == patch.op 38 | && Objects.equals(path, patch.path) 39 | && Objects.equals(value, patch.value); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(op, path, value); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return new StringJoiner(", ", Patch.class.getSimpleName() + "[", "]") 50 | .add("op=" + op) 51 | .add("path='" + path + "'") 52 | .add("value=" + value) 53 | .toString(); 54 | } 55 | 56 | public static Builder builder() { 57 | return new Builder(); 58 | } 59 | 60 | public static Builder add() { 61 | return builder().withOp(PatchOperation.ADD); 62 | } 63 | 64 | public static Builder remove() { 65 | return builder().withOp(PatchOperation.REMOVE); 66 | } 67 | 68 | public static Builder replace() { 69 | return builder().withOp(PatchOperation.REPLACE); 70 | } 71 | 72 | public static class Builder { 73 | private PatchOperation op; 74 | private String path; 75 | private Object value; 76 | 77 | public Builder withOp(PatchOperation op) { 78 | this.op = op; 79 | return this; 80 | } 81 | 82 | public Builder withPath(String path) { 83 | this.path = path; 84 | return this; 85 | } 86 | 87 | public Builder withValue(Object value) { 88 | this.value = value; 89 | return this; 90 | } 91 | 92 | public Patch build() { 93 | return new Patch(op, path, value); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/PatchOperation.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | /** 4 | * Represents all possible patch operations that can be used when 5 | * updating an item's details in 6 | * {@link com.sanctionco.opconnect.OPConnectClient#patchItem(String, String, Patch...)}. 7 | */ 8 | public enum PatchOperation { 9 | /** 10 | * The "add" operation. Used to add a value at a path. 11 | */ 12 | ADD("add"), 13 | 14 | /** 15 | * The "remove" operation. Used to remove a value at a path. 16 | */ 17 | REMOVE("remove"), 18 | 19 | /** 20 | * The "replace" operation. Used to replace the value at a path. 21 | */ 22 | REPLACE("replace"); 23 | 24 | private final String value; 25 | 26 | PatchOperation(String value) { 27 | this.value = value; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Purpose.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | /** 4 | * Describes the purpose of a {@link Field}. 5 | */ 6 | public enum Purpose { 7 | /** 8 | * A field that holds a username. 9 | */ 10 | USERNAME, 11 | 12 | /** 13 | * A field that holds a password. 14 | */ 15 | PASSWORD, 16 | 17 | /** 18 | * A field that holds notes. 19 | */ 20 | NOTES 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Section.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Objects; 7 | import java.util.StringJoiner; 8 | 9 | /** 10 | * Represents a section within an {@link Item}. 11 | */ 12 | public class Section { 13 | private final String id; 14 | private final String label; 15 | 16 | @JsonCreator 17 | public Section(@JsonProperty("id") String id, 18 | @JsonProperty("label") String label) { 19 | this.id = id; 20 | this.label = label; 21 | } 22 | 23 | /** 24 | * Get the ID of this section. 25 | * 26 | * @return the ID of this section 27 | */ 28 | public String getId() { 29 | return id; 30 | } 31 | 32 | /** 33 | * Get the label of this section. 34 | * 35 | * @return the label of this section 36 | */ 37 | public String getLabel() { 38 | return label; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | Section section = (Section) o; 46 | return id.equals(section.id); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(id); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return new StringJoiner(", ", Section.class.getSimpleName() + "[", "]") 57 | .add("id='" + id + "'") 58 | .add("label='" + label + "'") 59 | .toString(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Type.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; 4 | 5 | public enum Type { 6 | STRING, 7 | EMAIL, 8 | CONCEALED, 9 | URL, 10 | OTP, 11 | DATE, 12 | MONTH_YEAR, 13 | MENU, 14 | CREDIT_CARD_TYPE, 15 | CREDIT_CARD_NUMBER, 16 | PHONE, 17 | ADDRESS, 18 | GENDER, 19 | 20 | @JsonEnumDefaultValue 21 | UNKNOWN 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/URL.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Objects; 7 | import java.util.StringJoiner; 8 | 9 | /** 10 | * Represents a URL contained in an item. 11 | */ 12 | public class URL { 13 | private final String label; 14 | private final String url; 15 | private final Boolean primary; 16 | 17 | @JsonCreator 18 | URL(@JsonProperty("label") String label, 19 | @JsonProperty("href") String url, 20 | @JsonProperty("primary") Boolean primary) { 21 | this.label = label; 22 | this.url = url; 23 | this.primary = primary; 24 | } 25 | 26 | /** 27 | * Get the label associated with this URL. 28 | * 29 | * @return the label of the URL 30 | */ 31 | public String getLabel() { 32 | return label; 33 | } 34 | 35 | /** 36 | * Get the actual string URL. 37 | * 38 | * @return the string URL 39 | */ 40 | @JsonProperty("href") 41 | public String getUrl() { 42 | return url; 43 | } 44 | 45 | /** 46 | * Get whether this URL is a primary URL address or not. 47 | * 48 | * @return true if this URL is primary, false otherwise 49 | */ 50 | public Boolean getPrimary() { 51 | return primary; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) return true; 57 | if (o == null || getClass() != o.getClass()) return false; 58 | URL url1 = (URL) o; 59 | return Objects.equals(url, url1.url) 60 | && Objects.equals(primary, url1.primary) 61 | && Objects.equals(label, url1.label); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return Objects.hash(label, url, primary); 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return new StringJoiner(", ", URL.class.getSimpleName() + "[", "]") 72 | .add("label='" + label + "'") 73 | .add("url='" + url + "'") 74 | .add("primary=" + primary) 75 | .toString(); 76 | } 77 | 78 | /** 79 | * Create a new primary URL from the given string. 80 | * 81 | * @param url the string URL 82 | * @return a new instance of {@code URL} 83 | */ 84 | public static URL primary(String url) { 85 | return new URL("", url, true); 86 | } 87 | 88 | /** 89 | * Create a new non-primary URL from the given string. 90 | * 91 | * @param url the string URL 92 | * @return a new instance of {@code URL} 93 | */ 94 | public static URL standard(String url) { 95 | return new URL("", url, false); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/Vault.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAlias; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | 7 | import java.time.Instant; 8 | import java.util.Objects; 9 | import java.util.StringJoiner; 10 | 11 | /** 12 | * Represents a Vault in 1Password. See the 13 | * documentation 14 | * for more details. 15 | */ 16 | @JsonDeserialize(builder = Vault.Builder.class) 17 | public class Vault { 18 | private final String id; 19 | private final String name; 20 | private final String description; 21 | private final Integer attributeVersion; 22 | private final Integer contentVersion; 23 | private final Integer items; 24 | private final String type; 25 | private final Instant createdAt; 26 | private final Instant updatedAt; 27 | 28 | Vault(Builder builder) { 29 | this.id = builder.id; 30 | this.name = builder.name; 31 | this.description = builder.description; 32 | this.attributeVersion = builder.attributeVersion; 33 | this.contentVersion = builder.contentVersion; 34 | this.items = builder.items; 35 | this.type = builder.type; 36 | this.createdAt = builder.createdAt; 37 | this.updatedAt = builder.updatedAt; 38 | } 39 | 40 | /** 41 | * Get the ID of the vault. 42 | * 43 | * @return the ID of the vault 44 | */ 45 | public String getId() { 46 | return id; 47 | } 48 | 49 | /** 50 | * Get the name of the vault. 51 | * 52 | * @return the name of the vault 53 | */ 54 | public String getName() { 55 | return name; 56 | } 57 | 58 | /** 59 | * Get the description of the vault. 60 | * 61 | * @return the description of the vault 62 | */ 63 | public String getDescription() { 64 | return description; 65 | } 66 | 67 | /** 68 | * Get the version of the vault metadata. 69 | * 70 | * @return the version of the vault metadata 71 | */ 72 | @JsonProperty("attribute_version") 73 | public Integer getAttributeVersion() { 74 | return attributeVersion; 75 | } 76 | 77 | /** 78 | * Get the version of the vault contents. 79 | * 80 | * @return the version of the vault contents 81 | */ 82 | @JsonProperty("content_version") 83 | public Integer getContentVersion() { 84 | return contentVersion; 85 | } 86 | 87 | /** 88 | * Get the number of active items in the vault. 89 | * 90 | * @return the number of active items in the vault 91 | */ 92 | public Integer getItems() { 93 | return items; 94 | } 95 | 96 | /** 97 | * The type of vault. One of {@code EVERYONE} (the team shared vault), {@code PERSONAL} 98 | * (the private vault for the Connect server), or {@code USER_CREATED} (a vault created 99 | * by a user). 100 | * 101 | * @return the type of the vault 102 | */ 103 | public String getType() { 104 | return type; 105 | } 106 | 107 | /** 108 | * Get the {@link Instant} that the vault was created. 109 | * 110 | * @return the time that the vault was created 111 | */ 112 | @JsonProperty("created_at") 113 | public Instant getCreatedAt() { 114 | return createdAt; 115 | } 116 | 117 | /** 118 | * Get the {@link Instant} that the vault was last updated. 119 | * 120 | * @return the time that the vault was last updated 121 | */ 122 | @JsonProperty("updated_at") 123 | public Instant getUpdatedAt() { 124 | return updatedAt; 125 | } 126 | 127 | @Override 128 | public boolean equals(Object o) { 129 | if (this == o) return true; 130 | if (o == null || getClass() != o.getClass()) return false; 131 | Vault vault = (Vault) o; 132 | return id.equals(vault.id); 133 | } 134 | 135 | @Override 136 | public int hashCode() { 137 | return Objects.hash(id); 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | return new StringJoiner(", ", Vault.class.getSimpleName() + "[", "]") 143 | .add("id='" + id + "'") 144 | .add("name='" + name + "'") 145 | .add("description='" + description + "'") 146 | .add("attributeVersion=" + attributeVersion) 147 | .add("contentVersion=" + contentVersion) 148 | .add("items=" + items) 149 | .add("type='" + type + "'") 150 | .add("createdAt=" + createdAt) 151 | .add("updatedAt=" + updatedAt) 152 | .toString(); 153 | } 154 | 155 | public static Builder builder() { 156 | return new Builder(); 157 | } 158 | 159 | public static class Builder { 160 | private String id; 161 | private String name; 162 | private String description; 163 | private Integer attributeVersion; 164 | private Integer contentVersion; 165 | private Integer items; 166 | private String type; 167 | private Instant createdAt; 168 | private Instant updatedAt; 169 | 170 | public Builder withId(String id) { 171 | this.id = id; 172 | return this; 173 | } 174 | 175 | public Builder withName(String name) { 176 | this.name = name; 177 | return this; 178 | } 179 | 180 | public Builder withDescription(String description) { 181 | this.description = description; 182 | return this; 183 | } 184 | 185 | @JsonProperty("attribute_version") 186 | @JsonAlias("attributeVersion") 187 | public Builder withAttributeVersion(Integer attributeVersion) { 188 | this.attributeVersion = attributeVersion; 189 | return this; 190 | } 191 | 192 | @JsonProperty("content_version") 193 | @JsonAlias("contentVersion") 194 | public Builder withContentVersion(Integer contentVersion) { 195 | this.contentVersion = contentVersion; 196 | return this; 197 | } 198 | 199 | public Builder withItems(Integer items) { 200 | this.items = items; 201 | return this; 202 | } 203 | 204 | public Builder withType(String type) { 205 | this.type = type; 206 | return this; 207 | } 208 | 209 | @JsonProperty("created_at") 210 | @JsonAlias("createdAt") 211 | public Builder withCreatedAt(Instant createdAt) { 212 | this.createdAt = createdAt; 213 | return this; 214 | } 215 | 216 | @JsonProperty("updated_at") 217 | @JsonAlias("updatedAt") 218 | public Builder withUpdatedAt(Instant updatedAt) { 219 | this.updatedAt = updatedAt; 220 | return this; 221 | } 222 | 223 | public Vault build() { 224 | return new Vault(this); 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/apiactivity/APIRequest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.apiactivity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.time.Instant; 7 | import java.util.Objects; 8 | import java.util.StringJoiner; 9 | 10 | /** 11 | * Represents an {@code APIRequest} made to 1Password. 12 | */ 13 | public class APIRequest { 14 | private final String requestId; 15 | private final Instant timestamp; 16 | private final APIRequestAction action; 17 | private final APIRequestResult result; 18 | private final Actor actor; 19 | private final Resource resource; 20 | 21 | @JsonCreator 22 | APIRequest(@JsonProperty("requestId") String requestId, 23 | @JsonProperty("timestamp") Instant timestamp, 24 | @JsonProperty("action") APIRequestAction action, 25 | @JsonProperty("result") APIRequestResult result, 26 | @JsonProperty("actor") Actor actor, 27 | @JsonProperty("resource") Resource resource) { 28 | this.requestId = requestId; 29 | this.timestamp = timestamp; 30 | this.action = action; 31 | this.result = result; 32 | this.actor = actor; 33 | this.resource = resource; 34 | } 35 | 36 | /** 37 | * Get the id associated with this request. 38 | * 39 | * @return the id for the request 40 | */ 41 | public String getRequestId() { 42 | return requestId; 43 | } 44 | 45 | /** 46 | * Get the time this request was made. 47 | * 48 | * @return the time the request was made 49 | */ 50 | public Instant getTimestamp() { 51 | return timestamp; 52 | } 53 | 54 | /** 55 | * Get the action requested. 56 | * 57 | * @return the type of action requested 58 | */ 59 | public APIRequestAction getAction() { 60 | return action; 61 | } 62 | 63 | /** 64 | * Get the result of the request. 65 | * 66 | * @return the result of the request 67 | */ 68 | public APIRequestResult getResult() { 69 | return result; 70 | } 71 | 72 | /** 73 | * Get the actor who made the request. 74 | * 75 | * @return the {@link Actor} that made the request 76 | */ 77 | public Actor getActor() { 78 | return actor; 79 | } 80 | 81 | /** 82 | * Get the resource requested. 83 | * 84 | * @return the {@link Resource requested} 85 | */ 86 | public Resource getResource() { 87 | return resource; 88 | } 89 | 90 | @Override 91 | public boolean equals(Object o) { 92 | if (this == o) return true; 93 | if (o == null || getClass() != o.getClass()) return false; 94 | APIRequest that = (APIRequest) o; 95 | return Objects.equals(requestId, that.requestId) 96 | && Objects.equals(timestamp, that.timestamp) 97 | && action == that.action 98 | && result == that.result 99 | && Objects.equals(actor, that.actor) 100 | && Objects.equals(resource, that.resource); 101 | } 102 | 103 | @Override 104 | public int hashCode() { 105 | return Objects.hash(requestId, timestamp, action, result, actor, resource); 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return new StringJoiner(", ", APIRequest.class.getSimpleName() + "[", "]") 111 | .add("requestID='" + requestId + "'") 112 | .add("timestamp=" + timestamp) 113 | .add("action=" + action) 114 | .add("result=" + result) 115 | .add("actor=" + actor) 116 | .add("resource=" + resource) 117 | .toString(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/apiactivity/APIRequestAction.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.apiactivity; 2 | 3 | /** 4 | * Represents an action that occurred from an {@link APIRequest}. 5 | */ 6 | public enum APIRequestAction { 7 | /** 8 | * Represents a READ (i.e. GET) API request action. 9 | */ 10 | READ, 11 | 12 | /** 13 | * Represents a CREATE (i.e. POST) API request action. 14 | */ 15 | CREATE, 16 | 17 | /** 18 | * Represents an UPDATE (i.e. PUT or PATCH) API request action. 19 | */ 20 | UPDATE, 21 | 22 | /** 23 | * Represents a DELETE (i.e. DELETE) API request action. 24 | */ 25 | DELETE 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/apiactivity/APIRequestResult.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.apiactivity; 2 | 3 | /** 4 | * Represents a result of an {@link APIRequest}. 5 | */ 6 | public enum APIRequestResult { 7 | /** 8 | * Represents a successful API request. 9 | */ 10 | SUCCESS, 11 | 12 | /** 13 | * Represents an API request that was denied. 14 | */ 15 | DENY 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/apiactivity/Actor.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.apiactivity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Objects; 7 | import java.util.StringJoiner; 8 | 9 | /** 10 | * Represents an actor (1Password connect server) that performed an {@link APIRequest}. 11 | * 12 | *

See the 13 | * APIRequest documentation for more details. 14 | */ 15 | public class Actor { 16 | private final String id; 17 | private final String account; 18 | private final String jti; 19 | private final String userAgent; 20 | private final String ip; 21 | 22 | @JsonCreator 23 | Actor(@JsonProperty("id") String id, 24 | @JsonProperty("account") String account, 25 | @JsonProperty("jti") String jti, 26 | @JsonProperty("userAgent") String userAgent, 27 | @JsonProperty("ip") String ip) { 28 | this.id = id; 29 | this.account = account; 30 | this.jti = jti; 31 | this.userAgent = userAgent; 32 | this.ip = ip; 33 | } 34 | 35 | /** 36 | * Get the ID of the {@code Actor}. 37 | * 38 | * @return the id of the actor (connect sever) 39 | */ 40 | public String getId() { 41 | return id; 42 | } 43 | 44 | /** 45 | * Get the 1Password account ID. 46 | * 47 | * @return the id of the 1Password account the actor belongs to 48 | */ 49 | public String getAccount() { 50 | return account; 51 | } 52 | 53 | /** 54 | * Get the Access Token ID. 55 | * 56 | * @return the id of the access token used to authenticate the request 57 | */ 58 | public String getJti() { 59 | return jti; 60 | } 61 | 62 | /** 63 | * Get the user-agent string. 64 | * 65 | * @return the user agent string specified in the request 66 | */ 67 | public String getUserAgent() { 68 | return userAgent; 69 | } 70 | 71 | /** 72 | * Get the IP address. 73 | * 74 | * @return the ip address the request originated from 75 | */ 76 | public String getIp() { 77 | return ip; 78 | } 79 | 80 | @Override 81 | public boolean equals(Object o) { 82 | if (this == o) return true; 83 | if (o == null || getClass() != o.getClass()) return false; 84 | Actor actor = (Actor) o; 85 | return Objects.equals(id, actor.id) 86 | && Objects.equals(account, actor.account) 87 | && Objects.equals(jti, actor.jti) 88 | && Objects.equals(userAgent, actor.userAgent) 89 | && Objects.equals(ip, actor.ip); 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | return Objects.hash(id, account, jti, userAgent, ip); 95 | } 96 | 97 | @Override 98 | public String toString() { 99 | return new StringJoiner(", ", Actor.class.getSimpleName() + "[", "]") 100 | .add("id='" + id + "'") 101 | .add("account='" + account + "'") 102 | .add("jti='" + jti + "'") 103 | .add("userAgent='" + userAgent + "'") 104 | .add("ip='" + ip + "'") 105 | .toString(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/apiactivity/Resource.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.apiactivity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.sanctionco.opconnect.model.Id; 6 | 7 | import java.util.Objects; 8 | import java.util.StringJoiner; 9 | 10 | /** 11 | * Represents a resource that was specified in an {@link APIRequest}. 12 | */ 13 | public class Resource { 14 | private final ResourceType type; 15 | private final Id vaultId; 16 | private final Id itemId; 17 | private final Integer itemVersion; 18 | 19 | @JsonCreator 20 | Resource(@JsonProperty("type") ResourceType type, 21 | @JsonProperty("vault") Id vaultId, 22 | @JsonProperty("item") Id itemId, 23 | @JsonProperty("itemVersion") Integer itemVersion) { 24 | this.type = type; 25 | this.vaultId = vaultId; 26 | this.itemId = itemId; 27 | this.itemVersion = itemVersion; 28 | } 29 | 30 | /** 31 | * Get the type of the resource. 32 | * 33 | * @return the type of the resource requested 34 | */ 35 | public ResourceType getType() { 36 | return type; 37 | } 38 | 39 | /** 40 | * Get the ID of the of the vault requested. 41 | * 42 | * @return an object containing the ID of the vault requested 43 | */ 44 | public Id getVaultId() { 45 | return vaultId; 46 | } 47 | 48 | /** 49 | * Get the ID of the item requested. 50 | * 51 | * @return an object containing the ID of the item requested 52 | */ 53 | public Id getItemId() { 54 | return itemId; 55 | } 56 | 57 | /** 58 | * Get the version of the item requested. 59 | * 60 | * @return the version of the item requested 61 | */ 62 | public Integer getItemVersion() { 63 | return itemVersion; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) { 68 | if (this == o) return true; 69 | if (o == null || getClass() != o.getClass()) return false; 70 | Resource resource = (Resource) o; 71 | return type == resource.type 72 | && Objects.equals(vaultId, resource.vaultId) 73 | && Objects.equals(itemId, resource.itemId) 74 | && Objects.equals(itemVersion, resource.itemVersion); 75 | } 76 | 77 | @Override 78 | public int hashCode() { 79 | return Objects.hash(type, vaultId, itemId, itemVersion); 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return new StringJoiner(", ", Resource.class.getSimpleName() + "[", "]") 85 | .add("type=" + type) 86 | .add("vaultId=" + vaultId) 87 | .add("itemId=" + itemId) 88 | .add("itemVersion=" + itemVersion) 89 | .toString(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/apiactivity/ResourceType.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.apiactivity; 2 | 3 | /** 4 | * Represents a type of resource involved in an {@link APIRequest}. 5 | */ 6 | public enum ResourceType { 7 | /** 8 | * Represents the {@link com.sanctionco.opconnect.model.Item} resource. 9 | */ 10 | ITEM, 11 | 12 | /** 13 | * Represents the {@link com.sanctionco.opconnect.model.Vault} resource. 14 | */ 15 | VAULT 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/health/ConnectServer.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.health; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.StringJoiner; 9 | 10 | /** 11 | * Represents details on the 1Password Connect Server, including 12 | * its dependencies. 13 | */ 14 | public class ConnectServer { 15 | private final String name; 16 | private final String version; 17 | private final List dependencies; 18 | 19 | @JsonCreator 20 | ConnectServer(@JsonProperty("name") String name, 21 | @JsonProperty("version") String version, 22 | @JsonProperty("dependencies") List dependencies) { 23 | this.name = name; 24 | this.version = version; 25 | this.dependencies = dependencies; 26 | } 27 | 28 | /** 29 | * The name of the Connect server. 30 | * 31 | * @return the name 32 | */ 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | /** 38 | * The current version of the Connect server. 39 | * 40 | * @return the current version 41 | */ 42 | public String getVersion() { 43 | return version; 44 | } 45 | 46 | /** 47 | * The list of {@link Dependency} objects that the Connect server depends on. 48 | * 49 | * @return the list of {@link Dependency} objects 50 | */ 51 | public List getDependencies() { 52 | return dependencies; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (this == o) return true; 58 | if (o == null || getClass() != o.getClass()) return false; 59 | ConnectServer that = (ConnectServer) o; 60 | return Objects.equals(name, that.name) 61 | && Objects.equals(version, that.version) 62 | && Objects.equals(dependencies, that.dependencies); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(name, version, dependencies); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return new StringJoiner(", ", ConnectServer.class.getSimpleName() + "[", "]") 73 | .add("name='" + name + "'") 74 | .add("version='" + version + "'") 75 | .add("dependencies=" + dependencies) 76 | .toString(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/sanctionco/opconnect/model/health/Dependency.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.health; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Objects; 7 | import java.util.StringJoiner; 8 | 9 | /** 10 | * Represents a 1Password Connect Server dependency. 11 | */ 12 | public class Dependency { 13 | private final String service; 14 | private final String status; 15 | private final String message; 16 | 17 | @JsonCreator 18 | Dependency(@JsonProperty("service") String service, 19 | @JsonProperty("status") String status, 20 | @JsonProperty("message") String message) { 21 | this.service = service; 22 | this.status = status; 23 | this.message = message; 24 | } 25 | 26 | /** 27 | * The name of the dependency service. 28 | * 29 | * @return the name 30 | */ 31 | public String getService() { 32 | return service; 33 | } 34 | 35 | /** 36 | * The current status of the dependency. 37 | * 38 | * @return the status 39 | */ 40 | public String getStatus() { 41 | return status; 42 | } 43 | 44 | /** 45 | * The latest message that describes the status of the dependency. 46 | * 47 | * @return the message description 48 | */ 49 | public String getMessage() { 50 | return message; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | Dependency that = (Dependency) o; 58 | return Objects.equals(service, that.service); 59 | } 60 | 61 | @Override 62 | public int hashCode() { 63 | return Objects.hash(service); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return new StringJoiner(", ", Dependency.class.getSimpleName() + "[", "]") 69 | .add("service='" + service + "'") 70 | .add("status='" + status + "'") 71 | .add("message='" + message + "'") 72 | .toString(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect; 2 | 3 | import com.sanctionco.opconnect.model.Category; 4 | import com.sanctionco.opconnect.model.Field; 5 | import com.sanctionco.opconnect.model.File; 6 | import com.sanctionco.opconnect.model.Filter; 7 | import com.sanctionco.opconnect.model.GeneratorRecipe; 8 | import com.sanctionco.opconnect.model.Id; 9 | import com.sanctionco.opconnect.model.Item; 10 | import com.sanctionco.opconnect.model.Patch; 11 | import com.sanctionco.opconnect.model.Purpose; 12 | import com.sanctionco.opconnect.model.Section; 13 | import com.sanctionco.opconnect.model.Type; 14 | import com.sanctionco.opconnect.model.URL; 15 | import com.sanctionco.opconnect.model.Vault; 16 | import com.sanctionco.opconnect.model.apiactivity.APIRequest; 17 | import com.sanctionco.opconnect.model.apiactivity.APIRequestResult; 18 | import com.sanctionco.opconnect.model.health.ConnectServer; 19 | 20 | import java.time.Instant; 21 | import java.util.Arrays; 22 | import java.util.List; 23 | import java.util.Set; 24 | import java.util.concurrent.CompletionException; 25 | import java.util.stream.Collectors; 26 | 27 | import org.junit.jupiter.api.Assertions; 28 | import org.junit.jupiter.api.DisplayName; 29 | import org.junit.jupiter.api.MethodOrderer; 30 | import org.junit.jupiter.api.Order; 31 | import org.junit.jupiter.api.Test; 32 | import org.junit.jupiter.api.TestInstance; 33 | import org.junit.jupiter.api.TestMethodOrder; 34 | import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; 35 | import org.junit.jupiter.params.ParameterizedTest; 36 | import org.junit.jupiter.params.provider.EnumSource; 37 | 38 | import retrofit2.HttpException; 39 | 40 | import static org.junit.jupiter.api.Assertions.assertAll; 41 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 42 | import static org.junit.jupiter.api.Assertions.assertEquals; 43 | import static org.junit.jupiter.api.Assertions.assertFalse; 44 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 45 | import static org.junit.jupiter.api.Assertions.assertNotNull; 46 | import static org.junit.jupiter.api.Assertions.assertThrows; 47 | import static org.junit.jupiter.api.Assertions.assertTrue; 48 | import static org.junit.jupiter.api.Assertions.fail; 49 | 50 | @DisplayName("OPConnectClient") 51 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 52 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 53 | @EnabledIfEnvironmentVariable(named = "OP_ACCESS_TOKEN", matches = ".*") 54 | class IntegrationTest { 55 | private static final String TOKEN = System.getenv("OP_ACCESS_TOKEN"); 56 | private static final OPConnectClient CLIENT = OPConnectClient.builder() 57 | .withEndpoint("http://localhost:8080/") 58 | .withAccessToken(TOKEN) 59 | .build(); 60 | 61 | private static final String VAULT_ID = "5ve5wfpdu2kxxhj2jdozmes5re"; 62 | private static final String LOGIN_ITEM_ID = "piy7k3izsuzafhypw6iddpwhqe"; 63 | private static final String DOCUMENT_ITEM_ID = "ukg5hwis76syhwdgc6jqcyx4vq"; 64 | private static final String DOCUMENT_FILE_ID = "tf5sqssrufeevojd4urpmf4n3u"; 65 | private static final Integer CATEGORY_COUNT = Category.values().length - 1; 66 | private static final List ALL_ITEMS = CLIENT.listItems(VAULT_ID).join(); 67 | 68 | private String createdItemId; 69 | 70 | @Test 71 | void shouldListSingleVault() { 72 | List vaults = CLIENT.listVaults().join(); 73 | 74 | assertEquals(1, vaults.size()); 75 | 76 | Vault vault = vaults.get(0); 77 | 78 | assertAll("The vault properties are as expected", 79 | () -> assertEquals(VAULT_ID, vault.getId()), 80 | () -> assertEquals("Integration Test", vault.getName()), 81 | () -> assertEquals("Java SDK Integration Tests", vault.getDescription()), 82 | () -> assertTrue(vault.getCreatedAt().isBefore(Instant.now()))); 83 | } 84 | 85 | @Test 86 | void shouldListVaultsWithStringFilter() { 87 | List vaults = CLIENT.listVaults("name eq \"Integration Test\"").join(); 88 | 89 | assertEquals(1, vaults.size()); 90 | 91 | Vault vault = vaults.get(0); 92 | 93 | assertAll("The vault properties are as expected", 94 | () -> assertEquals(VAULT_ID, vault.getId()), 95 | () -> assertEquals("Integration Test", vault.getName()), 96 | () -> assertEquals("Java SDK Integration Tests", vault.getDescription()), 97 | () -> assertTrue(vault.getCreatedAt().isBefore(Instant.now()))); 98 | 99 | List noVaults = CLIENT.listVaults("name eq \"Nonexistent\"").join(); 100 | 101 | assertEquals(0, noVaults.size()); 102 | } 103 | 104 | @Test 105 | void shouldListVaultsWithFilterObject() { 106 | List vaults = CLIENT.listVaults(Filter.name().equals("Integration Test")).join(); 107 | 108 | assertEquals(1, vaults.size()); 109 | 110 | Vault vault = vaults.get(0); 111 | 112 | assertAll("The vault properties are as expected", 113 | () -> assertEquals(VAULT_ID, vault.getId()), 114 | () -> assertEquals("Integration Test", vault.getName()), 115 | () -> assertEquals("Java SDK Integration Tests", vault.getDescription()), 116 | () -> assertTrue(vault.getCreatedAt().isBefore(Instant.now()))); 117 | 118 | List noVaults = CLIENT.listVaults(Filter.name().equals("Nonexistent")).join(); 119 | 120 | assertEquals(0, noVaults.size()); 121 | } 122 | 123 | @Test 124 | void shouldFailToReadUnknownVaultId() { 125 | CompletionException e = assertThrows(CompletionException.class, 126 | () -> CLIENT.getVault(LOGIN_ITEM_ID).join()); 127 | 128 | assertTrue(e.getCause() instanceof HttpException); 129 | 130 | HttpException httpResponse = (HttpException) e.getCause(); 131 | 132 | assertEquals(403, httpResponse.code()); 133 | } 134 | 135 | @Test 136 | void shouldGetVaultDetails() { 137 | Vault vault = CLIENT.getVault(VAULT_ID).join(); 138 | 139 | assertAll("The vault properties are as expected", 140 | () -> assertEquals(VAULT_ID, vault.getId()), 141 | () -> assertEquals("Integration Test", vault.getName()), 142 | () -> assertEquals("Java SDK Integration Tests", vault.getDescription()), 143 | () -> assertTrue(vault.getCreatedAt().isBefore(Instant.now()))); 144 | } 145 | 146 | @Test 147 | @Order(1) 148 | void shouldListItems() { 149 | List items = CLIENT.listItems(VAULT_ID).join(); 150 | 151 | // CATEGORY_COUNT sample items, each of a different category 152 | assertEquals(CATEGORY_COUNT, items.size()); 153 | items.forEach(item -> assertEquals(VAULT_ID, item.getVaultId().getId())); 154 | 155 | Set categories = items.stream().map(Item::getCategory).collect(Collectors.toSet()); 156 | 157 | assertEquals(CATEGORY_COUNT, categories.size()); 158 | 159 | Arrays.stream(Category.values()) 160 | .forEach(category -> { 161 | // Skip CUSTOM as we don't have that item in the vault 162 | if (Category.CUSTOM.equals(category)) return; 163 | 164 | assertTrue(categories.contains(category)); 165 | }); 166 | } 167 | 168 | @Test 169 | @Order(1) 170 | void shouldListTitleFilteredItemsUsingString() { 171 | List sampleItems = CLIENT.listItems(VAULT_ID, "title co \"Sample\"").join(); 172 | assertEquals(CATEGORY_COUNT, sampleItems.size()); 173 | 174 | List passwordItems = CLIENT.listItems(VAULT_ID, "title eq \"Sample Password\"").join(); 175 | assertEquals(1, passwordItems.size()); 176 | 177 | List passwordAndCreditCard = CLIENT 178 | .listItems(VAULT_ID, "title eq \"Sample Password\" or title co \"Credit\"").join(); 179 | assertEquals(2, passwordAndCreditCard.size()); 180 | 181 | List noItems = CLIENT.listItems(VAULT_ID, "title eq \"Not Exist\"").join(); 182 | assertEquals(0, noItems.size()); 183 | } 184 | 185 | @Test 186 | @Order(1) 187 | void shouldListTitleFilteredItemsUsingFilter() { 188 | List sampleItems = CLIENT 189 | .listItems(VAULT_ID, Filter.title().contains("Sample")).join(); 190 | assertEquals(CATEGORY_COUNT, sampleItems.size()); 191 | 192 | List passwordItems = CLIENT 193 | .listItems(VAULT_ID, Filter.title().equals("Sample Password")).join(); 194 | assertEquals(1, passwordItems.size()); 195 | 196 | List passwordAndCreditCard = CLIENT 197 | .listItems(VAULT_ID, Filter.title() 198 | .equals("Sample Password") 199 | .or(Filter.title().contains("Credit"))) 200 | .join(); 201 | assertEquals(2, passwordAndCreditCard.size()); 202 | 203 | List noItems = CLIENT 204 | .listItems(VAULT_ID, Filter.title().equals("Not Exist")).join(); 205 | assertEquals(0, noItems.size()); 206 | } 207 | 208 | @Test 209 | void shouldFailToReadUnknownItem() { 210 | CompletionException e = assertThrows(CompletionException.class, 211 | () -> CLIENT.getItem(VAULT_ID, VAULT_ID).join()); 212 | 213 | assertTrue(e.getCause() instanceof HttpException); 214 | 215 | HttpException httpResponse = (HttpException) e.getCause(); 216 | 217 | assertEquals(404, httpResponse.code()); 218 | } 219 | 220 | @Test 221 | void shouldGetLoginItemDetails() { 222 | Item item = CLIENT.getItem(VAULT_ID, LOGIN_ITEM_ID).join(); 223 | 224 | assertAll("The item properties are as expected", 225 | () -> assertEquals("Sample Login", item.getTitle()), 226 | () -> assertEquals(Category.LOGIN, item.getCategory()), 227 | () -> assertEquals(1, item.getUrls().size()), 228 | () -> assertEquals("https://www.example.com", item.getUrls().get(0).getUrl()), 229 | () -> assertTrue(item.getUrls().get(0).getPrimary()), 230 | () -> assertTrue(item.getFavorite()), 231 | () -> assertEquals(2, item.getTags().size()), 232 | () -> assertFalse(item.getTrashed()), 233 | () -> assertEquals(4, item.getSections().size()), 234 | () -> assertTrue(item.getSections().contains( 235 | new Section("Section_vdb57dmcdx6mej4wpu632j6pru", "Test Section One"))), 236 | () -> assertEquals(6, item.getFields().size()) 237 | ); 238 | } 239 | 240 | @Test 241 | void shouldListFiles() { 242 | List files = CLIENT.listFiles(VAULT_ID, DOCUMENT_ITEM_ID).join(); 243 | 244 | assertEquals(1, files.size()); 245 | assertEquals("test.txt", files.get(0).getName()); 246 | 247 | // Also be able to get content 248 | files = CLIENT.listFiles(VAULT_ID, DOCUMENT_ITEM_ID, true).join(); 249 | 250 | assertEquals(1, files.size()); 251 | assertEquals("test.txt", files.get(0).getName()); 252 | assertNotNull(files.get(0).getContent()); 253 | assertEquals("Test\n", files.get(0).getDecodedContent()); 254 | } 255 | 256 | @Test 257 | void shouldGetFile() { 258 | File file = CLIENT.getFile(VAULT_ID, DOCUMENT_ITEM_ID, DOCUMENT_FILE_ID).join(); 259 | 260 | assertEquals("test.txt", file.getName()); 261 | 262 | // Also be able to get content 263 | File contentFile = CLIENT.getFile(VAULT_ID, DOCUMENT_ITEM_ID, DOCUMENT_FILE_ID, true).join(); 264 | 265 | assertEquals("test.txt", contentFile.getName()); 266 | assertNotNull(contentFile.getContent()); 267 | assertEquals("Test\n", contentFile.getDecodedContent()); 268 | } 269 | 270 | @Test 271 | void shouldGetFileContent() { 272 | String content = CLIENT.getFileContent(VAULT_ID, DOCUMENT_ITEM_ID, DOCUMENT_FILE_ID).join(); 273 | 274 | assertEquals("Test\n", content); 275 | } 276 | 277 | @ParameterizedTest 278 | @EnumSource(mode = EnumSource.Mode.EXCLUDE, names = { "CUSTOM" }) 279 | void shouldGetItemDetailsForEachCategory(Category category) { 280 | String id = ALL_ITEMS.stream() 281 | .filter(item -> category.equals(item.getCategory())) 282 | .findAny() 283 | .orElseGet(Assertions::fail) 284 | .getId(); 285 | 286 | Item item = CLIENT.getItem(VAULT_ID, id).join(); 287 | 288 | assertAll( 289 | () -> assertEquals(id, item.getId()), 290 | () -> assertEquals(category, item.getCategory())); 291 | } 292 | 293 | @Test 294 | @Order(2) 295 | void shouldCreateItem() { 296 | Item item = Item.login().withTitle("Integration Test Created Login") 297 | .withVaultId(VAULT_ID) 298 | .withField(Field.username("testuser").build()) 299 | .withField(Field.generatedPassword(GeneratorRecipe.letters().ofLength(30)).build()) 300 | .withUrl(URL.primary("https://www.test.com")) 301 | .build(); 302 | 303 | Item created = CLIENT.createItem(VAULT_ID, item).join(); 304 | 305 | this.createdItemId = created.getId(); 306 | 307 | assertAll("Created Item is as expected", 308 | () -> assertEquals("Integration Test Created Login", created.getTitle()), 309 | () -> assertEquals(Category.LOGIN, created.getCategory()), 310 | () -> assertEquals(3, created.getFields().size()), 311 | () -> assertEquals(Purpose.USERNAME, created.getFields().get(0).getPurpose()), 312 | () -> assertEquals("testuser", created.getFields().get(0).getValue()), 313 | () -> assertEquals(1, created.getUrls().size()), 314 | () -> assertEquals("https://www.test.com", created.getUrls().get(0).getUrl()) 315 | ); 316 | } 317 | 318 | @Test 319 | @Order(3) 320 | void shouldPatchItem() throws Exception { 321 | if (createdItemId == null) fail("The createItem test needs to run before patchItem"); 322 | 323 | // Wait for 1 second in order to make sure the created item exists 324 | Thread.sleep(1000); 325 | 326 | // Get the item first 327 | Item created = CLIENT.getItem(VAULT_ID, createdItemId).join(); 328 | String usernameFieldId = created.getFields().get(0).getId(); 329 | Field updatedField = Field.username("patchOne").build(); 330 | 331 | Item patched = CLIENT.patchItem(VAULT_ID, createdItemId, 332 | Patch.replace() 333 | .withValue(updatedField) 334 | .withPath("/fields/" + usernameFieldId) 335 | .build()).join(); 336 | 337 | assertAll( 338 | () -> assertEquals("Integration Test Created Login", patched.getTitle()), 339 | () -> assertEquals(Purpose.USERNAME, patched.getFields().get(0).getPurpose()), 340 | () -> assertEquals("patchOne", patched.getFields().get(0).getValue())); 341 | } 342 | 343 | @Test 344 | @Order(4) 345 | void shouldPatchWithMultipleChanges() throws Exception { 346 | if (createdItemId == null) fail("The createItem test needs to run before patchItem"); 347 | 348 | // Wait in order to make sure the created item exists 349 | Thread.sleep(400L); 350 | 351 | // Get the item first 352 | Item created = CLIENT.getItem(VAULT_ID, createdItemId).join(); 353 | 354 | String usernameFieldId = created.getFields().get(0).getId(); 355 | Field updatedUsername = Field.username("patchTwo").build(); 356 | 357 | Field newField = Field.labeled("multiPatchLabel") 358 | .withType(Type.STRING) 359 | .withValue("patching") 360 | .build(); 361 | 362 | Item patched = CLIENT.patchItem(VAULT_ID, createdItemId, 363 | Patch.add() 364 | .withValue(newField) 365 | .withPath("/fields").build(), 366 | Patch.replace() 367 | .withValue(updatedUsername) 368 | .withPath("/fields/" + usernameFieldId).build()) 369 | .join(); 370 | 371 | assertAll( 372 | () -> assertEquals("Integration Test Created Login", patched.getTitle()), 373 | () -> assertEquals(Purpose.USERNAME, patched.getFields().get(0).getPurpose()), 374 | () -> assertEquals("patchTwo", patched.getFields().get(0).getValue()), 375 | () -> assertEquals(created.getFields().size() + 1, patched.getFields().size())); 376 | } 377 | 378 | @Test 379 | @Order(5) 380 | void shouldReplaceItem() throws Exception { 381 | if (createdItemId == null) fail("The createItem test needs to run before replaceItem"); 382 | 383 | Thread.sleep(400L); 384 | 385 | Item replacement = Item.login().withTitle("Replaced Integration Test Created Login") 386 | .withId(createdItemId) 387 | .withVaultId(VAULT_ID) 388 | .withField(Field.username("replacementuser").build()) 389 | .withField(Field.generatedPassword(GeneratorRecipe.letters().ofLength(30)).build()) 390 | .withUrl(URL.primary("https://www.test.com")) 391 | .build(); 392 | 393 | Item replaced = CLIENT.replaceItem(VAULT_ID, createdItemId, replacement).join(); 394 | 395 | assertAll("Replaced Item is as expected", 396 | () -> assertEquals("Replaced Integration Test Created Login", replaced.getTitle()), 397 | () -> assertEquals(createdItemId, replaced.getId()), 398 | () -> assertEquals(Category.LOGIN, replaced.getCategory()), 399 | () -> assertEquals(3, replaced.getFields().size()), 400 | () -> assertEquals(Purpose.USERNAME, replaced.getFields().get(0).getPurpose()), 401 | () -> assertEquals("replacementuser", replaced.getFields().get(0).getValue()), 402 | () -> assertEquals(1, replaced.getUrls().size()), 403 | () -> assertEquals("https://www.test.com", replaced.getUrls().get(0).getUrl()) 404 | ); 405 | } 406 | 407 | @Test 408 | @Order(6) 409 | void shouldDeleteItem() throws Exception { 410 | if (createdItemId == null) fail("The createItem test needs to run before deleteItem"); 411 | 412 | // Deleting too soon after creation/update can fail 413 | Thread.sleep(400L); 414 | 415 | assertDoesNotThrow(() -> CLIENT.deleteItem(VAULT_ID, createdItemId).join()); 416 | } 417 | 418 | @Test 419 | void shouldListApiActivity() { 420 | List requests = CLIENT.listAPIActivity().join(); 421 | 422 | assertAll("Returned APIRequest list is reasonable", 423 | () -> assertNotNull(requests), 424 | () -> assertNotEquals(0, requests.size()), 425 | () -> assertEquals("HJGVEL46XVGGJFJQXYCADPF5RM", requests.get(0).getActor().getId()), 426 | () -> assertEquals("5R6XDPQ2B5GW3GLDTNKVH7BN6E", requests.get(0).getActor().getAccount()), 427 | () -> assertEquals(APIRequestResult.SUCCESS, requests.get(0).getResult()), 428 | () -> assertEquals(new Id(VAULT_ID, ""), requests.get(0).getResource().getVaultId())); 429 | 430 | // Limit to last 5 431 | List limitedRequests = CLIENT.listAPIActivity(5).join(); 432 | 433 | assertAll("Returned limited APIRequest list is reasonable", 434 | () -> assertNotNull(limitedRequests), 435 | () -> assertEquals(5, limitedRequests.size()), 436 | () -> assertEquals("HJGVEL46XVGGJFJQXYCADPF5RM", 437 | limitedRequests.get(0).getActor().getId()), 438 | () -> assertEquals("5R6XDPQ2B5GW3GLDTNKVH7BN6E", 439 | limitedRequests.get(0).getActor().getAccount())); 440 | 441 | List limitedOffsetRequests = CLIENT.listAPIActivity(6, 2).join(); 442 | 443 | assertAll("Returned limited APIRequest list is reasonable", 444 | () -> assertNotNull(limitedOffsetRequests), 445 | () -> assertEquals(6, limitedOffsetRequests.size()), 446 | () -> assertNotEquals( 447 | limitedRequests.get(0).getRequestId(), 448 | limitedOffsetRequests.get(0).getRequestId()), 449 | () -> assertEquals( 450 | limitedRequests.get(2).getRequestId(), 451 | limitedOffsetRequests.get(0).getRequestId()), 452 | () -> assertEquals( 453 | limitedRequests.get(3).getRequestId(), 454 | limitedOffsetRequests.get(1).getRequestId())); 455 | } 456 | 457 | @Test 458 | void shouldGetHealthStatus() { 459 | ConnectServer server = CLIENT.health().join(); 460 | 461 | assertNotNull(server); 462 | assertEquals("1Password Connect API", server.getName()); 463 | } 464 | 465 | @Test 466 | void shouldHeartbeat() { 467 | assertDoesNotThrow(() -> CLIENT.heartbeat().join()); 468 | } 469 | 470 | @Test 471 | void shouldGetMetrics() { 472 | String metrics = CLIENT.metrics().join(); 473 | 474 | assertNotNull(metrics); 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/OPConnectClientBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 7 | import static org.junit.jupiter.api.Assertions.assertThrows; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | class OPConnectClientBuilderTest { 11 | 12 | @Test 13 | void buildShouldThrowWithoutSettingEndpoint() { 14 | NullPointerException e = assertThrows(NullPointerException.class, 15 | () -> OPConnectClientBuilder.builder().build()); 16 | 17 | assertTrue(e.getMessage().startsWith("You must provide an endpoint")); 18 | } 19 | 20 | @Test 21 | void buildShouldThrowWithoutSettingAccessToken() { 22 | NullPointerException e = assertThrows(NullPointerException.class, 23 | () -> OPConnectClientBuilder.builder().withEndpoint("test").build()); 24 | 25 | assertTrue(e.getMessage().startsWith("You must provide an access token")); 26 | } 27 | 28 | @Test 29 | void shouldBuild() { 30 | OPConnectClientBuilder.builder() 31 | .withEndpoint("https://endpoint") 32 | .withAccessToken("token") 33 | .build(); 34 | } 35 | 36 | @Test 37 | void shouldBuildWithTimeout() { 38 | OPConnectClientBuilder.builder() 39 | .withEndpoint("https://endpoint") 40 | .withAccessToken("token") 41 | .withTimeoutInMilliseconds(5000L) 42 | .build(); 43 | } 44 | 45 | @Test 46 | void testEnsureTrailingSlashExistsNoChange() { 47 | String url = "https://www.sanctionco.com/"; 48 | String result = OPConnectClientBuilder.ensureTrailingSlashExists(url); 49 | 50 | assertEquals(url, result); 51 | } 52 | 53 | @Test 54 | void testEnsureTrailingSlashExistsNoSlash() { 55 | String url = "https://www.sanctionco.com"; 56 | String result = OPConnectClientBuilder.ensureTrailingSlashExists(url); 57 | 58 | assertNotEquals(url, result); 59 | assertEquals("https://www.sanctionco.com/", result); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/OPConnectVaultClientTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect; 2 | 3 | import com.sanctionco.opconnect.model.Filter; 4 | import com.sanctionco.opconnect.model.Item; 5 | import com.sanctionco.opconnect.model.Patch; 6 | 7 | import java.util.Collections; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.mockito.ArgumentMatchers.eq; 12 | import static org.mockito.ArgumentMatchers.same; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.verify; 15 | 16 | class OPConnectVaultClientTest { 17 | 18 | @Test 19 | void vaultClientPassesCallsThrough() { 20 | final OPConnectClient client = mock(OPConnectClient.class); 21 | final Item item = Item.builder().build(); 22 | final Patch patch = Patch.builder().build(); 23 | 24 | OPConnectVaultClient vaultClient = new OPConnectVaultClient(client, "testId"); 25 | 26 | vaultClient.getVault(); 27 | verify(client).getVault(eq("testId")); 28 | 29 | vaultClient.listItems(); 30 | verify(client).listItems(eq("testId")); 31 | 32 | vaultClient.listItems("filter"); 33 | verify(client).listItems(eq("testId"), eq("filter")); 34 | 35 | vaultClient.listItems(Filter.title().present()); 36 | verify(client).listItems(eq("testId"), eq("title pr")); 37 | 38 | vaultClient.getItem("testItemId"); 39 | verify(client).getItem(eq("testId"), eq("testItemId")); 40 | 41 | vaultClient.createItem(item); 42 | verify(client).createItem(eq("testId"), same(item)); 43 | 44 | vaultClient.replaceItem("itemId", item); 45 | verify(client).replaceItem(eq("testId"), eq("itemId"), same(item)); 46 | 47 | vaultClient.patchItem("itemId", Collections.singletonList(patch)); 48 | verify(client) 49 | .patchItem(eq("testId"), eq("itemId"), eq(Collections.singletonList(patch))); 50 | 51 | vaultClient.patchItem("itemId", patch); 52 | verify(client).patchItem(eq("testId"), eq("itemId"), same(patch)); 53 | 54 | vaultClient.deleteItem("itemId"); 55 | verify(client).deleteItem(eq("testId"), eq("itemId")); 56 | 57 | vaultClient.listFiles("itemId"); 58 | verify(client).listFiles(eq("testId"), eq("itemId")); 59 | 60 | vaultClient.listFiles("itemId", true); 61 | verify(client).listFiles(eq("testId"), eq("itemId"), eq(true)); 62 | 63 | vaultClient.getFile("itemId", "fileId"); 64 | verify(client).getFile(eq("testId"), eq("itemId"), eq("fileId")); 65 | 66 | vaultClient.getFile("itemId", "fileId", true); 67 | verify(client).getFile(eq("testId"), eq("itemId"), eq("fileId"), eq(true)); 68 | 69 | vaultClient.getFileContent("itemId", "fileId"); 70 | verify(client).getFileContent(eq("testId"), eq("itemId"), eq("fileId")); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/model/CharacterSetTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.stream.Stream; 7 | 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.Arguments; 10 | import org.junit.jupiter.params.provider.MethodSource; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | 15 | class CharacterSetTest { 16 | 17 | @ParameterizedTest(name = "{index} List of {1}") 18 | @MethodSource("provider") 19 | void listsShouldBeCorrect(List created, List expected) { 20 | assertEquals(expected.size(), created.size()); 21 | 22 | expected.forEach(set -> assertTrue(created.contains(set))); 23 | } 24 | 25 | @SuppressWarnings("unused") 26 | private static Stream provider() { 27 | return Stream.of( 28 | Arguments.of(CharacterSet.letters(), Collections.singletonList(CharacterSet.LETTERS)), 29 | Arguments.of(CharacterSet.digits(), Collections.singletonList(CharacterSet.DIGITS)), 30 | Arguments.of(CharacterSet.symbols(), Collections.singletonList(CharacterSet.SYMBOLS)), 31 | Arguments.of( 32 | CharacterSet.lettersAndDigits(), 33 | Arrays.asList(CharacterSet.LETTERS, CharacterSet.DIGITS)), 34 | Arguments.of( 35 | CharacterSet.lettersAndSymbols(), 36 | Arrays.asList(CharacterSet.LETTERS, CharacterSet.SYMBOLS)), 37 | Arguments.of( 38 | CharacterSet.digitsAndSymbols(), 39 | Arrays.asList(CharacterSet.DIGITS, CharacterSet.SYMBOLS)), 40 | Arguments.of( 41 | CharacterSet.allCharacters(), 42 | Arrays.asList(CharacterSet.LETTERS, CharacterSet.DIGITS, CharacterSet.SYMBOLS))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/model/FieldTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertAll; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | class FieldTest { 10 | 11 | @Test 12 | void shouldBuildUsername() { 13 | Field field = Field.username("testname").withLabel("username").build(); 14 | 15 | assertAll("Field properties are correct", 16 | () -> assertEquals(Purpose.USERNAME, field.getPurpose()), 17 | () -> assertEquals("testname", field.getValue()), 18 | () -> assertEquals("username", field.getLabel())); 19 | } 20 | 21 | @Test 22 | void shouldBuildPassword() { 23 | Field field = Field.password("pass").withLabel("password").build(); 24 | 25 | assertAll("Field properties are correct", 26 | () -> assertEquals(Purpose.PASSWORD, field.getPurpose()), 27 | () -> assertEquals("pass", field.getValue()), 28 | () -> assertEquals("password", field.getLabel())); 29 | } 30 | 31 | @Test 32 | void shouldBuildGeneratedPassword() { 33 | Field field = Field.generatedPassword().withLabel("password").build(); 34 | 35 | assertAll("Field properties are correct", 36 | () -> assertEquals(Purpose.PASSWORD, field.getPurpose()), 37 | () -> assertTrue(field.getGenerate()), 38 | () -> assertEquals("password", field.getLabel())); 39 | } 40 | 41 | @Test 42 | void shouldBuildGeneratedPasswordWithRecipe() { 43 | Field field = Field.generatedPassword(GeneratorRecipe.letters().ofLength(30)) 44 | .withLabel("password") 45 | .build(); 46 | 47 | assertAll("Field properties are correct", 48 | () -> assertEquals(Purpose.PASSWORD, field.getPurpose()), 49 | () -> assertTrue(field.getGenerate()), 50 | () -> assertEquals(GeneratorRecipe.letters().ofLength(30), field.getRecipe()), 51 | () -> assertEquals("password", field.getLabel())); 52 | } 53 | 54 | @Test 55 | void shouldBuildNote() { 56 | Field field = Field.note("My Note Contents").withLabel("note").build(); 57 | 58 | assertAll("Field properties are correct", 59 | () -> assertEquals(Purpose.NOTES, field.getPurpose()), 60 | () -> assertEquals("My Note Contents", field.getValue()), 61 | () -> assertEquals("note", field.getLabel())); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/model/FilterTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertAll; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 8 | 9 | class FilterTest { 10 | 11 | @Test 12 | void shouldBuildContainsFilter() { 13 | Filter filter = Filter.title().contains("test"); 14 | 15 | assertEquals("title co \"test\"", filter.getFilter()); 16 | } 17 | 18 | @Test 19 | void shouldBuildEqualsFilter() { 20 | Filter filter = Filter.title().equals("test"); 21 | 22 | assertEquals("title eq \"test\"", filter.getFilter()); 23 | } 24 | 25 | @Test 26 | void shouldBuildStartsWithFilter() { 27 | Filter filter = Filter.title().startsWith("test"); 28 | 29 | assertEquals("title sw \"test\"", filter.getFilter()); 30 | } 31 | 32 | @Test 33 | void shouldBuildPresentFilter() { 34 | Filter filter = Filter.title().present(); 35 | 36 | assertEquals("title pr", filter.getFilter()); 37 | } 38 | 39 | @Test 40 | void shouldBuildGreaterThanFilter() { 41 | Filter filter = Filter.title().greaterThan("a"); 42 | 43 | assertEquals("title gt \"a\"", filter.getFilter()); 44 | } 45 | 46 | @Test 47 | void shouldBuildGreaterThanOrEqualFilter() { 48 | Filter filter = Filter.title().greaterThanOrEqual("a"); 49 | 50 | assertEquals("title ge \"a\"", filter.getFilter()); 51 | } 52 | 53 | @Test 54 | void shouldBuildLessThanFilter() { 55 | Filter filter = Filter.title().lessThan("a"); 56 | 57 | assertEquals("title lt \"a\"", filter.getFilter()); 58 | } 59 | 60 | @Test 61 | void shouldBuildLessThanOrEqualFilter() { 62 | Filter filter = Filter.title().lessThanOrEqual("a"); 63 | 64 | assertEquals("title le \"a\"", filter.getFilter()); 65 | } 66 | 67 | @Test 68 | void shouldBuildFilterWithAnd() { 69 | Filter filter = Filter.title().contains("test") 70 | .and(Filter.title().equals("other")); 71 | 72 | assertEquals("(title co \"test\" and title eq \"other\")", filter.getFilter()); 73 | } 74 | 75 | @Test 76 | void shouldBuildFilterWithOr() { 77 | Filter filter = Filter.title().contains("test") 78 | .or(Filter.title().equals("other")); 79 | 80 | assertEquals("(title co \"test\" or title eq \"other\")", filter.getFilter()); 81 | } 82 | 83 | @Test 84 | void shouldBuildFilterWithAndOr() { 85 | Filter filter = Filter.title().contains("test") 86 | .and(Filter.title().equals("other") 87 | .or(Filter.title().startsWith("te"))); 88 | 89 | assertEquals("(title co \"test\" and (title eq \"other\" or title sw \"te\"))", 90 | filter.getFilter()); 91 | } 92 | 93 | @Test 94 | void shouldBuildNameFilter() { 95 | Filter filter = Filter.name().equals("test"); 96 | 97 | assertEquals("name eq \"test\"", filter.getFilter()); 98 | } 99 | 100 | @Test 101 | void shouldBuildAnyFilter() { 102 | Filter filter = Filter.onProperty("label").equals("test"); 103 | 104 | assertEquals("label eq \"test\"", filter.getFilter()); 105 | } 106 | 107 | @Test 108 | void hashCodeAndEqualsShouldWork() { 109 | Filter filter = Filter.title().equals("test"); 110 | Filter sameFilter = Filter.title().equals("test"); 111 | Filter diffOperation = Filter.title().contains("test"); 112 | Filter diffValue = Filter.title().equals("diff"); 113 | 114 | assertAll("Equals and hashcode works for same properties", 115 | () -> assertEquals(filter.hashCode(), sameFilter.hashCode()), 116 | () -> assertEquals(filter, sameFilter), 117 | () -> assertEquals(filter.toString(), sameFilter.toString())); 118 | 119 | assertAll("Equals and hashcode works for different operation property", 120 | () -> assertNotEquals(filter.hashCode(), diffOperation.hashCode()), 121 | () -> assertNotEquals(filter, diffOperation), 122 | () -> assertNotEquals(filter.toString(), diffOperation.toString())); 123 | 124 | assertAll("Equals and hashcode works for different value property", 125 | () -> assertNotEquals(filter.hashCode(), diffValue.hashCode()), 126 | () -> assertNotEquals(filter, diffValue), 127 | () -> assertNotEquals(filter.toString(), diffValue.toString())); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/model/GeneratorRecipeTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import java.util.List; 4 | import java.util.stream.Stream; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.Arguments; 9 | import org.junit.jupiter.params.provider.MethodSource; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | class GeneratorRecipeTest { 17 | 18 | @ParameterizedTest(name = "{index} Recipe of length 12 and characters {1}") 19 | @MethodSource("provider") 20 | void shouldBuild(GeneratorRecipe recipe, List expectedCharacters) { 21 | assertEquals(12, recipe.getLength()); 22 | assertEquals(expectedCharacters.size(), recipe.getCharacterSets().size()); 23 | 24 | expectedCharacters.forEach(set -> assertTrue(recipe.getCharacterSets().contains(set))); 25 | } 26 | 27 | @SuppressWarnings("unused") 28 | private static Stream provider() { 29 | return Stream.of( 30 | Arguments.of(GeneratorRecipe.letters().ofLength(12), CharacterSet.letters()), 31 | Arguments.of(GeneratorRecipe.digits().ofLength(12), CharacterSet.digits()), 32 | Arguments.of(GeneratorRecipe.symbols().ofLength(12), CharacterSet.symbols()), 33 | Arguments.of( 34 | GeneratorRecipe.lettersAndDigits().ofLength(12), 35 | CharacterSet.lettersAndDigits()), 36 | Arguments.of( 37 | GeneratorRecipe.lettersAndSymbols().ofLength(12), 38 | CharacterSet.lettersAndSymbols()), 39 | Arguments.of( 40 | GeneratorRecipe.digitsAndSymbols().ofLength(12), 41 | CharacterSet.digitsAndSymbols()), 42 | Arguments.of( 43 | GeneratorRecipe.allCharacters().ofLength(12), 44 | CharacterSet.allCharacters()), 45 | Arguments.of( 46 | GeneratorRecipe.withAllowedCharacters(CharacterSet.digitsAndSymbols()).ofLength(12), 47 | CharacterSet.digitsAndSymbols())); 48 | } 49 | 50 | @Test 51 | void hashCodeAndEqualsShouldWork() { 52 | GeneratorRecipe recipe = GeneratorRecipe.letters().ofLength(10); 53 | GeneratorRecipe sameRecipe = GeneratorRecipe.letters().ofLength(10); 54 | GeneratorRecipe diffLength = GeneratorRecipe.letters().ofLength(15); 55 | GeneratorRecipe diffCharacters = GeneratorRecipe.symbols().ofLength(10); 56 | 57 | assertAll("Equals and hashcode works for same properties", 58 | () -> assertEquals(recipe.hashCode(), sameRecipe.hashCode()), 59 | () -> assertEquals(recipe, sameRecipe), 60 | () -> assertEquals(recipe.toString(), sameRecipe.toString())); 61 | 62 | assertAll("Equals and hashcode works for different length property", 63 | () -> assertNotEquals(recipe.hashCode(), diffLength.hashCode()), 64 | () -> assertNotEquals(recipe, diffLength), 65 | () -> assertNotEquals(recipe.toString(), diffLength.toString())); 66 | 67 | assertAll("Equals and hashcode works for different characterSet property", 68 | () -> assertNotEquals(recipe.hashCode(), diffCharacters.hashCode()), 69 | () -> assertNotEquals(recipe, diffCharacters), 70 | () -> assertNotEquals(recipe.toString(), diffCharacters.toString())); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/model/URLTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertAll; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class URLTest { 12 | 13 | @Test 14 | void shouldBuildPrimaryUrl() { 15 | URL url = URL.primary("https://www.test.com"); 16 | 17 | assertTrue(url.getPrimary()); 18 | assertEquals("https://www.test.com", url.getUrl()); 19 | } 20 | 21 | @Test 22 | void shouldBuildNonPrimaryUrl() { 23 | URL url = URL.standard("https://www.test.com"); 24 | 25 | assertFalse(url.getPrimary()); 26 | assertEquals("https://www.test.com", url.getUrl()); 27 | } 28 | 29 | @Test 30 | void hashCodeAndEqualsShouldWork() { 31 | URL url = URL.primary("https://www.test.com"); 32 | URL sameUrl = URL.primary("https://www.test.com"); 33 | URL diffStringUrl = URL.primary("https://www.diff.com"); 34 | URL diffPrimaryUrl = URL.standard("https://www.test.com"); 35 | 36 | assertAll("Equals and hashcode works for same properties", 37 | () -> assertEquals(url.hashCode(), sameUrl.hashCode()), 38 | () -> assertEquals(url, sameUrl), 39 | () -> assertEquals(url.toString(), sameUrl.toString())); 40 | 41 | assertAll("Equals and hashcode works for different url property", 42 | () -> assertNotEquals(url.hashCode(), diffStringUrl.hashCode()), 43 | () -> assertNotEquals(url, diffStringUrl), 44 | () -> assertNotEquals(url.toString(), diffStringUrl.toString())); 45 | 46 | assertAll("Equals and hashcode works for different primary property", 47 | () -> assertNotEquals(url.hashCode(), diffPrimaryUrl.hashCode()), 48 | () -> assertNotEquals(url, diffPrimaryUrl), 49 | () -> assertNotEquals(url.toString(), diffPrimaryUrl.toString())); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/sanctionco/opconnect/model/apiactivity/ActorTest.java: -------------------------------------------------------------------------------- 1 | package com.sanctionco.opconnect.model.apiactivity; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | class ActorTest { 10 | private static final ObjectMapper MAPPER = new ObjectMapper(); 11 | private static final String JSON_PATH = "/fixtures/apirequest/actor.json"; 12 | 13 | @Test 14 | void shouldDeserialize() throws Exception { 15 | Actor expected = new Actor("test-id", "test-account", "test-jti", "test-useragent", "test-ip"); 16 | Actor actual = MAPPER.readValue(getClass().getResourceAsStream(JSON_PATH), Actor.class); 17 | 18 | assertEquals(expected, actual); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/apirequest/actor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "test-id", 3 | "account": "test-account", 4 | "jti": "test-jti", 5 | "userAgent": "test-useragent", 6 | "ip": "test-ip" 7 | } 8 | --------------------------------------------------------------------------------