Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add SSO feature for Store application #4316

Merged
merged 23 commits into from
Jul 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion components/apimgt/org.wso2.carbon.apimgt.core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@
<dependency>
<groupId>org.wso2.carbon</groupId>
<artifactId>org.wso2.carbon.core</artifactId>
<version>${carbon.kernel.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1632,7 +1632,6 @@ private static List<Integer> getConditionGroupIDs(Connection connection, String
conditionGroupIDs
.add(resultSet.getInt(APIMgtConstants.ThrottlePolicyConstants.COLUMN_CONDITION_ID));
}
;
return conditionGroupIDs;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private Workflow createWorkflowFromResultSet(ResultSet rs) throws SQLException,
workflow.setUpdatedTime(rs.getTimestamp("WF_UPDATED_TIME").toLocalDateTime());
workflow.setWorkflowReference(rs.getString("WF_REFERENCE"));
workflow.setWorkflowDescription(rs.getString("WF_STATUS_DESC"));
workflow.setAttributes(WorkflowUtils.jsonStringToMap(rs.getString("WF_ATTRIBUTES")));;
workflow.setAttributes(WorkflowUtils.jsonStringToMap(rs.getString("WF_ATTRIBUTES")));
} else {
throw new APIMgtDAOException("Invalid workflow type");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ public OAuthApplicationInfo retrieveApplication(String consumerKey) throws KeyMa

@Override
public AccessTokenInfo getNewAccessToken(AccessTokenRequest tokenRequest) throws KeyManagementException {

if (tokenRequest == null) {
throw new KeyManagementException("No information available to generate Token. AccessTokenRequest is null",
ExceptionCodes.INVALID_TOKEN_REQUEST);
Expand Down Expand Up @@ -321,7 +320,6 @@ public AccessTokenInfo getNewAccessToken(AccessTokenRequest tokenRequest) throws
" Error Response Body: " + response.body().toString(),
ExceptionCodes.ACCESS_TOKEN_GENERATION_FAILED);
}

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
public class ApplicationUtils {

private static final Logger log = LoggerFactory.getLogger(ApplicationUtils.class);

public static AccessTokenRequest createAccessTokenRequest(OAuthApplicationInfo oAuthApplication)
throws APIManagementException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class KeyManagerConstants {
public static final String TOKEN_ENDPOINT = "TokenEndpoint";
public static final String REVOKE_ENDPOINT = "RevokeEndpoint";
public static final String INTROSPECT_ENDPOINT = "introspectEndpoint";
public static final String AUTHORIZATION_ENDPOINT = "authorizationEndpoint";
public static final String OAUTH2_DEFAULT_SCOPE = "default";
public static final String OPEN_ID_CONNECT_SCOPE = "openid";
public static final String OAUTH_RESPONSE_ACCESSTOKEN = "access_token";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private static void executeSQL(String sql, Connection connection) {
+ "where index_type like '%DOMAIN%' and (domidx_status <> 'VALID' or domidx_opstatus <> 'VALID')";
try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(q);) {
while (rs.next()) {
if (rs.getString("index_name").equals("API_INDEX")) { // re build index if it has failed.
if ("API_INDEX".equals(rs.getString("index_name"))) { // re build index if it has failed.
String rebuild = "alter index API_INDEX rebuild";
statement.execute(rebuild);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@
<groupId>org.wso2.carbon.apimgt</groupId>
<artifactId>org.wso2.carbon.apimgt.rest.api.core</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon</groupId>
<artifactId>org.wso2.carbon.core</artifactId>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down Expand Up @@ -129,6 +147,22 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.wso2.carbon</groupId>
<artifactId>org.wso2.carbon.extensions.configuration.maven.plugin</artifactId>
<executions>
<execution>
<goals>
<goal>create-doc</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

Expand All @@ -145,6 +179,7 @@
javax.ws.rs.*; version="${javax.ws.rs.import.version.range}",
com.google.gson.*; version="${google.code.gson.import.version.range}",
org.wso2.carbon.messaging.*;version="${carbon.messaging.package.import.version.range}",
org.wso2.carbon.kernel.*;version="${carbon.kernel.package.import.version.range}"
</import.package>
<export.package>
!org.wso2.carbon.apimgt.rest.api.authenticator.internal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,40 @@
* specific language governing permissions and limitations
* under the License.
*/

package org.wso2.carbon.apimgt.rest.api.authenticator;

import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.apimgt.core.api.KeyManager;
import org.wso2.carbon.apimgt.core.exception.APIManagementException;
import org.wso2.carbon.apimgt.core.exception.ExceptionCodes;
import org.wso2.carbon.apimgt.core.impl.APIManagerFactory;
import org.wso2.carbon.apimgt.core.models.AccessTokenInfo;
import org.wso2.carbon.apimgt.core.util.KeyManagerConstants;
import org.wso2.carbon.apimgt.rest.api.authenticator.configuration.models.APIMStoreConfigurations;
import org.wso2.carbon.apimgt.rest.api.authenticator.constants.AuthenticatorConstants;
import org.wso2.carbon.apimgt.rest.api.authenticator.dto.ErrorDTO;
import org.wso2.carbon.apimgt.rest.api.authenticator.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.rest.api.authenticator.utils.AuthUtil;
import org.wso2.carbon.apimgt.rest.api.authenticator.utils.bean.AuthResponseBean;
import org.wso2.carbon.apimgt.core.exception.APIManagementException;
import org.wso2.carbon.apimgt.core.exception.ExceptionCodes;
import org.wso2.carbon.apimgt.rest.api.common.APIConstants;
import org.wso2.msf4j.Microservice;
import org.wso2.msf4j.Request;
import org.wso2.msf4j.formparam.FormDataParam;

import java.net.URI;
import java.net.URISyntaxException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
Expand All @@ -44,7 +57,6 @@
* This class provides access token during login from store app.
*
*/

@Component(
name = "org.wso2.carbon.apimgt.rest.api.authenticator.AuthenticatorAPI",
service = Microservice.class,
Expand All @@ -68,12 +80,15 @@ public Response authenticate(@Context Request request, @FormDataParam ("username
@FormDataParam ("validity_period") String validityPeriod,
@FormDataParam ("remember_me") boolean isRememberMe, @FormDataParam ("scopes") String scopesList) {
try {
LoginTokenService loginTokenService = new LoginTokenService();
KeyManager keyManager = APIManagerFactory.getInstance().getKeyManager();
AuthenticatorService authenticatorService = new AuthenticatorService(keyManager);
AuthResponseBean authResponseBean = new AuthResponseBean();
String appContext = AuthUtil.getAppContext(request);
String restAPIContext;
if (appContext.contains("editor") || request.getUri().contains("publisher")) {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + "/publisher";
if (appContext.contains(AuthenticatorConstants.EDITOR_APPLICATION) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can compare application contexts in this manner since they can change. Please create a Github issue for this, we need to fix it properly in all places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Github issue created at,
#4319 [C5][Authenticator] Compare application contexts

request.getUri().contains(AuthenticatorConstants.PUBLISHER_APPLICATION)) {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + "/" +
AuthenticatorConstants.PUBLISHER_APPLICATION;
} else {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + appContext;
}
Expand All @@ -88,14 +103,12 @@ public Response authenticate(@Context Request request, @FormDataParam ("username
return Response.status(Response.Status.UNAUTHORIZED).entity(errorDTO).build();
}
}
String tokens = loginTokenService
.getTokens(authResponseBean, appContext.substring(1), userName, password, grantType, refToken,
Long.parseLong(validityPeriod));
String accessToken = tokens.split(":")[0];
String refreshToken = null;
if (tokens.split(":").length > 1) {
refreshToken = tokens.split(":")[1];
}
AccessTokenInfo accessTokenInfo = authenticatorService.getTokens(appContext.substring(1),
null, grantType, userName, password, refToken,
Long.parseLong(validityPeriod));
authenticatorService.setAccessTokenData(authResponseBean, accessTokenInfo);
String accessToken = accessTokenInfo.getAccessToken();
String refreshToken = accessTokenInfo.getRefreshToken();

// The access token is stored as two cookies in client side. One is a normal cookie and other is a http
// only cookie. Hence we need to split the access token
Expand All @@ -110,7 +123,7 @@ public Response authenticate(@Context Request request, @FormDataParam ("username
.cookieBuilder(APIConstants.AccessTokenConstants.AM_TOKEN_MSF4J, part2, restAPIContext, true, true,
"");
NewCookie refreshTokenCookie, refreshTokenHttpOnlyCookie;
// refresh token is not set to cookie if remember me is not set.
// Refresh token is not set to cookie if remember me is not set.
if (refreshToken != null && (AuthenticatorConstants.REFRESH_GRANT.equals(grantType) || (
AuthenticatorConstants.PASSWORD_GRANT.equals(grantType) && isRememberMe))) {
String refTokenPart1 = refreshToken.substring(0, refreshToken.length() / 2);
Expand Down Expand Up @@ -146,31 +159,30 @@ public Response authenticate(@Context Request request, @FormDataParam ("username
"").build();
}
} catch (APIManagementException e) {

ErrorDTO errorDTO = AuthUtil.getErrorDTO(e.getErrorHandler(), null);

log.error(e.getMessage(), e);
return Response.status(e.getErrorHandler().getHttpStatusCode()).entity(errorDTO).build();
}
}

@POST
@Produces (MediaType.APPLICATION_JSON)
@Path ("/revoke")
@Path ("/logout")
public Response logout(@Context Request request) {
String appContext = AuthUtil.getAppContext(request);
String restAPIContext;
if (appContext.contains("editor")) {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + "/publisher";
if (appContext.contains(AuthenticatorConstants.EDITOR_APPLICATION)) {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + "/" + AuthenticatorConstants.PUBLISHER_APPLICATION;
} else {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + appContext;
}
String accessToken = AuthUtil
.extractTokenFromHeaders(request.getHeaders(), AuthenticatorConstants.ACCESS_TOKEN_2);
if (accessToken != null) {
try {
LoginTokenService loginTokenService = new LoginTokenService();
loginTokenService.revokeAccessToken(appContext.substring(1), accessToken);
KeyManager keyManager = APIManagerFactory.getInstance().getKeyManager();
AuthenticatorService authenticatorService = new AuthenticatorService(keyManager);
authenticatorService.revokeAccessToken(appContext.substring(1), accessToken);
// Lets invalidate all the cookies saved.
NewCookie appContextCookie = AuthUtil
.cookieBuilder(AuthenticatorConstants.ACCESS_TOKEN_2, "", appContext, true, true,
Expand All @@ -196,7 +208,108 @@ public Response logout(@Context Request request) {
errorDTO.setCode(ExceptionCodes.INVALID_AUTHORIZATION_HEADER.getErrorCode());
errorDTO.setMessage(ExceptionCodes.INVALID_AUTHORIZATION_HEADER.getErrorMessage());
return Response.status(Response.Status.UNAUTHORIZED).entity(errorDTO).build();
}

/**
* This method provides the DCR application information to the SSO-IS login.
*
* @param request Request to call the /login api
* @return Response - Response object with OAuth data
*/
@GET
@Path("/login/{appName}")
@Produces(MediaType.APPLICATION_JSON)
public Response redirect(@Context Request request) {
String appContext = AuthUtil.getAppContext(request);
String appName = appContext.substring(1);
try {
KeyManager keyManager = APIManagerFactory.getInstance().getKeyManager();
AuthenticatorService authenticatorService = new AuthenticatorService(keyManager);
JsonObject oAuthData = authenticatorService.getAuthenticationConfigurations(appName);
if (oAuthData.size() == 0) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error while creating the OAuth application!").build();
} else {
return Response.status(Response.Status.OK).entity(oAuthData).build();
}
} catch (APIManagementException e) {
ErrorDTO errorDTO = AuthUtil.getErrorDTO(e.getErrorHandler(), null);
log.error(e.getMessage(), e);
return Response.status(e.getErrorHandler().getHttpStatusCode()).entity(errorDTO).build();
}
}

/**
* This is the API which IDP redirects the user after authentication.
*
* @param request Request to call /callback api
* @return Response - Response with redirect URL
*/
@GET
@Path("/callback/{appName}")
@Produces(MediaType.APPLICATION_JSON)
public Response callback(@Context Request request) {
String appContext = AuthUtil.getAppContext(request).split("\\?")[0];
String appName = appContext.substring(1);
String restAPIContext;
if (appContext.contains(AuthenticatorConstants.EDITOR_APPLICATION) ||
request.getUri().contains(AuthenticatorConstants.PUBLISHER_APPLICATION)) {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + "/" + AuthenticatorConstants.PUBLISHER_APPLICATION;
} else {
restAPIContext = AuthenticatorConstants.REST_CONTEXT + appContext;
}
String requestURL = (String) request.getProperty(AuthenticatorConstants.REQUEST_URL);

APIMStoreConfigurations storeConfigs = ServiceReferenceHolder.getInstance().getAPIMStoreConfiguration();
AuthResponseBean authResponseBean = new AuthResponseBean();
String grantType = KeyManagerConstants.AUTHORIZATION_CODE_GRANT_TYPE;
try {
KeyManager keyManager = APIManagerFactory.getInstance().getKeyManager();
AuthenticatorService authenticatorService = new AuthenticatorService(keyManager);
AccessTokenInfo accessTokenInfo = authenticatorService.getTokens(appName, requestURL, grantType,
null, null, null, 0);
if (StringUtils.isEmpty(accessTokenInfo.toString())) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Access token generation failed!").build();
} else {
authenticatorService.setAccessTokenData(authResponseBean, accessTokenInfo);
String accessToken = accessTokenInfo.getAccessToken();
if (log.isDebugEnabled()) {
log.debug("Received access token for " + appName + " application.");
}
// Set Access Token cookies
String part1 = accessToken.substring(0, accessToken.length() / 2);
String part2 = accessToken.substring(accessToken.length() / 2);
NewCookie cookieWithAppContext = AuthUtil
.cookieBuilder(AuthenticatorConstants.ACCESS_TOKEN_1, part1, appContext,
true, false, "future");
NewCookie httpOnlyCookieWithAppContext = AuthUtil
.cookieBuilder(AuthenticatorConstants.ACCESS_TOKEN_2, part2, appContext,
true, true, "future");
NewCookie restAPIContextCookie = AuthUtil
.cookieBuilder(APIConstants.AccessTokenConstants.AM_TOKEN_MSF4J, part2, restAPIContext,
true, true, "future");
String authUser = authResponseBean.getAuthUser();
NewCookie authUserCookie = AuthUtil
.cookieBuilder(AuthenticatorConstants.AUTH_USER, authUser, appContext, true, false, "");
if (log.isDebugEnabled()) {
log.debug("Set cookies for " + appName + " application.");
}
// Redirect to the store/apis page (redirect URL)
URI targetURIForRedirection = new URI(storeConfigs.getApimBaseUrl() + appName);
return Response.status(Response.Status.FOUND)
.header(HttpHeaders.LOCATION, targetURIForRedirection).entity(authResponseBean)
.cookie(cookieWithAppContext, httpOnlyCookieWithAppContext,
restAPIContextCookie, authUserCookie)
.build();
}
} catch (APIManagementException e) {
ErrorDTO errorDTO = AuthUtil.getErrorDTO(e.getErrorHandler(), null);
log.error(e.getMessage(), e);
return Response.status(e.getErrorHandler().getHttpStatusCode()).entity(errorDTO).build();
} catch (URISyntaxException e) {
log.error(e.getMessage(), e);
return Response.status(e.getIndex()).build();
}
}
}
Loading