Skip to content

Commit

Permalink
[RHCLOUD-24628] Update Slack "channel" field restrictions (RedHatInsi…
Browse files Browse the repository at this point in the history
  • Loading branch information
g-duval authored Mar 15, 2024
1 parent 87c35ad commit 0ecbb62
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 10 deletions.
4 changes: 4 additions & 0 deletions .rhcicd/clowdapp-backend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ objects:
value: ${NOTIFICATIONS_USE_DEFAULT_TEMPLATE}
- name: NOTIFICATIONS_DRAWER_ENABLED
value: ${NOTIFICATIONS_DRAWER_ENABLED}
- name: NOTIFICATIONS_SLACK_FORBID_CHANNEL_USAGE_ENABLED
value: ${NOTIFICATIONS_SLACK_FORBID_CHANNEL_USAGE_ENABLED}
- name: SOURCES_PSK
valueFrom:
secretKeyRef:
Expand Down Expand Up @@ -251,3 +253,5 @@ parameters:
value: "false"
- name: NOTIFICATIONS_DRAWER_ENABLED
value: "false"
- name: NOTIFICATIONS_SLACK_FORBID_CHANNEL_USAGE_ENABLED
value: "false"
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
public class EndpointResource {

public static final String EMPTY_SLACK_CHANNEL_ERROR = "The channel field is required";
public static final String DEPRECATED_SLACK_CHANNEL_ERROR = "The channel field is deprecated";
public static final String UNSUPPORTED_ENDPOINT_TYPE = "Unsupported endpoint type";
public static final String REDACTED_CREDENTIAL = "*****";

Expand Down Expand Up @@ -273,7 +274,7 @@ public Endpoint createEndpoint(@Context SecurityContext sec,
}

if (endpoint.getType() == CAMEL && "slack".equals(endpoint.getSubType())) {
checkSlackChannel(endpoint.getProperties(CamelProperties.class));
checkSlackChannel(endpoint.getProperties(CamelProperties.class), null);
}

endpoint.setStatus(EndpointStatus.READY);
Expand All @@ -292,12 +293,22 @@ public Endpoint createEndpoint(@Context SecurityContext sec,
return endpointRepository.createEndpoint(endpoint);
}

private String checkSlackChannel(CamelProperties camelProperties) {
private void checkSlackChannel(CamelProperties camelProperties, CamelProperties previousCamelProperties) {
String channel = camelProperties.getExtras().get("channel");
if (channel == null || channel.isBlank()) {
throw new BadRequestException(EMPTY_SLACK_CHANNEL_ERROR);

if (featureFlipper.isSlackForbidChannelUsageEnabled()) {
// throw an exception if we receive a channel on endpoint creation
if (null == previousCamelProperties && channel != null) {
throw new BadRequestException(DEPRECATED_SLACK_CHANNEL_ERROR);
// throw an exception if we receive a channel update
} else if (channel != null && !channel.equals(previousCamelProperties.getExtras().get("channel"))) {
throw new BadRequestException(DEPRECATED_SLACK_CHANNEL_ERROR);
}
} else {
if (channel == null || channel.isBlank()) {
throw new BadRequestException(EMPTY_SLACK_CHANNEL_ERROR);
}
}
return channel;
}

@POST
Expand Down Expand Up @@ -453,28 +464,33 @@ public Response updateEndpoint(@Context SecurityContext sec,
endpoint.setOrgId(orgId);
endpoint.setId(id);

EndpointType endpointType = endpointRepository.getEndpointTypeById(orgId, id);
final Endpoint dbEndpoint = endpointRepository.getEndpoint(orgId, id);
if (dbEndpoint == null) {
throw new NotFoundException("Endpoint not found");
}
EndpointType endpointType = dbEndpoint.getType();

// This prevents from updating an endpoint from system EndpointType to a whatever EndpointType
checkSystemEndpoint(endpointType);

if (endpoint.getType() == CAMEL && "slack".equals(endpoint.getSubType())) {
checkSlackChannel(endpoint.getProperties(CamelProperties.class));
checkSlackChannel(endpoint.getProperties(CamelProperties.class), dbEndpoint.getProperties(CamelProperties.class));
}

endpointRepository.updateEndpoint(endpoint);

// Update the secrets in Sources.
final Endpoint dbEndpoint = endpointRepository.getEndpoint(orgId, id);
final Endpoint updatedDbEndpoint = endpointRepository.getEndpoint(orgId, id);
final EndpointProperties endpointProperties = endpoint.getProperties();
final EndpointProperties databaseEndpointProperties = dbEndpoint.getProperties();
final EndpointProperties databaseEndpointProperties = updatedDbEndpoint.getProperties();

if (endpointProperties instanceof SourcesSecretable incomingProperties && databaseEndpointProperties instanceof SourcesSecretable dep) {
// In order to be able to update the secrets in Sources, we need to grab the IDs of these secrets from the
// database endpoint, since the client won't be sending those IDs.
dep.setBasicAuthentication(incomingProperties.getBasicAuthentication());
dep.setSecretToken(incomingProperties.getSecretToken());
dep.setBearerAuthentication(incomingProperties.getBearerAuthentication());
this.secretUtils.updateSecretsForEndpoint(dbEndpoint);
this.secretUtils.updateSecretsForEndpoint(updatedDbEndpoint);
}

return Response.ok().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import static com.redhat.cloud.notifications.models.EndpointStatus.READY;
import static com.redhat.cloud.notifications.models.EndpointType.ANSIBLE;
import static com.redhat.cloud.notifications.models.HttpType.POST;
import static com.redhat.cloud.notifications.routers.EndpointResource.DEPRECATED_SLACK_CHANNEL_ERROR;
import static com.redhat.cloud.notifications.routers.EndpointResource.EMPTY_SLACK_CHANNEL_ERROR;
import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
Expand Down Expand Up @@ -686,6 +687,81 @@ void testMissingSlackChannel() {
assertEquals(EMPTY_SLACK_CHANNEL_ERROR, responseBody);
}

@Test
void testForbidSlackChannelUsage() {
try {
featureFlipper.setSlackForbidChannelUsageEnabled(true);
String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "user");
Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue);
MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS);

Map<String, String> extras = new HashMap<>(Map.of("channel", "")); // Having a channel value is invalid.
CamelProperties camelProperties = new CamelProperties();
camelProperties.setUrl("https://foo.com");
camelProperties.setExtras(extras);

Endpoint endpoint = new Endpoint();
endpoint.setType(EndpointType.CAMEL);
endpoint.setSubType("slack");
endpoint.setName("name");
endpoint.setDescription("description");
endpoint.setProperties(camelProperties);

String responseBody = given()
.header(identityHeader)
.when()
.contentType(JSON)
.body(Json.encode(endpoint))
.post("/endpoints")
.then()
.statusCode(400)
.extract().asString();

assertEquals(DEPRECATED_SLACK_CHANNEL_ERROR, responseBody);

extras.remove("channel");

String createdEndpoint = given()
.header(identityHeader)
.when()
.contentType(JSON)
.body(Json.encode(endpoint))
.post("/endpoints")
.then()
.statusCode(200)
.extract().asString();

final JsonObject jsonResponse = new JsonObject(createdEndpoint);
final String endpointUuidRaw = jsonResponse.getString("id");

// try to update endpoint without channel
given()
.header(identityHeader)
.contentType(JSON)
.pathParam("id", endpointUuidRaw)
.body(Json.encode(endpoint))
.when()
.put("/endpoints/{id}")
.then()
.statusCode(200);

// try to update endpoint with channel
extras.put("channel", "refused");
given()
.header(identityHeader)
.contentType(JSON)
.pathParam("id", endpointUuidRaw)
.body(Json.encode(endpoint))
.when()
.put("/endpoints/{id}")
.then()
.statusCode(400);

} finally {
featureFlipper.setSlackForbidChannelUsageEnabled(false);
}
}

@Test
void addSlackEndpoint() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ public class FeatureFlipper {
@ConfigProperty(name = "notifications.email.hcc-sender-name.enabled", defaultValue = "false")
boolean hccEmailSenderNameEnabled;

@ConfigProperty(name = "notifications.slack.forbid.channel.usage.enabled", defaultValue = "false")
boolean slackForbidChannelUsageEnabled;

void logFeaturesStatusAtStartup(@Observes StartupEvent event) {
Log.infof("=== %s startup status ===", FeatureFlipper.class.getSimpleName());
Log.infof("The behavior groups unique name constraint is %s", enforceBehaviorGroupNameUnicity ? "enabled" : "disabled");
Expand All @@ -96,6 +99,7 @@ void logFeaturesStatusAtStartup(@Observes StartupEvent event) {
Log.infof("The async aggregation is %s", asyncAggregation ? "enabled" : "disabled");
Log.infof("The Recipients resolver usage for daily digest is %s", useRecipientsResolverClowdappForDailyDigestEnabled ? "enabled" : "disabled");
Log.infof("HCC sender name is %s in emails", hccEmailSenderNameEnabled ? "enabled" : "disabled");
Log.infof("The disable of channel field on Slack is %s", slackForbidChannelUsageEnabled ? "enabled" : "disabled");
}

public boolean isEnforceBehaviorGroupNameUnicity() {
Expand Down Expand Up @@ -215,6 +219,15 @@ public void setHccEmailSenderNameEnabled(boolean hccEmailSenderNameEnabled) {
this.hccEmailSenderNameEnabled = hccEmailSenderNameEnabled;
}

public boolean isSlackForbidChannelUsageEnabled() {
return slackForbidChannelUsageEnabled;
}

public void setSlackForbidChannelUsageEnabled(boolean slackForbidChannelUsageEnabled) {
checkTestLaunchMode();
this.slackForbidChannelUsageEnabled = slackForbidChannelUsageEnabled;
}

/**
* This method throws an {@link IllegalStateException} if it is invoked with a launch mode different from
* {@link io.quarkus.runtime.LaunchMode#TEST TEST}. It should be added to methods that allow overriding a
Expand Down

0 comments on commit 0ecbb62

Please sign in to comment.