Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2013 Regents of the University of California and the
4
 *             National Center for Ecological Analysis and Synthesis
5
 *
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
 */
21
package edu.ucsb.nceas.metacat.authentication;
22

    
23
import java.io.File;
24
import java.io.FileOutputStream;
25
import java.io.IOException;
26
import java.io.OutputStreamWriter;
27
import java.io.UnsupportedEncodingException;
28
import java.net.ConnectException;
29
import java.security.GeneralSecurityException;
30
import java.util.HashMap;
31
import java.util.List;
32
import java.util.Vector;
33

    
34
import javax.crypto.Cipher;
35
import javax.crypto.SecretKey;
36
import javax.crypto.SecretKeyFactory;
37
import javax.crypto.spec.PBEKeySpec;
38
import javax.crypto.spec.PBEParameterSpec;
39

    
40
import org.apache.commons.codec.binary.Base64;
41
import org.apache.commons.configuration.ConfigurationException;
42
import org.apache.commons.configuration.XMLConfiguration;
43
import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
44
import org.apache.commons.logging.Log;
45
import org.apache.commons.logging.LogFactory;
46

    
47
import edu.ucsb.nceas.metacat.AuthInterface;
48
import edu.ucsb.nceas.metacat.properties.PropertyService;
49
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
50

    
51
/**
52
 * This an authentication class base on a username/password file.
53
 * It is an alternative authentication mechanism of the ldap authentication.
54
 * This is a singleton class and the password file looks like:
55
 *<?xml version="1.0" encoding="UTF-8" ?>
56
 * <subjects>
57
 *  <users>
58
 *      <user name="uid=tao,o=NCEAS,dc=ecoinformatics,dc=org">
59
 *          <password>*******</password>
60
 *          <group>nceas-dev</group>
61
 *      </user>
62
 *  </users>
63
 *  <groups>
64
 *    <group name="nceas-dev"/>
65
 *  </groups>
66
 * </subjects>
67
 * http://commons.apache.org/proper/commons-configuration/userguide/howto_xml.html
68
 * @author tao
69
 *
70
 */
