/**
* 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;
}
}