• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

square / keywhiz / 4514348909

pending completion
4514348909

push

github

Chloe Barker
Implement undelete in dao

5344 of 7098 relevant lines covered (75.29%)

0.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/client/src/main/java/keywhiz/client/KeywhizClient.java
1
/*
2
 * Copyright (C) 2015 Square, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package keywhiz.client;
18

19
import com.fasterxml.jackson.core.type.TypeReference;
20
import com.fasterxml.jackson.databind.ObjectMapper;
21
import com.google.common.collect.ImmutableMap;
22
import java.io.IOException;
23
import java.util.Base64;
24
import java.util.List;
25
import javax.annotation.Nullable;
26
import javax.ws.rs.core.HttpHeaders;
27
import keywhiz.api.ClientDetailResponse;
28
import keywhiz.api.GroupDetailResponse;
29
import keywhiz.api.LoginRequest;
30
import keywhiz.api.SecretDetailResponse;
31
import keywhiz.api.automation.v2.CreateClientRequestV2;
32
import keywhiz.api.automation.v2.CreateGroupRequestV2;
33
import keywhiz.api.automation.v2.CreateSecretRequestV2;
34
import keywhiz.api.automation.v2.PartialUpdateSecretRequestV2;
35
import keywhiz.api.model.Client;
36
import keywhiz.api.model.Group;
37
import keywhiz.api.model.SanitizedSecret;
38
import keywhiz.api.model.SecretSeries;
39
import okhttp3.Call;
40
import okhttp3.HttpUrl;
41
import okhttp3.MediaType;
42
import okhttp3.OkHttpClient;
43
import okhttp3.Request;
44
import okhttp3.RequestBody;
45
import okhttp3.Response;
46
import org.apache.http.HttpStatus;
47

48
import static com.google.common.base.Preconditions.checkArgument;
49
import static com.google.common.base.Preconditions.checkNotNull;
50
import static java.lang.String.format;
51

52
/**
53
 * Client for interacting with the Keywhiz Server.
54
 * <p>
55
 * Facilitates the manipulation of Clients, Groups, Secrets and the connections between them.
56
 */
