| // Copyright 2011 Google Inc. All Rights Reserved. | |
| // | |
| // 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.google.enterprise.adaptor; | |
| import com.google.enterprise.apis.client.GsaClient; | |
| import com.google.gdata.util.AuthenticationException; | |
| import com.sun.net.httpserver.HttpExchange; | |
| import com.sun.net.httpserver.HttpHandler; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.net.ConnectException; | |
| import java.net.HttpURLConnection; | |
| import java.net.URLDecoder; | |
| import java.net.UnknownHostException; | |
| import java.nio.charset.Charset; | |
| import java.util.logging.Level; | |
| import java.util.logging.Logger; | |
| /** | |
| * Require GSA-Administrator authentication before allowing requests. | |
| */ | |
| class AdministratorSecurityHandler implements HttpHandler { | |
| private static final Logger log | |
| = Logger.getLogger(AdministratorSecurityHandler.class.getName()); | |
| /** Key used to store the fact the user has been authenticated. */ | |
| private static final String SESSION_ATTR_NAME = "dashboard-authned"; | |
| /** Page to display when prompting for user credentials. */ | |
| private static final String LOGIN_PAGE = "resources/login.html"; | |
| /** Page to display when the user credentials are invalid. */ | |
| private static final String LOGIN_FAILED_PAGE = "resources/login-failed.html"; | |
| /** Page to display when the user credentials were not able to be verified. */ | |
| private static final String LOGIN_INDETERMINATE_PAGE | |
| = "resources/login-indeterminate.html"; | |
| /** Wrapped handler, for when the user is authenticated. */ | |
| private final HttpHandler handler; | |
| /** Manager that handles keeping track of authenticated users. */ | |
| private final SessionManager<HttpExchange> sessionManager; | |
| /** Trusted entity for performing authentication of user credentials. */ | |
| private final AuthnClient authnClient; | |
| AdministratorSecurityHandler(HttpHandler handler, | |
| SessionManager<HttpExchange> sessionManager, AuthnClient authnClient) { | |
| this.handler = handler; | |
| this.sessionManager = sessionManager; | |
| this.authnClient = authnClient; | |
| } | |
| public AdministratorSecurityHandler(HttpHandler handler, | |
| SessionManager<HttpExchange> sessionManager, String gsaHostname, | |
| boolean useHttps) { | |
| this(handler, sessionManager, new GsaAuthnClient(gsaHostname, useHttps)); | |
| } | |
| private void meteredHandle(HttpExchange ex) throws IOException { | |
| String pageToDisplay = LOGIN_PAGE; | |
| if ("POST".equals(ex.getRequestMethod())) { | |
| AuthzStatus authn = validUsernameAndPassword(ex); | |
| if (authn == AuthzStatus.PERMIT) { | |
| // Need the client to access the page via GET since the only reason the | |
| // request method was POST was because of submitting our login form. | |
| HttpExchanges.sendRedirect(ex, HttpExchanges.getRequestUri(ex)); | |
| return; | |
| } else if (authn == AuthzStatus.INDETERMINATE) { | |
| pageToDisplay = LOGIN_INDETERMINATE_PAGE; | |
| } else { | |
| pageToDisplay = LOGIN_FAILED_PAGE; | |
| } | |
| } | |
| // Send login page. | |
| InputStream is = this.getClass().getResourceAsStream(pageToDisplay); | |
| if (is == null) { | |
| throw new IOException("Could not load login page"); | |
| } | |
| byte[] page; | |
| try { | |
| page = IOHelper.readInputStreamToByteArray(is); | |
| } finally { | |
| is.close(); | |
| } | |
| HttpExchanges.respond( | |
| ex, HttpURLConnection.HTTP_FORBIDDEN, "text/html", page); | |
| } | |
| /** | |
| * Check POST data to see if the user can be authenticated by the GSA. This | |
| * abuses the {@code AuthzStatus} class, using it for Authn, but it was | |
| * convenient. | |
| * | |
| * @return {@code PERMIT} if user authenticated, {@code DENY} if invalid | |
| * credentials, and {@code INDETERMINATE} otherwise | |
| */ | |
| private AuthzStatus validUsernameAndPassword(HttpExchange ex) | |
| throws IOException { | |
| log.fine("Not already authenticated"); | |
| // Check to see if they provided a username and password. | |
| String username = null; | |
| String password = null; | |
| try { | |
| String request = IOHelper.readInputStreamToString( | |
| ex.getRequestBody(), Charset.forName("US-ASCII")); | |
| for (String pair : request.split("&")) { | |
| String[] splitPair = pair.split("=", 2); | |
| if (splitPair.length != 2) { | |
| continue; | |
| } | |
| splitPair[0] = URLDecoder.decode(splitPair[0], "UTF-8"); | |
| splitPair[1] = URLDecoder.decode(splitPair[1], "UTF-8"); | |
| if ("username".equals(splitPair[0])) { | |
| username = splitPair[1]; | |
| } else if ("password".equals(splitPair[0])) { | |
| password = splitPair[1]; | |
| } | |
| if (username != null && password != null) { | |
| break; | |
| } | |
| } | |
| } catch (Exception e) { | |
| log.log(Level.FINE, "Processing POST caused exception", e); | |
| // Assume that they were POSTing to a different page, since they didn't | |
| // provide the expected input. | |
| username = null; | |
| password = null; | |
| } | |
| if (username == null || password == null) { | |
| log.fine("Username or password is null. Not authenticated"); | |
| // Must not have been from our login page. | |
| return AuthzStatus.INDETERMINATE; | |
| } | |
| // Check to see if provided username and password are valid. | |
| AuthzStatus result = authnClient.authn(username, password); | |
| if (result == AuthzStatus.INDETERMINATE) { | |
| log.fine("Failed communicating with the GSA"); | |
| return result; | |
| } else if (result != AuthzStatus.PERMIT) { | |
| log.fine("GSA login was not successful"); | |
| return result; | |
| } | |
| // We have a winner. Store in the session that they are a valid user. | |
| log.fine("GSA login successful"); | |
| Session session = sessionManager.getSession(ex); | |
| session.setAttribute(SESSION_ATTR_NAME, true); | |
| return AuthzStatus.PERMIT; | |
| } | |
| @Override | |
| public void handle(HttpExchange ex) throws IOException { | |
| // Clickjacking defence. | |
| ex.getResponseHeaders().set("X-Frame-Options", "deny"); | |
| // This comment is no longer true and exists from before logging was done in | |
| // a filter. TODO(ejona): split into a separate filter and handler. | |
| // Perform fast-path checking here to prevent double-logging most requests. | |
| Session session = sessionManager.getSession(ex, false); | |
| if (session != null && session.getAttribute(SESSION_ATTR_NAME) != null) { | |
| handler.handle(ex); | |
| return; | |
| } | |
| meteredHandle(ex); | |
| } | |
| interface AuthnClient { | |
| public AuthzStatus authn(String username, String password); | |
| } | |
| static class GsaAuthnClient implements AuthnClient { | |
| private String gsaHostname; | |
| private boolean useHttps; | |
| public GsaAuthnClient(String gsaHostname, boolean useHttps) { | |
| this.gsaHostname = gsaHostname; | |
| this.useHttps = useHttps; | |
| } | |
| @Override | |
| public AuthzStatus authn(String username, String password) { | |
| String protocol = useHttps ? "https" : "http"; | |
| int port = useHttps ? 8443 : 8000; | |
| try { | |
| new GsaClient(protocol, gsaHostname, port, username, password); | |
| } catch (AuthenticationException e) { | |
| log.log(Level.FINE, "AuthenticationException", e); | |
| if (e.getCause() instanceof ConnectException) { | |
| return AuthzStatus.INDETERMINATE; | |
| } else if (e.getCause() instanceof UnknownHostException) { | |
| return AuthzStatus.INDETERMINATE; | |
| } | |
| return AuthzStatus.DENY; | |
| } | |
| return AuthzStatus.PERMIT; | |
| } | |
| } | |
| } |