diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerService.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerService.java index 32f4e5112a..dfd6af295a 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerService.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerService.java @@ -40,11 +40,11 @@ public interface KeyManagerService { @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Path("/token") Response generateAccessToken(@HeaderParam("Authorization") String basicAuthHeader, - @FormParam("client_id") String clientId, - @FormParam("client_secret") String clientSecret, @FormParam("refresh_token") String refreshToken, @FormParam("scope") String scope, @FormParam("grant_type") String grantType, @FormParam("assertion") String assertion, - @FormParam("admin_access_token") String admin_access_token); + @FormParam("admin_access_token") String admin_access_token, + @FormParam("username") String username, + @FormParam("password") String password); } diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerServiceImpl.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerServiceImpl.java index 4ad127527e..961951f865 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerServiceImpl.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension.api/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/api/KeyManagerServiceImpl.java @@ -19,8 +19,6 @@ package org.wso2.carbon.apimgt.keymgt.extension.api; import com.google.gson.Gson; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.wso2.carbon.apimgt.keymgt.extension.DCRResponse; import org.wso2.carbon.apimgt.keymgt.extension.TokenRequest; import org.wso2.carbon.apimgt.keymgt.extension.TokenResponse; @@ -65,13 +63,13 @@ public class KeyManagerServiceImpl implements KeyManagerService { @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Path("/token") public Response generateAccessToken(@HeaderParam("Authorization") String basicAuthHeader, - @FormParam("client_id") String clientId, - @FormParam("client_secret") String clientSecret, @FormParam("refresh_token") String refreshToken, @FormParam("scope") String scope, @FormParam("grant_type") String grantType, @FormParam("assertion") String assertion, - @FormParam("admin_access_token") String admin_access_token) { + @FormParam("admin_access_token") String admin_access_token, + @FormParam("username") String username, + @FormParam("password") String password) { try { if (basicAuthHeader == null) { String msg = "Invalid credentials. Make sure your API call is invoked with a Basic Authorization header."; @@ -82,8 +80,8 @@ public class KeyManagerServiceImpl implements KeyManagerService { TokenResponse resp = keyMgtService.generateAccessToken( new TokenRequest(encodedClientCredentials.split(":")[0], encodedClientCredentials.split(":")[1], refreshToken, scope, - grantType, assertion,admin_access_token)); - return Response.status(Response.Status.CREATED).entity(gson.toJson(resp)).build(); + grantType, assertion, admin_access_token, username, password)); + return Response.status(Response.Status.OK).entity(gson.toJson(resp)).build(); } catch (KeyMgtException e) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); } catch (BadRequestException e) { diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenRequest.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenRequest.java index ae5b668cba..860b267161 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenRequest.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenRequest.java @@ -26,9 +26,11 @@ public class TokenRequest { private String grantType; private String assertion; private String admin_access_token; + private String username; + private String password; public TokenRequest(String clientId, String clientSecret, String refreshToken, String scope, String grantType, - String assertion, String admin_access_token) { + String assertion, String admin_access_token, String username, String password) { this.clientId = clientId; this.clientSecret = clientSecret; this.refreshToken = refreshToken; @@ -36,6 +38,8 @@ public class TokenRequest { this.grantType = grantType; this.assertion = assertion; this.admin_access_token = admin_access_token; + this.username = username; + this.password = password; } public String getClientId() { @@ -93,4 +97,20 @@ public class TokenRequest { public void setAdminAccessToken(String admin_access_token) { this.admin_access_token = admin_access_token; } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } } diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenResponse.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenResponse.java index 0298813878..530fa1742d 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenResponse.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/TokenResponse.java @@ -33,6 +33,13 @@ public class TokenResponse { this.expires_in = expires_in; } + public TokenResponse(String access_token, String scope, String token_type, int expires_in) { + this.access_token = access_token; + this.scope = scope; + this.token_type = token_type; + this.expires_in = expires_in; + } + public String getAccessToken() { return access_token; } diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/service/KeyMgtServiceImpl.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/service/KeyMgtServiceImpl.java index 30412867ca..4640fc9a57 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/service/KeyMgtServiceImpl.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.keymgt.extension/src/main/java/org/wso2/carbon/apimgt/keymgt/extension/service/KeyMgtServiceImpl.java @@ -159,49 +159,34 @@ public class KeyMgtServiceImpl implements KeyMgtService { } String tenantDomain = MultitenantUtils.getTenantDomain(application.getOwner()); - - String username, password; - if (KeyMgtConstants.SUPER_TENANT.equals(tenantDomain)) { - kmConfig = getKeyManagerConfig(); - username = kmConfig.getAdminUsername(); - password = kmConfig.getAdminUsername(); - } else { - try { - username = getRealmService() - .getTenantUserRealm(-1234).getRealmConfiguration() - .getRealmProperty("reserved_tenant_user_username") + "@" + tenantDomain; - password = getRealmService() - .getTenantUserRealm(-1234).getRealmConfiguration() - .getRealmProperty("reserved_tenant_user_password"); - } catch (UserStoreException e) { - msg = "Error while loading user realm configuration"; - log.error(msg); - throw new KeyMgtException(msg); - } - } + kmConfig = getKeyManagerConfig(); + String appTokenEndpoint = kmConfig.getServerUrl() + KeyMgtConstants.OAUTH2_TOKEN_ENDPOINT; RequestBody appTokenPayload; switch (tokenRequest.getGrantType()) { case "client_credentials": + appTokenPayload = new FormBody.Builder() + .add("grant_type", "client_credentials") + .add("scope", tokenRequest.getScope()).build(); + break; case "password": appTokenPayload = new FormBody.Builder() .add("grant_type", "password") - .add("username", username) - .add("password", password) + .add("username", tokenRequest.getUsername()) + .add("password", tokenRequest.getPassword()) .add("scope", tokenRequest.getScope()).build(); break; - case "refresh_token": appTokenPayload = new FormBody.Builder() .add("grant_type", "refresh_token") - .add("refresh_token", tokenRequest.getRefreshToken()) - .add("scope", tokenRequest.getScope()).build(); + .add("refresh_token", tokenRequest.getRefreshToken()).build(); break; case "urn:ietf:params:oauth:grant-type:jwt-bearer": appTokenPayload = new FormBody.Builder() .add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer") .add("assertion", tokenRequest.getAssertion()) .add("scope", tokenRequest.getScope()).build(); + appTokenEndpoint += "?tenantDomain=carbon.super"; break; case "access_token": appTokenPayload = new FormBody.Builder() @@ -216,8 +201,6 @@ public class KeyMgtServiceImpl implements KeyMgtService { break; } - kmConfig = getKeyManagerConfig(); - String appTokenEndpoint = kmConfig.getServerUrl() + KeyMgtConstants.OAUTH2_TOKEN_ENDPOINT; Request request = new Request.Builder() .url(appTokenEndpoint) .addHeader(KeyMgtConstants.AUTHORIZATION_HEADER, Credentials.basic(tokenRequest.getClientId(), tokenRequest.getClientSecret())) @@ -239,12 +222,19 @@ public class KeyMgtServiceImpl implements KeyMgtService { .getTenantManager().getTenantId(tenantDomain); accessToken = tenantId + "_" + responseObj.getString("access_token"); } - return new TokenResponse(accessToken, - responseObj.getString("refresh_token"), - responseObj.getString("scope"), - responseObj.getString("token_type"), - responseObj.getInt("expires_in")); + if (tokenRequest.getGrantType().equals("client_credentials")) { + return new TokenResponse(accessToken, + responseObj.getString("scope"), + responseObj.getString("token_type"), + responseObj.getInt("expires_in")); + } else { + return new TokenResponse(accessToken, + responseObj.getString("refresh_token"), + responseObj.getString("scope"), + responseObj.getString("token_type"), + responseObj.getInt("expires_in")); + } } catch (APIManagementException e) { msg = "Error occurred while retrieving application"; log.error(msg); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/GroupManagementProviderServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/GroupManagementProviderServiceImpl.java index cd560eef40..c036e752af 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/GroupManagementProviderServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/GroupManagementProviderServiceImpl.java @@ -194,8 +194,8 @@ public class GroupManagementProviderServiceImpl implements GroupManagementProvid GroupManagementDAOFactory.beginTransaction(); DeviceGroup existingGroup = this.groupDAO.getGroup(groupId, tenantId); if (existingGroup != null) { - boolean existingGroupName = this.groupDAO.getGroup(deviceGroup.getName(), tenantId) != null; - if (existingGroupName) { + DeviceGroup existingGroupByName = this.groupDAO.getGroup(deviceGroup.getName(), tenantId); + if (existingGroupByName != null && existingGroupByName.getGroupId() != groupId) { throw new GroupAlreadyExistException("Group already exists with name '" + deviceGroup.getName() + "'."); } List groupsToUpdate = new ArrayList<>(); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java index f41f94a764..4120069a56 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java @@ -229,7 +229,7 @@ public class LoginHandler extends HttpServlet { * @throws IOException IO exception throws if an error occurred when invoking token endpoint */ private ProxyResponse getTokenResult(String encodedClientApp, JsonArray scopes) throws IOException { - HttpPost tokenEndpoint = new HttpPost(kmManagerUrl+ HandlerConstants.TOKEN_ENDPOINT); + HttpPost tokenEndpoint = new HttpPost(gatewayUrl + HandlerConstants.INTERNAL_TOKEN_ENDPOINT); tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + encodedClientApp); tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); String scopeString = HandlerUtil.getScopeString(scopes); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java index 6fc5afa8ef..30efb713d5 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java @@ -68,7 +68,7 @@ public class SsoLoginCallbackHandler extends HttpServlet { String scope = session.getAttribute("scope").toString(); - HttpPost tokenEndpoint = new HttpPost(keyManagerUrl + HandlerConstants.TOKEN_ENDPOINT); + HttpPost tokenEndpoint = new HttpPost(keyManagerUrl + HandlerConstants.OAUTH2_TOKEN_ENDPOINT); tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + session.getAttribute("encodedClientApp")); tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java index f6180c0a37..0111671724 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java @@ -325,7 +325,7 @@ public class SsoLoginHandler extends HttpServlet { * @throws IOException IO exception throws if an error occurred when invoking token endpoint */ private ProxyResponse getTokenResult(String encodedClientApp) throws IOException { - HttpPost tokenEndpoint = new HttpPost(keyManagerUrl + HandlerConstants.TOKEN_ENDPOINT); + HttpPost tokenEndpoint = new HttpPost(keyManagerUrl + HandlerConstants.OAUTH2_TOKEN_ENDPOINT); tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + encodedClientApp); tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java index 4e1f120734..997015ce8f 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java @@ -71,6 +71,7 @@ public class UserHandler extends HttpServlet { } String accessToken = authData.getAccessToken(); + String accessTokenWithoutPrefix = accessToken.substring(accessToken.indexOf("_") + 1); HttpPost tokenEndpoint = new HttpPost(keymanagerUrl + HandlerConstants.INTROSPECT_ENDPOINT); tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); @@ -79,7 +80,7 @@ public class UserHandler extends HttpServlet { String adminPassword = dmc.getKeyManagerConfigurations().getAdminPassword(); tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + Base64.getEncoder() .encodeToString((adminUsername + HandlerConstants.COLON + adminPassword).getBytes())); - StringEntity tokenEPPayload = new StringEntity("token=" + accessToken, + StringEntity tokenEPPayload = new StringEntity("token=" + accessTokenWithoutPrefix, ContentType.APPLICATION_FORM_URLENCODED); tokenEndpoint.setEntity(tokenEPPayload); ProxyResponse tokenStatus = HandlerUtil.execute(tokenEndpoint); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java index 828547c08f..bf9947a9af 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java @@ -22,7 +22,8 @@ public class HandlerConstants { public static final String PUBLISHER_APPLICATION_NAME = "application-mgt-publisher"; public static final String APP_REG_ENDPOINT = "/api-application-registration/register"; public static final String UI_CONFIG_ENDPOINT = "/api/device-mgt-config/v1.0/configurations/ui-config"; - public static final String TOKEN_ENDPOINT = "/oauth2/token"; + public static final String OAUTH2_TOKEN_ENDPOINT = "/oauth2/token"; + public static final String INTERNAL_TOKEN_ENDPOINT = "/token"; public static final String INTROSPECT_ENDPOINT = "/oauth2/introspect"; public static final String AUTHORIZATION_ENDPOINT = "/oauth2/authorize"; public static final String APIM_APPLICATIONS_ENDPOINT = "/api/am/devportal/v2/applications/"; diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java index 6be995f191..fb565bf59b 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java @@ -55,7 +55,6 @@ import org.json.JSONException; import org.json.JSONObject; import org.w3c.dom.Document; import io.entgra.ui.request.interceptor.beans.ProxyResponse; -import org.wso2.carbon.device.mgt.core.common.util.HttpUtil; import org.xml.sax.SAXException; import javax.servlet.http.HttpServletRequest; @@ -654,7 +653,7 @@ public class HandlerUtil { return tokenResultResponse; } public static ProxyResponse getTokenResult(AuthData authData, String keymanagerUrl) throws IOException { - HttpPost tokenEndpoint = new HttpPost(keymanagerUrl + HandlerConstants.TOKEN_ENDPOINT); + HttpPost tokenEndpoint = new HttpPost(keymanagerUrl + HandlerConstants.OAUTH2_TOKEN_ENDPOINT); StringEntity tokenEndpointPayload = new StringEntity( "grant_type=refresh_token&refresh_token=" + authData.getRefreshToken(), ContentType.APPLICATION_FORM_URLENCODED); @@ -735,4 +734,4 @@ public class HandlerUtil { public static boolean isPropertyDefined(String property) { return StringUtils.isEmpty(System.getProperty(property)); } -} \ No newline at end of file +} diff --git a/pull_request_template.md b/pull_request_template.md index 9b32185a46..ab4a6474ea 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,52 +1,52 @@ -## Purpose -> Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc. - -## Goals -> Describe the solutions that this feature/fix will introduce to resolve the problems described above - -## Approach -> Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here. - -## User stories -> Summary of user stories addressed by this change> - -## Release note -> Brief description of the new feature or bug fix as it will appear in the release notes - -## Documentation -> Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact - -## Training -> Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable - -## Certification -> Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why. - -## Marketing -> Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable - -## Automation tests - - Unit tests - > Code coverage information - - Integration tests - > Details about the test cases and coverage - -## Security checks - - Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes/no - - Ran FindSecurityBugs plugin and verified report? yes/no - - Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes/no - -## Samples -> Provide high-level details about the samples related to this feature - -## Related PRs -> List any other related PRs - -## Migrations (if applicable) -> Describe migration steps and platforms on which migration has been tested - -## Test environment -> List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested - -## Learning +## Purpose +> Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc. + +## Goals +> Describe the solutions that this feature/fix will introduce to resolve the problems described above + +## Approach +> Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email content-group@entgra.io to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here. + +## User stories +> Summary of user stories addressed by this change> + +## Release note +> Brief description of the new feature or bug fix as it will appear in the release notes + +## Documentation +> Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there's no doc impact + +## Training +> Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable + +## Certification +> Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why. + +## Marketing +> Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable + +## Automation tests + - Unit tests + > Code coverage information + - Integration tests + > Details about the test cases and coverage + +## Security checks + - Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes/no + - Ran FindSecurityBugs plugin and verified report? yes/no + - Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes/no + +## Samples +> Provide high-level details about the samples related to this feature + +## Related PRs +> List any other related PRs + +## Migrations (if applicable) +> Describe migration steps and platforms on which migration has been tested + +## Test environment +> List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested + +## Learning > Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem. \ No newline at end of file