blob: 00b91f9284846f0dc158625f4df40a50680a3302 [file] [log] [blame]
// 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.secmgr.authncontroller.ExportedState;
import com.google.enterprise.secmgr.modules.SamlClient;
import com.google.enterprise.secmgr.servlets.ResponseParser;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import org.joda.time.DateTime;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.StatusCode;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Date;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A servlet that implements the SAML "assertion consumer" role for SAML
* credentials gathering. Once the client has been authenticated with the GSA,
* the SSO service will redirect the client to us and include a SAML response.
* This {@code Handler} interprets the response and updates our knowledge about
* the client, followed by redirecting the client back to the original page they
* tried to visit.
*/
class SamlAssertionConsumerHandler implements HttpHandler {
private static final Logger log = Logger.getLogger(
SamlAssertionConsumerHandler.class.getName());
/**
* Manager that contains the {@link AuthnState} populated by {@link
* AuthnHandler}.
*/
private final SessionManager<HttpExchange> sessionManager;
/**
* @param sessionManager manager to use to find authn attempts in progress
*/
SamlAssertionConsumerHandler(SessionManager<HttpExchange> sessionManager) {
this.sessionManager = sessionManager;
SecurityManagerConfig.load();
}
@Override
public void handle(HttpExchange ex) throws IOException {
String requestMethod = ex.getRequestMethod();
if (!"GET".equals(requestMethod)) {
HttpExchanges.cannedRespond(ex, HttpURLConnection.HTTP_BAD_METHOD,
Translation.HTTP_BAD_METHOD);
return;
}
Session session = sessionManager.getSession(ex);
AuthnState authnState = (AuthnState) session.getAttribute(
AuthnState.SESSION_ATTR_NAME);
if (authnState == null) {
HttpExchanges.cannedRespond(ex, HttpURLConnection.HTTP_CONFLICT,
Translation.AUTHN_UNKNOWN_SESSION);
return;
}
if (authnState.isAuthenticated()) {
// TODO(ejona): keep track of each request, so that we can redirect here
HttpExchanges.cannedRespond(ex, HttpURLConnection.HTTP_CONFLICT,
Translation.AUTHN_RETRY);
return;
}
SamlClient client = authnState.getSamlClient();
URI origUri = authnState.getOriginalUri();
if (client == null || origUri == null) {
HttpExchanges.cannedRespond(ex, HttpURLConnection.HTTP_INTERNAL_ERROR,
Translation.AUTHN_NOT_STARTED);
return;
}
boolean authnSuccess;
// GET implies the assertion is being sent with the artifact binding.
log.info("Received assertion via artifact binding");
Response samlResponse = client.decodeArtifactResponse(
HttpExchanges.getRequestUri(ex),
new HttpExchangeInTransportAdapter(ex));
authnSuccess = consumeAssertion(client, samlResponse,
client.getArtifactAssertionConsumerService().getLocation(),
authnState);
if (authnSuccess) {
HttpExchanges.sendRedirect(ex, origUri);
} else {
HttpExchanges.cannedRespond(ex, HttpURLConnection.HTTP_FORBIDDEN,
Translation.HTTP_FORBIDDEN_AUTHN_FAILURE);
}
}
private boolean consumeAssertion(SamlClient client, Response samlResponse,
String recipient, AuthnState authnState) {
if (samlResponse == null) {
log.warning("SAML response is missing");
authnState.failAttempt();
return false;
}
// "some session id" is trash that is used for logging in the
// ResponseParser. We have no need for it, but we need to provide a value.
ResponseParser parser = ResponseParser.make(
client, recipient, samlResponse, "some session id");
if (!parser.isResponseValid()) {
log.warning("SAML response is invalid");
authnState.failAttempt();
return false;
}
String code = parser.getResponseStatus();
log.log(Level.INFO, "status code = {0}", new Object[] {code});
if (!code.equals(StatusCode.SUCCESS_URI)) {
log.log(Level.WARNING, "SAML IdP failed to resolve: {0}",
new Object[] {code});
authnState.failAttempt();
return false;
}
if (!parser.areAssertionsValid()) {
log.warning("One or more SAML assertions are invalid");
authnState.failAttempt();
return false;
}
String subjectName = parser.getSubject();
Set<String> groups = null;
String password = null;
ExportedState state = parser.getExportedState();
if (state != null) {
// Groups is also available via parser.getGroups, but this handles the
// state == null case more appropriately for our usage.
groups = state.getPviCredentials().getGroups();
password = state.getPviCredentials().getPassword();
}
DateTime expirationDateTime = parser.getExpirationTime();
long expirationTime = (expirationDateTime == null)
? Long.MAX_VALUE : expirationDateTime.getMillis();
log.log(Level.INFO, "SAML subject {0} verified {1}",
new Object[] {subjectName, new Date(expirationTime)});
AuthnIdentity identity = new AuthnIdentityImpl
.Builder(new UserPrincipal(subjectName))
.setGroups(GroupPrincipal.makeSet(groups))
.setPassword(password).build();
authnState.authenticated(identity, expirationTime);
return true;
}
}