Commit 06696691 authored by Vetle.Hjelmtvedt's avatar Vetle.Hjelmtvedt
Browse files

Merge branch 'task4' into 'master'

Task4

See merge request !10
parents 9f7d3f39 121b77d6
......@@ -11,5 +11,5 @@
<component name="PDMPlugin">
<option name="skipTestSources" value="false" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_15" default="true" project-jdk-name="15" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="false" project-jdk-name="15" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
package inf226.inchat;
import com.lambdaworks.crypto.SCryptUtil;
import inf226.util.Triple;
import inf226.util.immutable.List;
import inf226.util.Pair;
......@@ -16,12 +17,12 @@ public final class Account {
* and a list of channels which the user can post to.
*/
public final Stored<User> user;
public final List<Pair<String,Stored<Channel>>> channels;
public final List<Triple<String,Stored<Channel>,Role>> channels;
public final String hashedPassword;
public Account(final Stored<User> user,
final List<Pair<String,Stored<Channel>>> channels,
final List<Triple<String,Stored<Channel>,Role>> channels,
final String hashedPassword) {
this.user = user;
this.channels = channels;
......@@ -45,15 +46,9 @@ public final class Account {
*
* @return A new account object with the channel added.
*/
public Account joinChannel(final String alias,
final Stored<Channel> channel) {
Pair<String,Stored<Channel>> entry
= new Pair<String,Stored<Channel>>(alias,channel);
return new Account
(user,
List.cons(entry,
channels),
hashedPassword);
public Account joinChannel(final String alias, final Stored<Channel> channel, final Role role) {
Triple<String,Stored<Channel>,Role> entry = new Triple<String,Stored<Channel>,Role>(alias,channel, role);
return new Account (user,List.cons(entry,channels),hashedPassword);
}
......
......@@ -37,7 +37,7 @@ public final class AccountStorage
connection.createStatement()
.executeUpdate("CREATE TABLE IF NOT EXISTS Account (id TEXT PRIMARY KEY, version TEXT, user TEXT, password TEXT, FOREIGN KEY(user) REFERENCES User(id) ON DELETE CASCADE)");
connection.createStatement()
.executeUpdate("CREATE TABLE IF NOT EXISTS AccountChannel (account TEXT, channel TEXT, alias TEXT, ordinal INTEGER, PRIMARY KEY(account,channel), FOREIGN KEY(account) REFERENCES Account(id) ON DELETE CASCADE, FOREIGN KEY(channel) REFERENCES Channel(id) ON DELETE CASCADE)");
.executeUpdate("CREATE TABLE IF NOT EXISTS AccountChannel (account TEXT, channel TEXT, alias TEXT, ordinal INTEGER, role TEXT, PRIMARY KEY(account,channel), FOREIGN KEY(account) REFERENCES Account(id) ON DELETE CASCADE, FOREIGN KEY(channel) REFERENCES Channel(id) ON DELETE CASCADE)");
}
@Override
......@@ -59,12 +59,14 @@ public final class AccountStorage
account.channels.forEach(element -> {
String alias = element.first;
Stored<Channel> channel = element.second;
Role role = element.third;
try {
PreparedStatement statement1 = connection.prepareStatement("INSERT INTO AccountChannel VALUES(?,?,?,?");
PreparedStatement statement1 = connection.prepareStatement("INSERT INTO AccountChannel VALUES(?,?,?,?,?");
statement1.setObject(1, stored.identity);
statement1.setObject(2, channel.identity);
statement1.setString(3, alias);
statement1.setString(4, ordinal.get().toString());
statement1.setString(5, role.toString());
//Execute statement
statement1.executeUpdate();
......@@ -104,13 +106,14 @@ public final class AccountStorage
new_account.channels.forEach(element -> {
String alias = element.first;
Stored<Channel> channel = element.second;
Role role = element.third;
try {
PreparedStatement statement1 = connection.prepareStatement("INSERT INTO AccountChannel VALUES(?,?,?,?)");
PreparedStatement statement1 = connection.prepareStatement("INSERT INTO AccountChannel VALUES(?,?,?,?,?)");
statement1.setObject(1, account.identity);
statement1.setObject(2, channel.identity);
statement1.setString(3, alias);
statement1.setString(4, ordinal.get().toString());
statement1.setString(5,role.toString());
// Execute statement
statement1.executeUpdate();
......@@ -148,7 +151,7 @@ public final class AccountStorage
PreparedStatement accountStmt = connection.prepareStatement("SELECT version,user,password FROM Account WHERE id =?");
accountStmt.setString(1, id.toString());
PreparedStatement channelStmt = connection.prepareStatement("SELECT channel,alias,ordinal FROM AccountChannel WHERE account = ? ORDER BY ordinal DESC");
PreparedStatement channelStmt = connection.prepareStatement("SELECT channel,alias,ordinal,role FROM AccountChannel WHERE account = ? ORDER BY ordinal DESC");
channelStmt.setString(1, id.toString());
......@@ -163,14 +166,15 @@ public final class AccountStorage
accountResult.getString("password");
final Stored<User> user = userStore.get(userid);
// Get all the channels associated with this account
final List.Builder<Pair<String,Stored<Channel>>> channels = List.builder();
final List.Builder<Triple<String,Stored<Channel>,Role>> channels = List.builder();
while(channelResult.next()) {
final UUID channelId =
UUID.fromString(channelResult.getString("channel"));
final String alias = channelResult.getString("alias");
final Role role = Role.valueOf(channelResult.getString("role"));
channels.accept(
new Pair<String,Stored<Channel>>(
alias,channelStore.get(channelId)));
new Triple<String,Stored<Channel>,Role>(
alias,channelStore.get(channelId), role));
}
return (new Stored<Account>(new Account(user,channels.getList(),password),id,version));
} else {
......
......@@ -9,10 +9,8 @@ import java.io.IOException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import java.util.TreeMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.function.Consumer;
import java.lang.IllegalArgumentException;
import java.time.format.DateTimeFormatter;
......@@ -55,7 +53,17 @@ public class Handler extends AbstractHandler
* This is the entry point for HTTP requests.
* Some requests require login, while some can be processed
* without a valid session.
*
* 6193.. - bruker2
* 97ab.. - bruker1
*
*
*/
public void handle(String target,
Request baseRequest,
HttpServletRequest request,
......@@ -165,28 +173,50 @@ public class Handler extends AbstractHandler
if (!session.identity.equals(UUID.fromString(request.getParameter("CSRFToken")))) {return;}
if(request.getParameter("newmessage") != null) {
String message = Encode.forHtml((new Maybe<String>
(request.getParameter("message"))).get());
if(request.getParameter("newmessage") != null && hasPermission(inchat.getRole(account, channel).get(), "newmessage")) {
String message = (new Maybe<String>
(request.getParameter("message"))).get();
channel = inchat.postMessage(account,channel,message).get();
}
if(request.getParameter("deletemessage") != null) {
// Check if user has permission
Role role = inchat.getRole(account, channel).get();
boolean permission = hasPermission(role, "deletemessage");
UUID messageId =
UUID.fromString(Maybe.just(request.getParameter("message")).get());
Stored<Channel.Event> message = inchat.getEvent(messageId).get();
channel = inchat.deleteEvent(channel, message);
// Check if user owns this message
boolean ownerOfMsg = message.value.sender.equals(account.value.user.value.userName.toString());
if (permission || (ownerOfMsg && !role.equals(Role.OBSERVER))) {
channel = inchat.deleteEvent(channel, message);
}
}
if(request.getParameter("editmessage") != null) {
String message = Encode.forHtml((new Maybe<String>
(request.getParameter("content"))).get());
Role role = inchat.getRole(account, channel).get();
boolean permission = hasPermission(role, "editmessage");
String message = (new Maybe<String>
(request.getParameter("content"))).get();
UUID messageId =
UUID.fromString(Maybe.just(request.getParameter("message")).get());
Stored<Channel.Event> event = inchat.getEvent(messageId).get();
channel = inchat.editMessage(channel, event, message);
// Check if user owns this message
boolean ownerOfMsg = event.value.sender.equals(account.value.user.value.userName.toString());
if (permission || (ownerOfMsg && !role.equals(Role.OBSERVER))) {
channel = inchat.editMessage(channel, event, message);
}
}
if (request.getParameter("setpermission") != null){
String targetedUserName = request.getParameter("username");
String newRole = request.getParameter("role");
// If user is not owner, deny the request
if (Util.lookupTriple(account.value.channels, channel.value.name).get().equals(Role.OWNER)) {
channel = inchat.setRole(account, channel, targetedUserName, newRole.toUpperCase()).get();
}
}
// TODO: Handle requests to change user roles on channel.
}
......@@ -197,7 +227,12 @@ public class Handler extends AbstractHandler
printStandardTop(out, "inChat: " + alias);
out.println("<div class=\"main\">");
printChannelList(out, account.value, alias);
printChannel(out, channel, alias, session.identity);
// Only print channel if the user is not banned
if (!Util.lookupTriple(account.value.channels, channel.value.name).get().equals(Role.BANNED)) {
printChannel(out, channel, alias, session.identity);
} else {
out.println("<p> You are banned from this channel. <p>");
}
out.println("</div>");
out.println("</body>");
out.println("</html>");
......@@ -286,7 +321,7 @@ public class Handler extends AbstractHandler
final UUID channelId
= UUID.fromString(idparam.get());
Stored<Channel> channel
= inchat.joinChannel(account,channelId).get();
= inchat.joinChannel(account,channelId, Role.PARTICIPANT).get();
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.setHeader("Location","/channel/" + channel.value.name);
baseRequest.setHandled(true);
......@@ -532,6 +567,18 @@ public class Handler extends AbstractHandler
});
}
private boolean hasPermission(Role role, String request) {
// Permissions for a newmessage request
if (request.equals("newmessage")) {
return !(role.equals(Role.BANNED) || role.equals(Role.OBSERVER));
// Permissions for an editmessage or deletemessage request
} else if (request.equals("editmessage") || request.equals("deletemessage")) {
// Owner and mod can edit anything
return role.equals(Role.OWNER) || role.equals(Role.MODERATOR);
}
return false;
}
/**
* Load all the cookies into a map for easy retrieval.
*/
......
......@@ -2,6 +2,7 @@ package inf226.inchat;
import inf226.storage.*;
import inf226.util.Maybe;
import inf226.util.Triple;
import inf226.util.Util;
import java.util.TreeMap;
......@@ -53,7 +54,7 @@ public class InChat {
*/
@FunctionalInterface
private interface Operation<T,E extends Throwable> {
void run(final Consumer<T> result) throws E,DeletedException;
void run(final Consumer<T> result) throws E, DeletedException, IllegalAccessException, UpdatedException, Maybe.NothingException;
}
/**
* Execute an operation atomically in SQL.
......@@ -66,9 +67,7 @@ public class InChat {
op.run(result);
connection.commit();
return result.getMaybe();
}catch (SQLException e) {
System.err.println(e.toString());
}catch (DeletedException e) {
} catch (DeletedException | IllegalAccessException | SQLException | UpdatedException | Maybe.NothingException e) {
System.err.println(e.toString());
}
try {
......@@ -138,7 +137,7 @@ public class InChat {
return atomic(result -> {
Stored<Channel> channel
= channelStore.save(new Channel(name,List.empty()));
joinChannel(account, channel.identity);
joinChannel(account, channel.identity, Role.OWNER);
result.accept(channel);
});
}
......@@ -146,22 +145,12 @@ public class InChat {
/**
* Join a channel.
*/
public Maybe<Stored<Channel>> joinChannel(Stored<Account> account,
UUID channelID) {
public Maybe<Stored<Channel>> joinChannel(Stored<Account> account, UUID channelID, Role role) {
return atomic(result -> {
Stored<Channel> channel = channelStore.get(channelID);
Util.updateSingle(account,
accountStore,
a -> a.value.joinChannel(channel.value.name,channel));
Stored<Channel.Event> joinEvent
= channelStore.eventStore.save(
Channel.Event.createJoinEvent(channelID,
Instant.now(),
account.value.user.value.userName.toString()));
result.accept(
Util.updateSingle(channel,
channelStore,
c -> c.value.postEvent(joinEvent)));
Util.updateSingle(account,accountStore,a -> a.value.joinChannel(channel.value.name,channel, role));
Stored<Channel.Event> joinEvent = channelStore.eventStore.save(Channel.Event.createJoinEvent(channelID, Instant.now(),account.value.user.value.userName.toString()));
result.accept(Util.updateSingle(channel,channelStore, c -> c.value.postEvent(joinEvent)));
});
}
......@@ -228,6 +217,42 @@ public class InChat {
result.accept(channelStore.noChangeUpdate(channel.identity));
}).defaultValue(channel);
}
public Maybe<Stored<Channel>> setRole(Stored<Account> account, Stored<Channel> channel, String targetUserName, String targetRoleString){
return atomic(result -> {
Role targetRole = Role.valueOf(targetRoleString.toUpperCase());
Role role = Util.lookupTriple(account.value.channels, channel.value.name).get();
if (!role.equals(Role.OWNER)) {
throw new IllegalAccessException("Non-owner tried to change a role");
}
Stored<Account> targetAccount = accountStore.lookup(new UserName(targetUserName).toString());
// Update channel list of the account
List<Triple<String, Stored<Channel>, Role>> updatedChannels = targetAccount.value.channels.map(chan -> {
// If matching channel, update the role
if (chan.second.identity.equals(channel.identity)) {
return new Triple<>(chan.first, chan.second, targetRole);
// Else, return the same channel with same role
} else {
return chan;
}
});
if (updatedChannels.equals(targetAccount.value.channels)) {
updatedChannels.add(new Triple<>(channel.value.name, channel, targetRole));
}
accountStore.update(targetAccount, new Account(targetAccount.value.user, updatedChannels, targetAccount.value.hashedPassword));
result.accept(channel);
});
}
public Maybe<Role> getRole(Stored<Account> account, Stored<Channel> channel) {
return Util.lookupTriple(account.value.channels, channel.value.name);
}
}
package inf226.inchat;
public enum Role {
OWNER,
MODERATOR,
PARTICIPANT,
OBSERVER,
BANNED
}
package inf226.util;
import java.util.Objects;
public class Triple<A,B,C> {
public final A first;
public final B second;
public final C third;
public Triple(A first, B second, C third) {
this.first = first;
this.second = second;
this.third = third;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Triple<?, ?, ?> triple1 = (Triple<?, ?, ?>) o;
return Objects.equals(first, triple1.first) && Objects.equals(second, triple1.second) && Objects.equals(third, triple1.third);
}
@Override
public int hashCode() {
return Objects.hash(first, second, third);
}
}
......@@ -12,7 +12,7 @@ public class Util {
catch (Maybe.NothingException e) { /* Intensionally left blank */ }
}
public static<A,B> Maybe<B> lookup(List<Pair<A,B>> list, A key) {
public static<A,B,C> Maybe<B> lookup(List<Triple<A,B,C>> list, A key) {
final Maybe.Builder<B> result
= new Maybe.Builder<B>();
list.forEach(pair -> {
......@@ -22,12 +22,28 @@ public class Util {
return result.getMaybe();
}
public static <A,B,C> Maybe<C> lookupTriple(List<Triple<A,B,C>> list, A key){
final Maybe.Builder<C> result
= new Maybe.Builder<C>();
list.forEach(triple -> {
if(triple.first.equals(key))
result.accept(triple.third);
});
return result.getMaybe();
}
public static<A,B,C> List<Triple<A,B,C>> replaceTriple(List<Triple<A,B,C>> list, B key, C replacement){
return list.map(triple -> {
if (triple.second.equals(key)){
return new Triple<>(triple.first,triple.second,replacement);
}
else
return triple;
});
}
public static<A,Q, E extends Exception>
Stored<A> updateSingle(Stored<A> stored,
Storage<A,E> storage,
Function<Stored<A>,A> update)
throws E, DeletedException {
public static<A,Q, E extends Exception> Stored<A> updateSingle(Stored<A> stored, Storage<A,E> storage, Function<Stored<A>,A> update) throws E, DeletedException {
boolean updated = true;
while(true) {
try {
......@@ -38,8 +54,7 @@ public class Util {
}
}
public static<A,Q, E extends Exception> void deleteSingle(Stored<A> stored, Storage<A,E> storage)
throws E {
public static<A,Q, E extends Exception> void deleteSingle(Stored<A> stored, Storage<A,E> storage) throws E {
while(true) {
try {
storage.delete(stored);
......
......@@ -36,7 +36,7 @@ public class InchatTest{
Stored<Session> bobSession = inchat.login("Bob","worse").get();
Stored<Channel> channel = inchat.createChannel(aliceSession.value.account,"Awesome").get();
inchat.postMessage(aliceSession.value.account,channel, "Test message.").get();
inchat.joinChannel(bobSession.value.account,channel.identity).get();
inchat.joinChannel(bobSession.value.account,channel.identity, Role.PARTICIPANT).get();
connection.close();
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment