Archived
1
0
Fork 0

Setup model, user model and identification

This commit is contained in:
Ethanell 2021-12-08 09:47:29 +01:00
parent 2ebe7a68ae
commit a6ec8e5676
17 changed files with 426 additions and 18 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
.idea/ .idea/
/target/ /target/
/server.properties

View file

@ -43,6 +43,11 @@
<version>5.7.1</version> <version>5.7.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.1</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -15,11 +15,31 @@ public class Client {
private ObjectInputStream in; private ObjectInputStream in;
protected boolean started = false; protected boolean started = false;
public Client(String address, int port) throws IOException, InterruptedException { public Client(String address, int port, String uuid, String password) throws IOException, InterruptedException, Exception {
this.address = address; this.address = address;
this.port = port; this.port = port;
socket = new Socket(address, port); socket = new Socket(address, port);
out = new ObjectOutputStream(socket.getOutputStream()); out = new ObjectOutputStream(socket.getOutputStream());
while (!this.auth(uuid, password));
}
public boolean auth(String uuid, String password) throws IOException {
getIn();
out.writeUTF(uuid);
out.flush();
out.writeUTF(password);
out.flush();
String response = in.readUTF();
System.out.println(response);
if (response.startsWith("err:"))
return false;
else if (response.equals("logged"))
return true;
else
throw new IOException("Uk message");
} }
public void disconnectedServer() throws IOException { public void disconnectedServer() throws IOException {
@ -66,4 +86,10 @@ public class Client {
socket.close(); socket.close();
clientReceiveThread.interrupt(); clientReceiveThread.interrupt();
} }
public ObjectInputStream getIn() throws IOException {
if (in == null)
in = new ObjectInputStream(socket.getInputStream());
return in;
}
} }

View file

