1/*
2 * Copyright 2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.mockftpserver.fake;
17
18import org.mockftpserver.core.util.Assert;
19import org.mockftpserver.fake.filesystem.FileSystemEntry;
20import org.mockftpserver.fake.filesystem.Permissions;
21
22import java.util.List;
23
24/**
25 * Represents a single user account on the server, including the username, password, home
26 * directory, list of groups to which this user belongs, and default permissions applied to
27 * newly-created files and directories.
28 * <p/>
29 * The <code>username</code> and <code>homeDirectory</code> property must be non-null
30 * and non-empty. The <code>homeDirectory</code> property must also match the name of an existing
31 * directory within the file system configured for the <code>FakeFtpServer</code>.
32 * <p/>
33 * The group name applied to newly created files/directories is determined by the <code>groups</code> property.
34 * If null or empty, then the default group name ("users") is used. Otherwise, the first value in the
35 * <code>groups</code> List is used. The <code>groups</code> property defaults to an empty List.
36 * <p/>
37 * The default value for <code>defaultPermissionsForNewFile</code> is read and write permissions for
38 * all (user/group/world). The default value for <code>defaultPermissionsForNewDirectory</code> is read,
39 * write and execute permissions for all (user/group/world).
40 * <p/>
41 * The <code>isValidPassword()</code> method returns true if the specified password matches
42 * the password value configured for this user account. This implementation uses the
43 * <code>isEquals()</code> method to compare passwords.
44 * <p/>
45 * If you want to provide a custom comparison, for instance using encrypted passwords, you can
46 * subclass this class and override the <code>comparePassword()</code> method to provide your own
47 * custom implementation.
48 * <p/>
49 * If the <code>passwordCheckedDuringValidation</code> property is set to false, then the password
50 * value is ignored, and the <code>isValidPassword()</code> method just returns <code<true</code>.
51 * <p/>
52 * The <code>accountRequiredForLogin</code> property defaults to false. If it is set to true, then
53 * it is expected that the login for this account will require an ACCOUNT (ACCT) command after the
54 * PASSWORD (PASS) command is completed.
55 */
56public class UserAccount {
57
58    public static final String DEFAULT_USER = "system";
59    public static final String DEFAULT_GROUP = "users";
60    public static final Permissions DEFAULT_PERMISSIONS_FOR_NEW_FILE = new Permissions("rw-rw-rw-");
61    public static final Permissions DEFAULT_PERMISSIONS_FOR_NEW_DIRECTORY = Permissions.ALL;
62
63    private String username;
64    private String password;
65    private String homeDirectory;
66    private List groups;
67    private boolean passwordRequiredForLogin = true;
68    private boolean passwordCheckedDuringValidation = true;
69    private boolean accountRequiredForLogin = false;
70    private Permissions defaultPermissionsForNewFile = DEFAULT_PERMISSIONS_FOR_NEW_FILE;
71    private Permissions defaultPermissionsForNewDirectory = DEFAULT_PERMISSIONS_FOR_NEW_DIRECTORY;
72
73
74    /**
75     * Construct a new uninitialized instance.
76     */
77    public UserAccount() {
78    }
79
80    /**
81     * Construct a new initialized instance.
82     *
83     * @param username      - the user name
84     * @param password      - the password
85     * @param homeDirectory - the home directory
86     */
87    public UserAccount(String username, String password, String homeDirectory) {
88        setUsername(username);
89        setPassword(password);
90        setHomeDirectory(homeDirectory);
91    }
92
93    public String getUsername() {
94        return username;
95    }
96
97    public void setUsername(String username) {
98        this.username = username;
99    }
100
101    public String getPassword() {
102        return password;
103    }
104
105    public void setPassword(String password) {
106        this.password = password;
107    }
108
109    public String getHomeDirectory() {
110        return homeDirectory;
111    }
112
113    public void setHomeDirectory(String homeDirectory) {
114        this.homeDirectory = homeDirectory;
115    }
116
117    public List getGroups() {
118        return groups;
119    }
120
121    public void setGroups(List groups) {
122        this.groups = groups;
123    }
124
125    public boolean isPasswordRequiredForLogin() {
126        return passwordRequiredForLogin;
127    }
128
129    public void setPasswordRequiredForLogin(boolean passwordRequiredForLogin) {
130        this.passwordRequiredForLogin = passwordRequiredForLogin;
131    }
132
133    public boolean isPasswordCheckedDuringValidation() {
134        return passwordCheckedDuringValidation;
135    }
136
137    public void setPasswordCheckedDuringValidation(boolean passwordCheckedDuringValidation) {
138        this.passwordCheckedDuringValidation = passwordCheckedDuringValidation;
139    }
140
141    public boolean isAccountRequiredForLogin() {
142        return accountRequiredForLogin;
143    }
144
145    public void setAccountRequiredForLogin(boolean accountRequiredForLogin) {
146        this.accountRequiredForLogin = accountRequiredForLogin;
147    }
148
149    public Permissions getDefaultPermissionsForNewFile() {
150        return defaultPermissionsForNewFile;
151    }
152
153    public void setDefaultPermissionsForNewFile(Permissions defaultPermissionsForNewFile) {
154        this.defaultPermissionsForNewFile = defaultPermissionsForNewFile;
155    }
156
157    public Permissions getDefaultPermissionsForNewDirectory() {
158        return defaultPermissionsForNewDirectory;
159    }
160
161    public void setDefaultPermissionsForNewDirectory(Permissions defaultPermissionsForNewDirectory) {
162        this.defaultPermissionsForNewDirectory = defaultPermissionsForNewDirectory;
163    }
164
165    /**
166     * Return the name of the primary group to which this user belongs. If this account has no associated
167     * groups set, then this method returns the <code>DEFAULT_GROUP</code>. Otherwise, this method
168     * returns the first group name in the <code>groups</code> list.
169     *
170     * @return the name of the primary group for this user
171     */
172    public String getPrimaryGroup() {
173        return (groups == null || groups.isEmpty()) ? DEFAULT_GROUP : (String) groups.get(0);
174    }
175
176    /**
177     * Return true if the specified password is the correct, valid password for this user account.
178     * This implementation uses standard (case-sensitive) String comparison. Subclasses can provide
179     * custom comparison behavior, for instance using encrypted password values, by overriding this
180     * method.
181     *
182     * @param password - the password to compare against the configured value
183     * @return true if the password is correct and valid
184     * @throws org.mockftpserver.core.util.AssertFailedException
185     *          - if the username property is null
186     */
187    public boolean isValidPassword(String password) {
188        Assert.notNullOrEmpty(username, "username");
189        return !passwordCheckedDuringValidation || comparePassword(password);
190    }
191
192    /**
193     * @return true if this UserAccount object is valid; i.e. if the homeDirectory is non-null and non-empty.
194     */
195    public boolean isValid() {
196        return homeDirectory != null && homeDirectory.length() > 0;
197    }
198
199    /**
200     * @return the String representation of this object
201     */
202    public String toString() {
203        return "UserAccount[username=" + username + "; password=" + password + "; homeDirectory="
204                + homeDirectory + "; passwordRequiredForLogin=" + passwordRequiredForLogin + "]";
205    }
206
207    /**
208     * Return true if this user has read access to the file/directory represented by the specified FileSystemEntry object.
209     *
210     * @param entry - the FileSystemEntry representing the file or directory
211     * @return true if this use has read access
212     */
213    public boolean canRead(FileSystemEntry entry) {
214        Permissions permissions = entry.getPermissions();
215        if (permissions == null) {
216            return true;
217        }
218
219        if (equalOrBothNull(username, entry.getOwner())) {
220            return permissions.canUserRead();
221        }
222        if (groups != null && groups.contains(entry.getGroup())) {
223            return permissions.canGroupRead();
224        }
225        return permissions.canWorldRead();
226    }
227
228    /**
229     * Return true if this user has write access to the file/directory represented by the specified FileSystemEntry object.
230     *
231     * @param entry - the FileSystemEntry representing the file or directory
232     * @return true if this use has write access
233     */
234    public boolean canWrite(FileSystemEntry entry) {
235        Permissions permissions = entry.getPermissions();
236        if (permissions == null) {
237            return true;
238        }
239
240        if (equalOrBothNull(username, entry.getOwner())) {
241            return permissions.canUserWrite();
242        }
243        if (groups != null && groups.contains(entry.getGroup())) {
244            return permissions.canGroupWrite();
245        }
246        return permissions.canWorldWrite();
247    }
248
249    /**
250     * Return true if this user has execute access to the file/directory represented by the specified FileSystemEntry object.
251     *
252     * @param entry - the FileSystemEntry representing the file or directory
253     * @return true if this use has execute access
254     */
255    public boolean canExecute(FileSystemEntry entry) {
256        Permissions permissions = entry.getPermissions();
257        if (permissions == null) {
258            return true;
259        }
260
261        if (equalOrBothNull(username, entry.getOwner())) {
262            return permissions.canUserExecute();
263        }
264        if (groups != null && groups.contains(entry.getGroup())) {
265            return permissions.canGroupExecute();
266        }
267        return permissions.canWorldExecute();
268    }
269
270    /**
271     * Return true if the specified password matches the password configured for this user account.
272     * This implementation uses standard (case-sensitive) String comparison. Subclasses can provide
273     * custom comparison behavior, for instance using encrypted password values, by overriding this
274     * method.
275     *
276     * @param password - the password to compare against the configured value
277     * @return true if the passwords match
278     */
279    protected boolean comparePassword(String password) {
280        return password != null && password.equals(this.password);
281    }
282
283    /**
284     * Return true only if both Strings are null or they are equal (have the same contents).
285     *
286     * @param string1 - the first String
287     * @param string2 - the second String
288     * @return true if both are null or both are equal
289     */
290    protected boolean equalOrBothNull(String string1, String string2) {
291        return (string1 == null && string2 == null) || (string1 != null && string1.equals(string2));
292    }
293
294}