Skip to content

Commit

Permalink
Merge pull request #3959 from Agnul97/feature-accessTokenFetchImprove…
Browse files Browse the repository at this point in the history
…ment

Feature - Access Token fetch improval
  • Loading branch information
Coduz authored Mar 13, 2024
2 parents 8543ac3 + 7707183 commit b884439
Show file tree
Hide file tree
Showing 42 changed files with 823 additions and 142 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/kapua-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,17 @@ jobs:
with:
tag: '@endpoint'
needs-docker-images: 'true'
test-api-auth:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Clones Kapua repo inside the runner
uses: actions/checkout@v3
- uses: ./.github/actions/runTestsTaggedAs
with:
tag: '@rest_auth'
needs-docker-images: 'true'
junit-tests:
needs: build
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions assembly/api/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ ENV JAVA_OPTS "-Dapi.cors.origins.allowed=\${API_CORS_ORIGINS_ALLOWED} \
-Djob.engine.client.auth.mode=access_token \
-Dcipher.key=\${CIPHER_KEY} \
-Dcrypto.secret.key=\${CRYPTO_SECRET_KEY} \
-Dauthentication.token.expire.after=\${AUTH_TOKEN_TTL} \
-Dauthentication.refresh.token.expire.after=\${REFRESH_AUTH_TOKEN_TTL} \
\${JETTY_DEBUG_OPTS} \
\${JETTY_JMX_OPTS}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package org.eclipse.kapua.commons.rest.errors;

import org.eclipse.kapua.commons.rest.model.errors.ExceptionInfo;
import org.eclipse.kapua.commons.rest.model.errors.MfaRequiredExceptionInfo;
import org.eclipse.kapua.service.authentication.exception.KapuaAuthenticationErrorCodes;
import org.eclipse.kapua.service.authentication.exception.KapuaAuthenticationException;
Expand Down Expand Up @@ -47,6 +48,13 @@ public Response toResponse(KapuaAuthenticationException kapuaAuthenticationExcep
.build();
}

if (kapuaAuthenticationException.getCode().equals(KapuaAuthenticationErrorCodes.REFRESH_ERROR)) {
return Response
.status(Status.UNAUTHORIZED)
.entity(new ExceptionInfo(Status.UNAUTHORIZED.getStatusCode(), kapuaAuthenticationException, showStackTrace))
.build();
}

return Response.status(Status.UNAUTHORIZED).build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
}
int errorCode = httpResponse.getStatus();
if (errorCode >= 400) {
// if there's an error code at this point, return it and stop the chain
if (errorMessage == null) { //CORS filter passed...show propagated error message to the client
errorMessage = httpResponse.getHeader("exceptionMessagePropagatedToCORS");
}
httpResponse.setHeader("exceptionMessagePropagatedToCORS", null); //no more needed, delete from response header
httpResponse.sendError(errorCode, errorMessage);
return;
}
Expand Down
2 changes: 2 additions & 0 deletions deployment/docker/compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ services:
- KAPUA_DISABLE_DATASTORE
- LOGBACK_LOG_LEVEL
- SWAGGER=${KAPUA_SWAGGER_ENABLE:-true}
- AUTH_TOKEN_TTL=${AUTH_TOKEN_TTL:-1800000}
- REFRESH_AUTH_TOKEN_TTL=${REFRESH_AUTH_TOKEN_TTL:-18000000}
job-engine:
container_name: job-engine
image: kapua/kapua-job-engine:${IMAGE_VERSION}
Expand Down
7 changes: 7 additions & 0 deletions deployment/docker/compose/extras/docker-compose.api-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: '3.1'

