diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/FileAuthenticationService.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/FileAuthenticationService.java index cf0c15355da1b1a675fadecf9ffa01c1b0205a1e..28b4791bee50936cd863701e01983a7092e29bc4 100644 --- a/authentication/source/java/ch/systemsx/cisd/authentication/file/FileAuthenticationService.java +++ b/authentication/source/java/ch/systemsx/cisd/authentication/file/FileAuthenticationService.java @@ -29,7 +29,7 @@ import ch.systemsx.cisd.common.logging.LogFactory; /** * An implementation of {@link IAuthenticationService} that gets the authentication information from - * a password file. + * a password store (which is usually backed by a file). * <p> * The file contains: * <ul> @@ -51,21 +51,28 @@ public class FileAuthenticationService implements IAuthenticationService private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, FileAuthenticationService.class); - private final PasswordFile passwordFile; + private final IUserStore userStore; + + private static IUserStore createUserStore(final String passwordFileName) + { + final ILineStore lineStore = + new FileBasedLineStore(new File(passwordFileName), "Password file"); + return new LineBasedUserStore(lineStore); + } public FileAuthenticationService(final String passwordFileName) { - this.passwordFile = new PasswordFile(new File(passwordFileName)); + this(createUserStore(passwordFileName)); } - public FileAuthenticationService(final File passwordFile) + public FileAuthenticationService(IUserStore userStore) { - this.passwordFile = new PasswordFile(passwordFile); + this.userStore = userStore; } private String getToken() { - return passwordFile.getPath(); + return userStore.getId(); } /** @@ -84,7 +91,7 @@ public class FileAuthenticationService implements IAuthenticationService operationLog.warn(String.format(TOKEN_FAILURE_MSG_TEMPLATE, token, applicationToken)); return false; } - return passwordFile.isPasswordCorrect(user, password); + return userStore.isPasswordCorrect(user, password); } public Principal getPrincipal(String applicationToken, String user) @@ -95,12 +102,12 @@ public class FileAuthenticationService implements IAuthenticationService operationLog.warn(String.format(TOKEN_FAILURE_MSG_TEMPLATE, token, applicationToken)); return null; } - return passwordFile.tryGetUser(user).asPrincial(); + return userStore.tryGetUser(user).asPrincial(); } public void check() throws EnvironmentFailureException, ConfigurationFailureException { - passwordFile.check(); + userStore.check(); } } diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/FileBasedLineStore.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/FileBasedLineStore.java new file mode 100644 index 0000000000000000000000000000000000000000..70a5f680ab185f78d4040935c5f1d29ba31599ab --- /dev/null +++ b/authentication/source/java/ch/systemsx/cisd/authentication/file/FileBasedLineStore.java @@ -0,0 +1,188 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.authentication.file; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; + +/** + * An implementation of a {@line ILineStore} that is based on a file. + * + * @author Bernd Rinn + */ +final class FileBasedLineStore implements ILineStore +{ + + private static final Logger machineLog = + LogFactory.getLogger(LogCategory.MACHINE, FileBasedLineStore.class); + + private static final Logger operationLog = + LogFactory.getLogger(LogCategory.OPERATION, FileBasedLineStore.class); + + private final File file; + + private final File oldFile; + + private final File newFile; + + private final String fileDescription; + + FileBasedLineStore(File file, String fileDescription) + { + this.file = file; + this.oldFile = new File(file.getPath() + ".sv"); + this.newFile = new File(file.getPath() + ".tmp"); + this.fileDescription = fileDescription; + } + + public void check() throws ConfigurationFailureException + { + if (file.canRead() == false) + { + final String msg = + String.format(file.exists() ? "%s '%s' is not readable." + : "%s '%s' does not exist.", fileDescription, file.getAbsolutePath()); + operationLog.error(msg); + throw new ConfigurationFailureException(msg); + } + try + { + checkWritable(); + } catch (EnvironmentFailureException ex) + { + throw new ConfigurationFailureException(ex.getMessage()); + } + } + + private static void checkWritable(File file, String fileDescription) + throws EnvironmentFailureException + { + if (file.exists() == false) + { + try + { + FileUtils.touch(file); + } catch (IOException ex) + { + final String msg = + String.format("%s '%s' is not writable.", fileDescription, file + .getAbsolutePath()); + operationLog.error(msg); + throw new EnvironmentFailureException(msg); + } + } + if (file.canWrite() == false) + { + final String msg = + String.format("%s '%s' is not writable.", fileDescription, file + .getAbsolutePath()); + operationLog.error(msg); + throw new EnvironmentFailureException(msg); + } + } + + public boolean exists() + { + return file.exists(); + } + + public String getId() + { + return file.getPath(); + } + + @SuppressWarnings("unchecked") + private static List<String> primReadLines(File file) throws IOException + { + return FileUtils.readLines(file); + } + + public List<String> readLines() throws ConfigurationFailureException + { + if (file.exists() == false) + { + return new ArrayList<String>(); + } + if (file.canRead() == false) + { + final String msg = + String.format(file.exists() ? "%s '%s' cannot be read." + : "%s '%s' does not exist.", file.getAbsolutePath()); + operationLog.error(msg); + throw new ConfigurationFailureException(msg); + } + try + { + return primReadLines(file); + } catch (IOException ex) + { + final String msg = + String.format("Error when reading file '%s'.", file.getAbsolutePath()); + machineLog.error(msg, ex); + throw new EnvironmentFailureException(msg, ex); + } + } + + private static void primWriteLines(File file, List<String> lines) + { + if (file.canWrite() == false) + { + final String msg = + String.format(file.exists() ? "File '%s' cannot be written." + : "File '%s' does not exist.", file.getAbsolutePath()); + operationLog.error(msg); + throw new ConfigurationFailureException(msg); + } + try + { + FileUtils.writeLines(file, lines); + } catch (IOException ex) + { + final String msg = + String.format("Error when writing file '%s'.", file.getAbsolutePath()); + machineLog.error(msg, ex); + throw new EnvironmentFailureException(msg, ex); + } + } + + private void checkWritable() throws EnvironmentFailureException + { + checkWritable(file, fileDescription); + checkWritable(oldFile, fileDescription); + checkWritable(newFile, fileDescription); + } + + public void writeLines(List<String> lines) + { + checkWritable(); + primWriteLines(newFile, lines); + oldFile.delete(); + file.renameTo(oldFile); + newFile.renameTo(file); + } + +} diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/ILineStore.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/ILineStore.java new file mode 100644 index 0000000000000000000000000000000000000000..e4db6720e4bd90a69d81ee1698379551c4f2bc03 --- /dev/null +++ b/authentication/source/java/ch/systemsx/cisd/authentication/file/ILineStore.java @@ -0,0 +1,60 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.authentication.file; + +import java.util.List; + +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; + +/** + * An abstraction of a file that allows to store and retrieve lines. + * + * @author Bernd Rinn + */ +interface ILineStore +{ + + /** + * Returns a unique identifier for this line store. + */ + String getId(); + + /** + * Returns <code>true</code>, if this store exists. + */ + boolean exists(); + + /** + * Checks whether the store is operational. + * <p> + * Supposed to be called at program start up. + * + * @throws ConfigurationFailureException If this store is not operational. + */ + void check() throws ConfigurationFailureException; + + /** + * Returns the lines currently in this store. + */ + List<String> readLines() throws EnvironmentFailureException; + + /** + * Writes the <var>lines</var> to the store. + */ + void writeLines(List<String> lines) throws EnvironmentFailureException; +} diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/IUserStore.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/IUserStore.java new file mode 100644 index 0000000000000000000000000000000000000000..f223e76f38e272dfaa7d9b5c0b92f236af03f960 --- /dev/null +++ b/authentication/source/java/ch/systemsx/cisd/authentication/file/IUserStore.java @@ -0,0 +1,70 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.authentication.file; + +import java.util.List; + +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.utilities.ISelfTestable; + +/** + * An abstraction of a store for {@link UserEntry}s. + * + * @author Bernd Rinn + */ +interface IUserStore extends ISelfTestable +{ + /** + * Returns the unique identifier of the store. + */ + String getId(); + + /** + * Returns <code>true</code>, if the password file backing this class exists. + */ + boolean exists(); + + /** + * Returns the {@link UserEntry} of <var>user</var>, or <code>null</code>, if this user does + * not exist. + */ + UserEntry tryGetUser(String user) throws EnvironmentFailureException; + + /** + * Adds the <var>user</var> if it exists, otherwise updates (replaces) the entry with the given + * entry. + */ + void addOrUpdateUser(UserEntry user) throws EnvironmentFailureException; + + /** + * Removes the user with id <var>userId</var> if it exists. + * + * @return <code>true</code>, if the user has been removed. + */ + boolean removeUser(String userId) throws EnvironmentFailureException; + + /** + * Returns <code>true</code>, if <var>user</var> is known and has the given <var>password</var>. + */ + boolean isPasswordCorrect(String user, String password) throws EnvironmentFailureException; + + /** + * Returns a list of all users currently found in the password file. + */ + List<UserEntry> listUsers() throws EnvironmentFailureException; + +} diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/LineBasedUserStore.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/LineBasedUserStore.java new file mode 100644 index 0000000000000000000000000000000000000000..0dda072f481f404d900ea524d9f59697b93ab5bd --- /dev/null +++ b/authentication/source/java/ch/systemsx/cisd/authentication/file/LineBasedUserStore.java @@ -0,0 +1,152 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.authentication.file; + +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; + +/** + * A class to read and write {@link UserEntry}. + * + * @author Bernd Rinn + */ +final class LineBasedUserStore implements IUserStore +{ + + private final ILineStore lineStore; + + LineBasedUserStore(final ILineStore lineStore) + { + this.lineStore = lineStore; + } + + private UserEntry tryFindUserEntry(String user, List<String> passwordLines) + { + assert user != null; + assert passwordLines != null; + + for (String line : passwordLines) + { + final UserEntry entry = new UserEntry(line); + if (user.equals(entry.getUserId())) + { + return entry; + } + } + return null; + } + + public String getId() + { + return lineStore.getId(); + } + + public boolean exists() + { + return lineStore.exists(); + } + + public UserEntry tryGetUser(String user) + { + return tryFindUserEntry(user, lineStore.readLines()); + } + + public void addOrUpdateUser(UserEntry user) + { + assert user != null; + + final List<String> passwordLines = lineStore.readLines(); + boolean found = false; + for (int i = 0; i < passwordLines.size(); ++i) + { + final String line = passwordLines.get(i); + final UserEntry entry = new UserEntry(line); + if (entry.getUserId().equals(user.getUserId())) + { + passwordLines.set(i, user.asPasswordLine()); + found = true; + break; + } + } + if (found == false) + { + passwordLines.add(user.asPasswordLine()); + } + lineStore.writeLines(passwordLines); + } + + public boolean removeUser(String userId) + { + assert userId != null; + + final List<String> passwordLines = lineStore.readLines(); + boolean found = false; + for (int i = 0; i < passwordLines.size(); ++i) + { + final String line = passwordLines.get(i); + final UserEntry entry = new UserEntry(line); + if (userId.equals(entry.getUserId())) + { + passwordLines.remove(i); + found = true; + break; + } + } + if (found) + { + lineStore.writeLines(passwordLines); + } + return found; + } + + public boolean isPasswordCorrect(String user, String password) + { + assert user != null; + assert password != null; + + final UserEntry userEntryOrNull = tryFindUserEntry(user, lineStore.readLines()); + if (userEntryOrNull == null) + { + return false; + } + return userEntryOrNull.isPasswordCorrect(password); + } + + public List<UserEntry> listUsers() + { + final List<UserEntry> list = new ArrayList<UserEntry>(); + for (String line : lineStore.readLines()) + { + final UserEntry user = new UserEntry(line); + list.add(user); + } + return list; + } + + /** + * Checks whether this store is operational. + * + * @throws ConfigurationFailureException If the store is not operational. + */ + public void check() throws ConfigurationFailureException + { + lineStore.check(); + } + +} diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/PasswordEditorCommand.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/PasswordEditorCommand.java index 7437b4d667c2fba5a7c87d2c0596bcfa307aa0a0..a838c778c46b7b7f745a20e0803c1a238711398c 100644 --- a/authentication/source/java/ch/systemsx/cisd/authentication/file/PasswordEditorCommand.java +++ b/authentication/source/java/ch/systemsx/cisd/authentication/file/PasswordEditorCommand.java @@ -92,10 +92,11 @@ public class PasswordEditorCommand public static void main(String[] args) { final Parameters params = new Parameters(args); - final PasswordFile pwFile = new PasswordFile(getPasswordFile()); + final ILineStore lineStore = new FileBasedLineStore(getPasswordFile(), "Password file"); + final LineBasedUserStore pwFile = new LineBasedUserStore(lineStore); if (params.getCommand().equals(Parameters.Command.ADD) == false && pwFile.exists() == false) { - System.err.printf("File '%s' does not exist.\n", pwFile.getPath()); + System.err.printf("File '%s' does not exist.\n", pwFile.getId()); System.exit(1); } switch (params.getCommand()) diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/PasswordFile.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/PasswordFile.java deleted file mode 100644 index f51a097d34f97297d3f07870c9262bede747ce66..0000000000000000000000000000000000000000 --- a/authentication/source/java/ch/systemsx/cisd/authentication/file/PasswordFile.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2008 ETH Zuerich, CISD - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.systemsx.cisd.authentication.file; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.io.FileUtils; -import org.apache.log4j.Logger; - -import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; -import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; -import ch.systemsx.cisd.common.exceptions.UserFailureException; -import ch.systemsx.cisd.common.logging.LogCategory; -import ch.systemsx.cisd.common.logging.LogFactory; - -/** - * A class to read and write password files. - * - * @author Bernd Rinn - */ -final class PasswordFile -{ - - private static final Logger machineLog = - LogFactory.getLogger(LogCategory.MACHINE, PasswordFile.class); - - private static final Logger operationLog = - LogFactory.getLogger(LogCategory.OPERATION, PasswordFile.class); - - private final File passwordFile; - - PasswordFile(final File passwordFile) - { - this.passwordFile = passwordFile; - } - - @SuppressWarnings("unchecked") - private static List<String> primReadLines(File file) throws IOException - { - return FileUtils.readLines(file); - } - - private List<String> readPasswordLines() - { - if (passwordFile.canRead() == false) - { - final String msg = - String.format(passwordFile.exists() ? "File '%s' cannot be read." - : "File '%s' does not exist.", passwordFile.getAbsolutePath()); - operationLog.error(msg); - throw new ConfigurationFailureException(msg); - } - try - { - return primReadLines(passwordFile); - } catch (IOException ex) - { - final String msg = - String.format("Error when reading file '%s'.", passwordFile.getAbsolutePath()); - machineLog.error(msg, ex); - throw new EnvironmentFailureException(msg, ex); - } - } - - private static void writePasswordLines(File file, List<String> lines) - { - if (file.canWrite() == false) - { - final String msg = - String.format(file.exists() ? "File '%s' cannot be written." - : "File '%s' does not exist.", file.getAbsolutePath()); - operationLog.error(msg); - throw new ConfigurationFailureException(msg); - } - try - { - FileUtils.writeLines(file, lines); - } catch (IOException ex) - { - final String msg = - String.format("Error when writing file '%s'.", file.getAbsolutePath()); - machineLog.error(msg, ex); - throw new EnvironmentFailureException(msg, ex); - } - } - - private UserEntry tryFindUserEntry(String user, List<String> passwordLines) - { - assert user != null; - assert passwordLines != null; - - for (String line : passwordLines) - { - final UserEntry entry = new UserEntry(line); - if (user.equals(entry.getUserId())) - { - return entry; - } - } - return null; - } - - /** - * Returns the path of the password file. - */ - String getPath() - { - return passwordFile.getPath(); - } - - /** - * Returns <code>true</code>, if the password file backing this class exists. - */ - boolean exists() - { - return passwordFile.exists(); - } - - /** - * Returns the {@link UserEntry} of <var>user</var>, or <code>null</code>, if this user does - * not exist. - */ - UserEntry tryGetUser(String user) - { - return tryFindUserEntry(user, readPasswordLines()); - } - - /** - * Adds the <var>user</var> if it exists, otherwise updates (replaces) the entry with the given - * entry. - */ - void addOrUpdateUser(UserEntry user) - { - assert user != null; - - final File oldPasswordFile = new File(passwordFile.getPath() + ".sv"); - final File newPasswordFile = new File(passwordFile.getPath() + ".tmp"); - - checkWritable(passwordFile); - checkWritable(oldPasswordFile); - checkWritable(newPasswordFile); - - final List<String> passwordLines = - passwordFile.exists() ? readPasswordLines() : new ArrayList<String>(); - boolean found = false; - for (int i = 0; i < passwordLines.size(); ++i) - { - final String line = passwordLines.get(i); - final UserEntry entry = new UserEntry(line); - if (entry.getUserId().equals(user.getUserId())) - { - passwordLines.set(i, user.asPasswordLine()); - found = true; - break; - } - } - if (found == false) - { - passwordLines.add(user.asPasswordLine()); - } - writePasswordLines(newPasswordFile, passwordLines); - oldPasswordFile.delete(); - passwordFile.renameTo(oldPasswordFile); - newPasswordFile.renameTo(passwordFile); - } - - /** - * Removes the user with id <var>userId</var> if it exists. - * - * @return <code>true</code>, if the user has been removed. - */ - boolean removeUser(String userId) - { - assert userId != null; - - final File oldPasswordFile = new File(passwordFile.getPath() + ".sv"); - final File newPasswordFile = new File(passwordFile.getPath() + ".tmp"); - - checkWritable(passwordFile); - checkWritable(oldPasswordFile); - checkWritable(newPasswordFile); - - final List<String> passwordLines = readPasswordLines(); - boolean found = false; - for (int i = 0; i < passwordLines.size(); ++i) - { - final String line = passwordLines.get(i); - final UserEntry entry = new UserEntry(line); - if (userId.equals(entry.getUserId())) - { - passwordLines.remove(i); - found = true; - break; - } - } - if (found) - { - writePasswordLines(newPasswordFile, passwordLines); - oldPasswordFile.delete(); - passwordFile.renameTo(oldPasswordFile); - newPasswordFile.renameTo(passwordFile); - } - return found; - } - - private void checkWritable(File file) throws UserFailureException - { - if (file.exists() == false) - { - try - { - FileUtils.touch(file); - } catch (IOException ex) - { - final String msg = - String - .format("Password file '%s' is not writable.", file - .getAbsolutePath()); - operationLog.error(msg); - throw new UserFailureException(msg); - } - } - if (file.canWrite() == false) - { - final String msg = - String.format("Password file '%s' is not writable.", file.getAbsolutePath()); - operationLog.error(msg); - throw new UserFailureException(msg); - } - } - - /** - * Returns <code>true</code>, if <var>user</var> is known and has the given <var>password</var>. - */ - boolean isPasswordCorrect(String user, String password) - { - assert user != null; - assert password != null; - - final UserEntry userEntryOrNull = tryFindUserEntry(user, readPasswordLines()); - if (userEntryOrNull == null) - { - return false; - } - return userEntryOrNull.isPasswordCorrect(password); - } - - /** - * Returns a list of all users currently found in the password file. - */ - List<UserEntry> listUsers() - { - final List<UserEntry> list = new ArrayList<UserEntry>(); - for (String line : readPasswordLines()) - { - final UserEntry user = new UserEntry(line); - list.add(user); - } - return list; - } - - void check() throws EnvironmentFailureException, ConfigurationFailureException - { - if (passwordFile.canRead() == false) - { - final String msg = - String.format(passwordFile.exists() ? "Password file '%s' is not readable." - : "Password file '%s' does not exist.", passwordFile.getAbsolutePath()); - operationLog.error(msg); - throw new ConfigurationFailureException(msg); - } - } - -} diff --git a/authentication/source/java/ch/systemsx/cisd/authentication/file/UserEntry.java b/authentication/source/java/ch/systemsx/cisd/authentication/file/UserEntry.java index eed778c9f7a22e0294d0dae02678070d6a61a8eb..57afdd103f00c86089f6754ba32cd8fcd72f0fdb 100644 --- a/authentication/source/java/ch/systemsx/cisd/authentication/file/UserEntry.java +++ b/authentication/source/java/ch/systemsx/cisd/authentication/file/UserEntry.java @@ -19,6 +19,7 @@ package ch.systemsx.cisd.authentication.file; import org.apache.commons.lang.StringUtils; import ch.systemsx.cisd.authentication.Principal; +import ch.systemsx.cisd.common.utilities.AbstractHashable; import ch.systemsx.cisd.common.utilities.PasswordHasher; /** @@ -26,7 +27,7 @@ import ch.systemsx.cisd.common.utilities.PasswordHasher; * * @author Bernd Rinn */ -class UserEntry +class UserEntry extends AbstractHashable { private static final int NUMBER_OF_COLUMNS_IN_PASSWORD_FILE = 5; diff --git a/authentication/sourceTest/java/ch/systemsx/cisd/authentication/file/LineBasedUserStoreTest.java b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/file/LineBasedUserStoreTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ca83a31e3811455244828d0b63dc104aee15ab25 --- /dev/null +++ b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/file/LineBasedUserStoreTest.java @@ -0,0 +1,383 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.authentication.file; + +import static org.testng.AssertJUnit.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; + +/** + * Test cases for the {@link LineBasedUserStore}. + * + * @author Bernd Rinn + */ +public class LineBasedUserStoreTest +{ + private Mockery context; + + private ILineStore lineStore; + + private LineBasedUserStore userStore; + + @BeforeMethod + public final void setUp() + { + context = new Mockery(); + lineStore = context.mock(ILineStore.class); + userStore = new LineBasedUserStore(lineStore); + } + + @AfterMethod + public void tearDown() + { + context.assertIsSatisfied(); + } + + @Test + public void testGetId() + { + final String id = "Some Identifier"; + context.checking(new Expectations() + { + { + one(lineStore).getId(); + will(returnValue(id)); + } + }); + assertEquals(id, userStore.getId()); + context.assertIsSatisfied(); + } + + @Test + public void testStoreExists() + { + context.checking(new Expectations() + { + { + one(lineStore).exists(); + will(returnValue(true)); + } + }); + assertTrue(userStore.exists()); + context.assertIsSatisfied(); + } + + @Test + public void testStoreDoesntExist() + { + context.checking(new Expectations() + { + { + one(lineStore).exists(); + will(returnValue(false)); + } + }); + assertFalse(userStore.exists()); + context.assertIsSatisfied(); + } + + @Test + public void testCheck() + { + context.checking(new Expectations() + { + { + one(lineStore).check(); + } + }); + userStore.check(); + context.assertIsSatisfied(); + } + + @Test + public void testCheckFailed() + { + final String errorMessage = "My Message"; + context.checking(new Expectations() + { + { + one(lineStore).check(); + will(throwException(new ConfigurationFailureException(errorMessage))); + } + }); + try + { + userStore.check(); + fail("Failed to signal error condition"); + } catch (ConfigurationFailureException ex) + { + assertEquals(errorMessage, ex.getMessage()); + } + context.assertIsSatisfied(); + } + + @Test + public void testListUsers() + { + final UserEntry u1 = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry("uid2", "email2", "first2", "last2", "pwd2"); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3.asPasswordLine()); + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + } + }); + final List<UserEntry> list = userStore.listUsers(); + assertEquals(lines.size(), list.size()); + assertEquals(u1, list.get(0)); + assertEquals(u2, list.get(1)); + assertEquals(u3, list.get(2)); + context.assertIsSatisfied(); + } + + @Test + public void testTryGetUserFailedEmptyStore() + { + final List<String> lines = Collections.emptyList(); + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + } + }); + assertNull(userStore.tryGetUser("uid")); + context.assertIsSatisfied(); + } + + @Test + public void testTryGetUserFailed() + { + final UserEntry u1 = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry("uid2", "email2", "first2", "last2", "pwd2"); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3.asPasswordLine()); + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + } + }); + assertNull(userStore.tryGetUser("non-existent")); + context.assertIsSatisfied(); + } + + @Test + public void testTryGetUserSuccess() + { + final UserEntry u1 = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry("uid2", "email2", "first2", "last2", "pwd2"); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3.asPasswordLine()); + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + } + }); + assertEquals(u1, userStore.tryGetUser("uid1")); + context.assertIsSatisfied(); + } + + @Test + public void testIsPasswordCorrectSuccess() + { + final String uid2 = "uid2"; + final String pwd2 = "pwd2"; + final UserEntry u1 = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry(uid2, "email2", "first2", "last2", pwd2); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3.asPasswordLine()); + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + } + }); + assertTrue(userStore.isPasswordCorrect(uid2, pwd2)); + context.assertIsSatisfied(); + } + + @Test + public void testIsPasswordCorrectFailure() + { + final String uid2 = "uid2"; + final String pwd2 = "pwd2"; + final UserEntry u1 = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry(uid2, "email2", "first2", "last2", pwd2); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3.asPasswordLine()); + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + } + }); + assertFalse(userStore.isPasswordCorrect(uid2, pwd2.toUpperCase())); + context.assertIsSatisfied(); + } + + @Test + public void testAddFirstUser() + { + final UserEntry user = new UserEntry("uid", "email", "first", "last", "pwd"); + final String userLine = user.asPasswordLine(); + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(new ArrayList<String>())); + one(lineStore).writeLines(Collections.singletonList(userLine)); + } + }); + userStore.addOrUpdateUser(user); + context.assertIsSatisfied(); + } + + @Test + public void testAddSecondUser() + { + final UserEntry oldUser = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry newUser = new UserEntry("uid2", "email2", "first2", "last2", "pwd2"); + final String oldUserLine = oldUser.asPasswordLine(); + final String newUserLine = newUser.asPasswordLine(); + context.checking(new Expectations() + { + { + final List<String> lines = new ArrayList<String>(); + lines.add(oldUserLine); + one(lineStore).readLines(); + will(returnValue(lines)); + one(lineStore).writeLines(Arrays.asList(oldUserLine, newUserLine)); + } + }); + userStore.addOrUpdateUser(newUser); + context.assertIsSatisfied(); + } + + @Test + public void testUpdateUser() + { + final UserEntry u1 = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry("uid2", "email2", "first2", "last2", "pwd2"); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3.asPasswordLine()); + + final UserEntry u3Updated = new UserEntry("uid3", "email3U", "first3U", "last3U", "pwd3U"); + final List<String> linesUpdated = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3Updated.asPasswordLine()); + + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + one(lineStore).writeLines(linesUpdated); + } + }); + userStore.addOrUpdateUser(u3Updated); + context.assertIsSatisfied(); + } + + @Test + public void testRemoveUser() + { + final String uid1 = "uid1"; + final UserEntry u1 = new UserEntry(uid1, "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry("uid2", "email2", "first2", "last2", "pwd2"); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + new ArrayList<String>(Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3 + .asPasswordLine())); + + final List<String> linesUpdated = Arrays.asList(u2.asPasswordLine(), u3.asPasswordLine()); + + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + one(lineStore).writeLines(linesUpdated); + } + }); + userStore.removeUser(uid1); + context.assertIsSatisfied(); + } + + @Test + public void testRemoveNonExistingUser() + { + final UserEntry u1 = new UserEntry("uid1", "email1", "first1", "last1", "pwd1"); + final UserEntry u2 = new UserEntry("uid2", "email2", "first2", "last2", "pwd2"); + final UserEntry u3 = new UserEntry("uid3", "email3", "first3", "last3", "pwd3"); + final List<String> lines = + Arrays.asList(u1.asPasswordLine(), u2.asPasswordLine(), u3.asPasswordLine()); + + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + } + }); + userStore.removeUser("non-existing"); + context.assertIsSatisfied(); + } + + @Test + public void testRemoveLastUser() + { + final String uid1 = "uid1"; + final UserEntry u1 = new UserEntry(uid1, "email1", "first1", "last1", "pwd1"); + final List<String> lines = new ArrayList<String>(Arrays.asList(u1.asPasswordLine())); + + context.checking(new Expectations() + { + { + one(lineStore).readLines(); + will(returnValue(lines)); + one(lineStore).writeLines(Collections.<String> emptyList()); + } + }); + userStore.removeUser(uid1); + context.assertIsSatisfied(); + } +} diff --git a/authentication/sourceTest/java/ch/systemsx/cisd/authentication/file/UserEntryTest.java b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/file/UserEntryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d7e4571b920e1496622050580288f5071b0d3a1c --- /dev/null +++ b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/file/UserEntryTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.authentication.file; + +import org.testng.annotations.Test; + +import ch.systemsx.cisd.authentication.Principal; +import ch.systemsx.cisd.common.utilities.PasswordHasher; + +import static org.testng.AssertJUnit.*; + +/** + * Test cases for {@link UserEntry}. + * + * @author Bernd Rinn + */ +public class UserEntryTest +{ + private static final String PASSWORD = "passw0rd"; + + private static final String PASSWORD_HASH = PasswordHasher.computeSaltedHash(PASSWORD); + + private static final String PASSWORD_LINE = "id:email@dot.org:First:Last:" + PASSWORD_HASH; + + @Test + public void testRoundtrip() + { + final String line = PASSWORD_LINE; + final UserEntry entry = new UserEntry(line); + assertEquals(line, entry.asPasswordLine()); + } + + @Test + public void testGetters() + { + final UserEntry entry = new UserEntry(PASSWORD_LINE); + assertEquals("id", entry.getUserId()); + assertEquals("First", entry.getFirstName()); + assertEquals("Last", entry.getLastName()); + assertEquals("email@dot.org", entry.getEmail()); + assertTrue(entry.isPasswordCorrect(PASSWORD)); + assertFalse(entry.isPasswordCorrect(PASSWORD.replace('0', 'o'))); + } + + @Test + public void testConstructor() + { + final String id = "ID"; + final String firstName = "first"; + final String lastName = "laST"; + final String email = "a@b.edu"; + final UserEntry entry = new UserEntry(id, email, firstName, lastName, PASSWORD); + assertEquals(id, entry.getUserId()); + assertEquals(firstName, entry.getFirstName()); + assertEquals(lastName, entry.getLastName()); + assertEquals(email, entry.getEmail()); + assertTrue(entry.isPasswordCorrect(PASSWORD)); + assertFalse(entry.isPasswordCorrect(PASSWORD.replace('0', 'o'))); + } + + @Test + public void testSetters() + { + final String id = "id"; + final String firstName = "first1"; + final String lastName = "laST2"; + final String email = "a@b.edu"; + final UserEntry entry = new UserEntry(PASSWORD_LINE); + assertEquals(id, entry.getUserId()); + entry.setFirstName(firstName); + assertEquals(firstName, entry.getFirstName()); + entry.setLastName(lastName); + assertEquals(lastName, entry.getLastName()); + entry.setEmail(email); + assertEquals(email, entry.getEmail()); + entry.setPassword(PASSWORD.replace('0', 'o')); + assertFalse(entry.isPasswordCorrect(PASSWORD)); + assertTrue(entry.isPasswordCorrect(PASSWORD.replace('0', 'o'))); + final String line = String.format("%s:%s:%s:%s:", id, email, firstName, lastName, email); + assertTrue(entry.asPasswordLine().startsWith(line)); + } + + @Test + public void testAsPrincial() + { + final UserEntry entry = new UserEntry(PASSWORD_LINE); + final Principal principal = entry.asPrincial(); + assertEquals("id", principal.getUserId()); + assertEquals("First", principal.getFirstName()); + assertEquals("Last", principal.getLastName()); + assertEquals("email@dot.org", principal.getEmail()); + } + +}