/* * * * Copyright 2013 Jive Software * * * * Licensed 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 com.jivesoftware.sdk.service.filter; import com.google.common.collect.Maps; import com.jivesoftware.sdk.api.entity.JiveInstance; import com.jivesoftware.sdk.api.entity.JiveInstanceProvider; import com.jivesoftware.sdk.utils.JiveSDKUtils; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.annotation.Nonnull; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.inject.Singleton; import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.concurrent.TimeUnit; /** * Created by rrutan on 1/30/14. */ @Component @Singleton public class JiveAuthorizationValidator { private static final Logger log = LoggerFactory.getLogger(JiveAuthorizationValidator.class); public static final String JIVE_INSTANCE = JiveAuthorizationValidation.class.getSimpleName()+".JiveInstance"; private static final String PARAM_ALGORITHM = "algorithm"; private static final String PARAM_CLIENT_ID = "client_id"; private static final String PARAM_JIVE_URL = "jive_url"; private static final String PARAM_TENANT_ID = "tenant_id"; private static final String PARAM_TIMESTAMP = "timestamp"; private static final String PARAM_SIGNATURE = "signature"; private static final String JIVE_EXTN = "JiveEXTN "; private static final String QUERY_PARAM_SIGNATURE = "&" + PARAM_SIGNATURE + "="; private static final WebApplicationException BAD_REQUEST = new WebApplicationException( Response.status(Response.Status.BAD_REQUEST).build()); private static final WebApplicationException UNAUTHORIZED = new WebApplicationException( Response.status(Response.Status.UNAUTHORIZED).build()); @Autowired @Qualifier ("jiveInstanceProvider") private JiveInstanceProvider jiveInstanceProvider; public void authenticate(ContainerRequestContext request) { String authorization = request.getHeaderString(HttpHeaders.AUTHORIZATION); if (authorization == null) { throw UNAUTHORIZED; } if (log.isTraceEnabled()) { log.trace("Authz Header:\n"+authorization); } if (!authorization.startsWith(JIVE_EXTN) || !authorization.contains(QUERY_PARAM_SIGNATURE)) { if (log.isInfoEnabled()) { log.info("Jive authorization isn't properly formatted: " + authorization); } throw BAD_REQUEST; } // end if Map<String, String> paramMap = getParamsFromAuthz(authorization); if (log.isDebugEnabled()) { log.debug("Authz Parameters: \n"+paramMap); } String signature = paramMap.get(PARAM_SIGNATURE); String algorithm = paramMap.get(PARAM_ALGORITHM); String clientId = paramMap.get(PARAM_CLIENT_ID); String jiveUrl = paramMap.get(PARAM_JIVE_URL); String tenantId = paramMap.get(PARAM_TENANT_ID); String timeStampStr = paramMap.get(PARAM_TIMESTAMP); if (!JiveSDKUtils.isAllExist(algorithm, clientId, jiveUrl, tenantId, timeStampStr)) { log.error("Jive authorization is partial: " + paramMap); throw BAD_REQUEST; } // end if long timeStamp = Long.parseLong(timeStampStr); long millisPassed = System.currentTimeMillis() - timeStamp; if (millisPassed < 0 || millisPassed > TimeUnit.MINUTES.toMillis(5)) { log.error("Jive authorization is rejected since it's " + millisPassed + "ms old (max. allowed is 5 minutes): " + paramMap); throw UNAUTHORIZED; } // end if //JiveInstance jiveInstance = jiveInstanceProvider.getInstanceByTenantId(tenantId); JiveInstance jiveInstance = jiveInstanceProvider.getInstanceByTenantId(tenantId); if (jiveInstance == null) { log.error("Jive authorization failed due to invalid tenant ID: " + tenantId); throw UNAUTHORIZED; } // end if String expectedClientId = jiveInstance.getClientId(); if (!clientId.equals(expectedClientId)) { String msg = String.format("Jive authorization failed due to missing Client ID: Actual [%s], Expected [%s]", clientId, expectedClientId); log.error(msg); throw UNAUTHORIZED; } // end if String clientSecret = jiveInstance.getClientSecret(); String paramStrWithoutSignature = authorization.substring(JIVE_EXTN.length(), authorization.indexOf(QUERY_PARAM_SIGNATURE)); try { String expectedSignature = sign(paramStrWithoutSignature, clientSecret, algorithm); if (expectedSignature.equals(signature)) { //SAVING jiveInstance INSTANCE TO REQUEST //TODO: request.setProperty(JIVE_INSTANCE,jiveInstance); } else { log.error("Jive authorization failed due to tampered signature! Original authz: " + authorization); throw UNAUTHORIZED; } // end if } catch (Exception e) { log.error("Failed validating Jive auth. scheme"+e.getMessage()); throw UNAUTHORIZED; } // end try/catch } // end authenticate @Nonnull private String sign(@Nonnull String str, @Nonnull String clientSecret, @Nonnull String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { byte[] secret = Base64.decodeBase64(clientSecret); SecretKeySpec secretKeySpec = new SecretKeySpec(secret, algorithm); Mac mac = Mac.getInstance(algorithm); mac.init(secretKeySpec); mac.update(str.getBytes("UTF-8")); return Base64.encodeBase64String(mac.doFinal()).replaceAll("\\s+", ""); } // end sign @Nonnull private Map<String, String> getParamsFromAuthz(String authHeader) { if (!authHeader.startsWith(JIVE_EXTN)) { return Maps.newHashMap(); } // end if authHeader = authHeader.substring(JIVE_EXTN.length()); String[] params = authHeader.split("[?|&]"); Map<String, String> paramMap = Maps.newHashMap(); for (String param : params) { String[] tokens = param.split("="); if (tokens.length != 2) { return Maps.newHashMap(); } // end if paramMap.put(JiveSDKUtils.decodeUrl(tokens[0]), JiveSDKUtils.decodeUrl(tokens[1])); } // end for param return paramMap; } // end getParamsFromAuthz } // end class