Conversation
- Implemented batch secret retrieval methods in ResourceClient, Variables, and ResourceProvider. - Enhanced Endpoints class to support batch retrieval URI. - Added comprehensive unit tests for batch secret retrieval in ResourceClientTest.
|
Like the other PR, I also created a manual full integration test of this code but wasn't sure if it fit in the repo. I can add it if wanted. |
|
Thank you for the PR! We've added it to our backlog to review. Tracking internally as CNJR-13116. |
| private final URI authnUri; | ||
| private final URI secretsUri; | ||
|
|
||
| public Endpoints(final URI authnUri, final URI secretsUri){ |
There was a problem hiding this comment.
Old constructors should stay as mentioned on the other PR for the batch secrets retrival:
#127 (comment)
| this(applianceUrl, account, applianceUrl + "/authn"); | ||
| } | ||
|
|
||
| public Endpoints(String authnUri, String secretsUri){ |
|
|
||
| /** | ||
| * Fetch multiple secret values in one invocation. | ||
| * | ||
| * @param variableIds the variable IDs to retrieve | ||
| * @return a map of variable ID to secret value | ||
| */ | ||
| public Map<String, String> retrieveBatchSecrets(String... variableIds) { | ||
| return variables.retrieveBatchSecrets(variableIds); | ||
| } | ||
|
|
||
| /** | ||
| * List all resources visible to the authenticated identity. | ||
| * | ||
| * @return list of all resources | ||
| */ | ||
| public List<ConjurResource> listResources() { | ||
| return variables.listResources(); | ||
| } | ||
|
|
||
| /** | ||
| * List resources filtered by kind. | ||
| * | ||
| * @param kind the resource kind (e.g. "variable", "host") | ||
| * @return resources matching the given kind | ||
| */ | ||
| public List<ConjurResource> listResources(String kind) { | ||
| return variables.listResources(kind); | ||
| } | ||
|
|
||
| /** | ||
| * List resources with full query parameter control. | ||
| * | ||
| * @param kind resource kind filter (null for all kinds) | ||
| * @param search text search filter (null for no search) | ||
| * @param limit max results (null for server default) | ||
| * @param offset pagination offset (null for no offset) | ||
| * @return resources matching the query | ||
| */ | ||
| public List<ConjurResource> listResources(String kind, String search, Integer limit, Integer offset) { | ||
| return variables.listResources(kind, search, limit, offset); | ||
| } | ||
|
|
||
| /** | ||
| * Count resources visible to the authenticated identity. | ||
| * | ||
| * @param kind resource kind filter (null for all kinds) | ||
| * @param search text search filter (null for no search) | ||
| * @return the number of matching resources | ||
| */ | ||
| public int countResources(String kind, String search) { | ||
| return variables.countResources(kind, search); | ||
| } |
There was a problem hiding this comment.
All the methods seem to be unnecessary shortcuts. These should be accessed through for example variables
| public List<ConjurResource> listResources() { | ||
| return resourceClient.listResources(); | ||
| } | ||
|
|
||
| /** | ||
| * List resources filtered by kind. | ||
| * | ||
| * @param kind the resource kind (e.g. "variable", "host") | ||
| * @return resources matching the given kind | ||
| */ | ||
| public List<ConjurResource> listResources(String kind) { | ||
| return resourceClient.listResources(kind); | ||
| } | ||
|
|
||
| /** | ||
| * List resources with full query parameter control. | ||
| * | ||
| * @param kind resource kind filter (null for all kinds) | ||
| * @param search text search filter (null for no search) | ||
| * @param limit max results (null for server default) | ||
| * @param offset pagination offset (null for no offset) | ||
| * @return resources matching the query | ||
| */ | ||
| public List<ConjurResource> listResources(String kind, String search, Integer limit, Integer offset) { | ||
| return resourceClient.listResources(kind, search, limit, offset); | ||
| } | ||
|
|
||
| /** | ||
| * Count resources visible to the authenticated identity. | ||
| * | ||
| * @param kind resource kind filter (null for all kinds) | ||
| * @param search text search filter (null for no search) | ||
| * @return the number of matching resources | ||
| */ | ||
| public int countResources(String kind, String search) { | ||
| return resourceClient.countResources(kind, search); | ||
| } |
There was a problem hiding this comment.
As these methods are not specifically about the variables, maybe it would be a good idea to move them to a separate class i.e. Resources and manage it in Conjur class just like Variables.
| /** | ||
| * List all resources visible to the authenticated identity. | ||
| * | ||
| * @return list of all resources | ||
| * @see <a href="https://docs.cyberark.com/conjur-open-source/latest/en/content/developer/conjur_api_list_resources.htm">List Resources</a> | ||
| */ | ||
| @Override | ||
| public List<ConjurResource> listResources() { | ||
| return listResources(null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
| * List resources filtered by kind. | ||
| * | ||
| * @param kind the resource kind (e.g. "variable", "host", "user", "group", "layer", "policy", "webservice") | ||
| * @return resources matching the given kind | ||
| */ | ||
| @Override | ||
| public List<ConjurResource> listResources(String kind) { | ||
| return listResources(kind, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
| * List resources with full query parameter control. | ||
| * | ||
| * @param kind resource kind filter (null for all kinds) | ||
| * @param search text search filter (null for no search) | ||
| * @param limit max results per page (null for server default, max 1000) | ||
| * @param offset pagination offset (null for no offset) | ||
| * @return resources matching the query | ||
| * @throws WebApplicationException if the server returns an error response | ||
| */ | ||
| @Override | ||
| public List<ConjurResource> listResources(String kind, String search, Integer limit, Integer offset) { | ||
| URI resourcesUri = endpoints.getResourcesUri(); | ||
| StringBuilder uriBuilder = new StringBuilder(resourcesUri.toString()); | ||
| String separator = "?"; | ||
|
|
||
| if (kind != null && !kind.isEmpty()) { | ||
| uriBuilder.append(separator).append("kind=").append(encodeVariableId(kind)); | ||
| separator = "&"; | ||
| } | ||
| if (search != null && !search.isEmpty()) { | ||
| uriBuilder.append(separator).append("search=").append(encodeVariableId(search)); | ||
| separator = "&"; | ||
| } | ||
| if (limit != null) { | ||
| uriBuilder.append(separator).append("limit=").append(limit); | ||
| separator = "&"; | ||
| } | ||
| if (offset != null) { | ||
| uriBuilder.append(separator).append("offset=").append(offset); | ||
| } | ||
|
|
||
| URI targetUri = URI.create(uriBuilder.toString()); | ||
| Response response = client.target(targetUri).request().get(Response.class); | ||
| validateResponse(response); | ||
|
|
||
| String json = response.readEntity(String.class); | ||
| List<ConjurResource> resources = GSON.fromJson(json, LIST_RESOURCE_TYPE); | ||
| return resources != null ? resources : Collections.<ConjurResource>emptyList(); | ||
| } | ||
|
|
||
| /** | ||
| * Count resources visible to the authenticated identity. | ||
| * | ||
| * @param kind resource kind filter (null for all kinds) | ||
| * @param search text search filter (null for no search) | ||
| * @return the number of matching resources | ||
| * @throws WebApplicationException if the server returns an error response | ||
| */ | ||
| @Override | ||
| public int countResources(String kind, String search) { | ||
| URI resourcesUri = endpoints.getResourcesUri(); | ||
| StringBuilder uriBuilder = new StringBuilder(resourcesUri.toString()); | ||
| uriBuilder.append("?count=true"); | ||
|
|
||
| if (kind != null && !kind.isEmpty()) { | ||
| uriBuilder.append("&kind=").append(encodeVariableId(kind)); | ||
| } | ||
| if (search != null && !search.isEmpty()) { | ||
| uriBuilder.append("&search=").append(encodeVariableId(search)); | ||
| } | ||
|
|
||
| URI targetUri = URI.create(uriBuilder.toString()); | ||
| Response response = client.target(targetUri).request().get(Response.class); | ||
| validateResponse(response); | ||
|
|
||
| String body = response.readEntity(String.class).trim(); | ||
|
|
||
| // The server may return a plain integer or a JSON object like {"count":N} | ||
| if (body.startsWith("{")) { | ||
| @SuppressWarnings("unchecked") | ||
| Map<String, Double> parsed = GSON.fromJson(body, Map.class); | ||
| Double count = parsed.get("count"); | ||
| if (count == null) { | ||
| throw new IllegalStateException("Unexpected count response: " + body); | ||
| } | ||
| return count.intValue(); | ||
| } | ||
| return Integer.parseInt(body); | ||
| } |
There was a problem hiding this comment.
These methods could probably be condensed into just 2.
One for searching/listing resources and the other for counting them.
The listResources(String kind, String search, Integer limit, Integer offset) signature is already at 4 nullable params and from what I've seen the underlying API supports more filters.
Every new param will require a signature change here, in ResourceClient, Variables, and anywhere these are called.
I'd suggest introducing a kind of value object ResourceQueryParameters (or whatever better name You can think of) that would aggregate all the possible search params that conjur API supports(except. count) and adding a method like:
List<ConjurResource> listResources(ResourceQueryParameters params);
There could be a mapper that maps ResourceQueryParameters into an URL. So if there is any new param to be addded in the future, the signature of the method won't change, only the ResourceQueryParameters and a mapper would be affected.
For the counting method We could have:
int countResources(ResourceQueryParameters params);
This one would take the same QueryParams and build the URL with the count = true. It would also separate the dirty(but totally necessary due to the API specs) logic of mapping the count response json.
This would make the code easier to maintain, and the users of the api would be able to build their own ResourceQueryParameters instances with whatever params they like. With a builder pattern for the ResourceQueryParameters implemented it could be really nice to use.
|
Please add some usage examples to the Readme as well. |
|
You might also take a look at https://github.com/cyberark/conjur-sdk-java which is an OpenApi spec driven api, if it contains the needed functionalities. |
|
FYI We have some capacity that allows us to work on this and the PR 127 |
Closes #128
Builds on #127
Summary
Adds support for the Conjur List Resources API (
GET /resources/{account}) to the Java SDK. This builds on the Endpoints refactor and infrastructure introduced in #127 (batch secret retrieval).Changes
New files
ConjurResource.java— Model class for resources returned by the API. Includes fields forid,created_at,owner,policy,permissions,annotations,secrets, andpolicy_versions. Inner classesPermissionandAnnotationfor nested objects. Helper methodsgetKind()andgetIdentifier()to parse the{account}:{kind}:{identifier}resource ID format.Modified files
Endpoints.java— AddedgetResourcesUri()returning{applianceUrl}/resources/{account}ResourceProvider.java— AddedlistResources()(3 overloads) andcountResources()as default interface methods for backward compatibilityResourceClient.java— Full HTTP implementation: builds query parameters (kind,search,limit,offset,count), parses JSON arrays via Gson intoList<ConjurResource>, handles both JSON{"count":N}and plain integer count response formatsVariables.java— Delegation to ResourceClientConjur.java— Convenience methods on the entry point classResourceClientTest.java— 17 new unit tests covering: list all resources, filter by kind, text search, pagination, combined parameters, empty results, 401/403 error handling, full field parsing (permissions, annotations), special character encoding in search, count with JSON format, count with plain integer format, count by kind, count with search, count 401 error, and Endpoints URI derivationAPI
Testing