services:
kapua-api:
environment:
- AUTH_TOKEN_TTL=180000000
- REFRESH_AUTH_TOKEN_TTL=180000000
1 change: 1 addition & 0 deletions deployment/docker/unix/docker-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ docker_compose() {
echo "Dev mode enabled!"
COMPOSE_FILES+=(-f "${SCRIPT_DIR}/../compose/extras/docker-compose.db-dev.yml")
COMPOSE_FILES+=(-f "${SCRIPT_DIR}/../compose/extras/docker-compose.es-dev.yml")
COMPOSE_FILES+=(-f "${SCRIPT_DIR}/../compose/extras/docker-compose.api-dev.yml")
fi

# SSL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class BasicSteps extends TestBase {
public static final String TELEMETRY_CONSUMER_CONTAINER_NAME = "telemetry-consumer";
public static final String LIFECYCLE_CONSUMER_CONTAINER_NAME = "lifecycle-consumer";
public static final String AUTH_SERVICE_CONTAINER_NAME = "auth-service";
public static final String API_CONTAINER_NAME = "rest-api";
public static final int JOB_ENGINE_CONTAINER_PORT = 8080;

public static final String LAST_CREDENTIAL_ID = "LastCredentialId";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.eclipse.kapua.model.config.metatype.KapuaTocd;
import org.eclipse.kapua.model.config.metatype.KapuaToption;
import org.eclipse.kapua.model.config.metatype.MetatypeXmlRegistry;
import org.eclipse.kapua.service.authentication.token.AccessToken;
import org.eclipse.kapua.service.device.call.kura.model.bundle.KuraBundle;
import org.eclipse.kapua.service.device.call.kura.model.bundle.KuraBundles;
import org.eclipse.kapua.service.device.call.kura.model.configuration.KuraDeviceComponentConfiguration;
Expand Down Expand Up @@ -85,6 +86,8 @@
import org.eclipse.kapua.service.job.Job;
import org.eclipse.kapua.service.job.JobListResult;
import org.eclipse.kapua.service.job.JobXmlRegistry;
import org.eclipse.kapua.service.user.User;
import org.eclipse.kapua.service.user.UserListResult;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.slf4j.Logger;
Expand Down Expand Up @@ -191,7 +194,11 @@ public JAXBContext getJAXBContext() throws KapuaException {
DeviceConfiguration.class,
DevicePackages.class,
DevicePackageDownloadRequest.class,
DevicePackageUninstallRequest.class
DevicePackageUninstallRequest.class,

AccessToken.class,
User.class,
UserListResult.class
};
try {
Map<String, Object> properties = new HashMap<>(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public class DockerSteps {
private static final String LIFECYCLE_CONSUMER_IMAGE = "kapua-consumer-lifecycle";
private static final String TELEMETRY_CONSUMER_IMAGE = "kapua-consumer-telemetry";
private static final String AUTH_SERVICE_IMAGE = "kapua-service-authentication";
private static final String API_IMAGE = "kapua-api";
private static final List<String> DEFAULT_DEPLOYMENT_CONTAINERS_NAME;
private static final List<String> DEFAULT_BASE_DEPLOYMENT_CONTAINERS_NAME;
private static final int WAIT_COUNT = 120;//total wait time = 240 secs (120 * 2000ms)
Expand Down Expand Up @@ -104,6 +105,7 @@ public class DockerSteps {
DEFAULT_DEPLOYMENT_CONTAINERS_NAME.add(BasicSteps.LIFECYCLE_CONSUMER_CONTAINER_NAME);
DEFAULT_DEPLOYMENT_CONTAINERS_NAME.add(BasicSteps.AUTH_SERVICE_CONTAINER_NAME);
DEFAULT_DEPLOYMENT_CONTAINERS_NAME.add(BasicSteps.MESSAGE_BROKER_CONTAINER_NAME);
DEFAULT_DEPLOYMENT_CONTAINERS_NAME.add(BasicSteps.API_CONTAINER_NAME);
DEFAULT_BASE_DEPLOYMENT_CONTAINERS_NAME = new ArrayList<>();
DEFAULT_BASE_DEPLOYMENT_CONTAINERS_NAME.add(BasicSteps.JOB_ENGINE_CONTAINER_NAME);
DEFAULT_BASE_DEPLOYMENT_CONTAINERS_NAME.add(BasicSteps.EVENTS_BROKER_CONTAINER_NAME);
Expand Down Expand Up @@ -271,6 +273,41 @@ private void startBaseDockerEnvironmentInternal() throws Exception {
synchronized (this) {
this.wait(WAIT_FOR_JOB_ENGINE);
}

} catch (Exception e) {
logger.error("Error while starting base docker environment: {}", e.getMessage(), e);
throw e;
}
}

@Given("start rest-API container and dependencies with auth token TTL {string}ms and refresh token TTL {string}ms")
public void startApiDockerEnvironment(String tokenTTL, String refreshTokenTTL) throws Exception {
logger.info("Starting rest-api docker environment...");
stopFullDockerEnvironmentInternal();
try {
removeNetwork();
createNetwork();

startDBContainer(BasicSteps.DB_CONTAINER_NAME);
synchronized (this) {
this.wait(WAIT_FOR_DB);
}

startEventBrokerContainer(BasicSteps.EVENTS_BROKER_CONTAINER_NAME);
synchronized (this) {
this.wait(WAIT_FOR_EVENTS_BROKER);
}

startJobEngineContainer(BasicSteps.JOB_ENGINE_CONTAINER_NAME);
synchronized (this) {
this.wait(WAIT_FOR_JOB_ENGINE);
}

startAPIContainer(BasicSteps.API_CONTAINER_NAME, tokenTTL, refreshTokenTTL);
synchronized (this) {
this.wait(WAIT_FOR_JOB_ENGINE);
}

} catch (Exception e) {
logger.error("Error while starting base docker environment: {}", e.getMessage(), e);
throw e;
Expand Down Expand Up @@ -481,6 +518,19 @@ public void startDBContainer(String name) throws DockerException, InterruptedExc
logger.info("DB container started: {}", containerId);
}

@And("Start API container with name {string}")
public void startAPIContainer(String name, String tokenTTL, String refreshTokenTTL) throws DockerException, InterruptedException {
logger.info("Starting API container...");
ContainerConfig dbConfig = getApiContainerConfig(tokenTTL, refreshTokenTTL);
ContainerCreation dbContainerCreation = DockerUtil.getDockerClient().createContainer(dbConfig, name);
String containerId = dbContainerCreation.id();

DockerUtil.getDockerClient().startContainer(containerId);
DockerUtil.getDockerClient().connectToNetwork(containerId, networkId);
containerMap.put("api", containerId);
logger.info("API container started: {}", containerId);
}

@And("Start ES container with name {string}")
public void startESContainer(String name) throws DockerException, InterruptedException {
logger.info("Starting ES container...");
Expand Down Expand Up @@ -770,6 +820,32 @@ private ContainerConfig getDbContainerConfig() {
.build();
}

private ContainerConfig getApiContainerConfig(String tokenTTL, String refreshTokenTTL) {
final Map<String, List<PortBinding>> portBindings = new HashMap<>();
addHostPort(ALL_IP, portBindings, 8080, 8081);
addHostPort(ALL_IP, portBindings, 8443, 8443);
final HostConfig hostConfig = HostConfig.builder().portBindings(portBindings).build();

String[] ports = {
String.valueOf(8080),
String.valueOf(8443)
};

return ContainerConfig.builder()
.hostConfig(hostConfig)
.exposedPorts(ports)
.env(
"CRYPTO_SECRET_KEY=kapuaTestsKey!!!",
"KAPUA_DISABLE_DATASTORE=false",
//now I set very little TTL access token to help me in the test scenarios
"AUTH_TOKEN_TTL=" + tokenTTL,
"REFRESH_AUTH_TOKEN_TTL=" + refreshTokenTTL,
"SWAGGER=true"
)
.image("kapua/" + API_IMAGE + ":" + KAPUA_VERSION)
.build();
}

/**
* Creation of docker container configuration for telemetry consumer.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
*******************************************************************************/
package org.eclipse.kapua.qa.integration.steps;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.cucumber.guice.ScenarioScoped;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
Expand All @@ -23,6 +27,7 @@
import org.eclipse.kapua.service.authentication.token.AccessToken;
import org.eclipse.kapua.service.user.User;
import org.eclipse.kapua.service.user.UserListResult;
import org.jose4j.json.internal.json_simple.JSONObject;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -33,15 +38,20 @@
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;

@ScenarioScoped
public class RestClientSteps {

private static final Logger logger = LoggerFactory.getLogger(RestClientSteps.class);

private static final String TOKEN_ID = "tokenId";
private static final String TOKEN_ID = "tokenId"; //jwt
private static final String REFRESH_TOKEN = "refreshToken";
private static final String REST_RESPONSE = "restResponse";
private static final String REST_RESPONSE_CODE = "restResponseCode";

Expand Down Expand Up @@ -130,13 +140,101 @@ public void restPostCallWithJson(String resource, String json) throws Exception
}
}

@When("REST {string} call at {string} with JSON {string}")
public void restCall(String method, String resource, String json) throws Exception {
restCallInternal(method, resource, json, true);
}

public void restCallInternal(String method, String resource, String json, boolean authenticateCall) throws Exception {
// Create an instance of HttpClient
HttpClient httpClient = HttpClient.newHttpClient();
String host = (String) stepData.get("host");
String port = (String) stepData.get("port");

resource = insertStepData(resource);
// Define the URL you want to send the GET request to
String url = "http://" + host + ":" + port + resource;

HttpRequest.Builder baseBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept-Language", "UTF-8")
.header("accept", "application/json");

if (authenticateCall) {
String tokenId = (String) stepData.get(TOKEN_ID);
baseBuilder.setHeader("Authorization", "Bearer " + tokenId);
}

if (method.equals("POST")) {
baseBuilder.setHeader("Content-Type", "application/json");
baseBuilder.POST(HttpRequest.BodyPublishers.ofString(json));
}
else if (method.equals("GET")) {
baseBuilder.GET();
}

// Create an HttpRequest object
HttpRequest request = baseBuilder
.build();

try {
// Send the request and retrieve the response
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

// Print out the response status code
System.out.println("Response Code: " + response.statusCode());
stepData.put(REST_RESPONSE_CODE, response.statusCode());

// Print out the response body
System.out.println("Response Body: " + response.body());
stepData.put(REST_RESPONSE, response.body());

} catch (Exception e) {
// Handle exceptions
logger.error("Exception on REST POST call execution: " + resource);
throw e;
}
}

@When("I refresh last access token")
public void refreshToken() throws Exception {
String tokenId = (String) stepData.get(TOKEN_ID);
String refreshToken = (String) stepData.get(REFRESH_TOKEN);
String resource = "/v1/authentication/refresh";
JSONObject jsonObject = new JSONObject();
jsonObject.put("refreshToken", refreshToken);
jsonObject.put("tokenId", tokenId);
restCallInternal("POST", resource, jsonObject.toString(), false);
}

@When("I refresh access token using refresh token {string} and jwt {string}")
public void refreshPreciseToken(String refreshToken, String jwt) throws Exception {
if (!jwt.isEmpty()) {
stepData.put(TOKEN_ID, jwt);
}
if (!refreshToken.isEmpty()) {
stepData.put(REFRESH_TOKEN, refreshToken);
}
refreshToken();
}

@And("I extract {string} from the response and I save it in the key {string}")
public void exctractFromResponse(String field, String key) throws JsonProcessingException {
String response = (String) stepData.get(REST_RESPONSE);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonResponse = objectMapper.readTree(response);
stepData.put(key,jsonResponse.get(field).toString().replace("\"", ""));
String test = (String) stepData.get(key);
}

@Then("REST response containing text {string}")
public void restResponseContaining(String checkStr) throws Exception {
String restResponse = (String) stepData.get(REST_RESPONSE);
Assert.assertTrue(String.format("Response %s doesn't include %s.", restResponse, checkStr),
restResponse.contains(checkStr));
}


@Then("REST response containing Account")
public void restResponseContainingAccount() throws Exception {
String restResponse = (String) stepData.get(REST_RESPONSE);
Expand All @@ -161,6 +259,7 @@ public void restResponseContainingAccessToken() throws Exception {
AccessToken token = XmlUtil.unmarshalJson(restResponse, AccessToken.class);
Assert.assertTrue("Token is null.", token.getTokenId() != null);
stepData.put(TOKEN_ID, token.getTokenId());
stepData.put(REFRESH_TOKEN, token.getRefreshToken());
}

@Then("REST response containing User")
Expand Down Expand Up @@ -199,8 +298,8 @@ public void restResponseContainsLimitExceedValueWithValue(String value) throws E

@Given("^An authenticated user$")
public void anAuthenticationToken() throws Exception {
restPostCallWithJson("/v1/authentication/user",
"{\"password\": \"kapua-password\", \"username\": \"kapua-sys\"}");
restCallInternal("POST", "/v1/authentication/user",
"{\"password\": \"kapua-password\", \"username\": \"kapua-sys\"}", false);
restResponseContainingAccessToken();
}

Expand Down
Loading

0 comments on commit b884439

Please sign in to comment.