/*
* Copyright (C) 2014 Toshiaki Maki
*
* 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 am.ik.categolj2.app.authentication;
import am.ik.categolj2.app.Categolj2Cookies;
import am.ik.categolj2.config.Categolj2AdminProperties;
import am.ik.categolj2.core.logger.LogManager;
import am.ik.categolj2.core.web.RemoteAddresses;
import am.ik.categolj2.core.web.UserAgents;
import am.ik.categolj2.domain.model.LoginHistory;
import am.ik.categolj2.domain.service.loginhistory.LoginHistoryService;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.date.DateFactory;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
@Component
public class AuthenticationHelper {
private static Logger logger = LogManager.getLogger();
@Inject
LoginHistoryService loginHistoryService;
@Inject
DateFactory dateFactory;
@Inject
ObjectMapper objectMapper;
@Inject
Categolj2AdminProperties adminClientProperties;
public HttpEntity<MultiValueMap<String, Object>> createRopRequest(String username, String password) {
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("username", username);
params.add("password", password);
params.add("grant_type", "password");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
byte[] clientInfo = (adminClientProperties.getClientId() + ":" + adminClientProperties.getClientSecret())
.getBytes(StandardCharsets.UTF_8);
String basic = new String(Base64.getEncoder().encode(clientInfo), StandardCharsets.UTF_8);
headers.set(com.google.common.net.HttpHeaders.AUTHORIZATION, "Basic " + basic);
return new HttpEntity<>(params, headers);
}
int getRefreshTokenMaxAge(OAuth2AccessToken accessToken) {
return accessToken.getExpiresIn() * 10 /* FIXME */;
}
void saveAccessTokenInCookie(OAuth2AccessToken accessToken, HttpServletResponse response) throws UnsupportedEncodingException {
Cookie accessTokenValueCookie = new Cookie(Categolj2Cookies.ACCESS_TOKEN_VALUE_COOKIE,
URLEncoder.encode(accessToken.getValue(), "UTF-8"));
accessTokenValueCookie.setMaxAge(accessToken.getExpiresIn());
Cookie accessTokenExpireCookie = new Cookie(Categolj2Cookies.ACCESS_TOKEN_EXPIRATION_COOKIE,
URLEncoder.encode(String.valueOf(accessToken.getExpiration().getTime()), "UTF-8"));
accessTokenExpireCookie.setMaxAge(accessToken.getExpiresIn());
response.addCookie(accessTokenValueCookie);
response.addCookie(accessTokenExpireCookie);
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
Cookie refreshTokenCookie = new Cookie(Categolj2Cookies.REFRESH_TOKEN_VALUE_COOKIE,
URLEncoder.encode(refreshToken.getValue(), "UTF-8"));
refreshTokenCookie.setMaxAge(getRefreshTokenMaxAge(accessToken));
response.addCookie(refreshTokenCookie);
}
}
void removeCookie(String cookieName, HttpServletResponse response) throws UnsupportedEncodingException {
Cookie cookie = new Cookie(cookieName,
URLEncoder.encode("", "UTF-8"));
cookie.setMaxAge(0);
response.addCookie(cookie);
}
void removeAccessTokenFromCookie(HttpServletResponse response) throws UnsupportedEncodingException {
removeCookie(Categolj2Cookies.ACCESS_TOKEN_VALUE_COOKIE, response);
removeCookie(Categolj2Cookies.ACCESS_TOKEN_EXPIRATION_COOKIE, response);
}
void removeRefreshTokenFromCookie(HttpServletResponse response) throws UnsupportedEncodingException {
removeCookie(Categolj2Cookies.REFRESH_TOKEN_VALUE_COOKIE, response);
}
@SuppressWarnings("unchecked")
void writeLoginHistory(OAuth2AccessToken accessToken, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
// user
Map<String, ?> user = (Map<String, ?>) accessToken.getAdditionalInformation().get("user");
if (user != null) {
String username = (String) user.get("username");
String firstName = (String) user.get("firstName");
String lastName = (String) user.get("lastName");
String email = (String) user.get("email");
LoginHistory loginHistory = createHistory(username, request);
loginHistoryService.save(loginHistory);
saveUserInformationInCookie(username, firstName, lastName, email, accessToken, response);
} else {
logger.error("No user information! (access_token={})", accessToken);
}
}
void saveUserInformationInCookie(String username, String firstName, String lastName, String email, OAuth2AccessToken accessToken,
HttpServletResponse response) throws UnsupportedEncodingException {
try {
Cookie cookie = new Cookie(Categolj2Cookies.USER_COOKIE,
objectMapper.writeValueAsString(new UserInfo(username, firstName, lastName, email)));
cookie.setMaxAge(getRefreshTokenMaxAge(accessToken));
response.addCookie(cookie);
} catch (JsonProcessingException e) {
logger.error("JSON conversion failed!", e);
}
}
LoginHistory createHistory(String username, HttpServletRequest request) {
LoginHistory loginHistory = new LoginHistory();
loginHistory.setLoginAgent(UserAgents.getUserAgent(request));
loginHistory.setLoginHost(RemoteAddresses.getRemoteAddress(request));
loginHistory.setLoginDate(dateFactory.newDateTime());
loginHistory.setUsername(username);
return loginHistory;
}
void handleHttpStatusCodeException(HttpStatusCodeException e, RedirectAttributes attributes) throws IOException {
if (logger.isInfoEnabled()) {
logger.info("authentication failed (message={},X-Track={})",
e.getMessage(),
e.getResponseHeaders().get("X-Track"));
}
try {
OAuth2Exception oAuth2Exception = objectMapper.readValue(e.getResponseBodyAsByteArray(),
OAuth2Exception.class);
attributes.addAttribute("error", oAuth2Exception.getMessage());
} catch (JsonMappingException | JsonParseException ex) {
attributes.addAttribute("error", e.getMessage());
}
}
}