Skip to content

Commit

Permalink
OZ-573: Add support to authenticate with oauth2 via camel route (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
corneliouzbett authored Nov 28, 2024
1 parent c9e45ad commit 225915b
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ $RECYCLE.BIN/

# Windows shortcuts
*.lnk
.java-version


# End of https://www.gitignore.io/api/git,java,maven,eclipse,windows
24 changes: 24 additions & 0 deletions commons/src/main/java/com/ozonehis/eip/security/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright © 2021, Ozone HIS <info@ozone-his.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.ozonehis.eip.security;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class Constants {

public static final String BEARER_HTTP_AUTH_SCHEME = "Bearer";

public static final String HEADER_OAUTH2_URL = "oauth2.url";

public static final String HEADER_OAUTH2_CLIENT_ID = "oauth2.client.id";

public static final String HEADER_OAUTH2_CLIENT_SECRET = "oauth2.client.secret";

public static final String HEADER_OAUTH2_SCOPE = "oauth2.client.scope";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright © 2021, Ozone HIS <info@ozone-his.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.ozonehis.eip.security.oauth2;

import static com.ozonehis.eip.security.Constants.BEARER_HTTP_AUTH_SCHEME;
import static com.ozonehis.eip.security.Constants.HEADER_OAUTH2_CLIENT_ID;
import static com.ozonehis.eip.security.Constants.HEADER_OAUTH2_CLIENT_SECRET;
import static com.ozonehis.eip.security.Constants.HEADER_OAUTH2_SCOPE;
import static com.ozonehis.eip.security.Constants.HEADER_OAUTH2_URL;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.ProducerTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
@Setter
@Getter
@Component("eip.oauthProcessor")
public class OAuth2Processor implements Processor {

private final ProducerTemplate producerTemplate;

@Autowired
public OAuth2Processor(ProducerTemplate producerTemplate) {
this.producerTemplate = producerTemplate;
}

@Override
public void process(Exchange exchange) {
OAuth2Properties properties = OAuth2Properties.builder()
.authUrl(exchange.getMessage().getHeader(HEADER_OAUTH2_URL, String.class))
.clientScope(exchange.getMessage().getHeader(HEADER_OAUTH2_SCOPE, String.class))
.clientId(exchange.getMessage().getHeader(HEADER_OAUTH2_CLIENT_ID, String.class))
.clientSecret(exchange.getMessage().getHeader(HEADER_OAUTH2_CLIENT_SECRET, String.class))
.build();
validateOAuth2Properties(properties);
OAuth2Token authToken = callAccessTokenUri(properties.getAuthUrl(), buildOAuth2RequestBody(properties));
if (authToken == null) {
throw new IllegalStateException("OAuth2 token is null");
} else {
log.info("OAuth2 token is successfully obtained. Expires in {} seconds", authToken.getExpiresIn());
}
setAuthorizationHeader(exchange, authToken.getAccessToken());
}

private void validateOAuth2Properties(OAuth2Properties properties) {
if (properties == null || !properties.isValid()) {
throw new IllegalStateException("OAuth2 properties are not set properly or some properties are missing");
}
}

private String buildOAuth2RequestBody(OAuth2Properties properties) {
return "grant_type=client_credentials" + "&client_id="
+ properties.getClientId() + "&client_secret="
+ properties.getClientSecret() + "&scope="
+ properties.getClientScope();
}

private OAuth2Token callAccessTokenUri(String accessTokenUri, String requestBody) {
return producerTemplate.requestBodyAndHeader(
"direct:oauth2", requestBody, HEADER_OAUTH2_URL, accessTokenUri, OAuth2Token.class);
}

private void setAuthorizationHeader(Exchange exchange, String accessToken) {
exchange.getMessage().setHeader("Authorization", BEARER_HTTP_AUTH_SCHEME + " " + accessToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright © 2021, Ozone HIS <info@ozone-his.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.ozonehis.eip.security.oauth2;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OAuth2Properties {

private String authUrl;

private String clientId;

private String clientSecret;

private String clientScope;

public boolean isValid() {
return authUrl != null && clientId != null && clientSecret != null && clientScope != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright © 2021, Ozone HIS <info@ozone-his.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.ozonehis.eip.security.oauth2;

import static com.ozonehis.eip.security.Constants.HEADER_OAUTH2_URL;

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.dataformat.JsonLibrary;
import org.springframework.stereotype.Component;

@Component
public class OAuth2Route extends RouteBuilder {

@Override
public void configure() {
from("direct:oauth2")
.routeId("oauth2")
.setHeader("Content-Type", constant("application/x-www-form-urlencoded"))
.setHeader("CamelHttpMethod", constant("POST"))
.toD("${header." + HEADER_OAUTH2_URL + "}")
.unmarshal()
.json(JsonLibrary.Jackson, OAuth2Token.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright © 2021, Ozone HIS <info@ozone-his.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.ozonehis.eip.security.oauth2;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
public class OAuth2Token {

@JsonProperty("access_token")
private String accessToken;

@JsonProperty("expires_in")
private long expiresIn;

@JsonProperty("refresh_expires_in")
private long refreshExpiresIn;

@JsonProperty("token_type")
private String tokenType;

@JsonProperty("not-before-policy")
private long notBeforePolicy;

@JsonProperty("scope")
private String scope;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright © 2021, Ozone HIS <info@ozone-his.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.ozonehis.eip.security.oauth2;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.test.junit5.CamelTestSupport;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

public class OAuth2ProcessorTest extends CamelTestSupport {

public static final String TEST_ACCESS_TOKEN = "testAccessToken";

public static final String KEYCLOAK_SERVER_URL_AUTH = "https://keycloak-server-url/auth";

@Mock
private ProducerTemplate producerTemplate;

@InjectMocks
private OAuth2Processor oauth2Processor;

private static AutoCloseable mocksCloser;

@Mock
private Exchange exchange;

@Mock
private Message message;

@BeforeEach
public void setUp() {
mocksCloser = openMocks(this);
when(message.getHeader("oauth2.url", String.class)).thenReturn(KEYCLOAK_SERVER_URL_AUTH);
when(message.getHeader("oauth2.client.id", String.class)).thenReturn("clientId");
when(message.getHeader("oauth2.client.secret", String.class)).thenReturn("clientSecret");
when(message.getHeader("oauth2.client.scope", String.class)).thenReturn("scope");
when(exchange.getMessage()).thenReturn(message);
}

@AfterAll
public static void closeMocks() throws Exception {
mocksCloser.close();
}

@Test
public void shouldAddAuthorizationHeaderGivenClientIdAndSecret() {
// Setup
when(producerTemplate.requestBodyAndHeader(
anyString(), anyString(), anyString(), anyString(), eq(OAuth2Token.class)))
.thenReturn(getOauthToken());

// Replay
oauth2Processor.process(exchange);

// Verify
verify(exchange.getMessage(), times(1)).setHeader(eq("Authorization"), eq("Bearer " + TEST_ACCESS_TOKEN));
}

@Test
public void shouldThrowEIPAuthenticationExceptionGivenNullClientScope() {
// Setup
String accessTokenUri = "https://test.auth.com/token";
when(producerTemplate.requestBodyAndHeader(
anyString(), anyString(), eq("authUrl"), eq(accessTokenUri), eq(OAuth2Token.class)))
.thenReturn(getOauthToken());

// Replay & Verify
assertThrows(IllegalStateException.class, () -> oauth2Processor.process(exchange));
}

@Test
public void shouldThrowEIPAuthenticationExceptionGivenNullProperties() {
// Setup
String accessTokenUri = "https://test.auth.com/token";
when(producerTemplate.requestBodyAndHeader(
anyString(), anyString(), eq("authUrl"), eq(accessTokenUri), eq(OAuth2Token.class)))
.thenReturn(getOauthToken());

// Replay & Verify
assertThrows(IllegalStateException.class, () -> oauth2Processor.process(exchange));
}

private static OAuth2Token getOauthToken() {
OAuth2Token oAuthToken = new OAuth2Token();
oAuthToken.setAccessToken(TEST_ACCESS_TOKEN);
oAuthToken.setExpiresIn(3600);
oAuthToken.setRefreshExpiresIn(3600);
oAuthToken.setTokenType("Bearer");
oAuthToken.setNotBeforePolicy(0);
return oAuthToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright © 2021, Ozone HIS <info@ozone-his.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.ozonehis.eip.security.oauth2;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import org.junit.jupiter.api.Test;

public class OAuth2PropertiesTest {

@Test
public void shouldSetPropertiesCorrectlyWithBuilder() {
// Set the properties using the builder
OAuth2Properties properties = OAuth2Properties.builder()
.authUrl("https://example.com/auth")
.clientId("testClientId")
.clientSecret("testClientSecret")
.clientScope("testClientScope")
.build();

// Verify
assertEquals("https://example.com/auth", properties.getAuthUrl());
assertEquals("testClientId", properties.getClientId());
assertEquals("testClientSecret", properties.getClientSecret());
assertEquals("testClientScope", properties.getClientScope());
}

@Test
public void shouldSetPropertiesCorrectlyWithDefaults() {
// set default properties
OAuth2Properties properties = OAuth2Properties.builder().build();

// Verify
assertNull(properties.getAuthUrl());
assertNull(properties.getClientId());
assertNull(properties.getClientSecret());
assertNull(properties.getClientScope());
}

@Test
public void shouldSetPropertiesCorrectlyWithSetter() {
OAuth2Properties properties = new OAuth2Properties();

// Set the properties using the setter methods
properties.setAuthUrl("https://example.com/auth");
properties.setClientId("testClientId");
properties.setClientSecret("testClientSecret");
properties.setClientScope("testClientScope");

// Verify
assertEquals("https://example.com/auth", properties.getAuthUrl());
assertEquals("testClientId", properties.getClientId());
assertEquals("testClientSecret", properties.getClientSecret());
assertEquals("testClientScope", properties.getClientScope());
}
}
Loading

0 comments on commit 225915b

Please sign in to comment.