View Javadoc
1   /*
2    * Copyright 2012-2018 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.sso.oic;
17  
18  import java.io.IOException;
19  import java.util.Arrays;
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.http.HttpSession;
25  
26  import org.codelibs.core.lang.StringUtil;
27  import org.codelibs.core.net.UuidUtil;
28  import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
29  import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
30  import org.codelibs.fess.crawler.Constants;
31  import org.codelibs.fess.mylasta.direction.FessConfig;
32  import org.codelibs.fess.sso.SsoAuthenticator;
33  import org.codelibs.fess.util.ComponentUtil;
34  import org.lastaflute.web.login.credential.LoginCredential;
35  import org.lastaflute.web.response.HtmlResponse;
36  import org.lastaflute.web.util.LaRequestUtil;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
41  import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
42  import com.google.api.client.auth.oauth2.TokenResponse;
43  import com.google.api.client.http.GenericUrl;
44  import com.google.api.client.http.HttpTransport;
45  import com.google.api.client.http.javanet.NetHttpTransport;
46  import com.google.api.client.json.JsonFactory;
47  import com.google.api.client.json.JsonParser;
48  import com.google.api.client.json.JsonToken;
49  import com.google.api.client.json.jackson2.JacksonFactory;
50  import com.google.api.client.util.Base64;
51  
52  public class OpenIdConnectAuthenticator implements SsoAuthenticator {
53  
54      private static final Logger logger = LoggerFactory.getLogger(OpenIdConnectAuthenticator.class);
55  
56      private static final String OIC_STATE = "OIC_STATE";
57  
58      private final HttpTransport httpTransport = new NetHttpTransport();
59  
60      private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
61  
62      @Override
63      public LoginCredential getLoginCredential() {
64          return LaRequestUtil.getOptionalRequest().map(request -> {
65              final HttpSession session = request.getSession(false);
66              if (session != null) {
67                  final String sesState = (String) session.getAttribute(OIC_STATE);
68                  if (StringUtil.isNotBlank(sesState)) {
69                      session.removeAttribute(OIC_STATE);
70                      final String code = request.getParameter("code");
71                      final String reqState = request.getParameter("state");
72                      if (sesState.equals(reqState) && StringUtil.isNotBlank(code)) {
73                          return processCallback(request, code);
74                      }
75                      if (logger.isDebugEnabled()) {
76                          logger.debug("code:" + code + " state(request):" + reqState + " state(session):" + sesState);
77                      }
78                      return null;
79                  }
80              }
81  
82              return new ActionResponseCredential(() -> HtmlResponse.fromRedirectPathAsIs(getAuthUrl(request)));
83          }).orElse(null);
84      }
85  
86      protected String getAuthUrl(final HttpServletRequest request) {
87          final FessConfig fessConfig = ComponentUtil.getFessConfig();
88          final String state = UuidUtil.create();
89          request.getSession().setAttribute(OIC_STATE, state);
90          return new AuthorizationCodeRequestUrl(fessConfig.getOicAuthServerUrl(), fessConfig.getOicClientId())//
91                  .setScopes(Arrays.asList(fessConfig.getOicScope()))//
92                  .setResponseTypes(Arrays.asList("code"))//
93                  .setRedirectUri(fessConfig.getOicRedirectUrl())//
94                  .setState(state)//
95                  .build();
96      }
97  
98      protected LoginCredential processCallback(final HttpServletRequest request, final String code) {
99          try {
100             final TokenResponse tr = getTokenUrl(code);
101 
102             final String[] jwt = ((String) tr.get("id_token")).split("\\.");
103             final String jwtHeader = new String(Base64.decodeBase64(jwt[0]), Constants.UTF_8_CHARSET);
104             final String jwtClaim = new String(Base64.decodeBase64(jwt[1]), Constants.UTF_8_CHARSET);
105             final String jwtSigniture = new String(Base64.decodeBase64(jwt[2]), Constants.UTF_8_CHARSET);
106 
107             if (logger.isDebugEnabled()) {
108                 logger.debug("jwtHeader: " + jwtHeader);
109                 logger.debug("jwtClaim: " + jwtClaim);
110                 logger.debug("jwtSigniture: " + jwtSigniture);
111             }
112 
113             // TODO validate signiture
114 
115             final Map<String, Object> attributes = new HashMap<>();
116             attributes.put("accesstoken", tr.getAccessToken());
117             attributes.put("refreshtoken", tr.getRefreshToken() == null ? "null" : tr.getRefreshToken());
118             attributes.put("tokentype", tr.getTokenType());
119             attributes.put("expire", tr.getExpiresInSeconds());
120             attributes.put("jwtheader", jwtHeader);
121             attributes.put("jwtclaim", jwtClaim);
122             attributes.put("jwtsign", jwtSigniture);
123 
124             parseJwtClaim(jwtClaim, attributes);
125 
126             return new OpenIdConnectCredential(attributes);
127         } catch (final IOException e) {
128             if (logger.isDebugEnabled()) {
129                 logger.debug("Failed to process callbacked request.", e);
130             }
131         }
132         return null;
133     }
134 
135     protected void parseJwtClaim(final String jwtClaim, final Map<String, Object> attributes) throws IOException {
136         final JsonParser jsonParser = jsonFactory.createJsonParser(jwtClaim);
137         while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
138             final String name = jsonParser.getCurrentName();
139             if (name != null) {
140                 jsonParser.nextToken();
141 
142                 // TODO other parameters
143                 switch (name) {
144                 case "iss":
145                     attributes.put("iss", jsonParser.getText());
146                     break;
147                 case "sub":
148                     attributes.put("sub", jsonParser.getText());
149                     break;
150                 case "azp":
151                     attributes.put("azp", jsonParser.getText());
152                     break;
153                 case "email":
154                     attributes.put("email", jsonParser.getText());
155                     break;
156                 case "at_hash":
157                     attributes.put("at_hash", jsonParser.getText());
158                     break;
159                 case "email_verified":
160                     attributes.put("email_verified", jsonParser.getText());
161                     break;
162                 case "aud":
163                     attributes.put("aud", jsonParser.getText());
164                     break;
165                 case "iat":
166                     attributes.put("iat", jsonParser.getText());
167                     break;
168                 case "exp":
169                     attributes.put("exp", jsonParser.getText());
170                     break;
171                 }
172             }
173         }
174     }
175 
176     protected TokenResponse getTokenUrl(final String code) throws IOException {
177         final FessConfig fessConfig = ComponentUtil.getFessConfig();
178         return new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(fessConfig.getOicTokenServerUrl()), code)//
179                 .setGrantType("authorization_code")//
180                 .setRedirectUri(fessConfig.getOicRedirectUrl())//
181                 .set("client_id", fessConfig.getOicClientId())//
182                 .set("client_secret", fessConfig.getOicClientSecret())//
183                 .execute();
184     }
185 }