View Javadoc

1   /*
2   Karma core - Core of the Karma application
3   Copyright (C) 2004  Toolforge <www.toolforge.nl>
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19  package nl.toolforge.karma.core.vc;
20  
21  import nl.toolforge.karma.core.KarmaRuntimeException;
22  import nl.toolforge.karma.core.boot.WorkingContext;
23  import nl.toolforge.karma.core.location.PasswordScrambler;
24  import org.apache.commons.digester.Digester;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.xml.sax.SAXException;
28  
29  import java.io.File;
30  import java.io.FileWriter;
31  import java.io.IOException;
32  import java.text.MessageFormat;
33  import java.util.ArrayList;
34  import java.util.HashMap;
35  import java.util.Hashtable;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  
40  /***
41   * <p>When a {@link nl.toolforge.karma.core.location.Location} - more specifically, {@link VersionControlSystem} -
42   * requires authentication, Karma provides for a mechanism whereby an d<code>authenticators.xml</code>, located in the
43   * Karma configuration directory stores a username. Depending on the specific implementation of
44   * <code>VersionControlSystem</code>, password info is then retrieved.
45   *
46   * <p>The <code>Authenticator</code> class reads the <code>authenticators.xml</code> from the Karma configuration
47   * directory and can then map <code>VersionControlSystem</code> instances by checking if an <code>authenticator</code>
48   * element is present for the location. This mapping is done by checking if the <code>id</code> attributes for the
49   * <code>location</code> and the <code>authenticator</code> are the same.
50   *
51   * @author D.A. Smedes
52   * @version $Id: Authenticators.java,v 1.6 2004/11/03 20:53:19 asmedes Exp $
53   */
54  public final class Authenticators {
55  
56    private static Log logger = LogFactory.getLog(Authenticators.class);
57  
58    /***
59     * Singleton, helper.
60     */
61    private Authenticators() {}
62  
63    public static synchronized void changePassword(AuthenticatorKey key, String newPassword) throws AuthenticationException {
64  
65      if (key == null) {
66        throw new IllegalArgumentException("Authenticator key cannot be null.");
67      }
68  
69      Map authenticators = getAuthenticators();
70  
71      Authenticator authenticator = ((Authenticator) authenticators.get(key));
72  
73      if (authenticator == null) {
74        throw new AuthenticationException(AuthenticationException.AUTHENTICATOR_NOT_FOUND, new Object[]{key});
75      }
76  
77      authenticator.setPassword(PasswordScrambler.scramble(newPassword));
78  
79      try {
80        flush(authenticators);
81      } catch (IOException e) {
82        throw new AuthenticationException(e, AuthenticationException.AUTHENTICATOR_WRITE_ERROR);
83      }
84    }
85  
86    public static Authenticator getAuthenticator(Authenticator authenticator) throws AuthenticationException {
87      return getAuthenticator(authenticator.getAuthenticatorKey());
88    }
89  
90    /***
91     * Retrieves an <code>Authenticator</code> entry from the <code>authenticators.xml</code> file.
92     *
93     * @param key                      The key by which the <code>Authenticator</code> will be located.
94     * @return                         The <code>Authenticator</code> with key <code>key</code>.
95     * @throws AuthenticationException When no <code>Authenticator</code> can be found with key <code>key</code>.
96     */
97    public static Authenticator getAuthenticator(AuthenticatorKey key) throws AuthenticationException {
98  
99      if (key == null) {
100       throw new IllegalArgumentException("Authenticator key cannot be null.");
101     }
102 
103     Authenticator authenticator = ((Authenticator) getAuthenticators().get(key));
104 
105     if (authenticator == null) {
106       throw new AuthenticationException(AuthenticationException.AUTHENTICATOR_NOT_FOUND, new Object[]{key});
107     }
108 
109     return authenticator;
110   }
111 
112   private static Map authenticatorCache = null;
113   private static long lastModified = 0l;
114 
115   private synchronized static Map getAuthenticators() throws AuthenticationException {
116 
117     File authenticatorsFile = new File(WorkingContext.getConfigurationBaseDir(), "authenticators.xml");
118 
119     if (!authenticatorsFile.exists()) {
120       createNew();
121     }
122 
123     if (authenticatorCache == null) {
124       authenticatorCache = new Hashtable();
125       logger.debug("Creating authenticator cache from `authenticators.xml`.");
126     } else {
127 
128       // Check if the file has been changed outside the running Karma.
129       //
130       if (authenticatorsFile.lastModified() == lastModified) {
131         logger.debug("Using authenticator cache.");
132         return authenticatorCache;
133       } else {
134         authenticatorCache = new Hashtable();
135         logger.debug("Recreating authenticator cache from `authenticators.xml`.");
136       }
137     }
138 
139     // Create a list of authenticators, anything you can find.
140     //
141     Digester digester = getDigester();
142 
143     List subList = null;
144     try {
145       subList = (List) digester.parse(authenticatorsFile.getPath());
146     } catch (IOException e) {
147       throw new AuthenticationException(e, AuthenticationException.MISSING_AUTHENTICATOR_CONFIGURATION);
148     } catch (SAXException e) {
149       throw new AuthenticationException(e, AuthenticationException.AUTHENTICATOR_LOAD_ERROR);
150     }
151 
152     for (Iterator j = subList.iterator(); j.hasNext();) {
153       Authenticator authDescriptor = (Authenticator) j.next();
154       AuthenticatorKey key = new AuthenticatorKey(authDescriptor.getWorkingContext(), authDescriptor.getId());
155       if (authenticatorCache.containsKey(key)) {
156         throw new AuthenticationException(
157             AuthenticationException.DUPLICATE_AUTHENTICATOR_KEY,
158             new Object[] {key, WorkingContext.getConfigurationBaseDir().getPath()}
159         );
160       }
161       authenticatorCache.put(key, authDescriptor);
162     }
163 
164     // Maintain the lastModified time.
165     //
166     lastModified = authenticatorsFile.lastModified();
167 
168     return authenticatorCache;
169   }
170 
171   /***
172    * Adds an authenticator to <code>authenticators.xml</code> if the authenticator does not yet exist.
173    *
174    * @param authenticator
175    */
176   public static synchronized void addAuthenticator(Authenticator authenticator) throws AuthenticationException {
177 
178     if (authenticator == null) {
179       throw new IllegalArgumentException("Authenticator cannot be null.");
180     }
181 
182     // Add the authenticator.
183     //
184     getAuthenticators().put(authenticator.getAuthenticatorKey(), authenticator);
185 
186     try {
187       flush(getAuthenticators());
188     } catch (IOException e) {
189       throw new KarmaRuntimeException(e);
190     }
191   }
192 
193   /***
194    * Deletes an authenticator entry from <code>authenticators.xml</code>.
195    *
196    * @param authenticator
197    */
198   public static synchronized void deleteAuthenticator(Authenticator authenticator) throws AuthenticationException {
199 
200     // Add the authenticator.
201     //
202     getAuthenticators().remove(new AuthenticatorKey(authenticator.getWorkingContext(), authenticator.getWorkingContext()));
203 
204     try {
205       flush(getAuthenticators());
206     } catch (IOException e) {
207       throw new KarmaRuntimeException(e);
208     }
209   }
210 
211   private static void createNew() throws AuthenticationException {
212     try {
213       flush(new HashMap());
214     } catch (IOException e) {
215       throw new AuthenticationException(e, AuthenticationException.MISSING_AUTHENTICATOR_CONFIGURATION);
216     }
217   }
218 
219   private static synchronized void flush(Map authenticators) throws IOException {
220 
221     StringBuffer buffer = new StringBuffer();
222 
223     buffer.append("<?xml version=\"1.0\"?>\n\n");
224     buffer.append("<authenticators>\n");
225 
226     for (Iterator i = authenticators.values().iterator(); i.hasNext();) {
227       MessageFormat formatter = null;
228 
229       Authenticator a = (Authenticator) i.next();
230       if (a.getPassword() != null) {
231         formatter = new MessageFormat("  <authenticator working-context=\"{0}\" id=\"{1}\" username=\"{2}\" password=\"{3}\"/>\n");
232 
233         // todo I guess there is some default toolie available for this (XML escaper).
234 
235         String password = a.getPassword();
236         password = password.replaceAll("&", "&amp;");
237         password = password.replaceAll("<", "&lt;");
238         password = password.replaceAll(">", "&gt;");
239         password = password.replaceAll("\"", "&quot;");
240         password = password.replaceAll("'", "&apos;");
241 
242         buffer.append(formatter.format(new String[]{a.getWorkingContext(), a.getId(), a.getUsername(), password}));
243       } else {
244         formatter = new MessageFormat("  <authenticator working-context=\"{0}\" id=\"{1}\" username=\"{2}\"/>\n");
245         buffer.append(formatter.format(new String[]{a.getWorkingContext(), a.getId(), a.getUsername()}));
246       }
247     }
248 
249     buffer.append("</authenticators>\n");
250 
251     FileWriter writer = null;
252     // Write the manifest to the manifest store.
253     //
254     writer = new FileWriter(new File(WorkingContext.getConfigurationBaseDir(), "authenticators.xml"));
255     try {
256 
257       writer.write(buffer.toString());
258       writer.flush();
259 
260     } finally {
261       writer.close();
262     }
263   }
264 
265   private static Digester getDigester() {
266 
267     Digester digester = new Digester();
268 
269     digester.addObjectCreate("authenticators", ArrayList.class);
270     digester.addObjectCreate("authenticators/authenticator", Authenticator.class);
271     digester.addSetProperties("authenticators/authenticator");
272     digester.addSetProperties("authenticators/authenticator", new String[]{"working-context"}, new String[]{"workingContext"});
273     digester.addSetNext("authenticators/authenticator", "add");
274     return digester;
275   }
276 }