57
public class KeywhizClient {
58
  public static final MediaType JSON = MediaType.parse("application/json");
×
59

60
  private static final String NO_OWNER = null;
×
61

62
  public static class MalformedRequestException extends IOException {
×
63

64
    @Override public String getMessage() {
65
      return "Malformed request syntax from client (400)";
×
66
    }
67
  }
68

69
  public static class UnauthorizedException extends IOException {
×
70

71
    @Override public String getMessage() {
72
      return "Not allowed to login, password may be incorrect (401)";
×
73
    }
74
  }
75

76
  public static class ForbiddenException extends IOException {
×
77

78
    @Override public String getMessage() {
79
      return "Resource forbidden (403)";
×
80
    }
81
  }
82

83
  public static class NotFoundException extends IOException {
×
84

85
    @Override public String getMessage() {
86
      return "Resource not found (404)";
×
87
    }
88
  }
89

90
  public static class UnsupportedMediaTypeException extends IOException {
×
91

92
    @Override public String getMessage() {
93
      return "Resource media type is incorrect or incompatible (415)";
×
94
    }
95
  }
96

97
  public static class ConflictException extends IOException {
×
98

99
    @Override public String getMessage() {
100
      return "Conflicting resource (409)";
×
101
    }
102
  }
103

104
  public static class ValidationException extends IOException {
×
105

106
    @Override public String getMessage() {
107
      return "Malformed request semantics from client (422)";
×
108
    }
109
  }
110

111
  private final ObjectMapper mapper;
112
  private final OkHttpClient client;
113
  private final HttpUrl baseUrl;
114

115
  public KeywhizClient(ObjectMapper mapper, OkHttpClient client, HttpUrl baseUrl) {
×
116
    this.mapper = checkNotNull(mapper);
×
117
    this.client = checkNotNull(client);
×
118
    this.baseUrl = checkNotNull(baseUrl);
×
119
  }
×
120

121
  /**
122
   * Login to the Keywhiz server.
123
   * <p>
124
   * Future requests made using this client instance will be authenticated.
125
   *
126
   * @param username login username
127
   * @param password login password
128
   * @throws IOException if a network IO error occurs
129
   */
130
  public void login(String username, char[] password) throws IOException {
131
    httpPost(baseUrl.resolve("/admin/login"), LoginRequest.from(username, password));
×
132
  }
×
133

134
  public List<Group> allGroups() throws IOException {
135
    String response = httpGet(baseUrl.resolve("/admin/groups/"));
×
136
    return mapper.readValue(response, new TypeReference<List<Group>>() {
×
137
    });
138
  }
139

140
  public GroupDetailResponse createGroup(String name, String description,
141
      ImmutableMap<String, String> metadata) throws IOException {
142
    checkArgument(!name.isEmpty());
×
143
    String response = httpPost(baseUrl.resolve("/admin/groups"),
×
144
        CreateGroupRequestV2.builder()
×
145
            .name(name)
×
146
            .description(description)
×
147
            .metadata(metadata)
×
148
            .build());
×
149
    return mapper.readValue(response, GroupDetailResponse.class);
×
150
  }
151

152
  public GroupDetailResponse groupDetailsForId(long groupId) throws IOException {
153
    String response = httpGet(baseUrl.resolve(format("/admin/groups/%d", groupId)));
×
154
    return mapper.readValue(response, GroupDetailResponse.class);
×
155
  }
156

157
  public void deleteGroupWithId(long groupId) throws IOException {
158
    httpDelete(baseUrl.resolve(format("/admin/groups/%d", groupId)));
×
159
  }
×
160

161
  public List<SanitizedSecret> allSecrets() throws IOException {
162
    String response = httpGet(baseUrl.resolve("/admin/secrets?nameOnly=1"));
×
163
    return mapper.readValue(response, new TypeReference<List<SanitizedSecret>>() {
×
164
    });
165
  }
166

167
  public List<SanitizedSecret> allSecretsBatched(int idx, int num, boolean newestFirst)
168
      throws IOException {
169
    String response = httpGet(baseUrl.resolve(
×
170
        String.format("/admin/secrets?idx=%d&num=%d&newestFirst=%s", idx, num, newestFirst)));
×
171
    return mapper.readValue(response, new TypeReference<List<SanitizedSecret>>() {
×
172
    });
173
  }
174

175
  public SecretDetailResponse createSecret(
176
      String name,
177
      String description,
178
      byte[] content,
179
      ImmutableMap<String, String> metadata,
180
      long expiry) throws IOException {
181

182
    return createSecret(
×
183
        name,
184
        NO_OWNER,
185
        description,
186
        content,
187
        metadata,
188
        expiry
189
    );
190
  }
191

192
  public SecretDetailResponse createSecret(
193
      String name,
194
      String owner,
195
      String description,
196
      byte[] content,
197
      ImmutableMap<String, String> metadata,
198
      long expiry) throws IOException {
199

200
    checkArgument(!name.isEmpty());
×
201
    checkArgument(content.length > 0, "Content must not be empty");
×
202

203
    String b64Content = Base64.getEncoder().encodeToString(content);
×
204
    CreateSecretRequestV2 request =
205
        CreateSecretRequestV2.builder()
×
206
            .name(name)
×
207
            .owner(owner)
×
208
            .description(description)
×
209
            .content(b64Content)
×
210
            .metadata(metadata)
×
211
            .expiry(expiry)
×
212
            .build();
×
213
    String response = httpPost(baseUrl.resolve("/admin/secrets"), request);
×
214
    return mapper.readValue(response, SecretDetailResponse.class);
×
215
  }
216

217
  public SecretDetailResponse partialUpdateSecret(
218
      String secretName,
219
      PartialUpdateSecretRequestV2 request) throws IOException {
220
    HttpUrl url = baseUrl.resolve(
×
221
        format(
×
222
            "/admin/secrets/%s/partialupdate",
223
            secretName));
224
    String response = httpPost(url, request);
×
225
    return mapper.readValue(response, SecretDetailResponse.class);
×
226
  }
227

228
  public SecretDetailResponse updateSecret(String name, boolean descriptionPresent,
229
      String description, boolean contentPresent, byte[] content,
230
      boolean metadataPresent, ImmutableMap<String, String> metadata, boolean expiryPresent,
231
      long expiry) throws IOException {
232
    checkArgument(!name.isEmpty());
×
233

234
    String b64Content = Base64.getEncoder().encodeToString(content);
×
235
    PartialUpdateSecretRequestV2 request = PartialUpdateSecretRequestV2.builder()
×
236
        .descriptionPresent(descriptionPresent)
×
237
        .description(description)
×
238
        .contentPresent(contentPresent)
×
239
        .content(b64Content)
×
240
        .metadataPresent(metadataPresent)
×
241
        .metadata(metadata)
×
242
        .expiryPresent(expiryPresent)
×
243
        .expiry(expiry)
×
244
        .build();
×
245
    String response =
×
246
        httpPost(baseUrl.resolve(format("/admin/secrets/%s/partialupdate", name)), request);
×
247
    return mapper.readValue(response, SecretDetailResponse.class);
×
248
  }
249

250
  public SecretDetailResponse secretDetailsForId(long secretId) throws IOException {
251
    String response = httpGet(baseUrl.resolve(format("/admin/secrets/%d", secretId)));
×
252
    return mapper.readValue(response, SecretDetailResponse.class);
×
253
  }
254

255
  public List<SanitizedSecret> listSecretVersions(String name, int idx, int numVersions)
256
      throws IOException {
257
    String response = httpGet(baseUrl.resolve(
×
258
        format("/admin/secrets/versions/%s?versionIdx=%d&numVersions=%d", name, idx, numVersions)));
×
259
    return mapper.readValue(response, new TypeReference<List<SanitizedSecret>>() {
×
260
    });
261
  }
262

263
  public SecretDetailResponse rollbackSecret(String name, long version) throws IOException {
264
    String response =
×
265
        httpPost(baseUrl.resolve(format("/admin/secrets/rollback/%s/%d", name, version)), null);
×
266
    return mapper.readValue(response, SecretDetailResponse.class);
×
267
  }
268

269
  public void renameSecret(long secretId, String newName) throws IOException {
270
    httpPost(baseUrl.resolve(format("/admin/secrets/rename/%d/%s", secretId, newName)), null);
×
271
  }
×
272

273
  public void deleteSecretWithId(long secretId) throws IOException {
274
    httpDelete(baseUrl.resolve(format("/admin/secrets/%d", secretId)));
×
275
  }
×
276

277
  public void deleteSecretWithId(long secretId, String mode) {
278
    HttpUrl url = baseUrl.newBuilder()
×
279
        .addPathSegment("admin")
×
280
        .addPathSegment("secrets")
×
281
        .addPathSegment(Long.toString(secretId))
×
282
        .addQueryParameter("mode", mode)
×
283
        .build();
×
284
    httpDeleteQuietly(url);
×
285
  }
×
286

287
  public List<Client> allClients() throws IOException {
288
    String httpResponse = httpGet(baseUrl.resolve("/admin/clients/"));
×
289
    return mapper.readValue(httpResponse, new TypeReference<List<Client>>() {
×
290
    });
291
  }
292

293
  public ClientDetailResponse createClient(String name, String description, String spiffeId) throws IOException {
294
    checkArgument(!name.isEmpty());
×
295
    String response = httpPost(baseUrl.resolve("/admin/clients"),
×
296
        CreateClientRequestV2.builder()
×
297
            .name(name)
×
298
            .description(description)
×
299
            .spiffeId(spiffeId)
×
300
            .build());
×
301
    return mapper.readValue(response, ClientDetailResponse.class);
×
302
  }
303

304
  public ClientDetailResponse clientDetailsForId(long clientId) throws IOException {
305
    String response = httpGet(baseUrl.resolve(format("/admin/clients/%d", clientId)));
×
306
    return mapper.readValue(response, ClientDetailResponse.class);
×
307
  }
308

309
  public void deleteClientWithId(long clientId) throws IOException {
310
    httpDelete(baseUrl.resolve(format("/admin/clients/%d", clientId)));
×
311
  }
×
312

313
  public void enrollClientInGroupByIds(long clientId, long groupId) throws IOException {
314
    httpPut(baseUrl.resolve(format("/admin/memberships/clients/%d/groups/%d", clientId, groupId)));
×
315
  }
×
316

317
  public void evictClientFromGroupByIds(long clientId, long groupId) throws IOException {
318
    httpDelete(
×
319
        baseUrl.resolve(format("/admin/memberships/clients/%d/groups/%d", clientId, groupId)));
×
320
  }
×
321

322
  public void grantSecretToGroupByIds(long secretId, long groupId) throws IOException {
323
    httpPut(baseUrl.resolve(format("/admin/memberships/secrets/%d/groups/%d", secretId, groupId)));
×
324
  }
×
325

326
  public void revokeSecretFromGroupByIds(long secretId, long groupId) throws IOException {
327
    httpDelete(
×
328
        baseUrl.resolve(format("/admin/memberships/secrets/%d/groups/%d", secretId, groupId)));
×
329
  }
×
330

331
  public Client getClientByName(String name) throws IOException {
332
    checkArgument(!name.isEmpty());
×
333
    String response = httpGet(baseUrl.resolve("/admin/clients").newBuilder()
×
334
        .addQueryParameter("name", name)
×
335
        .build());
×
336
    return mapper.readValue(response, Client.class);
×
337
  }
338

339
  public Group getGroupByName(String name) throws IOException {
340
    checkArgument(!name.isEmpty());
×
341
    String response = httpGet(baseUrl.resolve("/admin/groups").newBuilder()
×
342
        .addQueryParameter("name", name)
×
343
        .build());
×
344
    return mapper.readValue(response, Group.class);
×
345
  }
346

347
  public SanitizedSecret getSanitizedSecretByName(String name) throws IOException {
348
    checkArgument(!name.isEmpty());
×
349
    String response =
×
350
        httpGet(baseUrl.resolve("/admin/secrets").newBuilder().addQueryParameter("name", name)
×
351
            .build());
×
352
    return mapper.readValue(response, SanitizedSecret.class);
×
353
  }
354

355
  @Nullable
356
  public List<SecretSeries> getDeletedSecretsByName(String name) throws IOException {
357
    checkArgument(!name.isEmpty());
×
358
    String response =
×
359
        httpGet(baseUrl.newBuilder()
×
360
            .addPathSegment("admin")
×
361
            .addPathSegment("secrets")
×
362
            .addPathSegment("deleted")
×
363
            .addPathSegment(name)
×
364
            .build());
×
365
    return mapper.readValue(response, new TypeReference<>() {
×
366
    });
367
  }
368

369
  public boolean isLoggedIn() throws IOException {
370
    HttpUrl url = baseUrl.resolve("/admin/me");
×
371
    Call call = client.newCall(new Request.Builder().get().url(url).build());
×
372
    return call.execute().code() != HttpStatus.SC_UNAUTHORIZED;
×
373
  }
374

375
  /**
376
   * Maps some of the common HTTP errors to the corresponding exceptions.
377
   */
378
  private void throwOnCommonError(int status) throws IOException {
379
    switch (status) {
×
380
      case HttpStatus.SC_BAD_REQUEST:
381
        throw new MalformedRequestException();
×
382
      case HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE:
383
        throw new UnsupportedMediaTypeException();
×
384
      case HttpStatus.SC_NOT_FOUND:
385
        throw new NotFoundException();
×
386
      case HttpStatus.SC_UNAUTHORIZED:
387
        throw new UnauthorizedException();
×
388
      case HttpStatus.SC_FORBIDDEN:
389
        throw new ForbiddenException();
×
390
      case HttpStatus.SC_CONFLICT:
391
        throw new ConflictException();
×
392
      case HttpStatus.SC_UNPROCESSABLE_ENTITY:
393
        throw new ValidationException();
×
394
    }
395
    if (status >= 400) {
×
396
      throw new IOException("Unexpected status code on response: " + status);
×
397
    }
398
  }
×
399

400
  private String makeCall(Request request) throws IOException {
401
    Response response = client.newCall(request).execute();
×
402
    try {
403
      throwOnCommonError(response.code());
×
404
    } catch (IOException e) {
×
405
      response.body().close();
×
406
      throw e;
×
407
    }
×
408
    return response.body().string();
×
409
  }
410

411
  private String httpGet(HttpUrl url) throws IOException {
412
    Request request = new Request.Builder()
×
413
        .url(url)
×
414
        .get()
×
415
        .build();
×
416

417
    return makeCall(request);
×
418
  }
419

420
  private String httpPost(HttpUrl url, Object content) throws IOException {
421
    RequestBody body = RequestBody.create(JSON, mapper.writeValueAsString(content));
×
422
    Request request = new Request.Builder()
×
423
        .url(url)
×
424
        .post(body)
×
425
        .addHeader(HttpHeaders.CONTENT_TYPE, JSON.toString())
×
426
        .build();
×
427

428
    return makeCall(request);
×
429
  }
430

431
  private String httpPut(HttpUrl url) throws IOException {
432
    Request request = new Request.Builder()
×
433
        .url(url)
×
434
        .put(RequestBody.create(MediaType.parse("text/plain"), ""))
×
435
        .build();
×
436

437
    return makeCall(request);
×
438
  }
439

440
  private String httpDelete(HttpUrl url) throws IOException {
441
    Request request = new Request.Builder()
×
442
        .url(url)
×
443
        .delete()
×
444
        .build();
×
445

446
    return makeCall(request);
×
447
  }
448

449
  private String httpDeleteQuietly(HttpUrl url) {
450
    try {
451
      return httpDelete(url);
×
452
    } catch (IOException e) {
×
453
      throw new RuntimeException(e);
×
454
    }
455
  }
456
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc