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}