@ -20,7 +20,7 @@ public class ClientReceive implements Runnable {
@Override @Override
public void run() { public void run() {
try { try {
in = new ObjectInputStream(socket.getInputStream()); in = client.getIn();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
return; return;

View file

@ -1,20 +1,21 @@
package fr.univ.lyon1.client; package fr.univ.lyon1.client;
import java.io.IOException;
public class MainClient { public class MainClient {
public static void main(String[] args) { public static void main(String[] args) {
try { try {
if (args.length != 2) { if (args.length != 4) {
printUsage(); printUsage();
} else { } else {
String address = args[0]; String address = args[0];
int port = Integer.parseInt(args[1]); int port = Integer.parseInt(args[1]);
Client c = new Client(address, port); String uuid = args[2];
String password = args[3];
Client c = new Client(address, port, uuid, password);
c.run(); c.run();
} }
} catch (IOException|InterruptedException e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
System.exit(1);
} }
} }
@ -22,5 +23,7 @@ public class MainClient {
System.out.println("java client.Client <address> <port>"); System.out.println("java client.Client <address> <port>");
System.out.println("\t<address>: server's ip address"); System.out.println("\t<address>: server's ip address");
System.out.println("\t<port>: server's port"); System.out.println("\t<port>: server's port");
System.out.println("\t<UUID>: user's UUID");
System.out.println("\t<Password>: user's password");
} }
} }

View file

@ -1,22 +1,31 @@
package fr.univ.lyon1.common; package fr.univ.lyon1.common;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID;
public class Message implements Serializable { public class Message implements Serializable {
private String sender; private User sender;
private final String content; private final String content;
private final UUID uuid;
public Message(String sender, String content) { public Message(User sender, String content) {
this.uuid = UUID.randomUUID();
this.sender = sender; this.sender = sender;
this.content = content; this.content = content;
} }
public void setSender(String sender) { public Message(UUID uuid, User sender, String content) {
this.uuid = uuid;
this.sender = sender;
this.content = content;
}
public void setSender(User sender) {
this.sender = sender; this.sender = sender;
} }
public String getSender() { public User getSender() {
return sender; return sender;
} }

View file

@ -0,0 +1,41 @@
package fr.univ.lyon1.common;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.UUID;
import java.util.regex.Matcher;
public class User implements Serializable {
private final UUID uuid;
private String username;
public User(UUID uuid, String username) {
this.uuid = uuid;
this.username = username;
}
public User(String username) {
this.uuid = UUID.randomUUID();
this.username = username;
}
public UUID getUUID() {
return uuid;
}
public String getUsername() {
return username;
}
@Override
public String toString() {
return username;
}
}

View file

@ -0,0 +1,13 @@
package fr.univ.lyon1.common.channel;
import java.util.UUID;
public abstract class Channel {
private final UUID uuid;
private String name;
public Channel(UUID uuid, String name) {
this.uuid = uuid;
this.name = name;
}
}

View file

@ -0,0 +1,10 @@
package fr.univ.lyon1.common.channel;
import java.util.UUID;
public class PrivateChannel extends Channel {
public PrivateChannel(UUID uuid, String name) {
super(uuid, name);
}
}

View file

@ -0,0 +1,9 @@
package fr.univ.lyon1.common.channel;
import java.util.UUID;
public class PublicChannel extends Channel {
public PublicChannel(UUID uuid, String name) {
super(uuid, name);
}
}

View file

@ -9,8 +9,8 @@ import java.io.IOException;
public class ClientGUI extends Client { public class ClientGUI extends Client {
private final MainGui gui; private final MainGui gui;
public ClientGUI(MainGui gui, String address, int port) throws IOException, InterruptedException { public ClientGUI(MainGui gui, String address, int port) throws IOException, InterruptedException, Exception {
super(address, port); super(address, port, null, null);
this.gui = gui; this.gui = gui;
} }

View file

@ -1,6 +1,8 @@
package fr.univ.lyon1.server; package fr.univ.lyon1.server;
import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.Message;
import fr.univ.lyon1.common.User;
import fr.univ.lyon1.server.models.UserModel;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
@ -15,11 +17,47 @@ public class ConnectedClient implements Runnable {
private final Socket socket; private final Socket socket;
private final ObjectOutputStream out; private final ObjectOutputStream out;
private ObjectInputStream in; private ObjectInputStream in;
private User user;
ConnectedClient(Server server, Socket socket) throws IOException { ConnectedClient(Server server, Socket socket) throws IOException {
this.server = server; this.server = server;
this.socket = socket; this.socket = socket;
this.out = new ObjectOutputStream(socket.getOutputStream()); this.out = new ObjectOutputStream(socket.getOutputStream());
System.out.println("New user try to auth");
while (!this.auth());
}
private boolean auth() throws IOException {
if (in == null)
in = new ObjectInputStream(socket.getInputStream());
String username = in.readUTF();
System.out.println("username: "+username);
String password = in.readUTF();
System.out.println("Pass: "+password);
if (username.isEmpty() || password.isEmpty()) {
out.writeUTF("err: Login required");
out.flush();
return false;
}
UserModel user = UserModel.get(username);
if (user == null)
out.writeUTF("err: Username not found !");
else if (!user.checkPassword(password))
out.writeUTF("err: Password invalid !");
else {
out.writeUTF("logged");
out.flush();
this.user = user;
return true;
}
out.flush();
return false;
} }
public Message sendMessage(Message message) throws IOException { public Message sendMessage(Message message) throws IOException {
@ -30,15 +68,13 @@ public class ConnectedClient implements Runnable {
public void run() { public void run() {
try { try {
in = new ObjectInputStream(socket.getInputStream());
while (true) { while (true) {
Message msg = (Message) in.readObject(); Message msg = (Message) in.readObject();
if (msg == null) if (msg == null)
break; break;
msg.setSender(String.valueOf(id)); msg.setSender(this.user);
server.broadcastMessage(msg, id); server.broadcastMessage(msg, id);
} }
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {

View file

@ -0,0 +1,62 @@
package fr.univ.lyon1.server;
import java.io.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class Database {
private static Database database;
private Connection connection;
private Database() {
Database.database = this;
try {
this.connection = getConnexion();
} catch (IOException | SQLException err) {
err.printStackTrace();
System.exit(1);
}
}
private String[] getCredentials() throws NullPointerException, IOException {
Properties props = new Properties();
File f = new File("server.properties");
if (!f.exists()) {
props.setProperty("db.url", "jdbc:mariadb://localhost:3306/chat");
props.setProperty("db.user", "chat");
props.setProperty("db.password", "password");
props.store(new FileWriter(f), "");
} else {
props.load(new FileReader(f));
}
return new String[]{props.getProperty("db.url"), props.getProperty("db.user"), props.getProperty("db.password")};
}
private Connection getConnexion() throws SQLException, IOException {
String[] credentials = getCredentials();
try {
Class.forName("org.mariadb.jdbc.Driver");
} catch (ClassNotFoundException err) {
System.err.println("MariaDB driver not found !");
System.exit(1);
}
return DriverManager.getConnection(credentials[0], credentials[1], credentials[2]);
}
public Connection getConnection() {
return connection;
}
public static Database getDatabase() {
if (Database.database == null)
return new Database();
return Database.database;
}
}

View file

@ -1,23 +1,27 @@
package fr.univ.lyon1.server; package fr.univ.lyon1.server;
import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.Message;
import fr.univ.lyon1.common.User;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
public class Server { public class Server {
private final int port; private final int port;
private List<ConnectedClient> clients = new ArrayList<>(); private List<ConnectedClient> clients = new ArrayList<>();
private static User serverUser = new User(UUID.fromString("3539b6bf-5eb3-41d4-893f-cbf0caa9ca74"), "server");
Server(int port) throws IOException { Server(int port) throws IOException {
this.port = port; this.port = port;
Database.getDatabase();
Thread connection = new Thread(new Connection(this)); Thread connection = new Thread(new Connection(this));
connection.start(); connection.start();
} }
public ConnectedClient addClient(ConnectedClient newClient) { public ConnectedClient addClient(ConnectedClient newClient) {
Message msg = new Message( "Server", newClient.getId() + " is connected !"); Message msg = new Message( serverUser, newClient.getId() + " is connected !");
clients.add(newClient); clients.add(newClient);
@ -51,7 +55,7 @@ public class Server {
clients.remove(client); clients.remove(client);
Message msg = new Message("Server", "Client "+client.getId()+" is disconnected"); Message msg = new Message(serverUser, "Client "+client.getId()+" is disconnected");
broadcastMessage(msg, -1); broadcastMessage(msg, -1);

View file

@ -0,0 +1,10 @@
package fr.univ.lyon1.server.models;
import fr.univ.lyon1.server.Database;
public interface Model {
Database database = Database.getDatabase();
}

View file

@ -0,0 +1,176 @@
package fr.univ.lyon1.server.models;
import fr.univ.lyon1.common.User;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Base64;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UserModel extends User implements Model {
private String passwordHash;
private final SecureRandom random = new SecureRandom();
public static final String ID = "$1$";
private static final int SIZE = 128;
private static final int COST = 16;
private static final String ALGORITHM = "PBKDF2WithHmacSHA1";
private static final Pattern LAYOUT = Pattern.compile("\\$1\\$(\\d\\d?)\\$(.{43})");
private static final String TABLE_NAME = "User";
public UserModel(String username, String password) {
super(username);
setPassword(password);
create();
}
private UserModel(UUID uuid, String username, String passwordHash) {
super(uuid, username);
this.passwordHash = passwordHash;
}
public static UserModel get(String username) {
try {
PreparedStatement ps = database.getConnection().prepareStatement("SELECT UUID FROM "+TABLE_NAME+" WHERE username = ?");
ps.setString(1, username);
if (ps.execute()) {
ResultSet rs = ps.getResultSet();
if (rs.next())
return get(UUID.fromString(rs.getString("UUID")));
}
} catch (SQLException err) {
err.printStackTrace();
return null;
}
return null;
}
public static UserModel get(UUID uuid) {
try {
PreparedStatement ps = database.getConnection().prepareStatement("SELECT * FROM "+TABLE_NAME+" WHERE UUID = ?");
ps.setString(1, uuid.toString());
if (ps.execute()) {
ResultSet rs = ps.getResultSet();
if (rs.next())
return new UserModel(
UUID.fromString(rs.getString("UUID")),
rs.getString("USERNAME"),
rs.getString("PASSWORD")
);
}
} catch (SQLException err) {
err.printStackTrace();
return null;
}
return null;
}
private boolean exist() {
try {
PreparedStatement ps = database.getConnection().prepareStatement("SELECT UUID FROM "+TABLE_NAME+" WHERE UUID = ?");
ps.setString(1, super.getUUID().toString());
ps.execute();
return ps.getResultSet().next();
} catch (SQLException err) {
err.printStackTrace();
return false;
}
}
private boolean create() {
try {
PreparedStatement ps = database.getConnection().prepareStatement("INSERT INTO "+TABLE_NAME+" (UUID, username, password) VALUES (?, ?, ?)");
ps.setString(1, super.getUUID().toString());
ps.setString(2, super.getUsername());
ps.setString(3, getPasswordHash());
return ps.executeUpdate() > 0;
} catch (SQLException err) {
err.printStackTrace();
return false;
}
}
public boolean save() {
if (!exist())
return create();
try {
PreparedStatement ps = database.getConnection().prepareStatement("UPDATE "+TABLE_NAME+" SET username = ?, password = ? WHERE UUID = ?");
ps.setString(1, super.getUsername());
ps.setString(2, getPasswordHash());
ps.setString(3, super.getUUID().toString());
return ps.executeUpdate() > 0;
} catch (SQLException err) {
err.printStackTrace();
return false;
}
}
public static void generateTable() {
try {
PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE "+TABLE_NAME+" ( UUID varchar(40) primary key, username varchar(16) unique, password varchar(256) )");
ps.executeUpdate();
} catch (SQLException err) {
err.printStackTrace();
}
}
public String getPasswordHash() {
return passwordHash;
}
public void setPassword(String password) {
byte[] passwordSalt = new byte[SIZE / 8];
random.nextBytes(passwordSalt);
byte[] dk = pbkdf2(password.toCharArray(), passwordSalt, 1 << COST);
byte[] hash = new byte[passwordSalt.length + dk.length];
System.arraycopy(passwordSalt, 0, hash, 0, passwordSalt.length);
System.arraycopy(dk, 0, hash, passwordSalt.length, dk.length);
Base64.Encoder enc = Base64.getUrlEncoder().withoutPadding();
passwordHash = ID + COST + '$' + enc.encodeToString(hash);
}
public boolean checkPassword(String password) {
Matcher m = LAYOUT.matcher(passwordHash);
if (!m.matches())
throw new IllegalArgumentException("Invalid token format");
int iterations = iterations(Integer.parseInt(m.group(1)));
byte[] hash = Base64.getUrlDecoder().decode(m.group(2));
byte[] salt = Arrays.copyOfRange(hash, 0, SIZE / 8);
byte[] check = pbkdf2(password.toCharArray(), salt, iterations);
int zero = 0;
for (int idx = 0; idx < check.length; ++idx)
zero |= hash[salt.length + idx] ^ check[idx];
return zero == 0;
}
private static int iterations(int cost) {
if ((cost < 0) || (cost > 30))
throw new IllegalArgumentException("cost: " + cost);
return 1 << cost;
}
private static byte[] pbkdf2(char[] password, byte[] salt, int iterations) {
KeySpec spec = new PBEKeySpec(password, salt, iterations, SIZE);
try {
SecretKeyFactory f = SecretKeyFactory.getInstance(ALGORITHM);
return f.generateSecret(spec).getEncoded();
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("Missing algorithm: " + ALGORITHM, ex);
}
catch (InvalidKeySpecException ex) {
throw new IllegalStateException("Invalid SecretKeyFactory", ex);
}
}
}

View file

@ -3,6 +3,9 @@ module fr.univ.lyon1.gui {
requires javafx.fxml; requires javafx.fxml;
requires org.kordamp.bootstrapfx.core; requires org.kordamp.bootstrapfx.core;
requires java.sql;
requires org.mariadb.jdbc;
opens fr.univ.lyon1.gui to javafx.fxml; opens fr.univ.lyon1.gui to javafx.fxml;
exports fr.univ.lyon1.gui; exports fr.univ.lyon1.gui;