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.Random;
33
import java.util.Vector;
34

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

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

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

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

    
459
    /*
460
     * Transform a byte array to a string
461
     */
462
    private static String base64Encode(byte[] bytes) {
463
        return Base64.encodeBase64String(bytes);
464
    }
465

    
466
    /*
467
     * Decrypt a string
468
     */
469
    private static String decrypt(String property) throws GeneralSecurityException, IOException {
470
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
471
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(masterPass));
472
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
473
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
474
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
475
    }
476

    
477
    /*
478
     * Transform a string to a byte array
479
     */
480
    private static byte[] base64Decode(String property) throws IOException {
481
        return Base64.decodeBase64(property);
482
    }
483
    
484
    /**
485
     * A internal class to generate random passowrd
486
     * @author tao
487
     *
488
     */
489
    static class RandomPasswordGenerator {
490
        private static final String ALPHA_CAPS  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
491
        private static final String ALPHA   = "abcdefghijklmnopqrstuvwxyz";
492
        private static final String NUM     = "0123456789";
493
        private static final String SPL_CHARS   = "!$^_-/";
494
     
495
        public static char[] generatePswd(int minLen, int maxLen, int noOfCAPSAlpha,
496
                int noOfDigits, int noOfSplChars) {
497
            if(minLen > maxLen)
498
                throw new IllegalArgumentException("Min. Length > Max. Length!");
499
            if( (noOfCAPSAlpha + noOfDigits + noOfSplChars) > minLen )
500
                throw new IllegalArgumentException
501
                ("Min. Length should be atleast sum of (CAPS, DIGITS, SPL CHARS) Length!");
502
            Random rnd = new Random();
503
            int len = rnd.nextInt(maxLen - minLen + 1) + minLen;
504
            char[] pswd = new char[len];
505
            int index = 0;
506
            for (int i = 0; i < noOfCAPSAlpha; i++) {
507
                index = getNextIndex(rnd, len, pswd);
508
                pswd[index] = ALPHA_CAPS.charAt(rnd.nextInt(ALPHA_CAPS.length()));
509
            }
510
            for (int i = 0; i < noOfDigits; i++) {
511
                index = getNextIndex(rnd, len, pswd);
512
                pswd[index] = NUM.charAt(rnd.nextInt(NUM.length()));
513
            }
514
            for (int i = 0; i < noOfSplChars; i++) {
515
                index = getNextIndex(rnd, len, pswd);
516
                pswd[index] = SPL_CHARS.charAt(rnd.nextInt(SPL_CHARS.length()));
517
            }
518
            for(int i = 0; i < len; i++) {
519
                if(pswd[i] == 0) {
520
                    pswd[i] = ALPHA.charAt(rnd.nextInt(ALPHA.length()));
521
                }
522
            }
523
            return pswd;
524
        }
525
     
526
        private static int getNextIndex(Random rnd, int len, char[] pswd) {
527
            int index = rnd.nextInt(len);
528
            while(pswd[index = rnd.nextInt(len)] != 0);
529
            return index;
530
        }
531
    }
532

    
533
}
(1-1/2)