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
    /**
187
     * Get all users. This is two-dimmention array. Each row is a user. The first element of
188
     * a row is the user name.
189
     */
190
    public String[][] getUsers(String user, String password)
191
                    throws ConnectException {
192
        List<Object> users = userpassword.getList(USERS+SLASH+USER+SLASH+AT+NAME);
193
        if(users != null && users.size() > 0) {
194
            String[][] usersArray = new String[users.size()][1];
195
            for(int i=0; i<users.size(); i++) {
196
                usersArray[i][0] = (String) users.get(i);
197
            }
198
            return usersArray;
199
        }
200
        return null;
201
    }
202
    
203
    @Override
204
    public String[] getUserInfo(String user, String password)
205
                    throws ConnectException {
206
        // TODO Auto-generated method stub
207
        return null;
208
    }
209
    
210
    
211
    @Override
212
    /**
213
     * Get the users for a particular group from the authentication service
214
     * The null will return if there is no user.
215
     * @param user
216
     *            the user for authenticating against the service
217
     * @param password
218
     *            the password for authenticating against the service
219
     * @param group
220
     *            the group whose user list should be returned
221
     * @returns string array of the user names belonging to the group
222
     */
223
    public String[] getUsers(String user, String password, String group)
224
                    throws ConnectException {
225
        List<Object> users = userpassword.getList(USERS+SLASH+USER+"["+GROUP+"='"+group+"']"+SLASH+AT+NAME);
226
        if(users != null && users.size() > 0) {
227
            String[] usersArray = new String[users.size()];
228
            for(int i=0; i<users.size(); i++) {
229
                usersArray[i] = (String) users.get(i);
230
            }
231
            return usersArray;
232
        }
233
        return null;
234
    }
235
    
236
    @Override
237
    /**
238
     * Get all groups from the authentication service. It returns a two dimmension array. Each row is a
239
     * group. The first column is the group name. The null will return if no group found.
240
     */
241
    public String[][] getGroups(String user, String password)
242
                    throws ConnectException {
243
        List<Object> groups = userpassword.getList(GROUPS+SLASH+GROUP+SLASH+AT+NAME);
244
        if(groups!= null && groups.size() >0) {
245
            String[][] groupsArray = new String[groups.size()][1];
246
            for(int i=0; i<groups.size(); i++) {
247
                groupsArray[i][0] = (String) groups.get(i);
248
            }
249
            return groupsArray;
250
        }
251
        return null;
252
    }
253
    
254
    @Override
255
    /**
256
     * Get groups from a specified user. It returns two dimmension array. Each row is a
257
     * group. The first column is the group name. The null will return if no group found.
258
     */
259
    public String[][] getGroups(String user, String password, String foruser)
260
                    throws ConnectException {
261
        List<Object> groups = userpassword.getList(USERS+SLASH+USER+"["+AT+NAME+"='"+foruser+"']"+SLASH+GROUP);
262
        if(groups != null && groups.size() > 0) {
263
            String[][] groupsArray = new String[groups.size()][1];
264
            for(int i=0; i<groups.size(); i++) {
265
                groupsArray[i][0] = (String) groups.get(i);
266
            }
267
            return groupsArray;
268
        }
269
        return null;
270
    }
271
    
272
    @Override
273
    public HashMap<String, Vector<String>> getAttributes(String foruser)
274
                    throws ConnectException {
275
        // TODO Auto-generated method stub
276
        return null;
277
    }
278
    
279
    @Override
280
    public HashMap<String, Vector<String>> getAttributes(String user,
281
                    String password, String foruser) throws ConnectException {
282
        // TODO Auto-generated method stub
283
        return null;
284
    }
285
    
286
    @Override
287
    public String getPrincipals(String user, String password)
288
                    throws ConnectException {
289
        // TODO Auto-generated method stub
290
        return null;
291
    }
292
    
293
    /**
294
     * Add a user to the file
295
     * @param userName the name of the user
296
     * @param groups  the groups the user belong to. The group should exist in the file
297
     * @param password  the password of the user
298
     */
