Skip to content

Commit

Permalink
feat: add integration controller
Browse files Browse the repository at this point in the history
The Integration Controller is the component that will listen for
Federation agents.

When an agent connects, the connection is authenticated using
`IntegrationWebsocketControllerAuthentication`. This class will ensure
that the provided authentication matches an existing Token/User.

Once authenticated, the agent will send a Hello Command. This command
is handled by `HelloCommandHandler` that will check that the integration
provided exists and matches with the provider given.
  • Loading branch information
jgiovaresco committed Mar 26, 2024
1 parent 7125ca4 commit 4e68178
Show file tree
Hide file tree
Showing 24 changed files with 911 additions and 24 deletions.
Expand Up @@ -27,7 +27,7 @@
*/
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Builder(toBuilder = true)
@Data
public class Integration {

Expand Down
Expand Up @@ -17,11 +17,17 @@

import java.util.Date;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

/**
* @author Azize ELAMRANI (azize.elamrani at graviteesource.com)
* @author GraviteeSource Team
*/
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
public class Token {

public enum AuditEvent implements Audit.AuditEvent {
Expand Down
Expand Up @@ -44,6 +44,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.gravitee.apim.rest.api</groupId>
<artifactId>gravitee-apim-rest-api-integration-controller</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.gravitee.apim.rest.api.idp</groupId>
<artifactId>gravitee-apim-rest-api-idp-api</artifactId>
Expand Down
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright © 2015 The Gravitee team (http://gravitee.io)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.gravitee.apim.rest.api</groupId>
<artifactId>gravitee-apim-rest-api</artifactId>
<version>${revision}${sha1}${changelist}</version>
</parent>

<artifactId>gravitee-apim-rest-api-integration-controller</artifactId>
<packaging>jar</packaging>

<name>Gravitee.io APIM - Management API - Integration Controller</name>
<description>Controller interacting with Integration Agents</description>

<dependencies>
<!-- Gravitee dependencies -->
<dependency>
<groupId>io.gravitee.exchange</groupId>
<artifactId>gravitee-exchange-api</artifactId>
<version>${gravitee-exchange.version}</version>
</dependency>
<dependency>
<groupId>io.gravitee.exchange</groupId>
<artifactId>gravitee-exchange-controller-core</artifactId>
<version>${gravitee-exchange.version}</version>
</dependency>
<dependency>
<groupId>io.gravitee.exchange</groupId>
<artifactId>gravitee-exchange-controller-websocket</artifactId>
<version>${gravitee-exchange.version}</version>
</dependency>
<dependency>
<groupId>io.gravitee.integration</groupId>
<artifactId>gravitee-integration-api</artifactId>
<version>${gravitee-integration-api.version}</version>
</dependency>
<dependency>
<groupId>io.gravitee.apim.rest.api</groupId>
<artifactId>gravitee-apim-rest-api-service</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>

<!-- Provided dependencies -->
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>io.gravitee.apim.rest.api</groupId>
<artifactId>gravitee-apim-rest-api-service</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>

</project>
@@ -0,0 +1,26 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.integration.controller.command;

import io.gravitee.exchange.api.controller.ControllerCommandContext;
import java.util.Set;

public record IntegrationCommandContext(boolean valid) implements ControllerCommandContext {
@Override
public boolean isValid() {
return valid;
}
}
@@ -0,0 +1,58 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.integration.controller.command;

import io.gravitee.apim.core.integration.crud_service.IntegrationCrudService;
import io.gravitee.exchange.api.command.Command;
import io.gravitee.exchange.api.command.CommandAdapter;
import io.gravitee.exchange.api.command.CommandHandler;
import io.gravitee.exchange.api.command.Reply;
import io.gravitee.exchange.api.command.ReplyAdapter;
import io.gravitee.exchange.api.controller.ControllerCommandContext;
import io.gravitee.exchange.api.controller.ControllerCommandHandlersFactory;
import io.gravitee.exchange.api.websocket.protocol.ProtocolVersion;
import io.gravitee.integration.controller.command.hello.HelloCommandHandler;
import java.util.List;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class IntegrationControllerCommandHandlerFactory implements ControllerCommandHandlersFactory {

private final IntegrationCrudService integrationCrudService;

@Override
public List<CommandHandler<? extends Command<?>, ? extends Reply<?>>> buildCommandHandlers(
final ControllerCommandContext controllerCommandContext
) {
return List.of(new HelloCommandHandler(integrationCrudService));
}

@Override
public List<CommandAdapter<? extends Command<?>, ? extends Command<?>, ? extends Reply<?>>> buildCommandAdapters(
ControllerCommandContext controllerCommandContext,
ProtocolVersion protocolVersion
) {
return List.of();
}

@Override
public List<ReplyAdapter<? extends Reply<?>, ? extends Reply<?>>> buildReplyAdapters(
ControllerCommandContext controllerCommandContext,
ProtocolVersion protocolVersion
) {
return List.of();
}
}
@@ -0,0 +1,69 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.integration.controller.command.hello;

import io.gravitee.apim.core.integration.crud_service.IntegrationCrudService;
import io.gravitee.exchange.api.command.CommandHandler;
import io.gravitee.exchange.api.command.hello.HelloReply;
import io.gravitee.exchange.api.command.hello.HelloReplyPayload;
import io.gravitee.integration.api.command.IntegrationCommandType;
import io.gravitee.integration.api.command.hello.HelloCommand;
import io.gravitee.integration.api.command.hello.HelloCommandPayload;
import io.reactivex.rxjava3.core.Single;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RequiredArgsConstructor
@Slf4j
public class HelloCommandHandler implements CommandHandler<HelloCommand, HelloReply> {

private final IntegrationCrudService integrationCrudService;

@Override
public String supportType() {
return IntegrationCommandType.HELLO.name();
}

@Override
public Single<HelloReply> handle(HelloCommand command) {
return Single
.fromCallable(() -> {
HelloCommandPayload payload = command.getPayload();

return integrationCrudService
.findById(payload.getTargetId())
.map(integration -> {
if (integration.getProvider().equals(payload.getProvider())) {
return new HelloReply(command.getId(), HelloReplyPayload.builder().targetId(integration.getId()).build());
}
return new HelloReply(
command.getId(),
String.format(
"Integration [id=%s] does not match. Expected provider [provider=%s]",
integration.getId(),
integration.getProvider()
)
);
})
.orElse(new HelloReply(command.getId(), String.format("Integration [id=%s] not found", payload.getTargetId())));
})
.doOnError(throwable ->
log.error("Unable to process hello command payload for target [{}]", command.getPayload().getTargetId(), throwable)
)
.onErrorReturn(throwable -> new HelloReply(command.getId(), throwable.getMessage()));
}
}
@@ -0,0 +1,110 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.integration.controller.spring;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.gravitee.apim.core.integration.crud_service.IntegrationCrudService;
import io.gravitee.apim.core.user.crud_service.UserCrudService;
import io.gravitee.exchange.api.configuration.IdentifyConfiguration;
import io.gravitee.exchange.api.controller.ControllerCommandHandlersFactory;
import io.gravitee.exchange.api.controller.ExchangeController;
import io.gravitee.exchange.api.websocket.command.ExchangeSerDe;
import io.gravitee.exchange.controller.websocket.WebSocketExchangeController;
import io.gravitee.exchange.controller.websocket.auth.WebSocketControllerAuthentication;
import io.gravitee.integration.api.websocket.command.IntegrationExchangeSerDe;
import io.gravitee.integration.controller.command.IntegrationControllerCommandHandlerFactory;
import io.gravitee.integration.controller.websocket.auth.IntegrationWebsocketControllerAuthentication;
import io.gravitee.node.api.cache.CacheManager;
import io.gravitee.node.api.certificate.KeyStoreLoaderFactoryRegistry;
import io.gravitee.node.api.certificate.KeyStoreLoaderOptions;
import io.gravitee.node.api.certificate.TrustStoreLoaderOptions;
import io.gravitee.node.api.cluster.ClusterManager;
import io.gravitee.rest.api.service.TokenService;
import io.vertx.rxjava3.core.Vertx;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;

@Configuration
public class IntegrationControllerConfiguration {

@Bean("integrationWebsocketControllerAuthentication")
public IntegrationWebsocketControllerAuthentication integrationWebsocketControllerAuthentication(
final TokenService tokenService,
final UserCrudService userCrudService
) {
return new IntegrationWebsocketControllerAuthentication(tokenService, userCrudService);
}

@Bean("integrationExchangeSerDe")
public IntegrationExchangeSerDe integrationExchangeSerDe() {
return new IntegrationExchangeSerDe(objectMapper());
}

@Bean("integrationIdentifyConfiguration")
public IdentifyConfiguration integrationPrefixConfiguration(final Environment environment) {
return new IdentifyConfiguration(environment, "integration");
}

@Bean("integrationControllerCommandHandlerFactory")
public IntegrationControllerCommandHandlerFactory integrationControllerCommandHandlerFactory(
final IntegrationCrudService integrationCrudService
) {
return new IntegrationControllerCommandHandlerFactory(integrationCrudService);
}

@Bean("integrationExchangeController")
public ExchangeController integrationExchangeController(
final @Lazy ClusterManager clusterManager,
final @Lazy CacheManager cacheManager,
final Vertx vertx,
final KeyStoreLoaderFactoryRegistry<KeyStoreLoaderOptions> keyStoreLoaderFactoryRegistry,
final KeyStoreLoaderFactoryRegistry<TrustStoreLoaderOptions> trustStoreLoaderFactoryRegistry,
final @Qualifier("integrationIdentifyConfiguration") IdentifyConfiguration identifyConfiguration,
final @Qualifier(
"integrationWebsocketControllerAuthentication"
) WebSocketControllerAuthentication<?> integrationWebsocketControllerAuthentication,
final @Qualifier(
"integrationControllerCommandHandlerFactory"
) ControllerCommandHandlersFactory integrationControllerCommandHandlerFactory,
final @Qualifier("integrationExchangeSerDe") ExchangeSerDe integrationExchangeSerDe
) {
return new WebSocketExchangeController(
identifyConfiguration,
clusterManager,
cacheManager,
vertx,
keyStoreLoaderFactoryRegistry,
trustStoreLoaderFactoryRegistry,
integrationWebsocketControllerAuthentication,
integrationControllerCommandHandlerFactory,
integrationExchangeSerDe
);
}

public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
}

0 comments on commit 4e68178

Please sign in to comment.