package com.tyndalehouse.step.core.service.impl; import static com.tyndalehouse.step.core.utils.IOUtils.closeQuietly; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.tyndalehouse.step.core.exceptions.StepInternalException; import com.tyndalehouse.step.core.exceptions.TranslatedException; import com.tyndalehouse.step.core.service.UserService; import com.tyndalehouse.step.core.utils.IOUtils; /** * A user service implementation, that checks whether a user is allowed in. Then given a number of parameters, * either registers the user automatically, or denies access... * * @author chrisburrell */ @Singleton public class UserServiceImpl implements UserService { private static final Pattern EMAIL = Pattern .compile("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"); private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class); private final File usersFile; private boolean autoRegister; private boolean enabled; private volatile Set<String> users = null; private volatile Writer userWriter; private volatile boolean refreshUsers = false; /** * Creates a user service * * @param autoRegister true if users are automatically registered and allowed through * @param enabled true to indicate a check should be performed * @param usersFileName the location of the users file */ @Inject public UserServiceImpl(@Named("app.user.autoregister") final boolean autoRegister, @Named("app.user.enablecheck") final boolean enabled, @Named("app.user.data") final String usersFileName) { this.autoRegister = autoRegister; this.enabled = enabled; this.usersFile = new File(usersFileName); } @Override public boolean checkUserIdentity(final String email, final String name) { validateEmail(email); if (!this.enabled) { return true; } ensureUsers(); final String properEmail = email.toLowerCase(Locale.ENGLISH); if (this.users.contains(properEmail)) { return true; } // otherwise check auto register if (this.autoRegister) { // add users to files try { createUser(properEmail, name); return true; } catch (final StepInternalException ex) { LOGGER.warn("Unable to write user. enabled=[{}], autoRegister=[{}]", this.enabled, this.autoRegister); LOGGER.info("Stack for write user exception is", ex); } } return false; } /** * throws an exception if the email address is invalid * * @param email email address */ private void validateEmail(final String email) { if (!EMAIL.matcher(email).matches()) { throw new TranslatedException("invalid_email", email); } } @Override public void refresh() { this.refreshUsers = true; this.enabled = true; } /** * @param email the email address of the user * @param name the name of the user */ private synchronized void createUser(final String email, final String name) { ensureFileIsOpenForWrite(); try { this.userWriter.write(email); this.userWriter.write(','); this.userWriter.write(name); this.userWriter.write(','); this.userWriter.write(new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss", Locale.ENGLISH) .format(new Date())); this.userWriter.write('\n'); this.userWriter.flush(); this.users.add(email); } catch (final IOException e) { IOUtils.closeQuietly(this.userWriter); throw new StepInternalException("Unable to write user", e); } } /** opens the user writer if not already open */ private void ensureFileIsOpenForWrite() { if (this.enabled && this.userWriter == null) { synchronized (this) { if (this.enabled && this.userWriter == null) { try { this.userWriter = new FileWriter(this.usersFile, true); } catch (final IOException e) { LOGGER.error("Unable to open writer", e); IOUtils.closeQuietly(this.userWriter); } } } } } /** populates the user reader if not already populated */ private void ensureUsers() { if (this.users == null || this.refreshUsers) { synchronized (this) { if (this.users == null || this.refreshUsers) { this.users = null; this.refreshUsers = false; this.users = readUsersFile(); } } } } /** * @return a set of users */ private synchronized Set<String> readUsersFile() { final Set<String> usersFromFile = new HashSet<String>(); if (!this.usersFile.exists()) { return usersFromFile; } FileInputStream fis = null; BufferedReader reader = null; InputStreamReader isr = null; try { fis = new FileInputStream(this.usersFile); isr = new InputStreamReader(fis); reader = new BufferedReader(isr); String line = null; while ((line = reader.readLine()) != null) { final String[] userEntry = line.split("[,]+"); if (userEntry.length < 2) { LOGGER.warn("Invalid user entry: [{}]", line); continue; } // add email to hashset usersFromFile.add(userEntry[0]); } } catch (final IOException e) { throw new StepInternalException("Unable to read file", e); } finally { IOUtils.closeQuietly(fis); IOUtils.closeQuietly(isr); IOUtils.closeQuietly(reader); } return usersFromFile; } /** * @param users the users to set */ void setUsers(final Set<String> users) { this.users = users; } /** * @param userWriter the userWriter to set */ void setUserWriter(final Writer userWriter) { this.userWriter = userWriter; } @Override public void setAutoRegister(final boolean autoRegister) { this.autoRegister = autoRegister; } @Override public void setEnabled(final boolean enabled) { this.enabled = enabled; if (!enabled) { // close writer synchronized (this) { closeQuietly(this.userWriter); } } } }