299
    public void addUser(String userName, String[] groups, String password) throws AuthenticationException{
300
        if(userName == null || userName.trim().equals("")) {
301
            throw new AuthenticationException("AuthFile.addUser - can't add a user whose name is null or blank.");
302
        }
303
        if(password == null || password.trim().equals("")) {
304
            throw new AuthenticationException("AuthFile.addUser - can't add a user whose password is null or blank.");
305
        }
306
        try {
307
            password = encrypt(password);
308
        } catch (Exception e) {
309
            throw new AuthenticationException("AuthFile.addUser - can't encript the password since "+e.getMessage());
310
        }
311
        
312
        if(!userExists(userName)) {
313
            if(userpassword != null) {
314
              userpassword.addProperty(USERS+" "+USER+AT+NAME, userName);
315
              userpassword.addProperty(USERS+SLASH+USER+"["+AT+NAME+"='"+userName+"']"+" "+PASSWORD, password);
316
              if(groups != null) {
317
                  for(int i=0; i<groups.length; i++) {
318
                      String group = groups[i];
319
                      if(group != null && !group.trim().equals("")) {
320
                          if(groupExists(group)) {
321
                              userpassword.addProperty(USERS+SLASH+USER+"["+AT+NAME+"='"+userName+"']"+" "+GROUP, group);
322
                          }
323
                      }
324
                  }
325
              }
326
              userpassword.reload();
327
             }
328
        } else {
329
            throw new AuthenticationException("AuthFile.addUser - can't add the user "+userName+" since it already exists.");
330
        }
331
    }
332
    
333
    /**
334
     * Add a group into the file
335
     * @param groupName the name of group
336
     */
337
    public void addGroup(String groupName) throws AuthenticationException{
338
        if(groupName == null || groupName.trim().equals("")) {
339
            throw new AuthenticationException("AuthFile.addGroup - can't add a group whose name is null or blank.");
340
        }
341
        if(!groupExists(groupName)) {
342
            if(userpassword != null) {
343
              userpassword.addProperty(GROUPS+" "+GROUP+AT+NAME, groupName);
344
              userpassword.reload();
345
             }
346
        } else {
347
            throw new AuthenticationException("AuthFile.addGroup - can't add the group "+groupName+" since it already exists.");
348
        }
349
    }
350
    
351
    /**
352
     * Reset the password for the user
353
     * @param userName  the name of the user. The user should already exist
354
     * @param password  the password of the user.
355
     * @return
356
     */
357
    public String modifyUserPassword(String userName, String password) {
358
        return password;
359
    }
360
    
361
    /**
362
     * Add a user to a group
363
     * @param userName  the name of the user. the user should already exist
364
     * @param group  the name of the group. the group should already exist
365
     */
366
    public void addUserToGroup(String userName, String group) {
367
        
368
    }
369
    
370
    /**
371
     * Remove a user from a group.
372
     * @param userName  the name of the user. the user should already exist.
373
     * @param group the name of the group
374
     */
375
    public void removeUserFromGroup(String userName, String group) {
376
        
377
    }
378
    
379
    /**
380
     * If the specified user name exist or not
381
     * @param userName the name of the user
382
     * @return true if the user eixsit
383
     */
384
    private boolean userExists(String userName) throws AuthenticationException{
385
        if(userName == null || userName.trim().equals("")) {
386
            throw new AuthenticationException("AuthFile.userExist - can't judge if a user exists when its name is null or blank.");
387
        }
388
        List<Object> users = userpassword.getList(USERS+SLASH+USER+SLASH+AT+NAME);
389
        if(users != null && users.contains(userName)) {
390
            return true;
391
        } else {
392
            return false;
393
        }
394
    }
395
    
396
    /**
397
     * If the specified group exist or not
398
     * @param groupName the name of the group
399
     * @return true if the user exists
400
     */
401
    private boolean groupExists(String groupName) throws AuthenticationException{
402
        if(groupName == null || groupName.trim().equals("")) {
403
            throw new AuthenticationException("AuthFile.groupExist - can't judge if a group exists when its name is null or blank.");
404
        }
405
        List<Object> groups = userpassword.getList(GROUPS+SLASH+GROUP+SLASH+AT+NAME);
406
        if(groups != null && groups.contains(groupName)) {
407
            return true;
408
        } else {
409
            return false;
410
        }
411
    }
412
    
413
    /*
414
     * Encrypt a string
415
     */
416
    private static String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException {
417
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
418
        //System.out.println("===================== tha master password "+masterPass);
419
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(masterPass));
420
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
421
        pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
422
        return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8")));
423
    }
424

    
425
    /*
426
     * Transform a byte array to a string
427
     */
428
    private static String base64Encode(byte[] bytes) {
429
        return Base64.encodeBase64String(bytes);
430
    }
431

    
432
    /*
433
     * Decrypt a string
434
     */
435
    private static String decrypt(String property) throws GeneralSecurityException, IOException {
436
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
437
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(masterPass));
438
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
439
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
440
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
441
    }
442

    
443
    /*
444
     * Transform a string to a byte array
445
     */
446
    private static byte[] base64Decode(String property) throws IOException {
447
        return Base64.decodeBase64(property);
448
    }
449

    
450
}
(1-1/2)