/* * Copyright 2012-2017 CodeLibs Project and the Others. * * 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 org.codelibs.fess.sso.oic; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.codelibs.core.lang.StringUtil; import org.codelibs.core.net.UuidUtil; import org.codelibs.fess.app.web.base.login.ActionResponseCredential; import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential; import org.codelibs.fess.crawler.Constants; import org.codelibs.fess.mylasta.direction.FessConfig; import org.codelibs.fess.sso.SsoAuthenticator; import org.codelibs.fess.util.ComponentUtil; import org.lastaflute.web.login.credential.LoginCredential; import org.lastaflute.web.response.HtmlResponse; import org.lastaflute.web.util.LaRequestUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonParser; import com.google.api.client.json.JsonToken; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.Base64; public class OpenIdConnectAuthenticator implements SsoAuthenticator { private static final Logger logger = LoggerFactory.getLogger(OpenIdConnectAuthenticator.class); private static final String OIC_STATE = "OIC_STATE"; private final HttpTransport httpTransport = new NetHttpTransport(); private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); @Override public LoginCredential getLoginCredential() { return LaRequestUtil.getOptionalRequest().map(request -> { final HttpSession session = request.getSession(false); if (session != null) { final String sesState = (String) session.getAttribute(OIC_STATE); if (StringUtil.isNotBlank(sesState)) { session.removeAttribute(OIC_STATE); final String code = request.getParameter("code"); final String reqState = request.getParameter("state"); if (sesState.equals(reqState) && StringUtil.isNotBlank(code)) { return processCallback(request, code); } if (logger.isDebugEnabled()) { logger.debug("code:" + code + " state(request):" + reqState + " state(session):" + sesState); } return null; } } return new ActionResponseCredential(() -> HtmlResponse.fromRedirectPathAsIs(getAuthUrl(request))); }).orElse(null); } protected String getAuthUrl(final HttpServletRequest request) { final FessConfig fessConfig = ComponentUtil.getFessConfig(); final String state = UuidUtil.create(); request.getSession().setAttribute(OIC_STATE, state); return new AuthorizationCodeRequestUrl(fessConfig.getOicAuthServerUrl(), fessConfig.getOicClientId())// .setScopes(Arrays.asList(fessConfig.getOicScope()))// .setResponseTypes(Arrays.asList("code"))// .setRedirectUri(fessConfig.getOicRedirectUrl())// .setState(state)// .build(); } protected LoginCredential processCallback(final HttpServletRequest request, final String code) { try { final TokenResponse tr = getTokenUrl(code); final String[] jwt = ((String) tr.get("id_token")).split("\\."); final String jwtHeader = new String(Base64.decodeBase64(jwt[0]), Constants.UTF_8_CHARSET); final String jwtClaim = new String(Base64.decodeBase64(jwt[1]), Constants.UTF_8_CHARSET); final String jwtSigniture = new String(Base64.decodeBase64(jwt[2]), Constants.UTF_8_CHARSET); if (logger.isDebugEnabled()) { logger.debug("jwtHeader: " + jwtHeader); logger.debug("jwtClaim: " + jwtClaim); logger.debug("jwtSigniture: " + jwtSigniture); } // TODO validate signiture final Map<String, Object> attributes = new HashMap<>(); attributes.put("accesstoken", tr.getAccessToken()); attributes.put("refreshtoken", tr.getRefreshToken() == null ? "null" : tr.getRefreshToken()); attributes.put("tokentype", tr.getTokenType()); attributes.put("expire", tr.getExpiresInSeconds()); attributes.put("jwtheader", jwtHeader); attributes.put("jwtclaim", jwtClaim); attributes.put("jwtsign", jwtSigniture); parseJwtClaim(jwtClaim, attributes); return new OpenIdConnectCredential(attributes); } catch (final IOException e) { if (logger.isDebugEnabled()) { logger.debug("Failed to process callbacked request.", e); } } return null; } protected void parseJwtClaim(final String jwtClaim, final Map<String, Object> attributes) throws IOException { final JsonParser jsonParser = jsonFactory.createJsonParser(jwtClaim); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { final String name = jsonParser.getCurrentName(); if (name != null) { jsonParser.nextToken(); // TODO other parameters switch (name) { case "iss": attributes.put("iss", jsonParser.getText()); break; case "sub": attributes.put("sub", jsonParser.getText()); break; case "azp": attributes.put("azp", jsonParser.getText()); break; case "email": attributes.put("email", jsonParser.getText()); break; case "at_hash": attributes.put("at_hash", jsonParser.getText()); break; case "email_verified": attributes.put("email_verified", jsonParser.getText()); break; case "aud": attributes.put("aud", jsonParser.getText()); break; case "iat": attributes.put("iat", jsonParser.getText()); break; case "exp": attributes.put("exp", jsonParser.getText()); break; } } } } protected TokenResponse getTokenUrl(final String code) throws IOException { final FessConfig fessConfig = ComponentUtil.getFessConfig(); return new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(fessConfig.getOicTokenServerUrl()), code)// .setGrantType("authorization_code")// .setRedirectUri(fessConfig.getOicRedirectUrl())// .set("client_id", fessConfig.getOicClientId())// .set("client_secret", fessConfig.getOicClientSecret())// .execute(); } }