-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OZ-573: Add support to authenticate with
oauth2
via camel route (#41)
- Loading branch information
1 parent
c9e45ad
commit 225915b
Showing
9 changed files
with
458 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
commons/src/main/java/com/ozonehis/eip/security/Constants.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} |
77 changes: 77 additions & 0 deletions
77
commons/src/main/java/com/ozonehis/eip/security/oauth2/OAuth2Processor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
commons/src/main/java/com/ozonehis/eip/security/oauth2/OAuth2Properties.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
commons/src/main/java/com/ozonehis/eip/security/oauth2/OAuth2Route.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
commons/src/main/java/com/ozonehis/eip/security/oauth2/OAuth2Token.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
110 changes: 110 additions & 0 deletions
110
commons/src/test/java/com/ozonehis/eip/security/oauth2/OAuth2ProcessorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
commons/src/test/java/com/ozonehis/eip/security/oauth2/OAuth2PropertiesTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
Oops, something went wrong.