/** * Copyright 2010 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eclipse.webdav.internal.authentication; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; import org.eclipse.webdav.IContext; import org.eclipse.webdav.IResponse; import org.eclipse.webdav.http.client.IAuthenticator; import org.eclipse.webdav.http.client.Request; import org.eclipse.webdav.internal.kernel.utils.Assert; /** * The <code>AuthorizationAuthority</code> authorizes client * <code>Request</codes>s for communication with HTTP servers. Subclasses * provide the necessary behavior for different authentication schemes. */ public class AuthorizationAuthority { public static String[] authenticationSchemes = {"Digest", "Basic"}; //$NON-NLS-1$ //$NON-NLS-2$ protected IAuthenticator authenticatorStore = null; /** * Creates a new authenticator that stores its authentication information * in the given authenticator store. * * @param authenticatorStore a store that holds authentication * information */ public AuthorizationAuthority(IAuthenticator authenticatorStore) { Assert.isNotNull(authenticatorStore); this.authenticatorStore = authenticatorStore; } /** * Authorizes the given request by setting its authorization credentials * in the given context. If the given response is not <code>null</code>, * it is assumed to contain an authenticate challenge that is used to * derive the authorization credentials. Returns true if the authorization * succeeds, and false otherwise. * * @param request the request to authorize * @param response the response containing the authenticate challenge * @param context the context where the authorization credentials are set * @param proxyServerUrl the URL of the proxy server, or <code>null</code> * if there is no proxy server * @param isProxyAuthorization a boolean indicating whether the * authorization credentials should be computed for the proxy server or * the origin server * @return a boolean indicating whether the request was successfully * authorized */ public boolean authorize(Request request, IResponse response, IContext context, URL proxyServerUrl, boolean isProxyAuthorization) { Assert.isNotNull(request); Assert.isNotNull(context); URL serverUrl = null; URL protectionSpaceUrl = null; if (isProxyAuthorization) { if (proxyServerUrl == null) { return false; } serverUrl = proxyServerUrl; protectionSpaceUrl = proxyServerUrl; } else { URL resourceUrl = request.getResourceUrl(); try { serverUrl = new URL(resourceUrl.getProtocol(), resourceUrl.getHost(), resourceUrl.getPort(), "/"); //$NON-NLS-1$ } catch (MalformedURLException e) { return false; } protectionSpaceUrl = resourceUrl; } if (response != null) { String challengeString = null; if (isProxyAuthorization) { challengeString = response.getContext().getProxyAuthenticate(); } else { challengeString = response.getContext().getWWWAuthenticate(); } if (challengeString == null) { return false; } AuthenticateChallenge challenge = null; try { challenge = new AuthenticateChallenge(challengeString); } catch (ParserException e) { return false; } String authScheme = challenge.getAuthScheme(); String realm = challenge.getRealm(); AuthorizationAuthority authority = getAuthorizationAuthority(authScheme); if (authority == null) { return false; } Map oldInfo = authenticatorStore.getAuthenticationInfo(serverUrl, realm, authScheme); Map info = authority.getAuthenticationInfo(challenge, oldInfo, serverUrl, protectionSpaceUrl); if (info == null) { return false; } authenticatorStore.addAuthenticationInfo(serverUrl, realm, authScheme, info); authenticatorStore.addProtectionSpace(protectionSpaceUrl, realm); } String realm = authenticatorStore.getProtectionSpace(protectionSpaceUrl); if (realm == null) { return false; } Map info = null; String authScheme = null; for (int i = 0; i < authenticationSchemes.length; ++i) { authScheme = authenticationSchemes[i]; info = authenticatorStore.getAuthenticationInfo(serverUrl, realm, authScheme); if (info != null) { break; } } if (info == null) { return false; } AuthorizationAuthority authority = getAuthorizationAuthority(authScheme); if (authority == null) { return false; } String authorization = authority.getAuthorization(request, info, serverUrl, protectionSpaceUrl, proxyServerUrl); if (authorization == null) { return false; } if (isProxyAuthorization) { if (authorization.equals(context.getProxyAuthorization())) return false; // we already had that auth so it must've failed context.setProxyAuthorization(authorization); } else { if (authorization.equals(context.getAuthorization())) return false; // we already had that auth so it must've failed context.setAuthorization(authorization); } return true; } /** * Confirms whether the given response is valid by proving the server * knows the client's authentication secret (password). Moreover, the * server may wish to communicate some authentication information in the * response for the purposes of authorizing future request. * * @param request the request that has already been sent * @param response the response back from the server to be verified * @param proxyServerUrl the URL of the proxy server, or <code>null</code> * if there is none * @returns a boolean indicating whether the given response is valid */ public boolean confirm(Request request, IResponse response, URL proxyServerUrl) { Assert.isNotNull(request); Assert.isNotNull(response); URL resourceUrl = request.getResourceUrl(); URL serverUrl = null; try { serverUrl = new URL(resourceUrl.getProtocol(), resourceUrl.getHost(), resourceUrl.getPort(), "/"); //$NON-NLS-1$ } catch (MalformedURLException e) { return false; } String realm = authenticatorStore.getProtectionSpace(resourceUrl); if (realm == null) { return false; } Map info = null; String authScheme = null; for (int i = 0; i < authenticationSchemes.length; ++i) { authScheme = authenticationSchemes[i]; info = authenticatorStore.getAuthenticationInfo(serverUrl, realm, authScheme); if (info != null) { break; } } if (info == null) { return false; } AuthorizationAuthority authority = getAuthorizationAuthority(authScheme); if (authority == null) { return false; } return authority.confirmResponse(request, response, proxyServerUrl); } /** * Confirms whether the given response is valid by proving the server * knows the client's authentication secret (password). Moreover, the * server may wish to communicate some authentication information in the * response for the purposes of authorizing future request. * <p>This method should be overridden by schema specific authenticators. * * @param request the request that has already been sent * @param response the response back from the server to be verified * @param proxyServerUrl the URL of the proxy server, or <code>null</code> * if there is none * @returns a boolean indicating whether the given response is valid */ protected boolean confirmResponse(Request request, IResponse response, URL proxyServerUrl) { Assert.isNotNull(request); Assert.isNotNull(response); return false; } /** * Returns the new authentication information gleaned from the given * authenticate challenge and the given old authentication information. * The old authentication information may be <code>null</code>. * The authentication information usually contains directives such as * usernames and passwords. * <p>This method should be overridden by schema specific authenticators. * * @param challenge the authenticate challenge from the server * @param oldInfo the old authentication information * @param serverUrl the URL of the server * @param protectionSpaceUrl the URL of the protected resource * @return new authentication information */ protected Map getAuthenticationInfo(AuthenticateChallenge challenge, Map oldInfo, URL serverUrl, URL protectionSpaceUrl) { Assert.isNotNull(challenge); Assert.isNotNull(serverUrl); Assert.isNotNull(protectionSpaceUrl); return null; } /** * Returns the authorization credentials for the given request. The * authorization credentials are derived from the given authentication * info. The authentication info may contain directives such as usernames * and passwords. * <p>This method should be overridden by schema specific authenticators. * * @param request the request being authorized * @param info the authentication information used to derive the * authorization credentials * @param serverUrl the URL of the server * @param protectionSpaceUrl the URL of the protected resource * @param proxyServerUrl the URL of the proxy server, or <code>null</code> * if there is none * @return the authorization credentials for the given request */ protected String getAuthorization(Request request, Map info, URL serverUrl, URL protectionSpaceUrl, URL proxyServerUrl) { Assert.isNotNull(request); Assert.isNotNull(info); Assert.isNotNull(serverUrl); Assert.isNotNull(protectionSpaceUrl); return null; } /** * Returns an authorization authority for the given authentication * scheme, or <code>null</code> if there is no such authority. * * @param scheme an authentication scheme, for example: "Basic" * @return an authorization authority for the given authentication scheme */ private AuthorizationAuthority getAuthorizationAuthority(String scheme) { try { scheme = Character.toUpperCase(scheme.charAt(0)) + scheme.substring(1).toLowerCase(); String packageName = "org.eclipse.webdav.internal.authentication"; //$NON-NLS-1$ String className = scheme + "Authority"; //$NON-NLS-1$ Class clazz = Class.forName(packageName + "." + className); //$NON-NLS-1$ Constructor constructor = clazz.getConstructor(new Class[] {IAuthenticator.class}); return (AuthorizationAuthority) constructor.newInstance(new Object[] {authenticatorStore}); } catch (ClassCastException e) { // ignore or log? } catch (Exception e) { // ignore or log? } return null; } /** * Computes the MD5 hash value of the given <code>String</code> and * returns the result as a HEX <code>String</code>. * * @param s * @return a HEX <code>String</code> containing the MD5 hash value of the * given <code>String</code> * @exception NoSuchAlgorithmException * @exception UnsupportedEncodingException */ protected String md5(String s) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md5 = MessageDigest.getInstance("MD5"); //$NON-NLS-1$ byte[] hash = md5.digest(s.getBytes("UTF8")); //$NON-NLS-1$ return HexConverter.toHex(hash); } /** * Computes the MD5 hash value of the body of the given request and * returns the result as a HEX <code>String</code>. * * @param request * @return a HEX <code>String</code> containing the MD5 hash value of the * body of the given request * @exception NoSuchAlgorithmException * @exception IOException */ protected String md5(Request request) throws NoSuchAlgorithmException, IOException { DigestOutputStream dos = new DigestOutputStream("MD5"); //$NON-NLS-1$ request.write(dos); String result = HexConverter.toHex(dos.digest()); dos.close(); return result; } /** * Returns the given <code>String</code> with its quotes removed. * * @param s a <code>String</code> * @return the given <code>String</code> with its quotes removed */ protected String unquote(String s) { if (s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"') //$NON-NLS-1$ //$NON-NLS-2$ return s.substring(1, s.length() - 1); return s; } }