001 /** 002 * Copyright (c) 2010 Yahoo! Inc. All rights reserved. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. See accompanying LICENSE file. 014 */ 015 package org.apache.oozie.client; 016 017 import org.apache.hadoop.security.authentication.client.AuthenticatedURL; 018 import org.apache.hadoop.security.authentication.client.AuthenticationException; 019 import org.apache.hadoop.security.authentication.client.Authenticator; 020 import org.apache.hadoop.security.authentication.client.KerberosAuthenticator; 021 022 import java.io.BufferedReader; 023 import java.io.File; 024 import java.io.FileReader; 025 import java.io.FileWriter; 026 import java.io.IOException; 027 import java.io.Writer; 028 import java.net.HttpURLConnection; 029 import java.net.URL; 030 031 /** 032 * This subclass of {@link XOozieClient} supports Kerberos HTTP SPNEGO and simple authentication. 033 */ 034 public class AuthOozieClient extends XOozieClient { 035 036 /** 037 * Java system property to specify a custom Authenticator implementation. 038 */ 039 public static final String AUTHENTICATOR_CLASS_SYS_PROP = "authenticator.class"; 040 041 /** 042 * Java system property that, if set the authentication token will be cached in the user home directory in a hidden 043 * file <code>.oozie-auth-token</code> with user read/write permissions only. 044 */ 045 public static final String USE_AUTH_TOKEN_CACHE_SYS_PROP = "oozie.auth.token.cache"; 046 047 /** 048 * File constant that defines the location of the authentication token cache file. 049 * <p/> 050 * It resolves to <code>${user.home}/.oozie-auth-token</code>. 051 */ 052 public static final File AUTH_TOKEN_CACHE_FILE = new File(System.getProperty("user.home"), ".oozie-auth-token"); 053 054 /** 055 * Create an instance of the AuthOozieClient. 056 * 057 * @param oozieUrl the Oozie URL 058 */ 059 public AuthOozieClient(String oozieUrl) { 060 super(oozieUrl); 061 } 062 063 /** 064 * Create an authenticated connection to the Oozie server. 065 * <p/> 066 * It uses Alfredo client authentication which by default supports 067 * Kerberos HTTP SPNEGO, Pseudo/Simple and anonymous. 068 * <p/> 069 * if the Java system property {@link #USE_AUTH_TOKEN_CACHE_SYS_PROP} is set to true Alfredo 070 * authentication token will be cached/used in/from the '.oozie-auth-token' file in the user 071 * home directory. 072 * 073 * @param url the URL to open a HTTP connection to. 074 * @param method the HTTP method for the HTTP connection. 075 * @return an authenticated connection to the Oozie server. 076 * @throws IOException if an IO error occurred. 077 * @throws OozieClientException if an oozie client error occurred. 078 */ 079 @Override 080 protected HttpURLConnection createConnection(URL url, String method) throws IOException, OozieClientException { 081 boolean useAuthFile = System.getProperty(USE_AUTH_TOKEN_CACHE_SYS_PROP, "false").equalsIgnoreCase("true"); 082 AuthenticatedURL.Token readToken = new AuthenticatedURL.Token(); 083 AuthenticatedURL.Token currentToken = new AuthenticatedURL.Token(); 084 085 if (useAuthFile) { 086 readToken = readAuthToken(); 087 if (readToken != null) { 088 currentToken = new AuthenticatedURL.Token(readToken.toString()); 089 } 090 } 091 092 if (currentToken.isSet()) { 093 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 094 conn.setRequestMethod("OPTIONS"); 095 AuthenticatedURL.injectToken(conn, currentToken); 096 if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { 097 AUTH_TOKEN_CACHE_FILE.delete(); 098 currentToken = new AuthenticatedURL.Token(); 099 } 100 } 101 102 if (!currentToken.isSet()) { 103 Authenticator authenticator = getAuthenticator(); 104 try { 105 new AuthenticatedURL(authenticator).openConnection(url, currentToken); 106 } 107 catch (AuthenticationException ex) { 108 AUTH_TOKEN_CACHE_FILE.delete(); 109 throw new OozieClientException(OozieClientException.AUTHENTICATION, 110 "Could not authenticate, " + ex.getMessage(), ex); 111 } 112 } 113 if (useAuthFile && !currentToken.equals(readToken)) { 114 writeAuthToken(currentToken); 115 } 116 HttpURLConnection conn = super.createConnection(url, method); 117 118 AuthenticatedURL.injectToken(conn, currentToken); 119 return conn; 120 } 121 122 123 /** 124 * Read a authentication token cached in the user home directory. 125 * <p/> 126 * 127 * @return the authentication token cached in the user home directory, NULL if none. 128 */ 129 protected AuthenticatedURL.Token readAuthToken() { 130 AuthenticatedURL.Token authToken = null; 131 if (AUTH_TOKEN_CACHE_FILE.exists()) { 132 try { 133 BufferedReader reader = new BufferedReader(new FileReader(AUTH_TOKEN_CACHE_FILE)); 134 String line = reader.readLine(); 135 reader.close(); 136 if (line != null) { 137 authToken = new AuthenticatedURL.Token(line); 138 } 139 } 140 catch (IOException ex) { 141 //NOP 142 } 143 } 144 return authToken; 145 } 146 147 /** 148 * Write the current authenthication token to the user home directory. 149 * <p/> 150 * The file is written with user only read/write permissions. 151 * <p/> 152 * If the file cannot be updated or the user only ready/write permissions cannot be set the file is deleted. 153 * 154 * @param authToken the authentication token to cache. 155 */ 156 protected void writeAuthToken(AuthenticatedURL.Token authToken) { 157 try { 158 Writer writer = new FileWriter(AUTH_TOKEN_CACHE_FILE); 159 writer.write(authToken.toString()); 160 writer.close(); 161 // sets read-write permissions to owner only 162 AUTH_TOKEN_CACHE_FILE.setReadable(false, false); 163 AUTH_TOKEN_CACHE_FILE.setReadable(true, true); 164 AUTH_TOKEN_CACHE_FILE.setWritable(true, true); 165 } 166 catch (Exception ex) { 167 // if case of any error we just delete the cache, if user-only 168 // write permissions are not properly set a security exception 169 // is thrown and the file will be deleted. 170 AUTH_TOKEN_CACHE_FILE.delete(); 171 } 172 } 173 174 /** 175 * Return the Alfredo Authenticator to use. 176 * <p/> 177 * It looks for value of the {@link #AUTHENTICATOR_CLASS_SYS_PROP} Java system property, if not set it uses Alfredo 178 * <code>KerberosAuthenticator</code> which supports both Kerberos HTTP SPNEGO and Pseudo/simple authentication. 179 * 180 * @return the Authenticator to use, <code>NULL</code> if none. 181 * 182 * @throws OozieClientException thrown if the authenticator could not be instatiated. 183 */ 184 protected Authenticator getAuthenticator() throws OozieClientException { 185 String className = System.getProperty(AUTHENTICATOR_CLASS_SYS_PROP, KerberosAuthenticator.class.getName()); 186 if (className != null) { 187 try { 188 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 189 Class klass = (cl != null) ? cl.loadClass(className) : getClass().getClassLoader().loadClass(className); 190 return (Authenticator) klass.newInstance(); 191 } 192 catch (Exception ex) { 193 throw new OozieClientException(OozieClientException.AUTHENTICATION, 194 "Could not instantiate Authenticator [" + className + "], " + 195 ex.getMessage(), ex); 196 } 197 } 198 else { 199 throw new OozieClientException(OozieClientException.AUTHENTICATION, 200 "Authenticator class not found [" + className + "]"); 201 } 202 } 203 204 }