001/* 002 * Copyright (c) 2017 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2017-07-04 14:25:38 -0700 (Tue, 04 Jul 2017) $' 007 * '$Revision: 1257 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029package org.kepler.webview.server.auth; 030 031import java.net.HttpURLConnection; 032 033import org.kepler.webview.server.WebViewServer; 034 035import io.vertx.core.AsyncResult; 036import io.vertx.core.Future; 037import io.vertx.core.Handler; 038import io.vertx.core.http.HttpClient; 039import io.vertx.core.http.HttpClientOptions; 040import io.vertx.core.http.HttpClientRequest; 041import io.vertx.core.json.JsonObject; 042import io.vertx.ext.auth.AuthProvider; 043import io.vertx.ext.auth.User; 044 045/** AuthProvider that authenticates against a Drupal site. 046 * 047 * @author Daniel Crawl 048 * @version $Id: DrupalAuth.java 1257 2017-07-04 21:25:38Z crawl $ 049 * 050 */ 051public class DrupalAuth implements AuthProvider { 052 053 /** Create a new DrupalAuth. 054 * @param host The hostname of the drupal site. 055 * @param service The name of the Services REST service. 056 * @param role The name of the role required by authorized users. 057 * @param groupsField The name of the user field containing the group name(s). 058 */ 059 public DrupalAuth(String host, String service, String role, String groupsField, 060 String fullNameField) { 061 062 _loginUrl = "?q=" + service + "/user/login.json"; 063 _logoutUrl = "?q=" + service + "/user/logout.json"; 064 _role = role; 065 _groupsField = "field_" + groupsField; 066 _fullNameField = "field_" + fullNameField; 067 068 _client = WebViewServer.vertx().createHttpClient( 069 new HttpClientOptions().setDefaultHost(host) 070 .setDefaultPort(443).setSsl(true).setTrustAll(true));//.setLogActivity(true)); 071 } 072 073 @Override 074 public void authenticate(JsonObject authInfo, Handler<AsyncResult<User>> handler) { 075 076 //System.out.println("authenticating for " + authInfo.getString("username")); 077 078 String username = authInfo.getString("username"); 079 if (username == null) { 080 handler.handle(Future.failedFuture("authInfo must contain username in 'username' field")); 081 } else { 082 String password = authInfo.getString("password"); 083 if (password == null) { 084 handler.handle(Future.failedFuture("authInfo must contain password in 'password' field")); 085 } else { 086 //System.out.println(_sessionTokenUrl); 087 _getCSRFToken(null, loginTokenRes -> { 088 if(loginTokenRes.failed()) { 089 handler.handle(Future.failedFuture(loginTokenRes.cause().getMessage())); 090 } else { 091 //String body = "{username=\"" + username + "\",password=\"" + password + "\"}"; 092 String body = new JsonObject().put("username", username).put("password", password).encode(); 093 //System.out.println(body); 094 _client.post(_loginUrl, loginResponse -> { 095 //System.out.println("login status " + loginResponse.statusCode()); 096 //System.out.println("login message " + loginResponse.statusMessage()); 097 if(loginResponse.statusCode() != HttpURLConnection.HTTP_OK) { 098 String message = loginResponse.statusMessage(); 099 if(message.indexOf(": ") > -1) { 100 message = message.substring(message.indexOf(": ") + 2); 101 } 102 handler.handle(Future.failedFuture(message)); 103 } else { 104 loginResponse.handler(loginBuffer -> { 105 JsonObject loginJson = loginBuffer.toJsonObject(); 106 User user = new DrupalUser(loginJson, _groupsField, _fullNameField, _role); 107 handler.handle(Future.succeededFuture(user)); 108 109 String sessionCookieStr = loginJson.getString("session_name") + "=" + loginJson.getString("sessid"); 110 _getCSRFToken(sessionCookieStr, logoutTokenRes -> { 111 if(logoutTokenRes.failed()) { 112 System.err.println("WARNING: could not get CSRF token for logout: " + 113 loginTokenRes.cause().getMessage()); 114 } else { 115 _client.post(_logoutUrl, logoutResponse -> { 116 //System.out.println("logout: " + logoutResponse.statusMessage()); 117 if(logoutResponse.statusCode() != HttpURLConnection.HTTP_OK) { 118 System.err.println("WARNING: could not log out: " + logoutResponse.statusMessage()); 119 System.out.println("Cookie" + loginJson.getString("session_name") + "=" + loginJson.getString("sessid")); 120 } 121 }).putHeader("X-CSRF-Token", logoutTokenRes.result()) 122 .putHeader("Cookie", sessionCookieStr) 123 .putHeader("Content-Type", "application/json") 124 .putHeader("Content-Length", String.valueOf(0)) 125 .end(); 126 } 127 }); 128 }); 129 } 130 }).putHeader("X-CSRF-Token", loginTokenRes.result()) 131 .putHeader("Content-Type", "application/json") 132 .putHeader("Content-Length", String.valueOf(body.length())) 133 .write(body) 134 .end(); 135 } 136 }); 137 } 138 } 139 140 } 141 142 private void _getCSRFToken(String sessionCookieStr, Handler<AsyncResult<String>> handler) { 143 HttpClientRequest request = _client.get(_sessionTokenUrl, response -> { 144 if(response.statusCode() != HttpURLConnection.HTTP_OK) { 145 handler.handle(Future.failedFuture("Failed to get session token while authenticating.")); 146 } else { 147 response.handler(buffer -> { 148 handler.handle(Future.succeededFuture(buffer.toString())); 149 }); 150 } 151 }); 152 153 if(sessionCookieStr != null) { 154 request.putHeader("Cookie", sessionCookieStr); 155 } 156 157 request.end(); 158 } 159 160 /** HTTP client to make requests from drupal. */ 161 private HttpClient _client; 162 163 /** REST login url */ 164 private String _loginUrl; 165 166 /** REST logout url */ 167 private String _logoutUrl; 168 169 /** Role user must have */ 170 private String _role; 171 172 /** Name of drupal user field that contains the user's group name(s). */ 173 private String _groupsField; 174 175 private String _fullNameField; 176 177 /** REST get session token url. */ 178 private final static String _sessionTokenUrl = "/?q=services/session/token"; 179 180}