Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"connector_secret.delete": {
"documentation": {
"url": null,
"description": "Deletes a connector secret."
},
"stability": "experimental",
"visibility":"private",
"headers":{
"accept": [ "application/json"]
},
"url":{
"paths":[
{
"path":"/_connector/_secret/{id}",
"methods":[ "DELETE" ],
"parts":{
"id":{
"type":"string",
"description":"The ID of the secret"
}
}
}
]
},
"params":{}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ public class ClusterPrivilegeResolver {

public static final NamedClusterPrivilege WRITE_CONNECTOR_SECRETS = new ActionClusterPrivilege(
"write_connector_secrets",
Set.of("cluster:admin/xpack/connector/secret/post")
Set.of("cluster:admin/xpack/connector/secret/post", "cluster:admin/xpack/connector/secret/delete")
);

private static final Map<String, NamedClusterPrivilege> VALUES = sortByAccessLevel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ setup:
catch: unauthorized

---
'Get connector secret - Missing secret id':
'Get connector secret - Secret does not exist':
- do:
connector_secret.get:
id: non-existing-secret-id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
setup:
- skip:
version: " - 8.12.99"
reason: Introduced in 8.13.0

---
'Delete connector secret - admin':
- do:
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }

- do:
connector_secret.delete:
id: $id
- match: { deleted: true }

- do:
connector_secret.get:
id: $id
catch: missing

---
'Delete connector secret - user with privileges':
- skip:
features: headers

- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }
- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.delete:
id: $id
- match: { deleted: true }
- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.get:
id: $id
catch: missing

---
'Delete connector secret - user without privileges':
- skip:
features: headers

- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }
- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVucHJpdmlsZWdlZDplbnRzZWFyY2gtdW5wcml2aWxlZ2VkLXVzZXI=" } # unprivileged
connector_secret.delete:
id: $id
catch: unauthorized

---
'Delete connector secret - Secret does not exist':
- do:
connector_secret.delete:
id: non-existing-secret-id
catch: missing
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,13 @@
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorServiceTypeAction;
import org.elasticsearch.xpack.application.connector.secrets.ConnectorSecretsFeature;
import org.elasticsearch.xpack.application.connector.secrets.ConnectorSecretsIndexService;
import org.elasticsearch.xpack.application.connector.secrets.action.DeleteConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.GetConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.PostConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.RestDeleteConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.RestGetConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.RestPostConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.TransportDeleteConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.TransportGetConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.secrets.action.TransportPostConnectorSecretAction;
import org.elasticsearch.xpack.application.connector.syncjob.action.CancelConnectorSyncJobAction;
Expand Down Expand Up @@ -271,6 +274,7 @@ protected XPackLicenseState getLicenseState() {
if (ConnectorSecretsFeature.isEnabled()) {
actionHandlers.addAll(
List.of(
new ActionHandler<>(DeleteConnectorSecretAction.INSTANCE, TransportDeleteConnectorSecretAction.class),
new ActionHandler<>(GetConnectorSecretAction.INSTANCE, TransportGetConnectorSecretAction.class),
new ActionHandler<>(PostConnectorSecretAction.INSTANCE, TransportPostConnectorSecretAction.class)
)
Expand Down Expand Up @@ -355,7 +359,9 @@ public List<RestHandler> getRestHandlers(
}

if (ConnectorSecretsFeature.isEnabled()) {
restHandlers.addAll(List.of(new RestGetConnectorSecretAction(), new RestPostConnectorSecretAction()));
restHandlers.addAll(
List.of(new RestGetConnectorSecretAction(), new RestPostConnectorSecretAction(), new RestDeleteConnectorSecretAction())
);
}

return Collections.unmodifiableList(restHandlers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.client.internal.OriginSettingClient;
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xpack.application.connector.secrets.action.DeleteConnectorSecretResponse;
import org.elasticsearch.xpack.application.connector.secrets.action.GetConnectorSecretResponse;
import org.elasticsearch.xpack.application.connector.secrets.action.PostConnectorSecretRequest;
import org.elasticsearch.xpack.application.connector.secrets.action.PostConnectorSecretResponse;
Expand Down Expand Up @@ -93,4 +95,19 @@ public void createSecret(PostConnectorSecretRequest request, ActionListener<Post
listener.onFailure(e);
}
}

public void deleteSecret(String id, ActionListener<DeleteConnectorSecretResponse> listener) {
try {
clientWithOrigin.prepareDelete(CONNECTOR_SECRETS_INDEX_NAME, id)
.execute(listener.delegateFailureAndWrap((delegate, deleteResponse) -> {
if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) {
delegate.onFailure(new ResourceNotFoundException("No secret with id [" + id + "]"));
return;
}
delegate.onResponse(new DeleteConnectorSecretResponse(deleteResponse.getResult() == DocWriteResponse.Result.DELETED));
}));
} catch (Exception e) {
listener.onFailure(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.application.connector.secrets.action;

import org.elasticsearch.action.ActionType;

public class DeleteConnectorSecretAction {

public static final String NAME = "cluster:admin/xpack/connector/secret/delete";

public static final ActionType<DeleteConnectorSecretResponse> INSTANCE = new ActionType<>(NAME);

private DeleteConnectorSecretAction() {/* no instances */}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.application.connector.secrets.action;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;
import java.util.Objects;

import static org.elasticsearch.action.ValidateActions.addValidationError;

public class DeleteConnectorSecretRequest extends ActionRequest {

private final String id;

public DeleteConnectorSecretRequest(String id) {
this.id = Objects.requireNonNull(id);
}

public DeleteConnectorSecretRequest(StreamInput in) throws IOException {
super(in);
this.id = in.readString();
}

public String id() {
return id;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(id);
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;

if (Strings.isNullOrEmpty(id)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: can we test for this edge case in yaml tests?

validationException = addValidationError("id missing", validationException);
}

return validationException;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DeleteConnectorSecretRequest that = (DeleteConnectorSecretRequest) o;
return Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.application.connector.secrets.action;

import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Objects;

public class DeleteConnectorSecretResponse extends ActionResponse implements ToXContentObject {

private final boolean deleted;

public DeleteConnectorSecretResponse(boolean deleted) {
this.deleted = deleted;
}

public DeleteConnectorSecretResponse(StreamInput in) throws IOException {
super(in);
this.deleted = in.readBoolean();
}

public boolean isDeleted() {
return deleted;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(deleted);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("deleted", deleted);
return builder.endObject();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DeleteConnectorSecretResponse that = (DeleteConnectorSecretResponse) o;
return deleted == that.deleted;
}

@Override
public int hashCode() {
return Objects.hash(deleted);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.application.connector.secrets.action;

import org.elasticsearch.client.internal.node.NodeClient;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.Scope;
import org.elasticsearch.rest.ServerlessScope;
import org.elasticsearch.rest.action.RestToXContentListener;

import java.io.IOException;
import java.util.List;

@ServerlessScope(Scope.INTERNAL)
public class RestDeleteConnectorSecretAction extends BaseRestHandler {

@Override
public String getName() {
return "connector_delete_secret";
}

@Override
public List<Route> routes() {
return List.of(new Route(RestRequest.Method.DELETE, "/_connector/_secret/{id}"));
}

@Override
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
final String id = request.param("id");
return restChannel -> client.execute(
DeleteConnectorSecretAction.INSTANCE,
new DeleteConnectorSecretRequest(id),
new RestToXContentListener<>(restChannel)
);
}
}
Loading