001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.web.auth;
018:
019: import javax.servlet.http.HttpSession;
020: import javax.crypto.Mac;
021: import javax.crypto.spec.SecretKeySpec;
022:
023: import java.util.Random;
024: import java.util.Map;
025: import java.math.BigInteger;
026: import java.security.NoSuchAlgorithmException;
027: import java.security.Key;
028: import java.security.InvalidKeyException;
029: import java.io.UnsupportedEncodingException;
030: import java.lang.ref.WeakReference;
031:
032: import org.apache.velocity.tools.view.context.ViewContext;
033:
034: import velosurf.util.Logger;
035:
036: import sun.misc.BASE64Encoder;
037: import sun.misc.BASE64Decoder;
038:
039: /**
040: * This abstract class implements an authentication mechanism. It is meant to be declared
041: * in toolbox.xml as a session-scoped tool.
042: *
043: * The password encryption method can be specified in <code>toolbox.xml</code> using the <code>method</code> parameter
044: * (when not specified, passwords are passed in clear).
045: *
046: * You will need to implement the same password encryption on the client side using the adequate
047: * javascript files. A <code>/src/javascript/md5.js</code> file is provided to help implementing the HmacMD5 method.
048: *
049: * Still, if you really want security, use HTTPS!
050: *
051: * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
052: */
053:
054: public abstract class BaseAuthenticator {
055:
056: /**
057: * get the password corresponding to a login.
058: * @param login login
059: * @return password or null
060: */
061: public abstract String getPassword(String login);
062:
063: /**
064: * Get the user object corresponding to a login
065: * @param login login
066: * @return user object
067: */
068: public abstract Object getUser(String login);
069:
070: /** encryption method */
071: private String method = null;
072:
073: /** challenge value */
074: private String challenge = null;
075:
076: /** random number generator */
077: private static Random random = new Random(System
078: .currentTimeMillis());
079:
080: /** length of challenge */
081: private static final int CHALLENGE_LENGTH = 256; // bits
082:
083: /** keep a reference on the session */
084: private WeakReference<HttpSession> session = null;
085:
086: /**
087: * initialize this tool.
088: * @param initData a view context
089: */
090: public void init(Object initData) {
091: if (!(initData instanceof ViewContext)) {
092: Logger
093: .error("auth: authenticator tool should be used in a session scope!");
094: }
095: HttpSession s = ((ViewContext) initData).getRequest()
096: .getSession(true);
097: session = new WeakReference<HttpSession>(s);
098: s.setAttribute(BaseAuthenticator.class.getName(), this );
099: }
100:
101: /**
102: * configure this tool.
103: * @param config map containing an optional "method" parameter
104: */
105: public void configure(Map config) {
106: method = (String) config.get("method");
107: }
108:
109: /**
110: * This method generates a new challenge each time it is called.
111: *
112: * @return a new 1024-bit challenge in base64
113: */
114: public String getChallenge() {
115: BigInteger bigint = new BigInteger(CHALLENGE_LENGTH, random);
116: challenge = new sun.misc.BASE64Encoder().encode(bigint
117: .toByteArray());
118: challenge = challenge.replace("\n", "");
119: // Logger.trace("auth: challenge="+challenge);
120: return challenge;
121: }
122:
123: /** Check received answer.
124: *
125: * @param login login
126: * @param answer received answer
127: * @return true if received answer is valid
128: */
129: public boolean checkLogin(String login, String answer) {
130: String password = getPassword(login);
131: if (password == null) {
132: /* password not found */
133: Logger.trace("auth: login " + login + " does not exist");
134: return false;
135: }
136: if (password.length() == 0 && answer.length() == 0) {
137: return true;
138: }
139: String correctAnswer = generateAnswer(password);
140: Logger.trace("auth: received=" + answer);
141: Logger.trace("auth: correct =" + correctAnswer);
142: return (correctAnswer != null && correctAnswer.equals(answer));
143: }
144:
145: /**
146: * Generate the correct answer.
147: * @param password
148: * @return encrypted answer
149: */
150: private String generateAnswer(String password) {
151: if (method == null) {
152: return password;
153: } else if (challenge == null) {
154: /* return something that will never match any password */
155: return getChallenge();
156: } else {
157: Logger.debug("auth: using method " + method);
158: try {
159: /* TODO: use utf8 (and find a way to convert an utf8 string into
160: an array of bytes on the javascript counterpart) */
161: Mac mac = Mac.getInstance(method);
162: mac.init(new SecretKeySpec(password
163: .getBytes("ISO-8859-1"), method));
164: byte[] hash = mac.doFinal(challenge
165: .getBytes("ISO-8859-1"));
166: String encoded = new BASE64Encoder().encode(hash);
167: /* strips the last(s) '=' */
168: int i;
169: while ((i = encoded.lastIndexOf('=')) != -1) {
170: encoded = encoded.substring(0, i);
171: }
172: return encoded;
173: } catch (NoSuchAlgorithmException nsae) {
174: Logger.error("auth: could not find algorithm '"
175: + method + "'");
176: Logger.log(nsae);
177: } catch (Exception e) {
178: Logger.error("auth: an unknown error occurred...");
179: Logger.log(e);
180: }
181: }
182: return null;
183: }
184:
185: public Object getLoggedUser() {
186: if (session == null) {
187: return null;
188: }
189: HttpSession sess = session.get();
190: if (sess == null) {
191: return null;
192: }
193: return sess.getAttribute("velosurf.auth.user");
194: }
195: }
|