Skip to content

[Issue #193] Integrate OAuth2 Authentication into Core Repository #676

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
92003ee
Important files assembled for OAuth2 testing
prayascoriolis Aug 29, 2024
1c7323b
resolved paths error
prayascoriolis Aug 29, 2024
7d35f86
commenting plugin in pom.xml for ruuning kfka test
prayascoriolis Aug 31, 2024
8b40305
Merge branch 'master' into ISSUE-193-OAuth2-Support
prayascoriolis Aug 31, 2024
aba383b
Description improved
authorjapps Aug 6, 2024
0603dbf
Install docker compose manually and try
authorjapps Aug 31, 2024
19690a1
Merge branch 'ISSUE-193-OAuth2-Support' of https://github.com/prayasc…
prayascoriolis Aug 31, 2024
d349285
uncommenting plugin in pom.xml for ruuning kfka test
prayascoriolis Aug 31, 2024
ef2b685
grant_type fetched from host.properties, name-value pair in post url
prayascoriolis Sep 5, 2024
159be2b
separate assertions & unit test class used for refresh & acces token …
prayascoriolis Sep 6, 2024
ea564c5
Access Token workflow implemented
prayascoriolis Sep 6, 2024
3f35c99
host.properties modified for access and refresh token
prayascoriolis Sep 6, 2024
aab323b
updated the comments for oauth2 code
prayascoriolis Sep 10, 2024
e529bae
fix: resolve issue with mock server not matching incoming requests
prayascoriolis Sep 11, 2024
adcb897
removed duplicate localhost_REST_fake_end_points_stubs.json from http…
prayascoriolis Sep 11, 2024
14d65c9
end to end access token implemetation with fake REST end points
prayascoriolis Sep 11, 2024
626f82e
Merge branch 'master' into ISSUE-193-OAuth2-Support
prayascoriolis Oct 29, 2024
0cc5aef
accountsUrl changed to accessTokenUrl, class names changed to CamelCa…
prayascoriolis Oct 30, 2024
2efa3c6
Merge branch 'ISSUE-193-OAuth2-Support' of https://github.com/prayasc…
prayascoriolis Oct 30, 2024
e7106cf
Merge branch 'master' into ISSUE-193-OAuth2-Support
prayascoriolis Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.jsmart.zerocode.core.httpclient.oauth2;

import java.util.Map;
import java.util.Timer;

import org.apache.http.client.methods.RequestBuilder;
import org.jsmart.zerocode.core.httpclient.BasicHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
* @author santhoshTpixler
*
*/


/*
* Note: This implementation supports the OAuth2.0 with refresh_token and access token
*
* Reference: https://tools.ietf.org/html/rfc6749#page-11
*
* REFRESH TOKEN
* 1. The refresh_token, access_token URL, client_id, grant_type and client_secret
* should be generated by the user and stored in the properties file specified by @TargetEnv("host.properties"),
* located at: http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/OAuth2/OAuth2Test_refreshToken.java
* 2. The grant_type should be set to refresh_token in the properties file.
* 3. For generating the refresh token REST Client such as Insomnia (https://insomnia.rest/) can
* be used.
*
* Note: Postman cannot be used as it does not show the refresh token.
*
* ACCESS TOKEN
* 1. The access_token URL, client_id, grant_type, and client_secret should be generated
* by the user and stored in the properties file specified by @TargetEnv("host.properties"),
* located at: http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/OAuth2/OAuth2Test_accessToken.java.
* 2. The grant_type should be set to client_credentials in the properties file.
*/
public class OAuth2HttpClient extends BasicHttpClient {

private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2HttpClient.class);

/*
* Properties to be fetched from the host.properties
*/
private static final String CLIENT_ID = "client_id";
private static final String CLIENT_SECRET = "client_secret";
private static final String REFRESH_TOKEN = "refresh_token";
private static final String ACCESS_TOKEN_URL = "access_token_url";
private static final String GRANT_TYPE = "grant_type";
/*
* If the Authorization header contains the replacement value as specified by the
* below constant, then it is replaced with the valid access token
*/
private static final String ACCESS_TOKEN_REPLACEMENT_VALUE = "DIY";
/*
* Time interval in which the accessToken should be renewed
*/
private static final long REFRESH_INTERVAL = 3540000;

private OAuth2Impl oauth2 = null;

@Inject
public OAuth2HttpClient(@Named(CLIENT_ID) String clientId, @Named(CLIENT_SECRET) String clientSecret,
@Named(REFRESH_TOKEN) String refreshToken, @Named(ACCESS_TOKEN_URL) String accountsURL, @Named(GRANT_TYPE) String grant_type) {
if ("refresh_token".equals(grant_type)) {
/*
* REFRESH TOKEN WORKFLOW
* generating access token using refresh tokens
*/
this.oauth2 = new OAuth2Impl(clientId, clientSecret, refreshToken, accountsURL, grant_type);
Timer timer = new Timer();
/*
* A Timer is started to periodically execute the OAuth2Impl's run() method,
* which will refresh the access token at intervals defined by REFRESH_INTERVAL
*/
timer.schedule(oauth2, 0, REFRESH_INTERVAL);
synchronized (oauth2) {
try {
// to ensure the access token is generated before proceeding.
oauth2.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} else if ("client_credentials".equals(grant_type)) {
/*
* ACCESS TOKEN WORKFLOW
* Fetching access token from host.properties
*/
this.oauth2 = new OAuth2Impl(clientId, clientSecret, accountsURL, grant_type);
oauth2.run();
}
else {
LOGGER.info("Incorrect grant_type in properties file");
}
}

@Override
public RequestBuilder handleHeaders(Map<String, Object> headers, RequestBuilder requestBuilder) {
String authorization = (String) headers.get("Authorization");
if (authorization != null && authorization.equals(ACCESS_TOKEN_REPLACEMENT_VALUE)) {
headers.put("Authorization", oauth2.getAccessToken());
LOGGER.info("Token injected into header.");
}
return super.handleHeaders(headers, requestBuilder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.jsmart.zerocode.core.httpclient.oauth2;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;

import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author santhoshTpixler
*
*/

/*
* Note: This implementation supports the OAuth2.0 with refresh_token
*
* Reference: https://tools.ietf.org/html/rfc6749#page-11
*/
public class OAuth2Impl extends TimerTask {
private String clienId;
private String clientSecret;
private String refreshToken;
private String accessTokenURL;
private String grant_type;

private String accessToken;
private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Impl.class);

public OAuth2Impl(String clientId, String clientSecret, String refreshToken, String accountsUrl, String grant_type) {
this.clienId = clientId;
this.clientSecret = clientSecret;
this.refreshToken = refreshToken;
this.accessTokenURL = accountsUrl;
this.grant_type = grant_type;
}

public OAuth2Impl(String clientId, String clientSecret, String accessToken, String grantType) {
this.clienId = clientId;
this.clientSecret = clientSecret;
this.accessTokenURL = accessToken;
this.grant_type = grantType;
}

@Override
public void run() {
generateToken();
}


public synchronized String getAccessToken() {
return accessToken;

}

private synchronized void setAccessToken(String token) {
this.accessToken = "Bearer " + token;
}

/**
* Makes a POST request to the accessTokenURL to fetch the accesstoken
*/
private synchronized void generateToken() {
try (CloseableHttpClient client = HttpClients.createDefault()) {
List<NameValuePair> nameValuePairs;
if ("refresh_token".equals(grant_type)) {
// for testing refresh tokens
nameValuePairs = new ArrayList<>(4);
nameValuePairs.add(new BasicNameValuePair("refresh_token", refreshToken));
nameValuePairs.add(new BasicNameValuePair("client_id", clienId));
nameValuePairs.add(new BasicNameValuePair("client_secret", clientSecret));
nameValuePairs.add(new BasicNameValuePair("grant_type", grant_type));
} else{
// for testing access tokens
nameValuePairs = new ArrayList<>(3);
nameValuePairs.add(new BasicNameValuePair("grant_type", grant_type));
nameValuePairs.add(new BasicNameValuePair("client_id", clienId));
nameValuePairs.add(new BasicNameValuePair("client_secret", clientSecret));
}
String encodedParams = URLEncodedUtils.format(nameValuePairs, "UTF-8");
StringBuilder URL = new StringBuilder(accessTokenURL);
URL.append('?');
URL.append(encodedParams);
HttpPost post = new HttpPost(String.valueOf(URL));
JSONObject jsonRespone = null;
try (CloseableHttpResponse response = client.execute(post);) {
try (InputStream stream = response.getEntity().getContent()) {
jsonRespone = new JSONObject(new JSONTokener(stream));
}
}
if (accessToken == null) {
setAccessToken(jsonRespone.getString("access_token"));
/*
* Since this is the first time generating the token, notifyAll()
* is called to wake up any threads waiting for the token, allowing
* them to proceed with the authenticated requests.
*/
this.notifyAll();
} else {
setAccessToken(jsonRespone.getString("access_token"));
}
} catch (Exception e) {
LOGGER.error("Cannot fetch access token from IAM", e);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ public RunMeFirstLocalMockRESTServer(int port) {
}

public static void main(String[] args) {
logger.debug("\n### REST Helper web-service starting...");
logger.info("\n### REST Helper web-service starting...");

new RunMeFirstLocalMockRESTServer(PORT).start();

logger.debug("\n### REST Helper web-service started.");
logger.info("\n### REST Helper web-service started.");

System.out.println("\n------ Done? To stop this REST server, simply press Ctrl+c or Stop button on your IDE -------");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jsmart.zerocode.testhelp.tests.OAuth2;

import org.jsmart.zerocode.core.domain.Scenario;
import org.jsmart.zerocode.core.domain.TargetEnv;
import org.jsmart.zerocode.core.domain.UseHttpClient;
import org.jsmart.zerocode.core.httpclient.oauth2.OAuth2HttpClient;
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Run this file For testing access tokens only.
* Provide essential values in host.properties file.
*/
@TargetEnv("host.properties")
@RunWith(ZeroCodeUnitRunner.class)
@UseHttpClient(OAuth2HttpClient.class)
public class TestOAuth2AccessToken {

// First run this Server for OAuth2 access_token_url be available
// --> RunMeFirstLocalMockRESTServer main()
@Test
@Scenario("helloworld_OAuth2/OAuth_supported_request_access_token.json")
public void testClientCredentialsFlow() {
// This test will use the access token flow
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jsmart.zerocode.testhelp.tests.OAuth2;

import org.jsmart.zerocode.core.domain.Scenario;
import org.jsmart.zerocode.core.domain.TargetEnv;
import org.jsmart.zerocode.core.domain.UseHttpClient;
import org.jsmart.zerocode.core.httpclient.oauth2.OAuth2HttpClient;
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Run this file For testing refresh tokens only.
* Provide essential values in host.properties file.
*/
@TargetEnv("host.properties")
@RunWith(ZeroCodeUnitRunner.class)
@UseHttpClient(OAuth2HttpClient.class)
public class TestOAuth2RefreshToken {

// First run this Server for OAuth2 access_token_url be available
// --> RunMeFirstLocalMockRESTServer main()
@Test
@Scenario("helloworld_OAuth2/OAuth_supported_request_refresh_token.json")
public void testRefreshTokenFlow() {
// This test will use the refresh token flow
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"scenarioName": "OAuth2 Client Credentials Flow Test",
"steps": [
{
"name": "get_user_details_with_authorization",
"url": "/api/v1/employee/id",
"method": "GET",
"request": {
"headers": {
"Authorization": "DIY"
}
},
"verify": {
"status": 200,
"body": {
"empId": "UK-LON-1002",
"city": "UK-London",
"dob": "1989-07-09"
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"scenarioName": "GIVEN- the REST end point, WHEN- I invoke GET, THEN- OAuth token will be given insted of DIY and I will receive the 200 status with body",
"steps": [
{
"name": "get_user_details_with_authorization",
"url": "/api/v1/employee/id",
"method": "GET",
"request": {
"headers": {
"Authorization": "DIY"
}
},
"verify": {
"status": 200,
"body": {
"empId": "UK-LON-1002",
"city": "UK-London",
"dob": "1989-07-09"
}
}
}
]
}
13 changes: 13 additions & 0 deletions http-testing/src/test/resources/host.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
restful.application.endpoint.host=http://localhost
restful.application.endpoint.port=9999
restful.application.endpoint.context=

# use grant_type = refresh_token for testing refresh tokens
# use grant_type = client_credentials for testing access tokens
grant_type=client_credentials

# for testing refresh tokens
refresh_token=refresh.token1224454564657556
client_id=client.id74528572945820
client_secret=client.secrect14879452304958245
access_token_url=http://localhost:9999/oauth/v2/token
Loading