Java-basierte Mailinglisten-Anwendung, die auf IMAP+SMTP aufsetzt, und damit (fast) jede Mailbox in eine Mailingliste verwandeln kann.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

287 lines
8.7 KiB

package de.srsoftware.widerhall.data;
import de.srsoftware.widerhall.Util;
import java.security.InvalidKeyException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.*;
import static de.srsoftware.widerhall.Constants.*;
import static de.srsoftware.widerhall.Util.t;
/**
* @author Stephan Richter
* This class represents User objects of the widerhall db.
*/
public class User {
public static final String TABLE_NAME = "Users";
public static final int PERMISSION_ADMIN = 1;
public static final int PERMISSION_CREATE_LISTS = 2;
public static final String HASHED_PASS = "hashedPassword";
public static final String SALT = "salt";
private static final HashMap<String,User> users = new HashMap<>();
private String email, salt, hashedPass, name;
private int permissions;
/**
* create a new user object
* @param email
* @param name
* @param salt
* @param hashedPass
* @param permissions
*/
public User(String email, String name, String salt, String hashedPass, int permissions) {
this.email = email.toLowerCase();
this.name = name;
this.salt = salt;
this.hashedPass = hashedPass;
this.permissions = permissions;
}
/*********** field accessors ***************/
public String email() {
return email;
}
public String hashedPassword() {
return hashedPass;
}
public String name() {
return name;
}
public int permissions(){
return permissions;
}
public String salt(){
return salt;
}
/************** end of field accessors ****************/
/**
* Add a new permission to the current user object.
* Also updates the corresponding db entry
* @param newPermission
* @throws SQLException
*/
public void addPermission(int newPermission) throws SQLException {
permissions |= newPermission;
Database.open()
.update(TABLE_NAME)
.set(PERMISSIONS,permissions)
.where(EMAIL,email())
.compile()
.run();
}
/**
* Create a new user object by hashing it's password and storing user data, salt and hashed password to the db.
* Initially, the user is created without any permissions.
* @param email
* @param name
* @param password
* @return
* @throws SQLException
*/
public static User create(String email, String name, String password) throws SQLException {
email = email.toLowerCase();
String salt = null;
String hashedPass = null;
if (password != null) {
salt = Util.sha256(email + name + LocalDate.now());
hashedPass = Util.sha256(password + salt);
}
return new User(email,name,salt,hashedPass,0).save();
}
/**
* create user table
* @throws SQLException
*/
public static void createTable() throws SQLException {
var sql = new StringBuilder()
.append("CREATE TABLE ").append(TABLE_NAME)
.append(" (")
.append(EMAIL).append(" ").append(VARCHAR).append(" NOT NULL PRIMARY KEY, ")
.append(NAME).append(" ").append(VARCHAR).append(", ")
.append(PERMISSIONS).append(" ").append(INT).append(", ")
.append(SALT).append(" ").append(VARCHAR).append(", ")
.append(HASHED_PASS).append(" ").append(VARCHAR)
.append(");");
Database.open().query(sql).compile().run();
}
/**
* Withdraw a specific permission from the user object.
* Updated permission flag will be written to db.
* @param newPermission
* @throws SQLException
*/
public void dropPermission(int newPermission) throws SQLException {
permissions ^= (permissions & newPermission);
Database.open().update(TABLE_NAME).set(PERMISSIONS,permissions).compile().run();
}
/**
* check, if User object has requested permission(s).
* @param permission
* @return
*/
public boolean hashPermission(int permission){
return (permissions & permission) > 0;
}
public static User load(String email) throws SQLException {
var rs = Database.open().select(TABLE_NAME).where(EMAIL,email.toLowerCase()).compile().exec();
try {
if (rs.next()) {
return User.from(rs);
}
return null;
} finally {
rs.close();
}
}
/**
* Load the list of all users. Internally calls loadAll(null)
* @return
* @throws SQLException
*/
public static List<User> loadAll() throws SQLException {
return loadAll(null);
}
/**
* Load the list of all users identified by the provided email list.
* If emails is null, all users are loaded.
* If emails is empty, an empty list well be returned.
* @param emails
* @return
* @throws SQLException
*/
public static List<User> loadAll(Collection<String> emails) throws SQLException {
if (emails != null && emails.isEmpty()) return List.of();
var userList = new ArrayList<User>();
var query = Database.open().select(TABLE_NAME);
if (emails != null) query.where(EMAIL,emails);
var rs = query.compile().exec();
while (rs.next()) userList.add(User.from(rs));
Collections.sort(userList,(u1,u2)->u1.name.compareTo(u2.name));
return userList;
}
/**
* Create a new User object from a ResultSet.
* This method is cached: If a User object with an identifying email has been loaded before, the already-loaded object will be returned.
* @param rs
* @return
* @throws SQLException
*/
private static User from(ResultSet rs) throws SQLException {
var email = rs.getString(EMAIL);
var user = users.get(email);
if (user == null) users.put(email,user = new User(
rs.getString(EMAIL),
rs.getString(NAME),
rs.getString(SALT),
rs.getString(HASHED_PASS),
rs.getInt(PERMISSIONS)));
return user;
}
/**
* Loads the user identified by it's email, but only if the provided password matches.
* @param email
* @param password
* @return
* @throws InvalidKeyException
* @throws SQLException
*/
public static User loadUser(String email, String password) throws InvalidKeyException, SQLException {
ResultSet rs = Database.open()
.select(TABLE_NAME)
.where(EMAIL,email.toLowerCase())
.compile()
.exec();
try {
if (rs.next()) {
var loadedUser = User.from(rs);
if (loadedUser.matching(password)) return loadedUser;
}
} finally {
rs.close();
}
throw new InvalidKeyException();
}
/**
* checks, if the provided password matches the User obejcts's original password by comparing hashes.
* @param password
* @return
*/
private boolean matching(String password) {
if (hashedPass == null) return password == null;
return hashedPass.equals(Util.sha256(password+salt));
}
/**
* Checks, whether the user table is empty
* @return
* @throws SQLException
*/
public static boolean noUsers() throws SQLException {
var rs = Database.open().select(TABLE_NAME,"count(*)").compile().exec();
try {
if (rs.next()) return rs.getInt(1) < 1;
} finally {
rs.close();
}
return false;
}
/**
* creates a readable permission list from the permission flag.
* @return
*/
public String permissionList(){
var list = new ArrayList<String>();
if (hashPermission(PERMISSION_ADMIN)) list.add("admin");
if (hashPermission(PERMISSION_CREATE_LISTS)) list.add("create lists");
return String.join(", ",list);
}
/**
* creates a map containing all of the Users data but the password.
* @return
*/
public Map<String,String> safeMap(){
return Map.of(NAME,name,EMAIL,email,PERMISSIONS,permissionList(),PASSWORD,t(hashedPassword() == null ? "no" : "yes"));
}
/**
* sae the current User object to the database
* @return
* @throws SQLException
*/
private User save() throws SQLException {
var req = Database.open().insertInto(TABLE_NAME).set(EMAIL,email).set(NAME,name);
if (salt != null) req.set(SALT,salt);
if (hashedPass != null) req.set(HASHED_PASS,hashedPass);
req.compile().run();
return this;
}
}