From a6ec8e5676cb83ee9940ed2051d04e267562b64a Mon Sep 17 00:00:00 2001 From: Florian Charlaix Date: Wed, 8 Dec 2021 09:47:29 +0100 Subject: [PATCH 1/6] Setup model, user model and identification --- .gitignore | 1 + pom.xml | 5 + src/fr/univ/lyon1/client/Client.java | 28 ++- src/fr/univ/lyon1/client/ClientReceive.java | 2 +- src/fr/univ/lyon1/client/MainClient.java | 13 +- src/fr/univ/lyon1/common/Message.java | 17 +- src/fr/univ/lyon1/common/User.java | 41 ++++ src/fr/univ/lyon1/common/channel/Channel.java | 13 ++ .../lyon1/common/channel/PrivateChannel.java | 10 + .../lyon1/common/channel/PublicChannel.java | 9 + src/fr/univ/lyon1/gui/ClientGUI.java | 4 +- src/fr/univ/lyon1/server/ConnectedClient.java | 42 ++++- src/fr/univ/lyon1/server/Database.java | 62 ++++++ src/fr/univ/lyon1/server/Server.java | 8 +- src/fr/univ/lyon1/server/models/Model.java | 10 + .../univ/lyon1/server/models/UserModel.java | 176 ++++++++++++++++++ src/module-info.java | 3 + 17 files changed, 426 insertions(+), 18 deletions(-) create mode 100644 src/fr/univ/lyon1/common/User.java create mode 100644 src/fr/univ/lyon1/common/channel/Channel.java create mode 100644 src/fr/univ/lyon1/common/channel/PrivateChannel.java create mode 100644 src/fr/univ/lyon1/common/channel/PublicChannel.java create mode 100644 src/fr/univ/lyon1/server/Database.java create mode 100644 src/fr/univ/lyon1/server/models/Model.java create mode 100644 src/fr/univ/lyon1/server/models/UserModel.java diff --git a/.gitignore b/.gitignore index 87afada..4d66140 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ /target/ +/server.properties diff --git a/pom.xml b/pom.xml index b0e06c3..2a98067 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,11 @@ 5.7.1 test + + org.mariadb.jdbc + mariadb-java-client + 2.7.1 + diff --git a/src/fr/univ/lyon1/client/Client.java b/src/fr/univ/lyon1/client/Client.java index 19718cc..b5f33cb 100644 --- a/src/fr/univ/lyon1/client/Client.java +++ b/src/fr/univ/lyon1/client/Client.java @@ -15,11 +15,31 @@ public class Client { private ObjectInputStream in; 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.port = port; socket = new Socket(address, port); 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 { @@ -66,4 +86,10 @@ public class Client { socket.close(); clientReceiveThread.interrupt(); } + + public ObjectInputStream getIn() throws IOException { + if (in == null) + in = new ObjectInputStream(socket.getInputStream()); + return in; + } } diff --git a/src/fr/univ/lyon1/client/ClientReceive.java b/src/fr/univ/lyon1/client/ClientReceive.java index ddccb12..4a906b2 100644 --- a/src/fr/univ/lyon1/client/ClientReceive.java +++ b/src/fr/univ/lyon1/client/ClientReceive.java @@ -20,7 +20,7 @@ public class ClientReceive implements Runnable { @Override public void run() { try { - in = new ObjectInputStream(socket.getInputStream()); + in = client.getIn(); } catch (IOException e) { e.printStackTrace(); return; diff --git a/src/fr/univ/lyon1/client/MainClient.java b/src/fr/univ/lyon1/client/MainClient.java index 82c088b..afc4565 100644 --- a/src/fr/univ/lyon1/client/MainClient.java +++ b/src/fr/univ/lyon1/client/MainClient.java @@ -1,20 +1,21 @@ package fr.univ.lyon1.client; -import java.io.IOException; - public class MainClient { public static void main(String[] args) { try { - if (args.length != 2) { + if (args.length != 4) { printUsage(); } else { String address = args[0]; 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(); } - } catch (IOException|InterruptedException e) { + } catch (Exception e) { e.printStackTrace(); + System.exit(1); } } @@ -22,5 +23,7 @@ public class MainClient { System.out.println("java client.Client
"); System.out.println("\t
: server's ip address"); System.out.println("\t: server's port"); + System.out.println("\t: user's UUID"); + System.out.println("\t: user's password"); } } diff --git a/src/fr/univ/lyon1/common/Message.java b/src/fr/univ/lyon1/common/Message.java index 15a8638..c8e5b8f 100644 --- a/src/fr/univ/lyon1/common/Message.java +++ b/src/fr/univ/lyon1/common/Message.java @@ -1,22 +1,31 @@ package fr.univ.lyon1.common; import java.io.Serializable; +import java.util.UUID; public class Message implements Serializable { - private String sender; + private User sender; 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.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; } - public String getSender() { + public User getSender() { return sender; } diff --git a/src/fr/univ/lyon1/common/User.java b/src/fr/univ/lyon1/common/User.java new file mode 100644 index 0000000..5a14351 --- /dev/null +++ b/src/fr/univ/lyon1/common/User.java @@ -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; + } +} diff --git a/src/fr/univ/lyon1/common/channel/Channel.java b/src/fr/univ/lyon1/common/channel/Channel.java new file mode 100644 index 0000000..fce3126 --- /dev/null +++ b/src/fr/univ/lyon1/common/channel/Channel.java @@ -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; + } +} diff --git a/src/fr/univ/lyon1/common/channel/PrivateChannel.java b/src/fr/univ/lyon1/common/channel/PrivateChannel.java new file mode 100644 index 0000000..92ecbd8 --- /dev/null +++ b/src/fr/univ/lyon1/common/channel/PrivateChannel.java @@ -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); + } +} diff --git a/src/fr/univ/lyon1/common/channel/PublicChannel.java b/src/fr/univ/lyon1/common/channel/PublicChannel.java new file mode 100644 index 0000000..068dec2 --- /dev/null +++ b/src/fr/univ/lyon1/common/channel/PublicChannel.java @@ -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); + } +} diff --git a/src/fr/univ/lyon1/gui/ClientGUI.java b/src/fr/univ/lyon1/gui/ClientGUI.java index 634aefc..cb19ab2 100644 --- a/src/fr/univ/lyon1/gui/ClientGUI.java +++ b/src/fr/univ/lyon1/gui/ClientGUI.java @@ -9,8 +9,8 @@ import java.io.IOException; public class ClientGUI extends Client { private final MainGui gui; - public ClientGUI(MainGui gui, String address, int port) throws IOException, InterruptedException { - super(address, port); + public ClientGUI(MainGui gui, String address, int port) throws IOException, InterruptedException, Exception { + super(address, port, null, null); this.gui = gui; } diff --git a/src/fr/univ/lyon1/server/ConnectedClient.java b/src/fr/univ/lyon1/server/ConnectedClient.java index 39d8d0b..8f3de7c 100644 --- a/src/fr/univ/lyon1/server/ConnectedClient.java +++ b/src/fr/univ/lyon1/server/ConnectedClient.java @@ -1,6 +1,8 @@ package fr.univ.lyon1.server; 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.IOException; @@ -15,11 +17,47 @@ public class ConnectedClient implements Runnable { private final Socket socket; private final ObjectOutputStream out; private ObjectInputStream in; + private User user; ConnectedClient(Server server, Socket socket) throws IOException { this.server = server; this.socket = socket; 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 { @@ -30,15 +68,13 @@ public class ConnectedClient implements Runnable { public void run() { try { - in = new ObjectInputStream(socket.getInputStream()); - while (true) { Message msg = (Message) in.readObject(); if (msg == null) break; - msg.setSender(String.valueOf(id)); + msg.setSender(this.user); server.broadcastMessage(msg, id); } } catch (IOException | ClassNotFoundException e) { diff --git a/src/fr/univ/lyon1/server/Database.java b/src/fr/univ/lyon1/server/Database.java new file mode 100644 index 0000000..b3651ab --- /dev/null +++ b/src/fr/univ/lyon1/server/Database.java @@ -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; + } +} diff --git a/src/fr/univ/lyon1/server/Server.java b/src/fr/univ/lyon1/server/Server.java index ca711d6..3e7d64f 100644 --- a/src/fr/univ/lyon1/server/Server.java +++ b/src/fr/univ/lyon1/server/Server.java @@ -1,23 +1,27 @@ package fr.univ.lyon1.server; import fr.univ.lyon1.common.Message; +import fr.univ.lyon1.common.User; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.UUID; public class Server { private final int port; private List clients = new ArrayList<>(); + private static User serverUser = new User(UUID.fromString("3539b6bf-5eb3-41d4-893f-cbf0caa9ca74"), "server"); Server(int port) throws IOException { this.port = port; + Database.getDatabase(); Thread connection = new Thread(new Connection(this)); connection.start(); } 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); @@ -51,7 +55,7 @@ public class Server { 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); diff --git a/src/fr/univ/lyon1/server/models/Model.java b/src/fr/univ/lyon1/server/models/Model.java new file mode 100644 index 0000000..aca3034 --- /dev/null +++ b/src/fr/univ/lyon1/server/models/Model.java @@ -0,0 +1,10 @@ +package fr.univ.lyon1.server.models; + +import fr.univ.lyon1.server.Database; + + +public interface Model { + Database database = Database.getDatabase(); + + +} diff --git a/src/fr/univ/lyon1/server/models/UserModel.java b/src/fr/univ/lyon1/server/models/UserModel.java new file mode 100644 index 0000000..95280db --- /dev/null +++ b/src/fr/univ/lyon1/server/models/UserModel.java @@ -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); + } + } +} diff --git a/src/module-info.java b/src/module-info.java index 39f3612..b6f9ef6 100644 --- a/src/module-info.java +++ b/src/module-info.java @@ -3,6 +3,9 @@ module fr.univ.lyon1.gui { requires javafx.fxml; requires org.kordamp.bootstrapfx.core; + requires java.sql; + + requires org.mariadb.jdbc; opens fr.univ.lyon1.gui to javafx.fxml; exports fr.univ.lyon1.gui; From 06bc4d545196a14490e4f65ae6102d2efa046b9c Mon Sep 17 00:00:00 2001 From: Florian Charlaix Date: Wed, 8 Dec 2021 17:05:33 +0100 Subject: [PATCH 2/6] Setup channel model and join a channel --- src/fr/univ/lyon1/client/Client.java | 26 +++- src/fr/univ/lyon1/client/ClientReceive.java | 12 +- src/fr/univ/lyon1/common/Channel.java | 32 +++++ src/fr/univ/lyon1/common/Message.java | 25 +++- src/fr/univ/lyon1/common/User.java | 9 -- src/fr/univ/lyon1/common/channel/Channel.java | 13 -- .../lyon1/common/channel/PrivateChannel.java | 10 -- .../lyon1/common/channel/PublicChannel.java | 9 -- src/fr/univ/lyon1/server/ConnectedClient.java | 67 ++++++++++- src/fr/univ/lyon1/server/Server.java | 22 ++-- .../lyon1/server/models/ChannelModel.java | 112 ++++++++++++++++++ src/fr/univ/lyon1/server/models/Model.java | 2 - .../lyon1/server/models/UserChannelModel.java | 96 +++++++++++++++ 13 files changed, 364 insertions(+), 71 deletions(-) create mode 100644 src/fr/univ/lyon1/common/Channel.java delete mode 100644 src/fr/univ/lyon1/common/channel/Channel.java delete mode 100644 src/fr/univ/lyon1/common/channel/PrivateChannel.java delete mode 100644 src/fr/univ/lyon1/common/channel/PublicChannel.java create mode 100644 src/fr/univ/lyon1/server/models/ChannelModel.java create mode 100644 src/fr/univ/lyon1/server/models/UserChannelModel.java diff --git a/src/fr/univ/lyon1/client/Client.java b/src/fr/univ/lyon1/client/Client.java index b5f33cb..33afdfe 100644 --- a/src/fr/univ/lyon1/client/Client.java +++ b/src/fr/univ/lyon1/client/Client.java @@ -1,11 +1,13 @@ package fr.univ.lyon1.client; import fr.univ.lyon1.common.Message; +import fr.univ.lyon1.common.User; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; +import java.util.List; public class Client { private final int port; @@ -15,12 +17,16 @@ public class Client { private ObjectInputStream in; protected boolean started = false; - public Client(String address, int port, String uuid, String password) throws IOException, InterruptedException, Exception { + public Client(String address, int port, String uuid, String password) throws Exception { this.address = address; this.port = port; socket = new Socket(address, port); out = new ObjectOutputStream(socket.getOutputStream()); while (!this.auth(uuid, password)); + out.writeObject("listUsers"); + out.flush(); + out.writeObject("join general"); + out.flush(); } public boolean auth(String uuid, String password) throws IOException { @@ -52,10 +58,9 @@ public class Client { } public String sendMessage(String content) { - Message msg = new Message(null, content); try { - out.writeObject(msg); + out.writeObject(new Message(content)); out.flush(); } catch (IOException e) { System.err.println("Fail to send message !"); @@ -71,9 +76,24 @@ public class Client { return msg; } + public void action(Object data) { + if (data instanceof Message) + messageReceived((Message) data); + else if (data instanceof List) { + List tmpList = (List) data; + if (tmpList.get(0) instanceof User) { + List users = (List) data; + for (User u : users) { + System.out.println(u); + } + } + } + } + public void run() throws InterruptedException, IOException { if (started) return; + Thread clientSendThread = new Thread(new ClientSend(this, out, socket)); clientSendThread.start(); diff --git a/src/fr/univ/lyon1/client/ClientReceive.java b/src/fr/univ/lyon1/client/ClientReceive.java index 4a906b2..f71a9f1 100644 --- a/src/fr/univ/lyon1/client/ClientReceive.java +++ b/src/fr/univ/lyon1/client/ClientReceive.java @@ -1,7 +1,5 @@ package fr.univ.lyon1.client; -import fr.univ.lyon1.common.Message; - import java.io.IOException; import java.io.ObjectInputStream; import java.net.Socket; @@ -27,15 +25,15 @@ public class ClientReceive implements Runnable { } while(true) { - Message msg; + Object data; try { - msg = (Message) in.readObject(); + data = in.readObject(); } catch (ClassNotFoundException|IOException e) { if (e instanceof SocketException) { System.err.println("Connexion closed"); break; } - System.err.println("Fail to read message object !"); + System.err.println("Fail to read object !"); e.printStackTrace(); try { Thread.sleep(1000); @@ -45,10 +43,10 @@ public class ClientReceive implements Runnable { continue; } - if (msg == null) + if (data == null) break; - this.client.messageReceived(msg); + this.client.action(data); } try { diff --git a/src/fr/univ/lyon1/common/Channel.java b/src/fr/univ/lyon1/common/Channel.java new file mode 100644 index 0000000..1ef983d --- /dev/null +++ b/src/fr/univ/lyon1/common/Channel.java @@ -0,0 +1,32 @@ +package fr.univ.lyon1.common; + +import java.io.Serializable; +import java.util.UUID; + +public class Channel implements Serializable { + private final UUID uuid; + private String name; + + public Channel(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + public Channel(String name) { + this.uuid = UUID.randomUUID(); + this.name = name; + } + + public UUID getUUID() { + return uuid; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/fr/univ/lyon1/common/Message.java b/src/fr/univ/lyon1/common/Message.java index c8e5b8f..f179891 100644 --- a/src/fr/univ/lyon1/common/Message.java +++ b/src/fr/univ/lyon1/common/Message.java @@ -4,27 +4,43 @@ import java.io.Serializable; import java.util.UUID; public class Message implements Serializable { + private Channel channel; private User sender; private final String content; private final UUID uuid; - public Message(User sender, String content) { + public Message(Channel channel, User sender, String content) { this.uuid = UUID.randomUUID(); + this.channel = channel; this.sender = sender; this.content = content; } - public Message(UUID uuid, User sender, String content) { + public Message(UUID uuid, Channel channel, User sender, String content) { this.uuid = uuid; + this.channel = channel; this.sender = sender; this.content = content; } + public Message(String content) { + this.uuid = UUID.randomUUID(); + this.content = content; + } + + public Message repley(User user, String content) { + return new Message(this.channel, user, content); + } + public void setSender(User sender) { this.sender = sender; } + public Channel getChannel() { + return channel; + } + public User getSender() { return sender; } @@ -35,6 +51,9 @@ public class Message implements Serializable { @Override public String toString() { - return sender + ": " + content; + if (channel != null) + return "#"+channel+" "+sender+": "+content; + else + return sender + ": " + content; } } diff --git a/src/fr/univ/lyon1/common/User.java b/src/fr/univ/lyon1/common/User.java index 5a14351..dd0f0c8 100644 --- a/src/fr/univ/lyon1/common/User.java +++ b/src/fr/univ/lyon1/common/User.java @@ -1,16 +1,7 @@ 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; diff --git a/src/fr/univ/lyon1/common/channel/Channel.java b/src/fr/univ/lyon1/common/channel/Channel.java deleted file mode 100644 index fce3126..0000000 --- a/src/fr/univ/lyon1/common/channel/Channel.java +++ /dev/null @@ -1,13 +0,0 @@ -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; - } -} diff --git a/src/fr/univ/lyon1/common/channel/PrivateChannel.java b/src/fr/univ/lyon1/common/channel/PrivateChannel.java deleted file mode 100644 index 92ecbd8..0000000 --- a/src/fr/univ/lyon1/common/channel/PrivateChannel.java +++ /dev/null @@ -1,10 +0,0 @@ -package fr.univ.lyon1.common.channel; - -import java.util.UUID; - -public class PrivateChannel extends Channel { - - public PrivateChannel(UUID uuid, String name) { - super(uuid, name); - } -} diff --git a/src/fr/univ/lyon1/common/channel/PublicChannel.java b/src/fr/univ/lyon1/common/channel/PublicChannel.java deleted file mode 100644 index 068dec2..0000000 --- a/src/fr/univ/lyon1/common/channel/PublicChannel.java +++ /dev/null @@ -1,9 +0,0 @@ -package fr.univ.lyon1.common.channel; - -import java.util.UUID; - -public class PublicChannel extends Channel { - public PublicChannel(UUID uuid, String name) { - super(uuid, name); - } -} diff --git a/src/fr/univ/lyon1/server/ConnectedClient.java b/src/fr/univ/lyon1/server/ConnectedClient.java index 8f3de7c..3f583d7 100644 --- a/src/fr/univ/lyon1/server/ConnectedClient.java +++ b/src/fr/univ/lyon1/server/ConnectedClient.java @@ -1,7 +1,9 @@ package fr.univ.lyon1.server; +import fr.univ.lyon1.common.Channel; import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.User; +import fr.univ.lyon1.server.models.ChannelModel; import fr.univ.lyon1.server.models.UserModel; import java.io.EOFException; @@ -9,6 +11,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; +import java.util.List; public class ConnectedClient implements Runnable { private static int idCounter = 0; @@ -17,7 +20,7 @@ public class ConnectedClient implements Runnable { private final Socket socket; private final ObjectOutputStream out; private ObjectInputStream in; - private User user; + private UserModel user; ConnectedClient(Server server, Socket socket) throws IOException { this.server = server; @@ -66,16 +69,66 @@ public class ConnectedClient implements Runnable { return message; } + private void actionMessage(Object data) throws IOException, ClassNotFoundException { + Message msg = (Message) data; + + if (msg.getContent().startsWith("/")) + command(msg); + else { + msg.setSender(this.user); + server.broadcastMessage(msg, id); + } + } + + private void command(Message msg) throws IOException { + String content = msg.getContent(); + if (content.equals("/list")) { + List users = server.getUsers(); + sendMessage(msg.repley(Server.getServerUser(), "Total: "+users.toArray().length + "\n" + String.join(", ", users.stream().map(User::getUsername).toList()))); + } else if (content.startsWith("/join")) + actionJoinChannel(content); + } + + private void actionListUsers() throws IOException { + out.writeObject(server.getUsers()); + out.flush(); + } + + private void actionJoinChannel(String msg) throws IOException { + String name = msg.replaceAll("^(/?join) ", ""); + System.out.println(name); + ChannelModel chan = ChannelModel.get(name); + + if (chan == null) { + chan = new ChannelModel(name); + chan.addUser(user); + } else + if (!chan.have(user)) + chan.addUser(user); + + out.writeObject(chan); + out.flush(); + + server.broadcastMessage(new Message(chan, Server.getServerUser(), user.getUsername()+" joined the channel !"), -1); + } + public void run() { try { while (true) { - Message msg = (Message) in.readObject(); - if (msg == null) + Object data = in.readObject(); + if (data == null) break; - msg.setSender(this.user); - server.broadcastMessage(msg, id); + if (data instanceof Message) + actionMessage(data); + else if (data instanceof String) { + String msg = (String) data; + if (data.equals("listUsers")) + actionListUsers(); + else if (((String) data).startsWith("join")) + actionJoinChannel(msg); + } } } catch (IOException | ClassNotFoundException e) { if (!(e instanceof EOFException)) { @@ -97,4 +150,8 @@ public class ConnectedClient implements Runnable { public int getId() { return id; } + + public User getUser() { + return user; + } } diff --git a/src/fr/univ/lyon1/server/Server.java b/src/fr/univ/lyon1/server/Server.java index 3e7d64f..9ab29d6 100644 --- a/src/fr/univ/lyon1/server/Server.java +++ b/src/fr/univ/lyon1/server/Server.java @@ -2,6 +2,7 @@ package fr.univ.lyon1.server; import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.User; +import fr.univ.lyon1.server.models.UserChannelModel; import java.io.IOException; import java.util.ArrayList; @@ -21,19 +22,16 @@ public class Server { } public ConnectedClient addClient(ConnectedClient newClient) { - Message msg = new Message( serverUser, newClient.getId() + " is connected !"); - clients.add(newClient); - broadcastMessage(msg, -1); - - System.out.println("Client "+newClient.getId()+" is connected !"); + System.out.println("Client "+newClient.getUser().getUsername()+" is connected !"); return newClient; } public int broadcastMessage(Message message, int id) { - for (ConnectedClient client : clients) { + List users = UserChannelModel.getUsers(message.getChannel()).stream().map(User::getUUID).toList(); + for (ConnectedClient client : clients.stream().filter(connectedClient -> users.contains(connectedClient.getUser().getUUID())).toList()) { if (id == -1 || client.getId() != id) try { client.sendMessage(message); @@ -55,10 +53,6 @@ public class Server { clients.remove(client); - Message msg = new Message(serverUser, "Client "+client.getId()+" is disconnected"); - - broadcastMessage(msg, -1); - System.out.println("Client "+client.getId()+" disconnected"); return client; } @@ -66,4 +60,12 @@ public class Server { public int getPort() { return port; } + + public static User getServerUser() { + return serverUser; + } + + public List getUsers() { + return clients.stream().map(ConnectedClient::getUser).toList(); + } } diff --git a/src/fr/univ/lyon1/server/models/ChannelModel.java b/src/fr/univ/lyon1/server/models/ChannelModel.java new file mode 100644 index 0000000..3c744dc --- /dev/null +++ b/src/fr/univ/lyon1/server/models/ChannelModel.java @@ -0,0 +1,112 @@ +package fr.univ.lyon1.server.models; + +import fr.univ.lyon1.common.Channel; +import fr.univ.lyon1.common.User; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.UUID; + +public class ChannelModel extends Channel implements Model { + private static final String TABLE_NAME = "Channel"; + + public ChannelModel(String name) { + super(name); + create(); + } + + private ChannelModel(UUID uuid, String name) { + super(uuid, name); + } + + public void addUser(User user) { + new UserChannelModel(user, this); + } + + public boolean have(User user) { + return UserChannelModel.exist(user, this); + } + + public static ChannelModel get(String name) { + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT UUID FROM "+TABLE_NAME+" WHERE name = ?"); + ps.setString(1, name); + 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 ChannelModel 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 ChannelModel( + UUID.fromString(rs.getString("UUID")), + rs.getString("NAME") + ); + } + } 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, name) VALUES (?, ?)"); + ps.setString(1, super.getUUID().toString()); + ps.setString(2, super.getName()); + 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 name = ? WHERE UUID = ?"); + ps.setString(1, super.getName()); + 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, name varchar(16) unique )"); + ps.executeUpdate(); + } catch (SQLException err) { + err.printStackTrace(); + } + } +} diff --git a/src/fr/univ/lyon1/server/models/Model.java b/src/fr/univ/lyon1/server/models/Model.java index aca3034..5903bdb 100644 --- a/src/fr/univ/lyon1/server/models/Model.java +++ b/src/fr/univ/lyon1/server/models/Model.java @@ -5,6 +5,4 @@ import fr.univ.lyon1.server.Database; public interface Model { Database database = Database.getDatabase(); - - } diff --git a/src/fr/univ/lyon1/server/models/UserChannelModel.java b/src/fr/univ/lyon1/server/models/UserChannelModel.java new file mode 100644 index 0000000..3858d62 --- /dev/null +++ b/src/fr/univ/lyon1/server/models/UserChannelModel.java @@ -0,0 +1,96 @@ +package fr.univ.lyon1.server.models; + +import fr.univ.lyon1.common.Channel; +import fr.univ.lyon1.common.User; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class UserChannelModel implements Model { + private User user; + private Channel channel; + + private static final String TABLE_NAME = "UserChannel"; + + public UserChannelModel(User user, Channel channel) { + this.user = user; + this.channel = channel; + + if (!exist(user, channel)) + create(); + } + + public static List getUsers(Channel channel) { + List users = new ArrayList<>(); + + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT userUUID FROM "+TABLE_NAME+" WHERE channelUUID = ?"); + ps.setString(1, channel.getUUID().toString()); + if (ps.execute()) { + ResultSet rs = ps.getResultSet(); + while (rs.next()) + users.add(UserModel.get(UUID.fromString(rs.getString("userUUID")))); + } + } catch (SQLException err) { + err.printStackTrace(); + return null; + } + return users; + } + + public static List getChannels(User user) { + List channels = new ArrayList<>(); + + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT channelUUID FROM "+TABLE_NAME+" WHERE userUUID = ?"); + ps.setString(1, user.getUUID().toString()); + if (ps.execute()) { + ResultSet rs = ps.getResultSet(); + while (rs.next()) + channels.add(ChannelModel.get(UUID.fromString(rs.getString("channelUUID")))); + } + } catch (SQLException err) { + err.printStackTrace(); + return null; + } + return channels; + } + + public static boolean exist(User user, Channel channel) { + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT 1 FROM "+TABLE_NAME+" WHERE userUUID = ? AND channelUUID = ?"); + ps.setString(1, user.getUUID().toString()); + ps.setString(2, channel.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+" (userUUID, channelUUID) VALUES (?, ?)"); + ps.setString(1, user.getUUID().toString()); + ps.setString(2, channel.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+" (userUUID varchar(40) not null references User(UUID), channelUUID varchar(40) not null references Channel(UUID), PRIMARY KEY (userUUID, channelUUID))"); + ps.executeUpdate(); + } catch (SQLException err) { + err.printStackTrace(); + } + } +} From f8a5cde8bf40edb5823b4f37619363ed387070e9 Mon Sep 17 00:00:00 2001 From: Florian Charlaix Date: Wed, 5 Jan 2022 08:58:33 +0100 Subject: [PATCH 3/6] Setup commands action --- src/fr/univ/lyon1/client/Client.java | 98 ++++++++------ src/fr/univ/lyon1/client/ClientReceive.java | 6 +- src/fr/univ/lyon1/common/Message.java | 3 +- src/fr/univ/lyon1/common/command/Command.java | 23 ++++ .../lyon1/common/command/CommandType.java | 27 ++++ .../lyon1/common/exception/ChatException.java | 7 + .../lyon1/common/exception/LoginInvalid.java | 7 + .../lyon1/common/exception/LoginRequired.java | 7 + .../common/exception/UnknownCommand.java | 9 ++ src/fr/univ/lyon1/gui/ClientGUI.java | 9 +- src/fr/univ/lyon1/server/ConnectedClient.java | 126 +++++++++--------- src/fr/univ/lyon1/server/Server.java | 3 - src/module-info.java | 1 + 13 files changed, 210 insertions(+), 116 deletions(-) create mode 100644 src/fr/univ/lyon1/common/command/Command.java create mode 100644 src/fr/univ/lyon1/common/command/CommandType.java create mode 100644 src/fr/univ/lyon1/common/exception/ChatException.java create mode 100644 src/fr/univ/lyon1/common/exception/LoginInvalid.java create mode 100644 src/fr/univ/lyon1/common/exception/LoginRequired.java create mode 100644 src/fr/univ/lyon1/common/exception/UnknownCommand.java diff --git a/src/fr/univ/lyon1/client/Client.java b/src/fr/univ/lyon1/client/Client.java index 33afdfe..abb6001 100644 --- a/src/fr/univ/lyon1/client/Client.java +++ b/src/fr/univ/lyon1/client/Client.java @@ -1,51 +1,39 @@ package fr.univ.lyon1.client; +import fr.univ.lyon1.common.Channel; import fr.univ.lyon1.common.Message; -import fr.univ.lyon1.common.User; +import fr.univ.lyon1.common.command.Command; +import fr.univ.lyon1.common.command.CommandType; +import fr.univ.lyon1.common.exception.ChatException; +import fr.univ.lyon1.common.exception.UnknownCommand; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; +import java.util.ArrayList; import java.util.List; public class Client { private final int port; private final String address; + private final String username; + private final String password; protected final Socket socket; protected final ObjectOutputStream out; private ObjectInputStream in; + private List channels = new ArrayList<>(); protected boolean started = false; - public Client(String address, int port, String uuid, String password) throws Exception { + + public Client(String address, int port, String username, String password) throws Exception { this.address = address; this.port = port; + this.username = username; + this.password = password; socket = new Socket(address, port); out = new ObjectOutputStream(socket.getOutputStream()); - while (!this.auth(uuid, password)); - out.writeObject("listUsers"); - out.flush(); - out.writeObject("join general"); - out.flush(); - } - - 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 { @@ -60,7 +48,7 @@ public class Client { public String sendMessage(String content) { try { - out.writeObject(new Message(content)); + out.writeObject(new Command(CommandType.message, List.of(new Message(content, channels.get(0))))); out.flush(); } catch (IOException e) { System.err.println("Fail to send message !"); @@ -70,26 +58,51 @@ public class Client { return content; } - public Message messageReceived(Message msg) { - System.out.println(); - System.out.println(msg); - return msg; + public void action(Object data) throws IOException { + if (data instanceof Command) + command((Command) data); + else if (data instanceof ChatException) + ((ChatException) data).printStackTrace(); + else { + out.writeObject(new UnknownCommand()); + out.flush(); + } } - public void action(Object data) { - if (data instanceof Message) - messageReceived((Message) data); - else if (data instanceof List) { - List tmpList = (List) data; - if (tmpList.get(0) instanceof User) { - List users = (List) data; - for (User u : users) { - System.out.println(u); - } - } + private void command(Command cmd) throws IOException { + switch (cmd.getType()) { + case login -> commandLogin(); + case message -> commandMessage(cmd); + case list -> commandList(cmd); + case join -> commandJoin(cmd); } } + private void commandLogin() throws IOException { + out.writeObject(new Command(CommandType.list, null)); + out.flush(); + out.writeObject(new Command(CommandType.join, List.of("general"))); + out.flush(); + } + + protected void commandMessage(Command cmd) { + System.out.println(); + System.out.println(cmd.getArgs().get(0)); + } + + private void commandList(Command cmd) { + List users = cmd.getArgs(); + for (Object u : users) { + System.out.println(u); + } + } + + private void commandJoin(Command cmd) { + Channel chan = (Channel) cmd.getArgs().get(0); + channels.add(chan); + System.out.println("You join "+chan); + } + public void run() throws InterruptedException, IOException { if (started) return; @@ -102,6 +115,9 @@ public class Client { started = true; + out.writeObject(new Command(CommandType.login, List.of(username, password))); + out.flush(); + clientSendThread.join(); socket.close(); clientReceiveThread.interrupt(); diff --git a/src/fr/univ/lyon1/client/ClientReceive.java b/src/fr/univ/lyon1/client/ClientReceive.java index f71a9f1..4f5aa9d 100644 --- a/src/fr/univ/lyon1/client/ClientReceive.java +++ b/src/fr/univ/lyon1/client/ClientReceive.java @@ -46,7 +46,11 @@ public class ClientReceive implements Runnable { if (data == null) break; - this.client.action(data); + try { + this.client.action(data); + } catch (IOException e) { + e.printStackTrace(); + } } try { diff --git a/src/fr/univ/lyon1/common/Message.java b/src/fr/univ/lyon1/common/Message.java index f179891..29d6786 100644 --- a/src/fr/univ/lyon1/common/Message.java +++ b/src/fr/univ/lyon1/common/Message.java @@ -24,9 +24,10 @@ public class Message implements Serializable { this.content = content; } - public Message(String content) { + public Message(String content, Channel channel) { this.uuid = UUID.randomUUID(); this.content = content; + this.channel = channel; } public Message repley(User user, String content) { diff --git a/src/fr/univ/lyon1/common/command/Command.java b/src/fr/univ/lyon1/common/command/Command.java new file mode 100644 index 0000000..d3e5ea6 --- /dev/null +++ b/src/fr/univ/lyon1/common/command/Command.java @@ -0,0 +1,23 @@ +package fr.univ.lyon1.common.command; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class Command implements Serializable { + private final CommandType type; + private final List args; + + public Command(CommandType type, List args) { + this.type = type; + this.args = args; + } + + public CommandType getType() { + return type; + } + + public List getArgs() { + return new ArrayList<>(args); + } +} diff --git a/src/fr/univ/lyon1/common/command/CommandType.java b/src/fr/univ/lyon1/common/command/CommandType.java new file mode 100644 index 0000000..6e36acc --- /dev/null +++ b/src/fr/univ/lyon1/common/command/CommandType.java @@ -0,0 +1,27 @@ +package fr.univ.lyon1.common.command; + +import java.io.Serializable; + +public enum CommandType implements Serializable { + login("login", "Login to the server"), + message("message", "Send a message"), + join("join", "Join a channel"), + leave("leave", "Leave a channel"), + list("list", "List all users"), + listChannels("listChannels", "List all channels"); + + private final String name; + private final String description; + CommandType(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} diff --git a/src/fr/univ/lyon1/common/exception/ChatException.java b/src/fr/univ/lyon1/common/exception/ChatException.java new file mode 100644 index 0000000..7749e46 --- /dev/null +++ b/src/fr/univ/lyon1/common/exception/ChatException.java @@ -0,0 +1,7 @@ +package fr.univ.lyon1.common.exception; + +public class ChatException extends Exception { + public ChatException(String message) { + super(message); + } +} diff --git a/src/fr/univ/lyon1/common/exception/LoginInvalid.java b/src/fr/univ/lyon1/common/exception/LoginInvalid.java new file mode 100644 index 0000000..b412ec0 --- /dev/null +++ b/src/fr/univ/lyon1/common/exception/LoginInvalid.java @@ -0,0 +1,7 @@ +package fr.univ.lyon1.common.exception; + +public class LoginInvalid extends ChatException { + public LoginInvalid(String message) { + super(message); + } +} diff --git a/src/fr/univ/lyon1/common/exception/LoginRequired.java b/src/fr/univ/lyon1/common/exception/LoginRequired.java new file mode 100644 index 0000000..72060ec --- /dev/null +++ b/src/fr/univ/lyon1/common/exception/LoginRequired.java @@ -0,0 +1,7 @@ +package fr.univ.lyon1.common.exception; + +public class LoginRequired extends ChatException { + public LoginRequired() { + super("Login required"); + } +} diff --git a/src/fr/univ/lyon1/common/exception/UnknownCommand.java b/src/fr/univ/lyon1/common/exception/UnknownCommand.java new file mode 100644 index 0000000..32a7c29 --- /dev/null +++ b/src/fr/univ/lyon1/common/exception/UnknownCommand.java @@ -0,0 +1,9 @@ +package fr.univ.lyon1.common.exception; + +import fr.univ.lyon1.common.command.Command; + +public class UnknownCommand extends ChatException { + public UnknownCommand() { + super("Command unknown"); + } +} diff --git a/src/fr/univ/lyon1/gui/ClientGUI.java b/src/fr/univ/lyon1/gui/ClientGUI.java index cb19ab2..18acf9e 100644 --- a/src/fr/univ/lyon1/gui/ClientGUI.java +++ b/src/fr/univ/lyon1/gui/ClientGUI.java @@ -2,22 +2,21 @@ package fr.univ.lyon1.gui; import fr.univ.lyon1.client.Client; import fr.univ.lyon1.client.ClientReceive; -import fr.univ.lyon1.common.Message; +import fr.univ.lyon1.common.command.Command; import java.io.IOException; public class ClientGUI extends Client { private final MainGui gui; - public ClientGUI(MainGui gui, String address, int port) throws IOException, InterruptedException, Exception { + public ClientGUI(MainGui gui, String address, int port) throws Exception { super(address, port, null, null); this.gui = gui; } @Override - public Message messageReceived(Message msg) { - gui.receiveMessage(msg.toString()); - return msg; + protected void commandMessage(Command cmd) { + gui.receiveMessage(cmd.getArgs().get(0).toString()); } @Override diff --git a/src/fr/univ/lyon1/server/ConnectedClient.java b/src/fr/univ/lyon1/server/ConnectedClient.java index 3f583d7..74ce713 100644 --- a/src/fr/univ/lyon1/server/ConnectedClient.java +++ b/src/fr/univ/lyon1/server/ConnectedClient.java @@ -3,6 +3,12 @@ package fr.univ.lyon1.server; import fr.univ.lyon1.common.Channel; import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.User; +import fr.univ.lyon1.common.command.Command; +import fr.univ.lyon1.common.command.CommandType; +import fr.univ.lyon1.common.exception.ChatException; +import fr.univ.lyon1.common.exception.LoginInvalid; +import fr.univ.lyon1.common.exception.LoginRequired; +import fr.univ.lyon1.common.exception.UnknownCommand; import fr.univ.lyon1.server.models.ChannelModel; import fr.univ.lyon1.server.models.UserModel; @@ -11,6 +17,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; +import java.util.Collections; import java.util.List; public class ConnectedClient implements Runnable { @@ -26,77 +33,67 @@ public class ConnectedClient implements Runnable { this.server = server; this.socket = socket; 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; + this.in = new ObjectInputStream(socket.getInputStream()); } public Message sendMessage(Message message) throws IOException { - out.writeObject(message); + out.writeObject(new Command(CommandType.message, List.of(message))); out.flush(); return message; } - private void actionMessage(Object data) throws IOException, ClassNotFoundException { - Message msg = (Message) data; + private void actionCommand(Command command) throws IOException, ChatException { + CommandType type = command.getType(); + if (user == null && type != CommandType.login) + throw new LoginRequired(); - if (msg.getContent().startsWith("/")) - command(msg); - else { - msg.setSender(this.user); - server.broadcastMessage(msg, id); + switch (command.getType()) { + case login -> commandLogin(command); + case message -> commandMessage(command); + case list -> commandList(); + case join -> commandJoin(command); } } - private void command(Message msg) throws IOException { - String content = msg.getContent(); - if (content.equals("/list")) { - List users = server.getUsers(); - sendMessage(msg.repley(Server.getServerUser(), "Total: "+users.toArray().length + "\n" + String.join(", ", users.stream().map(User::getUsername).toList()))); - } else if (content.startsWith("/join")) - actionJoinChannel(content); + private void commandLogin(Command cmd) throws IOException, ChatException { + List args = cmd.getArgs(); + + String username = (String) args.get(0); + System.out.println("username: "+username); + String password = (String) args.get(1); + System.out.println("Pass: "+password); + + if (username.isEmpty() || password.isEmpty()) + throw new LoginInvalid("Invalid args"); + + UserModel user = UserModel.get(username); + + if (user == null) + throw new LoginInvalid("Username not found"); + else if (!user.checkPassword(password)) + throw new LoginInvalid("Password invalid"); + else { + out.writeObject(new Command(CommandType.login, null)); + out.flush(); + this.user = user; + System.out.println("Client "+user.getUsername()+" is connected !"); + } } - private void actionListUsers() throws IOException { - out.writeObject(server.getUsers()); + private void commandMessage(Command cmd) { + Message msg = (Message) cmd.getArgs().get(0); + msg.setSender(this.user); + // ToDo: Check the user channel + server.broadcastMessage(msg, id); + } + + private void commandList() throws IOException { + out.writeObject(new Command(CommandType.list, Collections.singletonList(server.getUsers()))); out.flush(); } - private void actionJoinChannel(String msg) throws IOException { - String name = msg.replaceAll("^(/?join) ", ""); - System.out.println(name); + private void commandJoin(Command cmd) throws IOException { + String name = (String) cmd.getArgs().get(0); ChannelModel chan = ChannelModel.get(name); if (chan == null) { @@ -106,7 +103,7 @@ public class ConnectedClient implements Runnable { if (!chan.have(user)) chan.addUser(user); - out.writeObject(chan); + out.writeObject(new Command(CommandType.join, List.of((Channel) chan))); out.flush(); server.broadcastMessage(new Message(chan, Server.getServerUser(), user.getUsername()+" joined the channel !"), -1); @@ -115,19 +112,18 @@ public class ConnectedClient implements Runnable { public void run() { try { while (true) { - Object data = in.readObject(); if (data == null) break; - if (data instanceof Message) - actionMessage(data); - else if (data instanceof String) { - String msg = (String) data; - if (data.equals("listUsers")) - actionListUsers(); - else if (((String) data).startsWith("join")) - actionJoinChannel(msg); + try { + if (data instanceof Command) + actionCommand((Command) data); + else + throw new UnknownCommand(); + } catch (ChatException e) { + out.writeObject(e); + out.flush(); } } } catch (IOException | ClassNotFoundException e) { diff --git a/src/fr/univ/lyon1/server/Server.java b/src/fr/univ/lyon1/server/Server.java index 9ab29d6..8bf6058 100644 --- a/src/fr/univ/lyon1/server/Server.java +++ b/src/fr/univ/lyon1/server/Server.java @@ -23,9 +23,6 @@ public class Server { public ConnectedClient addClient(ConnectedClient newClient) { clients.add(newClient); - - System.out.println("Client "+newClient.getUser().getUsername()+" is connected !"); - return newClient; } diff --git a/src/module-info.java b/src/module-info.java index b6f9ef6..670c9b7 100644 --- a/src/module-info.java +++ b/src/module-info.java @@ -8,5 +8,6 @@ module fr.univ.lyon1.gui { requires org.mariadb.jdbc; opens fr.univ.lyon1.gui to javafx.fxml; + exports fr.univ.lyon1.common.command; exports fr.univ.lyon1.gui; } \ No newline at end of file From 7caec71de3e0b008e64f2847814682a7d712452b Mon Sep 17 00:00:00 2001 From: Florian Charlaix Date: Wed, 5 Jan 2022 09:05:11 +0100 Subject: [PATCH 4/6] Init database with tables and test user --- src/fr/univ/lyon1/server/Database.java | 17 +++++++++++++++++ .../univ/lyon1/server/models/ChannelModel.java | 2 +- .../lyon1/server/models/UserChannelModel.java | 2 +- src/fr/univ/lyon1/server/models/UserModel.java | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/fr/univ/lyon1/server/Database.java b/src/fr/univ/lyon1/server/Database.java index b3651ab..8756122 100644 --- a/src/fr/univ/lyon1/server/Database.java +++ b/src/fr/univ/lyon1/server/Database.java @@ -1,5 +1,9 @@ package fr.univ.lyon1.server; +import fr.univ.lyon1.server.models.ChannelModel; +import fr.univ.lyon1.server.models.UserChannelModel; +import fr.univ.lyon1.server.models.UserModel; + import java.io.*; import java.sql.Connection; import java.sql.DriverManager; @@ -18,6 +22,8 @@ public class Database { err.printStackTrace(); System.exit(1); } + + init(); } private String[] getCredentials() throws NullPointerException, IOException { @@ -59,4 +65,15 @@ public class Database { return new Database(); return Database.database; } + + private void init() { + UserModel.generateTable(); + ChannelModel.generateTable(); + UserChannelModel.generateTable(); + + if (UserModel.get("test") == null) { + UserModel u = new UserModel("test", "test"); + u.save(); + } + } } diff --git a/src/fr/univ/lyon1/server/models/ChannelModel.java b/src/fr/univ/lyon1/server/models/ChannelModel.java index 3c744dc..a5d52fa 100644 --- a/src/fr/univ/lyon1/server/models/ChannelModel.java +++ b/src/fr/univ/lyon1/server/models/ChannelModel.java @@ -103,7 +103,7 @@ public class ChannelModel extends Channel implements Model { public static void generateTable() { try { - PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE "+TABLE_NAME+" ( UUID varchar(40) primary key, name varchar(16) unique )"); + PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS "+TABLE_NAME+" ( UUID varchar(40) primary key, name varchar(16) unique )"); ps.executeUpdate(); } catch (SQLException err) { err.printStackTrace(); diff --git a/src/fr/univ/lyon1/server/models/UserChannelModel.java b/src/fr/univ/lyon1/server/models/UserChannelModel.java index 3858d62..8e7b728 100644 --- a/src/fr/univ/lyon1/server/models/UserChannelModel.java +++ b/src/fr/univ/lyon1/server/models/UserChannelModel.java @@ -87,7 +87,7 @@ public class UserChannelModel implements Model { public static void generateTable() { try { - PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE "+TABLE_NAME+" (userUUID varchar(40) not null references User(UUID), channelUUID varchar(40) not null references Channel(UUID), PRIMARY KEY (userUUID, channelUUID))"); + PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS "+TABLE_NAME+" (userUUID varchar(40) not null references User(UUID), channelUUID varchar(40) not null references Channel(UUID), PRIMARY KEY (userUUID, channelUUID))"); ps.executeUpdate(); } catch (SQLException err) { err.printStackTrace(); diff --git a/src/fr/univ/lyon1/server/models/UserModel.java b/src/fr/univ/lyon1/server/models/UserModel.java index 95280db..4624640 100644 --- a/src/fr/univ/lyon1/server/models/UserModel.java +++ b/src/fr/univ/lyon1/server/models/UserModel.java @@ -118,7 +118,7 @@ public class UserModel extends User implements Model { 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) )"); + PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS "+TABLE_NAME+" ( UUID varchar(40) primary key, username varchar(16) unique, password varchar(256) )"); ps.executeUpdate(); } catch (SQLException err) { err.printStackTrace(); From 38b86b16ded5ce9c8cf7ad4737c39d1ed712dbd9 Mon Sep 17 00:00:00 2001 From: Florian Charlaix Date: Wed, 5 Jan 2022 10:00:07 +0100 Subject: [PATCH 5/6] Setup SSL for WebSocket connection --- src/fr/univ/lyon1/client/Client.java | 20 +++++++- src/fr/univ/lyon1/common/ChatSSL.java | 57 +++++++++++++++++++++++ src/fr/univ/lyon1/server/Connection.java | 18 ++++++- src/main/resources/servercert.p12 | Bin 0 -> 1204 bytes 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/fr/univ/lyon1/common/ChatSSL.java create mode 100644 src/main/resources/servercert.p12 diff --git a/src/fr/univ/lyon1/client/Client.java b/src/fr/univ/lyon1/client/Client.java index abb6001..ec650a1 100644 --- a/src/fr/univ/lyon1/client/Client.java +++ b/src/fr/univ/lyon1/client/Client.java @@ -1,12 +1,17 @@ package fr.univ.lyon1.client; import fr.univ.lyon1.common.Channel; +import fr.univ.lyon1.common.ChatSSL; import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.command.Command; import fr.univ.lyon1.common.command.CommandType; import fr.univ.lyon1.common.exception.ChatException; import fr.univ.lyon1.common.exception.UnknownCommand; +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -31,11 +36,24 @@ public class Client { this.port = port; this.username = username; this.password = password; - socket = new Socket(address, port); + socket = initSSL(); out = new ObjectOutputStream(socket.getOutputStream()); getIn(); } + private Socket initSSL() throws IOException { + SSLContext ctx = ChatSSL.getSSLContext(); + + SocketFactory factory = ctx.getSocketFactory(); + + Socket connection = factory.createSocket(address, port); + ((SSLSocket) connection).setEnabledProtocols(new String[] {ChatSSL.tlsVersion}); + SSLParameters sslParams = new SSLParameters(); + sslParams.setEndpointIdentificationAlgorithm("HTTPS"); + ((SSLSocket) connection).setSSLParameters(sslParams); + return connection; + } + public void disconnectedServer() throws IOException { socket.close(); out.close(); diff --git a/src/fr/univ/lyon1/common/ChatSSL.java b/src/fr/univ/lyon1/common/ChatSSL.java new file mode 100644 index 0000000..f00b7c1 --- /dev/null +++ b/src/fr/univ/lyon1/common/ChatSSL.java @@ -0,0 +1,57 @@ +package fr.univ.lyon1.common; + +import fr.univ.lyon1.server.Connection; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.CertificateException; + +/* +keytool -genkeypair -alias server -keyalg EC \ +-sigalg SHA384withECDSA -keysize 256 -keystore servercert.p12 \ +-storetype pkcs12 -v -storepass abc123 -validity 10000 -ext san=ip:127.0.0.1 + */ + +public class ChatSSL { + public static String trustStoreName = "servercert.p12"; + public static String keyStoreName = "servercert.p12"; + public static String tlsVersion = "TLSv1.2"; + private static char[] trustStorePassword = "abc123".toCharArray(); + private static char[] keyStorePassword = "abc123".toCharArray(); + + public static SSLContext getSSLContext() { + + try { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream tstore = Connection.class + .getResourceAsStream("/" + trustStoreName); + trustStore.load(tstore, trustStorePassword); + tstore.close(); + TrustManagerFactory tmf = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream kstore = Connection.class + .getResourceAsStream("/" + keyStoreName); + keyStore.load(kstore, keyStorePassword); + KeyManagerFactory kmf = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, keyStorePassword); + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), + SecureRandom.getInstanceStrong()); + + return ctx; + } catch (KeyStoreException | IOException | NoSuchAlgorithmException | KeyManagementException | CertificateException | UnrecoverableKeyException e) { + e.printStackTrace(); + System.exit(1); + } + + return null; + } +} diff --git a/src/fr/univ/lyon1/server/Connection.java b/src/fr/univ/lyon1/server/Connection.java index 7ab3b09..251f763 100644 --- a/src/fr/univ/lyon1/server/Connection.java +++ b/src/fr/univ/lyon1/server/Connection.java @@ -1,5 +1,8 @@ package fr.univ.lyon1.server; +import fr.univ.lyon1.common.ChatSSL; + +import javax.net.ssl.*; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; @@ -10,7 +13,20 @@ public class Connection implements Runnable { Connection(Server server) throws IOException { this.server = server; - this.serverSocket = new ServerSocket(server.getPort()); + this.serverSocket = initSSL(); + } + + private SSLServerSocket initSSL() throws IOException { + + SSLContext ctx = ChatSSL.getSSLContext(); + + SSLServerSocketFactory factory = ctx.getServerSocketFactory(); + ServerSocket listener = factory.createServerSocket(server.getPort()); + SSLServerSocket sslListener = (SSLServerSocket) listener; + + sslListener.setNeedClientAuth(true); + sslListener.setEnabledProtocols(new String[]{ChatSSL.tlsVersion}); + return sslListener; } public void run() { diff --git a/src/main/resources/servercert.p12 b/src/main/resources/servercert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..19df118305fa21b898f97669169f69329767f5d6 GIT binary patch literal 1204 zcmXqLV%fmN$ZXKW62-=;)#lOmotKfFaX}M{H%k+XJ5bo!povinMT$|HrHN4-C@c)b z{A}D%T|8WjObZ(K8Z_=QNQ0|q!Px@3%g8-nmp%d%g)VfpkFTvdXIBNQL{?!tUiUq&7D+F*Rg3 zZW47?G|zuK-6P#>*|PVQ$(l-jXDsH&b#8C-4A}AY&Y7Pr(xxABG%kMNPg>SnocuX{ z=gV1%dG3*qly3*8@;tUn3H-RwTIS4k#Q>Gwy(=f|D!THsJ>%-%fNTTn`6X=et!M0Z zPpQtodq`@H_;TO$SN-#+*O}=rs!94afxByG>%>Z-+jnC>+I_n6disl*tIQ;ioRDUE z{98M&*R*1PzS*S>iOade*0!CW?Pa|6is?-6C$EGWL+hC)vvDudP3Ny-HyoYCRH*zg*7jpNrckKIrW0&uQ5Y2L? z%gg#1JU4xH{PAhUyuI_4%eH?~^iXjVakBaGX0`E$8$NlHj+8DpSkUyb_LSD{U8$!Y zF+Nc`aNIm>GJCma({7IQaZ$czYXi0(vR?O~b-BjW1*vo1zh8OltJA{Q&tINh>hsBT zj}rTfurMag7m*ut)wZ}Cl)5TX=*@WaWv+tAtY7mVwM`IxwZi_C>*Gz#xm8|z>Tjwx zlqmK0|GRQH^KO!;T+;tLVe8o4rp%7dikUF=w%|^SU+i|X(vP2>`uSj2q_Fp1$(hrO zew6qzzL!7r`a`%~+V_NNwY**B*{eAd+GpjQ-8kFoQOMf0)8wbT$oX8Tdp^nH_3FK8 z^IiXnPKe73c+vjrhREFMZD%Yc`gYkr`}<4!`K%tE??n~v zdF|7ATTai(Et$VOz~IPNm+odR=@`EA{v83XO9L&udg?cadp>k?FIaYbXZEB8qHzYk z28IT_@Vv~#$jZQ?aHvpFvaMv!g-=~e{5Ee9@LiU6?3O9}oLBKVD=)KhXC7e@QMY-< Y{*w7?4VMy&Tb Date: Fri, 7 Jan 2022 10:25:57 +0100 Subject: [PATCH 6/6] Add channel check on message broadcast --- .../univ/lyon1/common/exception/NotInChannel.java | 9 +++++++++ src/fr/univ/lyon1/server/ConnectedClient.java | 15 ++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 src/fr/univ/lyon1/common/exception/NotInChannel.java diff --git a/src/fr/univ/lyon1/common/exception/NotInChannel.java b/src/fr/univ/lyon1/common/exception/NotInChannel.java new file mode 100644 index 0000000..5a2473f --- /dev/null +++ b/src/fr/univ/lyon1/common/exception/NotInChannel.java @@ -0,0 +1,9 @@ +package fr.univ.lyon1.common.exception; + +import fr.univ.lyon1.common.Channel; + +public class NotInChannel extends ChatException { + public NotInChannel(Channel channel) { + super("Your not in channel "+channel); + } +} diff --git a/src/fr/univ/lyon1/server/ConnectedClient.java b/src/fr/univ/lyon1/server/ConnectedClient.java index 74ce713..09f98c6 100644 --- a/src/fr/univ/lyon1/server/ConnectedClient.java +++ b/src/fr/univ/lyon1/server/ConnectedClient.java @@ -5,10 +5,7 @@ import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.User; import fr.univ.lyon1.common.command.Command; import fr.univ.lyon1.common.command.CommandType; -import fr.univ.lyon1.common.exception.ChatException; -import fr.univ.lyon1.common.exception.LoginInvalid; -import fr.univ.lyon1.common.exception.LoginRequired; -import fr.univ.lyon1.common.exception.UnknownCommand; +import fr.univ.lyon1.common.exception.*; import fr.univ.lyon1.server.models.ChannelModel; import fr.univ.lyon1.server.models.UserModel; @@ -80,11 +77,15 @@ public class ConnectedClient implements Runnable { } } - private void commandMessage(Command cmd) { + private void commandMessage(Command cmd) throws NotInChannel { Message msg = (Message) cmd.getArgs().get(0); msg.setSender(this.user); - // ToDo: Check the user channel - server.broadcastMessage(msg, id); + + ChannelModel chan = ChannelModel.get(msg.getChannel().getUUID()); + if (chan == null || !chan.have(this.user)) + throw new NotInChannel(chan); + else + server.broadcastMessage(msg, id); } private void commandList() throws IOException {