/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.io.dav.http; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.TreeMap; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ abstract class HTTPAuthentication { private Map<String, String> myChallengeParameters; private String myUserName; private char[] myPassword; private static final String AUTH_METHODS_PROPERTY = "svnkit.http.methods"; private static final String OLD_AUTH_METHODS_PROPERTY = "javasvn.http.methods"; protected HTTPAuthentication (SVNPasswordAuthentication credentials) { if (credentials != null) { myUserName = credentials.getUserName(); myPassword = credentials.getPasswordValue(); } } protected HTTPAuthentication (String name, char[] password) { myUserName = name; myPassword = password; } protected HTTPAuthentication () { } public void setChallengeParameter(String name, String value) { Map<String, String> params = getChallengeParameters(); params.put(name, value); } public String getChallengeParameter(String name) { if (myChallengeParameters == null) { return null; } return (String)myChallengeParameters.get(name); } protected Map<String, String> getChallengeParameters() { if (myChallengeParameters == null) { myChallengeParameters = new TreeMap<String, String>(); } return myChallengeParameters; } public void setCredentials(SVNPasswordAuthentication credentials) { if (credentials != null) { myUserName = credentials.getUserName(); myPassword = credentials.getPasswordValue(); } } public String getRawUserName() { return myUserName; } public String getUserName() { return myUserName; } public char[] getPassword() { return myPassword; } public void setUserName(String name) { myUserName = name; } public void setPassword(char[] password) { myPassword = password; } public static HTTPAuthentication parseAuthParameters(Collection<String> authHeaderValues, HTTPAuthentication prevResponse, String charset, Collection<String> authTypes, ISVNAuthenticationManager authManager, int requestID) throws SVNException { if (authHeaderValues == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Missing HTTP authorization method"); SVNErrorManager.error(err, SVNLogType.NETWORK); } HTTPAuthentication auth = null; String authHeader = null; // sort auth headers accordingly to priorities. authHeaderValues = sortSchemes(authHeaderValues, authTypes); for (Iterator<String> authSchemes = authHeaderValues.iterator(); authSchemes.hasNext();) { authHeader = authSchemes.next(); String source = authHeader.trim(); // parse strings: name="value" or name=value int index = source.indexOf(' '); if (index <= 0) { index = source.length(); if (!"NTLM".equalsIgnoreCase(source.substring(0, index)) && !"Negotiate".equalsIgnoreCase(source.substring(0, index))) { continue; } } String method = source.substring(0, index); source = source.substring(index).trim(); if ("Basic".equalsIgnoreCase(method)) { auth = new HTTPBasicAuthentication(charset); if (source.indexOf("realm=") >= 0) { source = source.substring(source.indexOf("realm=") + "realm=".length()); source = source.trim(); if (source.startsWith("\"")) { source = source.substring(1); } if (source.endsWith("\"")) { source = source.substring(0, source.length() - 1); } //parameters.put("realm", source); auth.setChallengeParameter("realm", source); } break; } else if ("Digest".equalsIgnoreCase(method)) { auth = new HTTPDigestAuthentication(charset); char[] chars = (source + " ").toCharArray(); int tokenIndex = 0; boolean parsingToken = true; String name = null; String value; int quotesCount = 0; for(int i = 0; i < chars.length; i++) { if (parsingToken) { if (chars[i] == '=') { name = new String(chars, tokenIndex, i - tokenIndex); name = name.trim(); tokenIndex = i + 1; parsingToken = false; } } else { if (chars[i] == '\"') { quotesCount = quotesCount > 0 ? 0 : 1; } else if ( i + 1 >= chars.length || (chars[i] == ',' && quotesCount == 0)) { value = new String(chars, tokenIndex, i - tokenIndex); value = value.trim(); if (value.charAt(0) == '\"' && value.charAt(value.length() - 1) == '\"') { value = value.substring(1); value = value.substring(0, value.length() - 1); } //parameters.put(name, value); auth.setChallengeParameter(name, value); tokenIndex = i + 1; parsingToken = true; } } } HTTPDigestAuthentication digestAuth = (HTTPDigestAuthentication)auth; digestAuth.init(); break; } else if ("NTLM".equalsIgnoreCase(method)) { HTTPNTLMAuthentication ntlmAuth = null; if (source.length() == 0) { String ntlmImpl = System.getProperty("svnkit.http.ntlm", "java:apache"); if ("jna".equalsIgnoreCase(ntlmImpl)) { ntlmAuth = HTTPNativeNTLMAuthentication.newInstance(charset); if (ntlmAuth != null) { ntlmAuth.parseChallenge(null); } } if (ntlmAuth == null) { if ("jna".equalsIgnoreCase(ntlmImpl)) { ntlmImpl = "java"; } if ("java:apache".equalsIgnoreCase(ntlmImpl)) { ntlmAuth = HTTPApacheNTLMAuthentication.newInstance(charset, HTTPApacheNTLMAuthentication.APACHE_ENGINE); } else if ("java:jcifs".equalsIgnoreCase(ntlmImpl)) { ntlmAuth = HTTPApacheNTLMAuthentication.newInstance(charset, HTTPApacheNTLMAuthentication.JCIFS_ENGINE); } else { ntlmAuth = new HTTPNTLMAuthentication(charset); } } ntlmAuth.setType1State(); } else { ntlmAuth = (HTTPNTLMAuthentication)prevResponse; ntlmAuth.parseChallenge(source); ntlmAuth.setType3State(); } auth = ntlmAuth; break; } else if ("Negotiate".equalsIgnoreCase(method)) { HTTPNegotiateAuthentication negoAuth = null; if (source.length() == 0) { // Check for a custom negotiation implementation, created by the auth manager if (authManager instanceof IHTTPNegotiateAuthenticationFactory) { negoAuth = ((IHTTPNegotiateAuthenticationFactory) authManager).createNegotiateAuthentication( prevResponse instanceof HTTPNegotiateAuthentication ? (HTTPNegotiateAuthentication) prevResponse : null, requestID); } else { if (DefaultHTTPNegotiateAuthentication.isSupported()) { if (prevResponse instanceof DefaultHTTPNegotiateAuthentication) { negoAuth = new DefaultHTTPNegotiateAuthentication((DefaultHTTPNegotiateAuthentication)prevResponse); } else { negoAuth = new DefaultHTTPNegotiateAuthentication(); try { negoAuth.needsLogin(); } catch (Throwable th) { // SecurityException might be thrown in case configuration file // is missing, then consider Negotiate as not supporter negoAuth = null; } } } } if (negoAuth != null) { negoAuth.respondTo(null); } } else { // If this is a server response with a token, then negotiate authentication is already in progress. // NOTE: this should never happen in practice since negotiate authentication should be completed with a // single token. After successful authentication at the server end, the token from the server is sent // in a Authentication-Info header. negoAuth = (HTTPNegotiateAuthentication)prevResponse; negoAuth.respondTo(source); } if (negoAuth != null) { auth = negoAuth; break; } } } if (auth == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "HTTP authorization method ''{0}'' is not supported", authHeader); SVNErrorManager.error(err, SVNLogType.NETWORK); } if (prevResponse != null) { auth.setUserName(prevResponse.getRawUserName()); auth.setPassword(prevResponse.getPassword()); } return auth; } public static boolean isSchemeSupportedByServer(String scheme, Collection<String> authHeaderValues) throws SVNException { if (authHeaderValues == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Missing HTTP authorization method"); SVNErrorManager.error(err, SVNLogType.NETWORK); } String authHeader = null; for (Iterator<String> authSchemes = authHeaderValues.iterator(); authSchemes.hasNext();) { authHeader = authSchemes.next(); String source = authHeader.trim(); int index = source.indexOf(' '); if (index <= 0) { index = source.length(); } String method = source.substring(0, index); if (method.equalsIgnoreCase(scheme)) { return true; } } return false; } private static Collection<String> sortSchemes(Collection<String> authHeaders, Collection<String> authTypes) { String priorities = System.getProperty(AUTH_METHODS_PROPERTY, System.getProperty(OLD_AUTH_METHODS_PROPERTY)); final List<String> schemes = new ArrayList<String>(); if (authTypes != null && !authTypes.isEmpty()) { schemes.addAll(authTypes); } else if (priorities != null && !"".equals(priorities.trim())) { for(StringTokenizer tokens = new StringTokenizer(priorities, " ,"); tokens.hasMoreTokens();) { String scheme = tokens.nextToken(); if (!schemes.contains(scheme)) { schemes.add(scheme); } } } else { return authHeaders; } List<String> ordered = new ArrayList<String>(authHeaders); Collections.sort(ordered, new Comparator<String>() { public int compare(String o1, String o2) { String header1 = o1; String header2 = o2; String scheme1 = getSchemeName(header1); String scheme2 = getSchemeName(header2); int index1 = schemes.indexOf(scheme1); int index2 = schemes.indexOf(scheme2); index1 = index1 < 0 ? Integer.MAX_VALUE : index1; index2 = index2 < 0 ? Integer.MAX_VALUE : index2; if (index1 == index2) { return 0; } return index1 > index2 ? 1 : -1; } }); return ordered; } private static String getSchemeName(String header) { String source = header.trim(); int index = source.indexOf(' '); if (index <= 0) { index = source.length(); } return source.substring(0, index); } public abstract String getAuthenticationScheme(); public abstract String authenticate() throws SVNException; protected static byte[] getASCIIBytes(final String data) { return getBytes(data, "US-ASCII"); } protected static byte[] getBytes(final String data, String charset) { try { return data.getBytes(charset); } catch (UnsupportedEncodingException e) { return data.getBytes(); } } protected static byte[] getBytes(final char[] data, String charset) { return SVNEncodingUtil.getBytes(data, charset); } public static void clear(byte[] array) { SVNEncodingUtil.clearArray(array); } public static void clear(char[] array) { SVNEncodingUtil.clearArray(array); } }