71
public class AuthFile implements AuthInterface {
72
    private static final String NAME = "name";
73
    private static final String PASSWORD = "password";
74
    private static final String SLASH = "/";
75
    private static final String AT = "@";
76
    private static final String SUBJECTS = "subjects";
77
    private static final String USERS = "users";
78
    private static final String USER = "user";
79
    private static final String GROUPS = "groups";
80
    private static final String GROUP = "group";
81
    private static final String INITCONTENT = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"+
82
                                    "<"+SUBJECTS+">\n"+"<"+USERS+">\n"+"</"+USERS+">\n"+"<"+GROUPS+">\n"+"</"+GROUPS+">\n"+"</"+SUBJECTS+">\n";
83
    
84
    private static final byte[] SALT = {
85
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
86
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
87
    };
88
    private static Log log = LogFactory.getLog(AuthFile.class);
89
    private static AuthFile authFile = null;
90
    private XMLConfiguration userpassword = null;
91
    private static String passwordFilePath = null;
92
    private static  char[] masterPass = "enfldsgbnlsngdlksdsgm".toCharArray();
93
    /**
94
     * Get the instance of the AuthFile
95
     * @return
96
     * @throws AuthenticationException
97
     */
98
    public static AuthFile getInstance() throws AuthenticationException {
99
        if(authFile == null) {
100
            authFile = new AuthFile();
101
        }
102
        return authFile;
103
    }
104
    
105
    /**
106
     * Get the instance of the AuthFile from specified password file
107
     * @return
108
     * @throws AuthenticationException
109
     */
110
    public static AuthFile getInstance(String passwordFile) throws AuthenticationException {
111
        passwordFilePath = passwordFile;
112
        if(authFile == null) {
113
            authFile = new AuthFile();
114
        }
115
        return authFile;
116
    }
117
    
118
    /**
119
     * Constructor
120
     */
121
    private AuthFile() throws AuthenticationException {
122
        try {
123
            init();
124
        } catch (Exception e) {
125
            throw new AuthenticationException(e.getMessage());
126
        }
127
        
128
    }
129
    
130
    /*
131
     * Initialize the user/password configuration
132
     */
133
    private void init() throws PropertyNotFoundException, IOException, ConfigurationException {
134
        if(passwordFilePath == null) {
135
            passwordFilePath  = PropertyService.getProperty("auth.file.path");
136
        }
137
        File passwordFile = new File(passwordFilePath);
138
        try {
139
            String password = PropertyService.getProperty("auth.file.pass");
140
            if(password != null && !password.trim().equals("")) {
141
                masterPass = password.toCharArray();
142
            }
143
        }catch(PropertyNotFoundException e) {
144
            log.warn("AuthFile.init - can't find the auth.file.pass in the metacat.properties. Metacat will use the default one as password.");
145
        }
146
       
147
        //if the password file doesn't exist, create a new one and set the initial content
148
        if(!passwordFile.exists()) {
149
            passwordFile.createNewFile();
150
            OutputStreamWriter writer = null;
151
            FileOutputStream output = null;
152
            try {
153
              output = new FileOutputStream(passwordFile);
154
              writer = new OutputStreamWriter(output, "UTF-8");
155
              writer.write(INITCONTENT);
156
            } finally {
157
              writer.close();
158
              output.close();
159
            }
160
          }
161
          userpassword = new XMLConfiguration(passwordFile);
162
          userpassword.setExpressionEngine(new XPathExpressionEngine());
163
          userpassword.setAutoSave(true);
164
          userpassword.setDelimiterParsingDisabled(true);
165
          userpassword.setAttributeSplittingDisabled(true);
166
    }
167
    
168
    @Override
169
    public boolean authenticate(String user, String password)
170
                    throws AuthenticationException {
171
        String passwordRecord = userpassword.getString(USERS+SLASH+USER+"["+AT+NAME+"='"+user+"']"+SLASH+PASSWORD);
172
        if(passwordRecord != null) {
173
            try {
174
                passwordRecord = decrypt(passwordRecord);
175
            } catch (Exception e) {
176
                throw new AuthenticationException("AuthFile.authenticate - can't decrypt the password for the user "+user+" since "+e.getMessage());
177
            }
178
            if(passwordRecord.equals(password)) {
179
                return true;
180
            }
181
        }
182
        return false;
183
    }
184
    
185
    @Override
186
    public String[][] getUsers(String user, String password)
187
                    throws ConnectException {
188
        // TODO Auto-generated method stub
189
        return null;
190
    }
191
    
192
    @Override
193
    public String[] getUserInfo(String user, String password)
194
                    throws ConnectException {
195
        // TODO Auto-generated method stub
196
        return null;
197
    }
198
    
199
    @Override
200
    public String[] getUsers(String user, String password, String group)
201
                    throws ConnectException {
202
        // TODO Auto-generated method stub
203
        return null;
204
    }
205
    
206
    @Override
207
    public String[][] getGroups(String user, String password)
208
                    throws ConnectException {
209
        // TODO Auto-generated method stub
210
        return null;
211
    }
212
    
213
    @Override
214
    public String[][] getGroups(String user, String password, String foruser)
215
                    throws ConnectException {
216
        // TODO Auto-generated method stub
217
        return null;
218
    }
219
    
220
    @Override
221
    public HashMap<String, Vector<String>> getAttributes(String foruser)
222
                    throws ConnectException {
223
        // TODO Auto-generated method stub
224
        return null;
225
    }
226
    
227
    @Override
228
    public HashMap<String, Vector<String>> getAttributes(String user,
229
                    String password, String foruser) throws ConnectException {
230
        // TODO Auto-generated method stub
231
        return null;
232
    }
233
    
234
    @Override
235
    public String getPrincipals(String user, String password)
236
                    throws ConnectException {
237
        // TODO Auto-generated method stub
238
        return null;
239
    }
240
    
241
    /**
242
     * Add a user to the file
243
     * @param userName the name of the user
244
     * @param groups  the groups the user belong to. The group should exist in the file
245
     * @param password  the password of the user
246
     */
247
    public void addUser(String userName, String[] groups, String password) throws AuthenticationException{
248
        if(userName == null || userName.trim().equals("")) {
249
            throw new AuthenticationException("AuthFile.addUser - can't add a user whose name is null or blank.");
250
        }
251
        if(password == null || password.trim().equals("")) {
252
            throw new AuthenticationException("AuthFile.addUser - can't add a user whose password is null or blank.");
253
        }
254
        try {
255
            password = encrypt(password);
256
        } catch (Exception e) {
257
            throw new AuthenticationException("AuthFile.addUser - can't encript the password since "+e.getMessage());
258
        }
259
        
260
        if(!userExists(userName)) {
261
            if(userpassword != null) {
262
              userpassword.addProperty(USERS+" "+USER+AT+NAME, userName);
263
              userpassword.addProperty(USERS+SLASH+USER+"["+AT+NAME+"='"+userName+"']"+" "+PASSWORD, password);
264
              if(groups != null) {
265
                  for(int i=0; i<groups.length; i++) {
266
                      String group = groups[i];
267
                      if(group != null && !group.trim().equals("")) {
268
                          if(groupExists(group)) {
269
                              userpassword.addProperty(USERS+SLASH+USER+"["+AT+NAME+"='"+userName+"']"+" "+GROUP, group);
270
                          }
271
                      }
272
                  }
273
              }
274
              userpassword.reload();
275
             }
276
        } else {
277
            throw new AuthenticationException("AuthFile.addUser - can't add the user "+userName+" since it already exists.");
278
        }
279
    }
280
    
281
    /**
282
     * Add a group into the file
283
     * @param groupName the name of group
284
     */
285
    public void addGroup(String groupName) throws AuthenticationException{
286
        if(groupName == null || groupName.trim().equals("")) {
287
            throw new AuthenticationException("AuthFile.addGroup - can't add a group whose name is null or blank.");
288
        }
289
        if(!groupExists(groupName)) {
290
            if(userpassword != null) {
291
              userpassword.addProperty(GROUPS+" "+GROUP+AT+NAME, groupName);
292
              userpassword.reload();
293
             }
294
        } else {
295
            throw new AuthenticationException("AuthFile.addGroup - can't add the group "+groupName+" since it already exists.");
296
        }
297
    }
298
    
299
    /**
300
     * Reset the password for the user
301
     * @param userName  the name of the user. The user should already exist
302
     * @param password  the password of the user.
303
     * @return
304
     */
305
    public String modifyUserPassword(String userName, String password) {
306
        return password;
307
    }
308
    
309
    /**
310
     * Add a user to a group
311
     * @param userName  the name of the user. the user should already exist
312
     * @param group  the name of the group. the group should already exist
313
     */
314
    public void addUserToGroup(String userName, String group) {
315
        
316
    }
317
    
318
    /**
319
     * Remove a user from a group.
320
     * @param userName  the name of the user. the user should already exist.
321
     * @param group the name of the group
322
     */
323
    public void removeUserFromGroup(String userName, String group) {
324
        
325
    }
326
    
327
    /**
328
     * If the specified user name exist or not
329
     * @param userName the name of the user
330
     * @return true if the user eixsit
331
     */
332
    private boolean userExists(String userName) throws AuthenticationException{
333
        if(userName == null || userName.trim().equals("")) {
334
            throw new AuthenticationException("AuthFile.userExist - can't judge if a user exists when its name is null or blank.");
335
        }
336
        List<Object> users = userpassword.getList(USERS+SLASH+USER+SLASH+AT+NAME);
337
        if(users != null && users.contains(userName)) {
338
            return true;
339
        } else {
340
            return false;
341
        }
342
    }
343
    
344
    /**
345
     * If the specified group exist or not
346
     * @param groupName the name of the group
347
     * @return true if the user exists
348
     */
349
    private boolean groupExists(String groupName) throws AuthenticationException{
350
        if(groupName == null || groupName.trim().equals("")) {
351
            throw new AuthenticationException("AuthFile.groupExist - can't judge if a group exists when its name is null or blank.");
352
        }
353
        List<Object> groups = userpassword.getList(GROUPS+SLASH+GROUP+SLASH+AT+NAME);
354
        if(groups != null && groups.contains(groupName)) {
355
            return true;
356
        } else {
357
            return false;
358
        }
359
    }
360
    
361
    /*
362
     * Encrypt a string
363
     */
364
    private static String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException {
365
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
366
        //System.out.println("===================== tha master password "+masterPass);
367
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(masterPass));
368
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
369
        pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
370
        return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8")));
371
    }
372

    
373
    /*
374
     * Transform a byte array to a string
375
     */
376
    private static String base64Encode(byte[] bytes) {
377
        return Base64.encodeBase64String(bytes);
378
    }
379

    
380
    /*
381
     * Decrypt a string
382
     */
383
    private static String decrypt(String property) throws GeneralSecurityException, IOException {
384
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
385
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(masterPass));
386
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
387
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
388
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
389
    }
390

    
391
    /*
392
     * Transform a string to a byte array
393
     */
394
    private static byte[] base64Decode(String property) throws IOException {
395
        return Base64.decodeBase64(property);
396
    }
397

    
398
}
(1-1/2)