@@ -24,7 +24,6 @@ dependencies { | |||
'com.sparkjava:spark-core:2.8.0', | |||
'net.sf.trove4j:trove4j:3.0.3', | |||
'com.google.code.gson:gson:2.8.5', | |||
//'com.google.guava:guava:27.0.1-jre', | |||
'com.mashape.unirest:unirest-java:1.4.9', | |||
'org.apache.commons:commons-lang3:3.8.1', | |||
'org.apache.logging.log4j:log4j-core:2.10.0' | |||
@@ -1,11 +0,0 @@ | |||
package config; | |||
group '/api', { | |||
get '/benchmarks' | |||
get '/unconfirmedBenchmarks' | |||
post '/submit' | |||
post '/confirm' | |||
post '/delete' | |||
}, [ | |||
action: 'main' | |||
] |
@@ -0,0 +1,21 @@ | |||
.DS_Store | |||
node_modules | |||
/dist | |||
# local env files | |||
.env.local | |||
.env.*.local | |||
# Log files | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
# Editor directories and files | |||
.idea | |||
.vscode | |||
*.suo | |||
*.ntvs* | |||
*.njsproj | |||
*.sln | |||
*.sw? |
@@ -8,6 +8,7 @@ import com.google.inject.Inject; | |||
import fr.litarvan.paladin.OnStart; | |||
import fr.litarvan.paladin.OnStop; | |||
import fr.litarvan.paladin.PaladinApp; | |||
import fr.slixe.benchmarks.service.AuthService; | |||
import fr.slixe.benchmarks.service.BenchmarkService; | |||
import spark.Spark; | |||
@@ -21,11 +22,17 @@ public class App | |||
@Inject | |||
private BenchmarkService benchmarkService; | |||
@Inject | |||
private AuthService authService; | |||
@OnStart | |||
public void start() | |||
{ | |||
log.info("Loading benchmarks..."); | |||
benchmarkService.loadBenchmarks(); | |||
log.info("Loading admins..."); | |||
authService.loadUsers(); | |||
} | |||
@OnStop | |||
@@ -33,7 +40,7 @@ public class App | |||
{ | |||
log.info("Shutting down http service..."); | |||
Spark.stop(); | |||
log.info("Saving benchmarks..."); | |||
benchmarkService.saveBenchmarks(); | |||
@@ -6,13 +6,20 @@ import fr.slixe.benchmarks.http.SparkHttpServer; | |||
public class Main | |||
{ | |||
private static Paladin paladin; | |||
public static void main(String[] args) | |||
{ | |||
Paladin paladin = PaladinBuilder.create(App.class) | |||
paladin = PaladinBuilder.create(App.class) | |||
.addModule(new MyModule()) | |||
.loadCommandLineArguments(args) | |||
.build(); | |||
paladin.start(new SparkHttpServer(paladin, paladin.getConfig().get("port", int.class))); | |||
} | |||
public static Paladin getPaladin() | |||
{ | |||
return paladin; | |||
} | |||
} |
@@ -7,25 +7,25 @@ import com.google.gson.GsonBuilder; | |||
import com.google.inject.AbstractModule; | |||
import fr.slixe.benchmarks.serialization.BenchmarkAdapter; | |||
import fr.slixe.benchmarks.serialization.UserAdapter; | |||
public class MyModule extends AbstractModule { | |||
public class MyModule extends AbstractModule | |||
{ | |||
@Override | |||
protected void configure() | |||
{ | |||
bind(Gson.class).toInstance(new GsonBuilder().registerTypeAdapter(Benchmark.class, new BenchmarkAdapter()).setPrettyPrinting().addSerializationExclusionStrategy(new ExclusionStrategy() | |||
{ | |||
@Override | |||
public boolean shouldSkipField(FieldAttributes f) | |||
{ | |||
return f.getAnnotation(JsonIgnore.class) != null; | |||
} | |||
bind(Gson.class).toInstance(new GsonBuilder().registerTypeAdapter(Benchmark.class, new BenchmarkAdapter()) | |||
.registerTypeAdapter(User.class, new UserAdapter()).setPrettyPrinting() | |||
.addSerializationExclusionStrategy(new ExclusionStrategy() { | |||
@Override | |||
public boolean shouldSkipField(FieldAttributes f) { | |||
return f.getAnnotation(JsonIgnore.class) != null; | |||
} | |||
@Override | |||
public boolean shouldSkipClass(Class<?> clazz) | |||
{ | |||
return false; | |||
} | |||
}).create()); | |||
@Override | |||
public boolean shouldSkipClass(Class<?> clazz) { | |||
return false; | |||
} | |||
}).create()); | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
package fr.slixe.benchmarks; | |||
public class User { | |||
private final String username; | |||
private final String hashedPassword; | |||
private final String salt; | |||
public User(String username, String hashedPassword, String salt) | |||
{ | |||
this.username = username; | |||
this.hashedPassword = hashedPassword; | |||
this.salt = salt; | |||
} | |||
public String getUsername() | |||
{ | |||
return this.username; | |||
} | |||
public String getHashedPassword() | |||
{ | |||
return this.hashedPassword; | |||
} | |||
public String getSalt() | |||
{ | |||
return this.salt; | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
package fr.slixe.benchmarks.http; | |||
import fr.litarvan.paladin.AfterEvent; | |||
import fr.litarvan.paladin.BeforeEvent; | |||
import fr.litarvan.paladin.http.Middleware; | |||
import fr.litarvan.paladin.http.Request; | |||
import fr.litarvan.paladin.http.Response; | |||
import fr.litarvan.paladin.http.routing.RequestException; | |||
import fr.litarvan.paladin.http.routing.Route; | |||
import fr.slixe.benchmarks.User; | |||
public class AuthMiddleware extends Middleware | |||
{ | |||
@Override | |||
public void before(BeforeEvent event, Request request, Response response, Route route) throws RequestException | |||
{ | |||
if (request.getSession().get(User.class) == null) { | |||
throw new UnauthorizedOperationException(); | |||
} | |||
} | |||
@Override | |||
public void after(AfterEvent event, Request request, Response response, Route route) throws RequestException {} | |||
} |
@@ -0,0 +1,11 @@ | |||
package fr.slixe.benchmarks.http; | |||
import fr.litarvan.paladin.http.routing.RequestException; | |||
public class UnauthorizedOperationException extends RequestException | |||
{ | |||
public UnauthorizedOperationException() | |||
{ | |||
super("You don't have the rights to do that"); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
package fr.slixe.benchmarks.http.controller; | |||
import org.slf4j.Logger | |||
import org.slf4j.LoggerFactory | |||
import com.google.inject.Inject | |||
import fr.litarvan.paladin.Session | |||
import fr.litarvan.paladin.http.Controller | |||
import fr.litarvan.paladin.http.routing.RequestParams | |||
import fr.slixe.benchmarks.User | |||
import fr.slixe.benchmarks.http.InvalidParameterException | |||
import fr.slixe.benchmarks.service.AuthService | |||
public class AuthController extends Controller { | |||
private static final Logger log = LoggerFactory.getLogger("DERO HTTP Auth Controller") | |||
@Inject | |||
private AuthService authService | |||
@RequestParams(required = ["username", "password"]) | |||
def login(String username, String password, Session session) | |||
{ | |||
if (password.length() > 64) { | |||
throw new InvalidParameterException("Password is too long") | |||
} | |||
User user = authService.loginUsername(username, password) | |||
if (user == null) { | |||
throw new InvalidParameterException("Username or password is incorrect") | |||
} | |||
log.info(String.format("User %s is now logged in from %s.", user.getUsername())) | |||
session[User] = user | |||
[ | |||
token: session.token | |||
] | |||
} | |||
def validate(Session session) | |||
{ | |||
[ | |||
logged: session[User] != null | |||
] | |||
} | |||
def logout(Session session) | |||
{ | |||
session[User] = null | |||
} | |||
} |
@@ -1,12 +1,9 @@ | |||
package fr.slixe.benchmarks.http.controller; | |||
import java.util.List; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.google.gson.Gson; | |||
import com.google.gson.JsonObject; | |||
import com.google.inject.Inject; | |||
import fr.litarvan.paladin.http.Controller; | |||
@@ -70,7 +67,7 @@ public class MainController extends Controller { | |||
* @param int benchID | |||
*/ | |||
@RequestParams(required = ["benchID"]) | |||
def confirm(int benchID) //TODO Add auth | |||
def confirm(int benchID) | |||
{ | |||
log.debug("Confirmation of Benchmark $benchID") | |||
@@ -88,7 +85,7 @@ public class MainController extends Controller { | |||
* @param int benchID | |||
*/ | |||
@RequestParams(required = ["benchID"]) | |||
def delete(int benchID) //TODO Add auth | |||
def delete(int benchID) | |||
{ | |||
log.debug("Removing Benchmark $benchID") | |||
@@ -0,0 +1,40 @@ | |||
package fr.slixe.benchmarks.serialization; | |||
import java.lang.reflect.Type; | |||
import com.google.gson.JsonDeserializationContext; | |||
import com.google.gson.JsonDeserializer; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParseException; | |||
import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
import fr.slixe.benchmarks.User; | |||
public class UserAdapter implements JsonSerializer<User>, JsonDeserializer<User> { | |||
@Override | |||
public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException | |||
{ | |||
JsonObject jsonObject = json.getAsJsonObject(); | |||
String username = jsonObject.get("username").getAsString(); | |||
String hashedPassword = jsonObject.get("hashedPassword").getAsString(); | |||
String salt = jsonObject.get("salt").getAsString(); | |||
return new User(username, hashedPassword, salt); | |||
} | |||
@Override | |||
public JsonElement serialize(User src, Type typeOfSrc, JsonSerializationContext context) | |||
{ | |||
JsonObject json = new JsonObject(); | |||
json.addProperty("username", src.getUsername()); | |||
json.addProperty("hashedPassword", src.getHashedPassword()); | |||
json.addProperty("salt", src.getSalt()); | |||
return json; | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
package fr.slixe.benchmarks.service; | |||
import java.io.File; | |||
import java.io.FileReader; | |||
import java.io.FileWriter; | |||
import java.io.IOException; | |||
import java.lang.reflect.Type; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.google.common.hash.Hashing; | |||
import com.google.common.primitives.Bytes; | |||
import com.google.gson.Gson; | |||
import com.google.gson.reflect.TypeToken; | |||
import com.google.gson.stream.JsonReader; | |||
import com.google.inject.Inject; | |||
import com.google.inject.Singleton; | |||
import fr.slixe.benchmarks.User; | |||
@Singleton | |||
public class AuthService { | |||
private static final Type USER_TYPE = new TypeToken<List<User>>(){}.getType(); | |||
private static final Logger log = LoggerFactory.getLogger("DERO Auth Service"); | |||
private final Map<String, User> users = new ConcurrentHashMap<>(); | |||
private final File file = new File("admins.json"); | |||
@Inject | |||
private Gson gson; | |||
public void loadUsers() | |||
{ | |||
if (!this.file.exists()) | |||
try { | |||
log.info("admins.json not found, creating it with default user."); | |||
this.file.createNewFile(); | |||
createExampleUser(); | |||
} catch (IOException e) { | |||
log.error(e.getMessage()); | |||
} | |||
else | |||
try { | |||
JsonReader jsonReader = new JsonReader(new FileReader(file)); | |||
List<User> users = gson.fromJson(jsonReader, USER_TYPE); | |||
for (User user : users) | |||
{ | |||
this.users.put(user.getUsername(), user); | |||
} | |||
jsonReader.close(); | |||
} catch (IOException e) { | |||
log.error(e.getMessage()); | |||
} | |||
} | |||
private void createExampleUser() | |||
{ | |||
String username = "Slixe"; | |||
String password = "password123"; | |||
String salt = "salt123"; | |||
this.users.put(username, new User(username, AuthService.hash(password, salt), salt)); | |||
try (FileWriter fileWriter = new FileWriter(this.file)) { | |||
gson.toJson(this.users.values(), USER_TYPE, fileWriter); | |||
} catch (IOException e) { | |||
log.error(e.getMessage()); | |||
} | |||
} | |||
public User loginUsername(String username, String password) | |||
{ | |||
return login(users.get(username), password); | |||
} | |||
public User login(User user, String password) | |||
{ | |||
if (user == null) { | |||
return null; | |||
} | |||
String hash = AuthService.hash(password, user.getSalt()); | |||
if (!hash.equals(user.getHashedPassword())) | |||
{ | |||
return null; | |||
} | |||
return user; | |||
} | |||
public static String hash(String password, String salt) | |||
{ | |||
byte[] passwordSalted = Bytes.concat(salt.getBytes(), password.getBytes()); | |||
return Hashing.sha512().hashBytes(passwordSalted).toString(); | |||
} | |||
} |
@@ -78,7 +78,10 @@ public class BenchmarkService { | |||
public boolean addUnconfirmedBenchmarks(Benchmark benchmark) | |||
{ | |||
return this.unconfirmedBenchmarks.add(benchmark); | |||
boolean result = this.unconfirmedBenchmarks.add(benchmark); | |||
saveBenchmarks(this.unconfirmedBenchmarksFile, this.unconfirmedBenchmarks); | |||
return result; | |||
} | |||
public boolean confirmBenchmark(int benchID) | |||
@@ -89,6 +92,7 @@ public class BenchmarkService { | |||
Benchmark benchmark = opt.get(); | |||
this.unconfirmedBenchmarks.remove(benchmark); | |||
this.confirmedBenchmarks.add(benchmark); | |||
saveBenchmarks(); | |||
} | |||
return opt.isPresent(); | |||
@@ -102,9 +106,13 @@ public class BenchmarkService { | |||
result = this.confirmedBenchmarks.removeIf(e -> e.getId() == benchID); | |||
} | |||
if (result) { | |||
this.saveBenchmarks(); | |||
} | |||
return result; | |||
} | |||
public int lastBenchId() | |||
{ | |||
if (this.confirmedBenchmarks.size() == 0 && this.unconfirmedBenchmarks.size() == 0) | |||
@@ -1,4 +1,3 @@ | |||
{ | |||
"port": 8080, | |||
"password": "HelloWorld123" | |||
"port": 8080 | |||
} |
@@ -3,6 +3,8 @@ package config; | |||
import java.util.concurrent.TimeUnit | |||
import fr.litarvan.paladin.http.AcceptCrossOriginRequestsMiddleware | |||
import fr.slixe.benchmarks.http.AuthMiddleware | |||
import fr.slixe.benchmarks.http.controller.AuthController | |||
import fr.slixe.benchmarks.http.controller.MainController | |||
[ | |||
@@ -12,9 +14,14 @@ import fr.slixe.benchmarks.http.controller.MainController | |||
* The app controllers, call them whatever you want to | |||
*/ | |||
controllers: [ | |||
main: MainController | |||
main: MainController, | |||
auth: AuthController | |||
], | |||
routeMiddlewares: [ | |||
auth: AuthMiddleware | |||
], | |||
/** | |||
* Global middlewares (applied on all routes) | |||
*/ |
@@ -0,0 +1,23 @@ | |||
package config; | |||
group '/api', { | |||
get '/benchmarks', 'main:benchmarks' | |||
group '', { | |||
get '/unconfirmedBenchmarks' | |||
post '/submit' | |||
post '/confirm' | |||
post '/delete' | |||
}, [ | |||
action: 'main', | |||
middleware: 'auth' | |||
] | |||
group '/auth', { | |||
post '/validate' | |||
post '/login' | |||
post '/logout' | |||
}, [ | |||
action: 'auth' | |||
] | |||
} |