001/* 002 * Copyright 2014 Red Hat, Inc. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * and Apache License v2.0 which accompanies this distribution. 007 * 008 * The Eclipse Public License is available at 009 * http://www.eclipse.org/legal/epl-v10.html 010 * 011 * The Apache License v2.0 is available at 012 * http://www.opensource.org/licenses/apache2.0.php 013 * 014 * You may elect to redistribute this code under either of these licenses. 015 */ 016 017package org.kepler.webview.server.auth; 018 019import java.util.Base64; 020 021import io.vertx.core.http.HttpHeaders; 022import io.vertx.core.http.HttpServerRequest; 023import io.vertx.core.json.JsonObject; 024import io.vertx.ext.auth.AuthProvider; 025import io.vertx.ext.auth.User; 026import io.vertx.ext.web.RoutingContext; 027import io.vertx.ext.web.Session; 028import io.vertx.ext.web.handler.impl.BasicAuthHandlerImpl; 029 030/** Basic authentication handler that uses "WebView" as the scheme 031 * instead of "Basic" so that 401 responses are not caught by the browser. 032 * 033 * Copied from vertx BasicAuthHandlerImpl. 034 * 035 * @author <a href="http://pmlopes@gmail.com">Paulo Lopes</a> 036 * @author <a href="http://tfox.org">Tim Fox</a> 037 */ 038public class WebViewAuthHandlerImpl extends BasicAuthHandlerImpl { 039 040 041 public WebViewAuthHandlerImpl(AuthProvider authProvider, String realm) { 042 super(authProvider, realm); 043 } 044 045 @Override 046 public void handle(RoutingContext context) { 047 User user = context.user(); 048 if (user != null) { 049 // Already authenticated in, just authorize 050 authorizeUser(user, context); 051 } else { 052 HttpServerRequest request = context.request(); 053 String authorization = request.headers().get(HttpHeaders.AUTHORIZATION); 054 055 //System.out.println(authorization); 056 057 if (authorization == null) { 058 handle401(context, "Basic"); 059 } else { 060 String suser; 061 String spass; 062 String sscheme; 063 064 try { 065 String[] parts = authorization.split(" "); 066 sscheme = parts[0]; 067 String decoded = new String(Base64.getDecoder().decode(parts[1])); 068 int colonIdx = decoded.indexOf(":"); 069 if(colonIdx!=-1) { 070 suser = decoded.substring(0,colonIdx); 071 spass = decoded.substring(colonIdx+1); 072 } else { 073 suser = decoded; 074 spass = null; 075 } 076 } catch (ArrayIndexOutOfBoundsException e) { 077 handle401(context, "Basic"); 078 return; 079 } catch (IllegalArgumentException | NullPointerException e) { 080 // IllegalArgumentException includes PatternSyntaxException 081 context.fail(e); 082 return; 083 } 084 085 // handle either "WebView" or "Basic" scheme 086 if (!"WebView".equals(sscheme) && !"Basic".equals(sscheme)) { 087 context.fail(400); 088 } else { 089 JsonObject authInfo = new JsonObject().put("username", suser).put("password", spass); 090 authProvider.authenticate(authInfo, res -> { 091 if (res.succeeded()) { 092 User authenticated = res.result(); 093 context.setUser(authenticated); 094 Session session = context.session(); 095 if (session != null) { 096 // the user has upgraded from unauthenticated to authenticated 097 // session should be upgraded as recommended by owasp 098 session.regenerateId(); 099 } 100 authorizeUser(authenticated, context); 101 } else { 102 // if not able to authenticate, send 401 with same scheme as in request 103 handle401(context, sscheme); 104 } 105 }); 106 } 107 } 108 } 109 } 110 111 private void authorizeUser(final User user, final RoutingContext context) { 112 this.authorize(user, authZ -> { 113 if (authZ.failed()) { 114 this.processException(context, authZ.cause()); 115 return; 116 } 117 context.next(); 118 }); 119 } 120 121 private void handle401(RoutingContext context, String scheme) { 122 context.response().putHeader("WWW-Authenticate", scheme + " realm=\"" + realm + "\"") 123 .putHeader("Content-type", "text/plain"); 124 context.fail(401); 125 } 126}