package services;
import helpers.MD5Crypt;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import play.Logger;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* @author fo
*/
public class AccountService {
private static final String mLimitWriteDirective =
"<Location /resource/%s>\n" +
" <LimitExcept GET>\n" +
" Require group admin\n" +
" Require user %s\n" +
" </LimitExcept>\n" +
"</Location>";
private String mApache2ctl = "sudo apache2ctl graceful";
private final File mTokenDir;
private final File mUserFile;
private final File mGroupFile;
private final File mProfileFile;
private final File mPermissionsDir;
public AccountService(File aTokenDir, File aUserFile, File aGroupFile, File aProfileFile, File aPermissionsDir) {
mTokenDir = aTokenDir;
mUserFile = aUserFile;
mGroupFile = aGroupFile;
mProfileFile = aProfileFile;
mPermissionsDir = aPermissionsDir;
}
public void setPermissions(String aId, String aUser) {
String entry = String.format(mLimitWriteDirective, aId, aUser);
String fileName = aId.substring(aId.lastIndexOf(":") + 1).trim();
try {
FileUtils.writeStringToFile(new File(mPermissionsDir, fileName), entry, StandardCharsets.UTF_8);
} catch (IOException e) {
Logger.error("Could not create permission file", e);
}
refresh();
}
public boolean removePermissions(String aId) {
String fileName = aId.substring(aId.lastIndexOf(":") + 1).trim();
boolean status = FileUtils.deleteQuietly(new File(mPermissionsDir, fileName));
refresh();
return status;
}
public void setApache2Ctl(String aApache2ctl) {
mApache2ctl = aApache2ctl;
}
public void refresh() {
try {
Process apache2ctl = Runtime.getRuntime().exec(mApache2ctl);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(apache2ctl.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(apache2ctl.getErrorStream()));
Logger.debug(IOUtils.toString(stdInput));
Logger.debug(IOUtils.toString(stdError));
} catch (IOException e) {
Logger.error("Could not restart Apache", e);
}
}
public String addUser(String username, String password) {
if (!userExists(username) && !pendingVerification(username)) {
try {
String token = getEncryptedUsername(username);
File tokenFile = new File(mTokenDir, token);
FileUtils.writeStringToFile(tokenFile, buildEntry(username, password), StandardCharsets.UTF_8);
return token;
} catch (IOException e) {
Logger.error("Could not write token file", e);
}
}
return null;
}
public boolean deleteUser(String username) {
if (!userExists(username)) {
return false;
}
try {
List<String> userDb = Files.readAllLines(mUserFile.toPath());
for (final ListIterator<String> i = userDb.listIterator(); i.hasNext();) {
if (i.next().startsWith(username)) {
i.remove();
break;
}
}
FileUtils.writeLines(mUserFile,userDb);
return true;
} catch (IOException e) {
Logger.error("Could not write user file", e);
return false;
}
}
public String verifyToken(String token) {
File tokenFile = new File(mTokenDir, token);
if (tokenFile.exists()) {
try {
String entry = FileUtils.readFileToString(tokenFile, StandardCharsets.UTF_8);
new BufferedWriter(new FileWriter(mUserFile, true)).append(entry.concat("\n")).close();
FileUtils.forceDelete(tokenFile);
return entry.split(":")[0];
} catch (IOException e) {
Logger.error("Could not process token file", e);
}
}
return null;
}
public boolean userExists(String username) {
try {
return FileUtils.readFileToString(mUserFile, StandardCharsets.UTF_8).contains(username);
} catch (IOException e) {
Logger.error("Could not read user file", e);
}
return false;
}
public boolean pendingVerification(String username) {
return new File(mTokenDir, getEncryptedUsername(username)).exists();
}
public boolean updatePassword(String username, String current, String updated) {
return validatePassword(username, current) && setPassword(username, updated);
}
public boolean setPassword(String username, String password) {
if (!userExists(username)) {
return false;
}
try {
List<String> userDb = Files.readAllLines(mUserFile.toPath());
for (final ListIterator<String> i = userDb.listIterator(); i.hasNext();) {
if (i.next().startsWith(username)) {
i.set(buildEntry(username, password));
break;
}
}
FileUtils.writeLines(mUserFile, userDb);
return true;
} catch (IOException e) {
Logger.error("Could not read user file", e);
return false;
}
}
public boolean validatePassword(String username, String password) {
if (!userExists(username)) {
return false;
}
String entry = getEntry(username);
return entry != null && MD5Crypt.verifyPassword(password, entry.split(":")[1]);
}
// FIXME: unit tests
public List<String> getGroups(String username) {
List<String> groups = new ArrayList<>();
try {
List<String> lines = Files.readAllLines(mGroupFile.toPath());
for (String line : lines) {
String[] entry = line.split(":");
String group = entry[0].trim();
List<String> users = Arrays.asList(entry[1].split(" +"));
if (users.contains(username)) {
groups.add(group);
}
}
} catch (IOException e) {
Logger.error("Could not read group file", e);
}
return groups;
}
public List<String> getGroups() {
List<String> groups = new ArrayList<>();
try {
List<String> lines = Files.readAllLines(mGroupFile.toPath());
for (String line : lines) {
String[] entry = line.split(":");
String group = entry[0].trim();
groups.add(group);
}
} catch (IOException e) {
Logger.error("Could not read group file", e);
}
return groups;
}
/**
* Update all groups at once.
*
* @param groups The map of group name with all users of that group
* @return True on success
*/
public boolean setGroups(Map<String, List<String>> groups) {
List<String> lines = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : groups.entrySet()) {
lines.add(entry.getKey().concat(": ").concat(String.join(" ", entry.getValue())));
}
try {
FileUtils.writeLines(mGroupFile, lines);
return true;
} catch (IOException e) {
Logger.error("Could not write group file", e);
return false;
}
}
public List<String> getUsers() {
List<String> users = new ArrayList<>();
try {
List<String> lines = Files.readAllLines(mUserFile.toPath());
for (String line : lines) {
String[] entry = line.split(":");
String user = entry[0].trim();
users.add(user);
}
} catch (IOException e) {
Logger.error("Could not read user file", e);
}
return users;
}
public List<String> getUsers(String aGroup) {
try {
List<String> lines = Files.readAllLines(mGroupFile.toPath());
for (String line : lines) {
String[] entry = line.split(":");
String group = entry[0].trim();
if (group.equals(aGroup)) {
return Arrays.asList(entry[1].split(" +"));
}
}
} catch (IOException e) {
Logger.error("Could not read user file", e);
}
return new ArrayList<>();
}
public boolean setProfileId(String username, String profileId) {
if (!userExists(username) && !StringUtils.isEmpty(profileId)) {
return false;
}
try {
List<String> profileDb = Files.readAllLines(mProfileFile.toPath());
for (final ListIterator<String> i = profileDb.listIterator(); i.hasNext();) {
if (i.next().startsWith(username)) {
if (StringUtils.isEmpty(profileId)) {
i.remove();
} else {
i.set(username.concat(" ").concat(profileId));
}
FileUtils.writeLines(mProfileFile, profileDb);
return true;
}
}
if (!StringUtils.isEmpty(profileId)) {
FileUtils.writeLines(mProfileFile, Collections.singletonList(username.concat(" ").concat(profileId)), true);
}
return true;
} catch (IOException e) {
Logger.error("Could not write profile file", e);
return false;
}
}
public String getProfileId(String username) {
try {
List<String> lines = Files.readAllLines(mProfileFile.toPath());
for (String line : lines) {
String[] entry = line.split(" ");
String name = entry[0].trim();
if (name.equals(username)) {
return entry[1].trim();
}
}
} catch (IOException e) {
Logger.error("Could not read profile file", e);
}
return null;
}
public String getUsername(String profileId) {
try {
List<String> lines = Files.readAllLines(mProfileFile.toPath());
for (String line : lines) {
String[] entry = line.split(" ");
String id = entry[1].trim();
if (id.equals(profileId)) {
return entry[0].trim();
}
}
} catch (IOException e) {
Logger.error("Could not read profile file", e);
}
return null;
}
private String getEntry(String username) {
try {
List<String> userDb = Files.readAllLines(mUserFile.toPath());
for (final ListIterator<String> i = userDb.listIterator(); i.hasNext();) {
String entry = i.next();
if (entry.startsWith(username)) {
return entry;
}
}
} catch (IOException e) {
Logger.error("Could not read user file", e);
return null;
}
return null;
}
private static String buildEntry(String username, String password) {
return username.concat(":").concat(MD5Crypt.apacheCrypt(password));
}
private static String getEncryptedUsername(String email) {
return DigestUtils.sha256Hex(email);
}
}