/*
* Copyright (c) 2011 Google Inc.
*
* 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.
*/
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Runs a Jetty server on a free port, waiting for OAuth to redirect to it with the verification
* code.
* <p>
* Mostly copied from oacurl by phopkins@google.com.
* </p>
*
* @author Yaniv Inbar
*/
public final class LocalServerReceiver implements VerificationCodeReceiver {
private static final String CALLBACK_PATH = "/Callback";
/** Server or {@code null} before {@link #getRedirectUri()}. */
private Server server;
/** Verification code or {@code null} before received. */
volatile String code;
@Override
public String getRedirectUri() throws Exception {
int port = getUnusedPort();
server = new Server(port);
for (Connector c : server.getConnectors()) {
c.setHost("localhost");
}
server.addHandler(new CallbackHandler());
server.start();
return "http://localhost:" + port + CALLBACK_PATH;
}
@Override
public synchronized String waitForCode() {
try {
this.wait();
} catch (InterruptedException exception) {
// should not happen
}
return code;
}
@Override
public void stop() throws Exception {
if (server != null) {
server.stop();
server = null;
}
}
private static int getUnusedPort() throws IOException {
Socket s = new Socket();
s.bind(null);
try {
return s.getLocalPort();
} finally {
s.close();
}
}
/**
* Jetty handler that takes the verifier token passed over from the OAuth provider and stashes it
* where {@link #waitForCode} will find it.
*/
class CallbackHandler extends AbstractHandler {
@Override
public void handle(
String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
throws IOException {
if (!CALLBACK_PATH.equals(target)) {
return;
}
writeLandingHtml(response);
response.flushBuffer();
((Request) request).setHandled(true);
String error = request.getParameter("error");
if (error != null) {
System.out.println("Authorization failed. Error=" + error);
System.out.println("Quitting.");
System.exit(1);
}
code = request.getParameter("code");
synchronized (LocalServerReceiver.this) {
LocalServerReceiver.this.notify();
}
}
private void writeLandingHtml(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/html");
PrintWriter doc = response.getWriter();
doc.println("<html>");
doc.println("<head><title>OAuth 2.0 Authentication Token Recieved</title></head>");
doc.println("<body>");
doc.println("Received verification code. Closing...");
doc.println("<script type='text/javascript'>");
// We open "" in the same window to trigger JS ownership of it, which lets
// us then close it via JS, at least in Chrome.
doc.println("window.setTimeout(function() {");
doc.println(" window.open('', '_self', ''); window.close(); }, 1000);");
doc.println("if (window.opener) { window.opener.checkToken(); }");
doc.println("</script>");
doc.println("</body>");
doc.println("</HTML>");
doc.flush();
}
}
}