/*
* Copyright 2014 by SCSK Corporation.
*
* This file is part of PrimeCloud Controller(TM).
*
* PrimeCloud Controller(TM) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PrimeCloud Controller(TM) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PrimeCloud Controller(TM). If not, see <http://www.gnu.org/licenses/>.
*/
package jp.primecloud.auto.api;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import jp.primecloud.auto.common.log.LoggingUtils;
import jp.primecloud.auto.config.Config;
import jp.primecloud.auto.entity.crud.ApiCertificate;
import jp.primecloud.auto.entity.crud.User;
import jp.primecloud.auto.exception.AutoApplicationException;
import jp.primecloud.auto.util.MessageUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
public class ApiFilter extends ApiSupport implements ContainerRequestFilter {
private static final Integer SECURE_WAIT_TIME = Integer.parseInt(Config.getProperty("pccApi.secureWaitTime"));
private static final String ALLOW_API = Config.getProperty("api.allowApi");
@Context
private HttpServletRequest servletRequest;
/**
* {@inheritDoc}
*/
public ContainerRequest filter(ContainerRequest request) {
URI uri = request.getRequestUri();
// API名のチェック
String apiName = uri.getPath().substring(request.getBaseUri().getPath().length());
if (StringUtils.isEmpty(apiName)) {
// API名が存在しない場合
throw new AutoApplicationException("EAPI-000008", "URL", uri.toString());
}
// APIの実行許可チェック
if (StringUtils.isNotEmpty(ALLOW_API)) {
Pattern pattern = Pattern.compile(ALLOW_API);
Matcher matcher = pattern.matcher(apiName);
if (!matcher.matches()) {
// APIの実行が許可されていない場合
throw new AutoApplicationException("EAPI-000023", apiName);
}
}
// URIのパラメータをBase64デコードしてマップにする
Map<String, String> decodeParamMap = getDecodedParamMap(uri);
String accessId = decodeParamMap.get(PARAM_NAME_ACCESS_ID);
String signature = decodeParamMap.get(PARAM_NAME_SIGNATURE);
String timestamp = decodeParamMap.get(PARAM_NAME_TIMESTAMP);
// パラメータの存在チェック
ApiValidate.validateAccessId(accessId);
ApiValidate.validateSignature(signature);
ApiValidate.validateTimestamp(timestamp);
// API認証情報の取得
ApiCertificate apiCertificate = apiCertificateDao.readByApiAccessId(accessId);
if (apiCertificate == null) {
// API認証情報が存在しない場合
try {
Thread.sleep(SECURE_WAIT_TIME.intValue() * 1000);
} catch (InterruptedException ignore) {
}
throw new AutoApplicationException("EAPI-000008", PARAM_NAME_ACCESS_ID, accessId);
}
if (BooleanUtils.isNotTrue(apiCertificate.getEnabled())) {
// API認証情報が無効の場合
throw new AutoApplicationException("EAPI-000024", accessId);
}
// ユーザ情報の取得
User accessUser = userDao.read(apiCertificate.getUserNo());
if (accessUser == null) {
// ユーザ情報が存在しない場合
throw new AutoApplicationException("EAPI-100000", "User", "UserNo", apiCertificate.getUserNo());
}
if (BooleanUtils.isNotTrue(accessUser.getEnabled())) {
// ユーザが無効の場合
throw new AutoApplicationException("EAPI-000025");
}
// Signatureの合致チェック
String uriText = createUriQueryParams(decodeParamMap);
String encodeUriText = encodeSHA256(uriText, apiCertificate.getApiSecretKey());
if (!encodeUriText.equals(signature)) {
// Signatureが合致しない場合
try {
Thread.sleep(SECURE_WAIT_TIME.intValue() * 1000);
} catch (InterruptedException ignore) {
}
throw new AutoApplicationException("EAPI-000008", "URL", uri.toString());
}
// ユーザ情報をリクエストに保存
servletRequest.setAttribute(PARAM_NAME_USER, accessUser);
// ログ出力用データを設定
LoggingUtils.setUserNo(accessUser.getUserNo());
LoggingUtils.setUserName(accessUser.getUsername());
// デコードしたパラメータを再設定
for (String key : decodeParamMap.keySet()) {
request.getQueryParameters().putSingle(key, decodeParamMap.get(key));
}
// 最終利用日時を更新
apiCertificate.setLastUseDate(new Date());
apiCertificateDao.update(apiCertificate);
// アクセスログ出力
log.info(MessageUtils.getMessage("IAPI-000001", accessUser.getUsername(), apiName));
return request;
}
/**
* Base64デコード済みのリクエストパラメータを取得する。
*
* @param url URL
* @return LinkedHashMap<パラメータ名, パラメータ値>
*/
@SuppressWarnings("static-access")
private Map<String, String> getDecodedParamMap(URI uri) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
String queryUrlText = uri.getQuery();
if (StringUtils.isEmpty(queryUrlText)) {
return map;
}
try {
Base64 base64 = new Base64(true);
String decodedUri = new String(base64.decodeBase64(queryUrlText.getBytes("UTF-8")), "UTF-8");
for (String param : StringUtils.split(decodedUri, "&")) {
String[] array = StringUtils.split(param, "=", 2);
String key = array[0];
String value = array[1];
map.put(key, value);
}
} catch (UnsupportedEncodingException e) {
throw new AutoApplicationException("EAPI-000008", e, "URL", uri.toString());
}
return map;
}
/**
* HMAC-SHA256ハッシュアルゴリズムでエンコードする。
*
* @param plainText エンコード対象の文字列
* @param keyText
* @return
*/
private static String encodeSHA256(String plainText, String keyText) {
try {
SecretKey secretKey = new SecretKeySpec(keyText.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] plainBytes = plainText.getBytes("UTF-8");
byte[] encodedBytes = mac.doFinal(plainBytes);
byte[] hexBytes = new Hex().encode(encodedBytes);
return new String(hexBytes, "UTF-8");
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* URI(Signature含まない)文字列を作成する。
*
* @param decodeParamMap base64デコード済みのリクエストパラメータ
* @return リクエストパラメータを元にしたURL(Signature含まない)
*/
private String createUriQueryParams(Map<String, String> decodeParamMap) {
String baseUriText = servletRequest.getServletPath() + servletRequest.getPathInfo() + "?";
StringBuilder uriText = new StringBuilder(baseUriText);
for (Entry<String, String> parameter : decodeParamMap.entrySet()) {
if (PARAM_NAME_SIGNATURE.equals(parameter.getKey())) {
continue;
}
uriText.append(parameter.getKey()).append("=").append(parameter.getValue()).append("&");
}
uriText.delete(uriText.length() - 1, uriText.length());
return uriText.toString();
}
}