/* * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.wso2.carbon.bpmn.rest.security; import com.fasterxml.jackson.databind.ObjectMapper; import org.activiti.engine.IdentityService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.cxf.configuration.security.AuthorizationPolicy; import org.apache.cxf.jaxrs.ext.RequestHandler; import org.apache.cxf.jaxrs.model.ClassResourceInfo; import org.apache.cxf.message.Message; import org.wso2.carbon.bpmn.core.exception.BPMNAuthenticationException; import org.wso2.carbon.bpmn.rest.common.RestErrorResponse; import org.wso2.carbon.bpmn.rest.common.exception.RestApiBasicAuthenticationException; import org.wso2.carbon.bpmn.rest.common.utils.BPMNOSGIService; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.registry.core.config.RegistryContext; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.user.core.tenant.TenantManager; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; import java.util.ArrayList; import java.util.Map; public class AuthenticationHandler implements RequestHandler { public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; protected Log log = LogFactory.getLog(AuthenticationHandler.class); private final static String AUTH_TYPE_BASIC = "Basic"; private final static String AUTH_TYPE_NONE = "None"; private final static String AUTH_TYPE_OAuth = "Bearer"; /** * Implementation of RequestHandler.handleRequest method. * This method retrieves userName and password from Basic auth header, * and tries to authenticate against carbon user store * <p/> * Upon successful authentication allows process to proceed to retrieve requested REST resource * Upon invalid credentials returns a HTTP 401 UNAUTHORIZED response to client * Upon receiving a userStoreExceptions or IdentityException returns HTTP 500 internal server error to client * * @param message * @param classResourceInfo * @return Response */ public Response handleRequest(Message message, ClassResourceInfo classResourceInfo) { AuthorizationPolicy policy = message.get(AuthorizationPolicy.class); if (policy != null) { if (AUTH_TYPE_BASIC.equals(policy.getAuthorizationType())) { return handleBasicAuth(policy); } else if (AUTH_TYPE_OAuth.equals(policy.getAuthorizationType())) { return handleOAuth(message); } } return authenticationFail(AUTH_TYPE_BASIC); } protected Response handleBasicAuth(AuthorizationPolicy policy) { String username = policy.getUserName(); String password = policy.getPassword(); try { if (authenticate(username, password)) { return null; } } catch (RestApiBasicAuthenticationException e) { log.error("Could not authenticate user : " + username + "against carbon userStore", e); } return authenticationFail(); } protected Response handleOAuth(Message message) { ArrayList<String> headers = ((Map<String, ArrayList>) message.get(Message.PROTOCOL_HEADERS)).get(AUTHORIZATION_HEADER_NAME); if (headers != null) { String authHeader = headers.get(0); if (authHeader.startsWith(AUTH_TYPE_OAuth)) { return authenticationFail(AUTH_TYPE_OAuth); } } return authenticationFail(AUTH_TYPE_OAuth); } /** * Checks whether a given userName:password combination authenticates correctly against carbon userStore * Upon successful authentication returns true, false otherwise * * @param userName * @param password * @return * @throws RestApiBasicAuthenticationException wraps and throws exceptions occur when trying to authenticate * the user */ private boolean authenticate(String userName, String password) throws RestApiBasicAuthenticationException { boolean authStatus; try { IdentityService identityService = BPMNOSGIService.getIdentityService(); authStatus = identityService.checkPassword(userName, password); if (!authStatus) { return false; } } catch (BPMNAuthenticationException e) { throw new RestApiBasicAuthenticationException(e.getMessage(), e); } String tenantDomain = MultitenantUtils.getTenantDomain(userName); String tenantAwareUserName = MultitenantUtils.getTenantAwareUsername(userName); String userNameWithTenantDomain = tenantAwareUserName + "@" + tenantDomain; RealmService realmService = RegistryContext.getBaseInstance().getRealmService(); TenantManager mgr = realmService.getTenantManager(); int tenantId = 0; try { tenantId = mgr.getTenantId(tenantDomain); // tenantId == -1, means an invalid tenant. if (tenantId == -1) { if (log.isDebugEnabled()) { log.debug("Basic authentication request with an invalid tenant : " + userNameWithTenantDomain); } return false; } } catch (UserStoreException e) { throw new RestApiBasicAuthenticationException( "Identity exception thrown while getting tenant ID for user : " + userNameWithTenantDomain, e); } /* Upon successful authentication existing thread local carbon context * is updated to mimic the authenticated user */ PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); carbonContext.setUsername(tenantAwareUserName); carbonContext.setTenantId(tenantId); carbonContext.setTenantDomain(tenantDomain); return true; } private Response authenticationFail() { return authenticationFail(AUTH_TYPE_BASIC); } private Response authenticationFail(String authType) { //authentication failed, request the authetication, add the realm name if needed to the value of WWW-Authenticate RestErrorResponse restErrorResponse = new RestErrorResponse(); restErrorResponse.setErrorMessage("Authentication required"); restErrorResponse.setStatusCode(Response.Status.UNAUTHORIZED.getStatusCode()); ObjectMapper mapper = new ObjectMapper(); String jsonString = null; try { jsonString = mapper.writeValueAsString(restErrorResponse); } catch (IOException e) { //log the error and continue. No need to specifically handle it log.error("Error Json String conversion failed", e); } return Response.status(restErrorResponse.getStatusCode()).type(MediaType.APPLICATION_JSON).header(WWW_AUTHENTICATE, authType).entity(jsonString).build(); } }