diff --git a/build.gradle b/build.gradle
index a1e5ee99..3395138d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,6 +12,7 @@ def projectVersion = project.version
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
+def isRelease = true
if (project.hasProperty("preRelease")) {
apply from: './pre-release.gradle'
@@ -21,6 +22,7 @@ if (project.hasProperty("preRelease")) {
projectVersion += "-rc." + i
} else if (!project.hasProperty('release')) {
+ isRelease = false
projectVersion += '-SNAPSHOT'
}
@@ -125,7 +127,10 @@ task processSourceReplacements(type: org.gradle.api.tasks.Sync) {
}
compileJava {
- source = processSourceReplacements.outputs
+ if (isRelease) {
+ source = processSourceReplacements.outputs
+ }
+
options.compilerArgs << "-Xlint:deprecation"
}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/PathParam.java b/src/api/java/de/fearnixx/jeak/reflect/PathParam.java
deleted file mode 100644
index 6a8cdbd0..00000000
--- a/src/api/java/de/fearnixx/jeak/reflect/PathParam.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.fearnixx.jeak.reflect;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark a parameter to be filled by a request parameter from a call.
- *
- * type(): REQUIRED if its something else than a {@link String} Specify the type of the expected variable.
- *
- * name(): REQUIRED Specify the name of the expected variable.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
-public @interface PathParam {
- Class> type() default String.class;
- String name();
-}
\ No newline at end of file
diff --git a/src/api/java/de/fearnixx/jeak/reflect/RequestBody.java b/src/api/java/de/fearnixx/jeak/reflect/RequestBody.java
deleted file mode 100644
index 2cfdd660..00000000
--- a/src/api/java/de/fearnixx/jeak/reflect/RequestBody.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package de.fearnixx.jeak.reflect;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark a parameter to be filled by the request body of a call.
- *
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
-public @interface RequestBody {
-}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/RequestMapping.java b/src/api/java/de/fearnixx/jeak/reflect/RequestMapping.java
deleted file mode 100644
index 5096d2ba..00000000
--- a/src/api/java/de/fearnixx/jeak/reflect/RequestMapping.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.fearnixx.jeak.reflect;
-
-import de.fearnixx.jeak.service.controller.RequestMethod;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark a method as method to be available via the controller.
- *
- * method(): REQUIRED Specify the used HTTP-method.
- *
- * endpoint(): REQUIRED Specify the endpoint for the annotated method.
- *
- * isSecured(): Specify whether the calls to this endpoint should use an authorization scheme.
- *
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface RequestMapping {
- RequestMethod method();
- String endpoint();
- boolean isSecured() default true;
-}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/RequestParam.java b/src/api/java/de/fearnixx/jeak/reflect/RequestParam.java
deleted file mode 100644
index 3e64aa44..00000000
--- a/src/api/java/de/fearnixx/jeak/reflect/RequestParam.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.fearnixx.jeak.reflect;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark a parameter to be filled by a query parameter from a call.
- *
- * type(): REQUIRED if its something else than a {@link String} Specify the type of the expected variable.
- *
- * name(): REQUIRED Specify the name of the expected variable.
- *
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
-public @interface RequestParam {
- Class> type() default String.class;
- String name();
-}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/Transactional.java b/src/api/java/de/fearnixx/jeak/reflect/Transactional.java
deleted file mode 100644
index 46430a99..00000000
--- a/src/api/java/de/fearnixx/jeak/reflect/Transactional.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package de.fearnixx.jeak.reflect;
-
-/**
- * Indicates a listener method to be transactional.
- * Events will be preceded by a call to
- */
-public @interface Transactional {
-}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/http/Authenticated.java b/src/api/java/de/fearnixx/jeak/reflect/http/Authenticated.java
new file mode 100644
index 00000000..78733daa
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/reflect/http/Authenticated.java
@@ -0,0 +1,23 @@
+package de.fearnixx.jeak.reflect.http;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Designates a method annotated with {@link RequestMapping} as requiring authentication.
+ * To facilitate basic authorization checks, a permission can be set as required for the endpoint.
+ *
+ * @since 1.2.0 (experimental)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Authenticated {
+
+ /**
+ * The permissions required to access the endpoint at all.
+ * The requesting party must have all the given permissions in order to access the endpoint (AND).
+ */
+ String[] permissions() default {};
+}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/http/RequestMapping.java b/src/api/java/de/fearnixx/jeak/reflect/http/RequestMapping.java
new file mode 100644
index 00000000..dbf28c31
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/reflect/http/RequestMapping.java
@@ -0,0 +1,28 @@
+package de.fearnixx.jeak.reflect.http;
+
+import de.fearnixx.jeak.service.http.RequestMethod;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Designates a method as being a receiver for HTTP-requests.
+ *
+ * @since 1.2.0 (experimental)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface RequestMapping {
+
+ /**
+ * The HTTP method associated with this endpoint.
+ */
+ RequestMethod method() default RequestMethod.GET;
+
+ /**
+ * URI appendix for this endpoint.
+ */
+ String endpoint();
+}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/RestController.java b/src/api/java/de/fearnixx/jeak/reflect/http/RestController.java
similarity index 59%
rename from src/api/java/de/fearnixx/jeak/reflect/RestController.java
rename to src/api/java/de/fearnixx/jeak/reflect/http/RestController.java
index 3fac1cfc..53aca251 100644
--- a/src/api/java/de/fearnixx/jeak/reflect/RestController.java
+++ b/src/api/java/de/fearnixx/jeak/reflect/http/RestController.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.reflect;
+package de.fearnixx.jeak.reflect.http;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -8,15 +8,18 @@
/**
* Marks a class as a REST controller. One plugin can have multiple controllers, so the controller determines
* to which plugin it belongs by using the pluginId.
- *
- * endpoint(): REQUIRE if you use more than one controller. Specify the endpoint of the controller.
- *
- * pluginId(): Specify the id of the used plugin. This is independent of the pluginId specified in {@link de.fearnixx.jeak.reflect.JeakBotPlugin}.
- *
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RestController {
+
+ /**
+ * Your plugin id, since controllers are grouped by plugin ID to avoid path collisions between plugins.
+ */
String pluginId();
- String endpoint();
+
+ /**
+ * An endpoint path prefix for all request mappings within this class.
+ */
+ String path();
}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/http/params/PathParam.java b/src/api/java/de/fearnixx/jeak/reflect/http/params/PathParam.java
new file mode 100644
index 00000000..ddd00a7b
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/reflect/http/params/PathParam.java
@@ -0,0 +1,20 @@
+package de.fearnixx.jeak.reflect.http.params;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies an endpoint method parameter to be derived from the requests path pattern.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface PathParam {
+
+ /**
+ * If the parameter name from the path pattern differs from the method parameter name, this should be the
+ * name used in the path pattern.
+ */
+ String name() default "";
+}
\ No newline at end of file
diff --git a/src/api/java/de/fearnixx/jeak/reflect/http/params/QueryParam.java b/src/api/java/de/fearnixx/jeak/reflect/http/params/QueryParam.java
new file mode 100644
index 00000000..f11756ec
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/reflect/http/params/QueryParam.java
@@ -0,0 +1,19 @@
+package de.fearnixx.jeak.reflect.http.params;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Designates a methods parameter to be filled by a request parameter.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface QueryParam {
+
+ /**
+ * If the parameter name differs from the HTTP-contract, this should be the name used in the contract.
+ */
+ String name() default "";
+}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/http/params/RequestBody.java b/src/api/java/de/fearnixx/jeak/reflect/http/params/RequestBody.java
new file mode 100644
index 00000000..2f9fa515
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/reflect/http/params/RequestBody.java
@@ -0,0 +1,19 @@
+package de.fearnixx.jeak.reflect.http.params;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Designates an endpoint method parameter to be filled with the request body received.
+ * This is only applicable to {@link de.fearnixx.jeak.service.http.RequestMethod#POST}, {@link de.fearnixx.jeak.service.http.RequestMethod#PUT}
+ * and {@link de.fearnixx.jeak.service.http.RequestMethod#PATCH}
+ *
+ * @implNote Currently, only two method parameter types are supported! If the type is {@link String}, the serialized content will be used.
+ * If the type is of a custom class, Jackson will be used to attempt JSON deserialization of the request body.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface RequestBody {
+}
diff --git a/src/api/java/de/fearnixx/jeak/reflect/http/params/RequestContext.java b/src/api/java/de/fearnixx/jeak/reflect/http/params/RequestContext.java
new file mode 100644
index 00000000..396b183b
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/reflect/http/params/RequestContext.java
@@ -0,0 +1,33 @@
+package de.fearnixx.jeak.reflect.http.params;
+
+import de.fearnixx.jeak.service.http.request.IRequestContext;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes a parameter to be filled from the request context.
+ *
+ * @apiNote The parameter injection will work within the bounds of class assignability compatibility.
+ * @implNote Some usages of this cause side-effects, please see the implementation notes on {@link IRequestContext.Attributes}
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequestContext {
+
+ /**
+ * Denotes the attribute name to be used for the lookup.
+ * If empty, the parameter injection will attempt to insert the {@link IRequestContext} instance.
+ *
+ * @see IRequestContext.Attributes for more information on available attributes.
+ */
+ String attribute() default "";
+
+ /**
+ * Whether or not this parameter has to be set.
+ * Unset, non-required values are passed as {@code null}.
+ */
+ boolean required() default true;
+}
diff --git a/src/api/java/de/fearnixx/jeak/service/controller/IRestControllerService.java b/src/api/java/de/fearnixx/jeak/service/http/IControllerService.java
similarity index 60%
rename from src/api/java/de/fearnixx/jeak/service/controller/IRestControllerService.java
rename to src/api/java/de/fearnixx/jeak/service/http/IControllerService.java
index 51952b61..d7ca78d7 100644
--- a/src/api/java/de/fearnixx/jeak/service/controller/IRestControllerService.java
+++ b/src/api/java/de/fearnixx/jeak/service/http/IControllerService.java
@@ -1,28 +1,27 @@
-package de.fearnixx.jeak.service.controller;
+package de.fearnixx.jeak.service.http;
import java.util.Map;
import java.util.Optional;
/**
* The controller manager allows plugins to register services to a specified REST method.
- *
*/
-public interface IRestControllerService {
+public interface IControllerService {
/**
* Registers a new REST controller to the controller manager.
*
- * @param cntrlrClass The class the controller provides.
- * @param restController The controller to be registered.
- * @param The Type of the service.
+ * @param cntrlrClass The class the controller provides.
+ * @param instance The controller to be registered.
+ * @param The Type of the service.
*/
- void registerController(Class cntrlrClass, T restController);
+ void registerController(Class cntrlrClass, T instance);
/**
* Optionally get a controller of the specified class.
*
* @param cntrlrClass The desired controller class.
- * @param The desired controller type.
+ * @param The desired controller type.
* @return An {@link Optional} representing the result.
*/
Optional provide(Class cntrlrClass);
@@ -33,16 +32,16 @@ public interface IRestControllerService {
* This method performs no checks!
*
* @param cntrlrClass The desired service class.
- * @param The desired controller type.
+ * @param The desired controller type.
* @return Either the controller instance,
- * or {@core null} and a {@link ClassCastException} is thrown.
+ * or {@code null} and a {@link ClassCastException} is thrown.
*/
T provideUnchecked(Class cntrlrClass);
/**
* Provide all the registered controllers.
*
- * @return A {@link Map, Object>} representing the controller.
+ * @return A {@code Map, Object>} representing the controller.
*/
Map, Object> provideAll();
}
diff --git a/src/api/java/de/fearnixx/jeak/service/controller/IResponseEntity.java b/src/api/java/de/fearnixx/jeak/service/http/IResponseEntity.java
similarity index 86%
rename from src/api/java/de/fearnixx/jeak/service/controller/IResponseEntity.java
rename to src/api/java/de/fearnixx/jeak/service/http/IResponseEntity.java
index a79bfa42..f336ca6b 100644
--- a/src/api/java/de/fearnixx/jeak/service/controller/IResponseEntity.java
+++ b/src/api/java/de/fearnixx/jeak/service/http/IResponseEntity.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.controller;
+package de.fearnixx.jeak.service.http;
import java.util.Map;
diff --git a/src/api/java/de/fearnixx/jeak/service/controller/RequestMethod.java b/src/api/java/de/fearnixx/jeak/service/http/RequestMethod.java
similarity index 66%
rename from src/api/java/de/fearnixx/jeak/service/controller/RequestMethod.java
rename to src/api/java/de/fearnixx/jeak/service/http/RequestMethod.java
index ec784442..d8e4b54d 100644
--- a/src/api/java/de/fearnixx/jeak/service/controller/RequestMethod.java
+++ b/src/api/java/de/fearnixx/jeak/service/http/RequestMethod.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.controller;
+package de.fearnixx.jeak.service.http;
public enum RequestMethod {
POST,
diff --git a/src/api/java/de/fearnixx/jeak/service/controller/ResponseEntity.java b/src/api/java/de/fearnixx/jeak/service/http/ResponseEntity.java
similarity index 98%
rename from src/api/java/de/fearnixx/jeak/service/controller/ResponseEntity.java
rename to src/api/java/de/fearnixx/jeak/service/http/ResponseEntity.java
index a533f86b..b994e305 100644
--- a/src/api/java/de/fearnixx/jeak/service/controller/ResponseEntity.java
+++ b/src/api/java/de/fearnixx/jeak/service/http/ResponseEntity.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.controller;
+package de.fearnixx.jeak.service.http;
import java.util.HashMap;
import java.util.Map;
diff --git a/src/api/java/de/fearnixx/jeak/service/controller/exceptions/RegisterControllerException.java b/src/api/java/de/fearnixx/jeak/service/http/exceptions/RegisterControllerException.java
similarity index 89%
rename from src/api/java/de/fearnixx/jeak/service/controller/exceptions/RegisterControllerException.java
rename to src/api/java/de/fearnixx/jeak/service/http/exceptions/RegisterControllerException.java
index 2971a664..6b4bc9ac 100644
--- a/src/api/java/de/fearnixx/jeak/service/controller/exceptions/RegisterControllerException.java
+++ b/src/api/java/de/fearnixx/jeak/service/http/exceptions/RegisterControllerException.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.controller.exceptions;
+package de.fearnixx.jeak.service.http.exceptions;
public class RegisterControllerException extends RuntimeException{
public RegisterControllerException(String message) {
diff --git a/src/api/java/de/fearnixx/jeak/service/http/request/IRequestContext.java b/src/api/java/de/fearnixx/jeak/service/http/request/IRequestContext.java
new file mode 100644
index 00000000..e98500f3
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/service/http/request/IRequestContext.java
@@ -0,0 +1,44 @@
+package de.fearnixx.jeak.service.http.request;
+
+import de.fearnixx.jeak.service.http.request.token.IAuthenticationToken;
+
+import java.util.Optional;
+
+public interface IRequestContext {
+
+ /**
+ * Optionally retrieves a request attribute from the context.
+ * For officially supported attributes, see {@link Attributes}.
+ */
+ Optional optAttribute(String name, Class hint);
+
+ final class Attributes {
+
+ /**
+ * {@link IRequestContext}
+ *
+ * @apiNote Self-reference, mainly for internal purposes.
+ */
+ public static final String REQUEST_CONTEXT = "http:requestContext";
+
+ /**
+ * {@link IAuthenticationToken}
+ *
+ * @apiNote Optionally filled, if authentication is successful AND the "Token" authentication scheme is used.
+ *
+ * @implNote Required parameter injections cause {@link org.eclipse.jetty.http.HttpStatus#UNAUTHORIZED_401} on unsuccessful authentication.
+ */
+ public static final String AUTHENTICATION_TOKEN = "auth:token:authenticationToken";
+
+ /**
+ * {@link de.fearnixx.jeak.teamspeak.data.IUser}
+ *
+ * @apiNote Optionally filled, if authentication is successful AND the subject is an user.
+ * @implNote Required parameter injections cause {@link org.eclipse.jetty.http.HttpStatus#UNAUTHORIZED_401} on unsuccessful authentication or {@link org.eclipse.jetty.http.HttpStatus#FORBIDDEN_403} for principals that aren't users.
+ */
+ public static final String AUTHENTICATION_USER = "auth:subject:authenticatedUser";
+
+ private Attributes() {
+ }
+ }
+}
diff --git a/src/api/java/de/fearnixx/jeak/service/http/request/token/IAuthenticationToken.java b/src/api/java/de/fearnixx/jeak/service/http/request/token/IAuthenticationToken.java
new file mode 100644
index 00000000..f09186b0
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/service/http/request/token/IAuthenticationToken.java
@@ -0,0 +1,16 @@
+package de.fearnixx.jeak.service.http.request.token;
+
+import de.fearnixx.jeak.service.permission.base.ISubject;
+import de.fearnixx.jeak.teamspeak.data.IUser;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+public interface IAuthenticationToken {
+
+ String getTokenString();
+
+ IUser getTokenOwner();
+
+ Optional getExpiry();
+}
diff --git a/src/api/java/de/fearnixx/jeak/service/http/request/token/ITokenAuthService.java b/src/api/java/de/fearnixx/jeak/service/http/request/token/ITokenAuthService.java
new file mode 100644
index 00000000..7b283b28
--- /dev/null
+++ b/src/api/java/de/fearnixx/jeak/service/http/request/token/ITokenAuthService.java
@@ -0,0 +1,16 @@
+package de.fearnixx.jeak.service.http.request.token;
+
+import de.fearnixx.jeak.teamspeak.data.IUser;
+
+import java.time.ZonedDateTime;
+
+public interface ITokenAuthService {
+
+ IAuthenticationToken generateToken(IUser tokenOwner);
+
+ void setTokenExpiry(IAuthenticationToken token, ZonedDateTime expiryValue);
+
+ void revokeToken(IAuthenticationToken token);
+
+ void revokeTokens(IUser tokenOwner);
+}
diff --git a/src/main/java/de/fearnixx/jeak/JeakBot.java b/src/main/java/de/fearnixx/jeak/JeakBot.java
index 58ef55bf..76995f95 100644
--- a/src/main/java/de/fearnixx/jeak/JeakBot.java
+++ b/src/main/java/de/fearnixx/jeak/JeakBot.java
@@ -13,9 +13,9 @@
import de.fearnixx.jeak.service.command.ICommandService;
import de.fearnixx.jeak.service.command.TypedCommandService;
import de.fearnixx.jeak.service.command.matcher.MatcherRegistry;
-import de.fearnixx.jeak.service.controller.RestControllerService;
import de.fearnixx.jeak.service.database.DatabaseService;
import de.fearnixx.jeak.service.event.IEventService;
+import de.fearnixx.jeak.service.http.ControllerService;
import de.fearnixx.jeak.service.locale.LocalizationService;
import de.fearnixx.jeak.service.mail.MailService;
import de.fearnixx.jeak.service.notification.NotificationService;
@@ -23,7 +23,6 @@
import de.fearnixx.jeak.service.profile.ProfileService;
import de.fearnixx.jeak.service.task.ITaskService;
import de.fearnixx.jeak.service.teamspeak.UserService;
-import de.fearnixx.jeak.service.token.TokenService;
import de.fearnixx.jeak.service.util.UtilCommands;
import de.fearnixx.jeak.task.TaskService;
import de.fearnixx.jeak.teamspeak.IServer;
@@ -195,8 +194,7 @@ protected void doServiceStartup() {
initializeService(new ProfileService(new File(confDir, "profiles")));
initializeService(new PermissionService());
initializeService(new UserService());
- initializeService(new TokenService());
- initializeService(new RestControllerService());
+ initializeService(new ControllerService());
if (ENABLE_VOICE_CONNECTIONS) {
initializeService(new VoiceConnectionService());
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/RestControllerService.java b/src/main/java/de/fearnixx/jeak/service/controller/RestControllerService.java
deleted file mode 100644
index b4309b67..00000000
--- a/src/main/java/de/fearnixx/jeak/service/controller/RestControllerService.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package de.fearnixx.jeak.service.controller;
-
-import de.fearnixx.jeak.event.bot.IBotStateEvent;
-import de.fearnixx.jeak.reflect.*;
-import de.fearnixx.jeak.service.controller.connection.ControllerRequestVerifier;
-import de.fearnixx.jeak.service.controller.connection.HttpServer;
-import de.fearnixx.jeak.service.controller.connection.RestConfiguration;
-import de.fearnixx.jeak.service.controller.controller.ControllerContainer;
-import de.fearnixx.jeak.service.controller.controller.IncapableDummyAdapter;
-import de.fearnixx.jeak.service.controller.controller.SparkAdapter;
-import de.fearnixx.jeak.service.controller.exceptions.RegisterControllerException;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-@FrameworkService(serviceInterface = IRestControllerService.class)
-public class RestControllerService implements IRestControllerService {
-
- private final Map, Object> controllers;
- private HttpServer httpServer;
- private ControllerRequestVerifier connectionVerifier;
- private RestConfiguration restConfiguration;
-
- @Inject
- private IInjectionService injectionService;
-
- public RestControllerService() {
- this(new HashMap<>());
- }
-
- public RestControllerService(Map, Object> controllers) {
- this.connectionVerifier = new ControllerRequestVerifier();
- this.restConfiguration = new RestConfiguration();
- this.controllers = controllers;
- this.httpServer = IncapableDummyAdapter.EXPERIMENTAL_REST_ENABLED ?
- new SparkAdapter(connectionVerifier, restConfiguration)
- : new IncapableDummyAdapter(restConfiguration);
- }
-
- @Listener
- public void onPreInt(IBotStateEvent.IPreInitializeEvent preInitializeEvent) {
- injectionService.injectInto(connectionVerifier);
- injectionService.injectInto(restConfiguration);
- restConfiguration.loadConfig();
- injectionService.injectInto(httpServer);
- httpServer.start();
- }
-
-
- @Override
- public void registerController(Class cntrlrClass, T restController) {
- ControllerContainer controllerContainer = new ControllerContainer(restController);
- if (!doesControllerAlreadyExist(restController)) {
- controllers.put(cntrlrClass, restController);
- httpServer.registerController(controllerContainer);
- } else {
- throw new RegisterControllerException("There is already a controller with the same endpoint");
- }
- }
-
- @Override
- public Optional provide(Class cntrlrClass) {
- Object cntrlr = controllers.getOrDefault(cntrlrClass, null);
- return Optional.ofNullable((T) cntrlr);
- }
-
- @Override
- public T provideUnchecked(Class cntrlrClass) {
- return (T) controllers.get(cntrlrClass);
- }
-
- @Override
- public Map, Object> provideAll() {
- return controllers;
- }
-
- private boolean doesControllerAlreadyExist(T restController) {
- Class> controllerClass = restController.getClass();
- if (controllers.containsKey(controllerClass)) {
- return false;
- }
- return controllers.keySet().stream()
- .filter(aClass -> extractPluginId(aClass).equals(extractPluginId(controllerClass)))
- .anyMatch(aClass -> extractControllerName(aClass).equals(extractControllerName(controllerClass)));
- }
-
- private String extractControllerName(Class> clazz) {
- return clazz.getAnnotation(RestController.class).endpoint();
- }
-
- private String extractPluginId(Class> clazz) {
- return clazz.getAnnotation(RestController.class).pluginId();
- }
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/connection/ControllerRequestVerifier.java b/src/main/java/de/fearnixx/jeak/service/controller/connection/ControllerRequestVerifier.java
deleted file mode 100644
index 6d9303f3..00000000
--- a/src/main/java/de/fearnixx/jeak/service/controller/connection/ControllerRequestVerifier.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package de.fearnixx.jeak.service.controller.connection;
-
-import de.fearnixx.jeak.reflect.Inject;
-import de.fearnixx.jeak.service.token.ITokenService;
-
-public class ControllerRequestVerifier implements IConnectionVerifier {
- private static final String TOKEN_TEXT = "Token ";
-
- @Inject
- private ITokenService tokenService;
-
- @Override
- public boolean verifyRequest(String endpoint, String authorizationText) {
- boolean isAuthorized = false;
- if (isToken(authorizationText)) {
- isAuthorized = tokenService.verifyToken(endpoint, extractToken(authorizationText));
- }
- return isAuthorized;
- }
-
- private boolean isToken(String authorizationText) {
- return authorizationText.contains(TOKEN_TEXT);
- }
-
- /**
- * Extract the token from a given String. This requires that it actually is a token.
- *
- * @param authorizationText The text as {@links String}.
- * @return The token as {@link String}
- */
- private String extractToken(String authorizationText) {
- return authorizationText.replace(TOKEN_TEXT, "");
- }
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/connection/IConnectionVerifier.java b/src/main/java/de/fearnixx/jeak/service/controller/connection/IConnectionVerifier.java
deleted file mode 100644
index 26725d91..00000000
--- a/src/main/java/de/fearnixx/jeak/service/controller/connection/IConnectionVerifier.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package de.fearnixx.jeak.service.controller.connection;
-
-/**
- * Used to verify an authorization text.
- *
- */
-public interface IConnectionVerifier {
- /**
- * Verify an HTTP-Authorization text.
- *
- * @param endpoint The endpoint to verify the token for.
- * @param authorizationText The text from the HTTP-Authorization header.
- * @return true if the request could be verified,
- * false otherwise.
- */
- boolean verifyRequest(String endpoint, String authorizationText);
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/testImpls/SecondTestController.java b/src/main/java/de/fearnixx/jeak/service/controller/testImpls/SecondTestController.java
deleted file mode 100644
index a1f692b3..00000000
--- a/src/main/java/de/fearnixx/jeak/service/controller/testImpls/SecondTestController.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.fearnixx.jeak.service.controller.testImpls;
-
-import de.fearnixx.jeak.reflect.RequestBody;
-import de.fearnixx.jeak.reflect.RequestMapping;
-import de.fearnixx.jeak.reflect.RequestParam;
-import de.fearnixx.jeak.reflect.RestController;
-import de.fearnixx.jeak.service.controller.RequestMethod;
-
-@RestController(endpoint = "/test", pluginId = "testPluginId")
-public class SecondTestController {
-
- @RequestMapping(method = RequestMethod.GET, endpoint = "/hello")
- public DummyObject hello() {
- return new DummyObject("second", 20);
- }
-
- @RequestMapping(method = RequestMethod.GET, endpoint = "/info/:name")
- public String returnSentInfo(@RequestParam(name = "name") String name) {
- return "second" + name;
- }
-
- @RequestMapping(method = RequestMethod.POST, endpoint = "/body")
- public String sendBody(@RequestBody() String string) {
- return "second body " + string;
- }
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/testImpls/TestController.java b/src/main/java/de/fearnixx/jeak/service/controller/testImpls/TestController.java
deleted file mode 100644
index 8f27578f..00000000
--- a/src/main/java/de/fearnixx/jeak/service/controller/testImpls/TestController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package de.fearnixx.jeak.service.controller.testImpls;
-
-import de.fearnixx.jeak.reflect.*;
-import de.fearnixx.jeak.service.controller.IResponseEntity;
-import de.fearnixx.jeak.service.controller.RequestMethod;
-import de.fearnixx.jeak.service.controller.ResponseEntity;
-import org.eclipse.jetty.http.HttpStatus;
-
-@RestController(pluginId = "testPluginId", endpoint = "/test")
-public class TestController {
-
- @RequestMapping(method = RequestMethod.GET, endpoint = "/hello")
- public ResponseEntity hello() {
- return new ResponseEntity.Builder(new DummyObject("Finn", 20))
- .withHeader("Cache-Control", "max-age=0")
- .withStatus(HttpStatus.OK_200)
- .build();
- }
-
- @RequestMapping(method = RequestMethod.GET, endpoint = "/info")
- public String returnSentInfo(@RequestParam(name = "name") String name) {
- return "received " + name;
- }
-
- @RequestMapping(method = RequestMethod.POST, endpoint = "/body")
- public String sendBody(@RequestBody String string) {
- return "this is the body " + string;
- }
-
- @RequestMapping(method = RequestMethod.GET, endpoint = "/int", isSecured = false)
- public String sendStuff(@RequestParam(name = "num", type = Integer.class) Integer num) {
- return "received" + num;
- }
-
- public IResponseEntity hallo() {
- return new ResponseEntity.Builder()
- .withHeader("some-header", "GET")
- .build();
- }
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/http/ControllerService.java b/src/main/java/de/fearnixx/jeak/service/http/ControllerService.java
new file mode 100644
index 00000000..dcdb3e71
--- /dev/null
+++ b/src/main/java/de/fearnixx/jeak/service/http/ControllerService.java
@@ -0,0 +1,114 @@
+package de.fearnixx.jeak.service.http;
+
+import de.fearnixx.jeak.event.bot.IBotStateEvent;
+import de.fearnixx.jeak.reflect.FrameworkService;
+import de.fearnixx.jeak.reflect.IInjectionService;
+import de.fearnixx.jeak.reflect.Inject;
+import de.fearnixx.jeak.reflect.Listener;
+import de.fearnixx.jeak.reflect.http.RestController;
+import de.fearnixx.jeak.service.IServiceManager;
+import de.fearnixx.jeak.service.event.IEventService;
+import de.fearnixx.jeak.service.http.connection.HttpServer;
+import de.fearnixx.jeak.service.http.connection.RestConfiguration;
+import de.fearnixx.jeak.service.http.controller.ControllerContainer;
+import de.fearnixx.jeak.service.http.controller.IncapableDummyAdapter;
+import de.fearnixx.jeak.service.http.controller.SparkAdapter;
+import de.fearnixx.jeak.service.http.exceptions.RegisterControllerException;
+import de.fearnixx.jeak.service.http.request.auth.token.TokenAuthService;
+import de.fearnixx.jeak.service.http.request.token.ITokenAuthService;
+
+import java.util.*;
+
+@FrameworkService(serviceInterface = IControllerService.class)
+public class ControllerService implements IControllerService {
+
+ private final Map, Object> controllers;
+ private final HttpServer httpServer;
+ private final RestConfiguration restConfiguration;
+
+ private TokenAuthService tokenAuthService;
+
+ @Inject
+ private IServiceManager serviceManager;
+
+ @Inject
+ private IEventService eventService;
+
+ @Inject
+ private IInjectionService injectionService;
+
+ public ControllerService() {
+ this(new HashMap<>());
+ }
+
+ public ControllerService(Map, Object> controllers) {
+ this.tokenAuthService = new TokenAuthService();
+ this.restConfiguration = new RestConfiguration();
+ this.controllers = controllers;
+ this.httpServer = IncapableDummyAdapter.EXPERIMENTAL_REST_ENABLED ?
+ new SparkAdapter(restConfiguration, tokenAuthService)
+ : new IncapableDummyAdapter(restConfiguration);
+ }
+
+ @Listener
+ public void onPreInt(IBotStateEvent.IPreInitializeEvent preInitializeEvent) {
+ injectionService.injectInto(tokenAuthService);
+ eventService.registerListener(tokenAuthService);
+ serviceManager.registerService(ITokenAuthService.class, tokenAuthService);
+
+ injectionService.injectInto(restConfiguration);
+ restConfiguration.loadConfig();
+ injectionService.injectInto(httpServer);
+ }
+
+ @Listener
+ public void onInit(IBotStateEvent.IInitializeEvent event) {
+ httpServer.start();
+ }
+
+ @Override
+ public void registerController(Class cntrlrClass, T instance) {
+ ControllerContainer controllerContainer = new ControllerContainer(instance);
+ if (!doesControllerAlreadyExist(instance)) {
+ controllers.put(cntrlrClass, instance);
+ httpServer.registerController(controllerContainer);
+ } else {
+ throw new RegisterControllerException("There is already a controller with the same endpoint");
+ }
+ }
+
+ @Override
+ public Optional provide(Class cntrlrClass) {
+ Object cntrlr = controllers.getOrDefault(cntrlrClass, null);
+ return Optional.ofNullable((T) cntrlr);
+ }
+
+ @Override
+ public T provideUnchecked(Class cntrlrClass) {
+ Objects.requireNonNull(cntrlrClass, "Controller class type hint cannot be null!");
+ return cntrlrClass.cast(controllers.get(cntrlrClass));
+ }
+
+ @Override
+ public Map, Object> provideAll() {
+ return Collections.unmodifiableMap(controllers);
+ }
+
+ private boolean doesControllerAlreadyExist(T restController) {
+ Class> controllerClass = restController.getClass();
+ if (controllers.containsKey(controllerClass)) {
+ return false;
+ }
+ return controllers.keySet().stream()
+ .filter(aClass -> extractPluginId(aClass).equals(extractPluginId(controllerClass)))
+ .anyMatch(aClass -> extractControllerPath(aClass).equals(extractControllerPath(controllerClass)));
+ }
+
+ private String extractControllerPath(Class> clazz) {
+ return clazz.getAnnotation(RestController.class).path();
+ }
+
+ private String extractPluginId(Class> clazz) {
+ return clazz.getAnnotation(RestController.class).pluginId();
+ }
+}
diff --git a/src/main/java/de/fearnixx/jeak/service/http/ControllerUtil.java b/src/main/java/de/fearnixx/jeak/service/http/ControllerUtil.java
new file mode 100644
index 00000000..11977aa7
--- /dev/null
+++ b/src/main/java/de/fearnixx/jeak/service/http/ControllerUtil.java
@@ -0,0 +1,15 @@
+package de.fearnixx.jeak.service.http;
+
+import java.util.regex.Pattern;
+
+public class ControllerUtil {
+
+ private static final Pattern startWithPattern = Pattern.compile("^/*(.+)$");
+ private static final Pattern endWithPattern = Pattern.compile("^(.+?)/*$");
+
+ public static String joinWithSlash(String a, String b) {
+ final var first = a != null && !a.isBlank() ? endWithPattern.matcher(a).group(1) : "";
+ final var last = b != null && !b.isBlank() ? startWithPattern.matcher(b).group(1) : "";
+ return first + "/" + last;
+ }
+}
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/connection/HttpServer.java b/src/main/java/de/fearnixx/jeak/service/http/connection/HttpServer.java
similarity index 84%
rename from src/main/java/de/fearnixx/jeak/service/controller/connection/HttpServer.java
rename to src/main/java/de/fearnixx/jeak/service/http/connection/HttpServer.java
index c13dd4db..a0533db2 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/connection/HttpServer.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/connection/HttpServer.java
@@ -1,17 +1,16 @@
-package de.fearnixx.jeak.service.controller.connection;
+package de.fearnixx.jeak.service.http.connection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import de.fearnixx.jeak.reflect.PathParam;
-import de.fearnixx.jeak.reflect.RequestParam;
-import de.fearnixx.jeak.service.controller.controller.ControllerContainer;
-import de.fearnixx.jeak.service.controller.controller.ControllerMethod;
-import de.fearnixx.jeak.service.controller.controller.MethodParameter;
+import de.fearnixx.jeak.reflect.http.params.PathParam;
+import de.fearnixx.jeak.reflect.http.params.QueryParam;
+import de.fearnixx.jeak.service.http.controller.ControllerContainer;
+import de.fearnixx.jeak.service.http.controller.ControllerMethod;
+import de.fearnixx.jeak.service.http.controller.MethodParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.xbill.DNS.RPRecord;
import spark.Request;
import java.io.IOException;
@@ -94,20 +93,19 @@ protected Object transformRequestOption(String string, Request request, MethodPa
}
/**
- * Retrieve the name from a {@link RequestParam} annotated value. Only call the method, if you are sure the used
- * {@link MethodParameter} is annotated with an {@link RequestParam}.
+ * Retrieve the name from a {@link QueryParam} annotated value. Only call the method, if you are sure the used
+ * {@link MethodParameter} is annotated with an {@link QueryParam}.
*
* @param methodParameter
* @return The name of the annotated variable.
*/
protected String getRequestParamName(MethodParameter methodParameter) {
- Function function = annotation -> ((RequestParam) annotation).name();
- return (String) methodParameter.callAnnotationFunction(function, RequestParam.class).get();
+ Function function = annotation -> ((QueryParam) annotation).name();
+ return (String) methodParameter.callAnnotationFunction(function, QueryParam.class).get();
}
- protected Object getRequestParamType(MethodParameter methodParameter) {
- Function function = annotation -> ((RequestParam) annotation).type();
- return methodParameter.callAnnotationFunction(function, RequestParam.class).orElse(String.class);
+ protected Class> getRequestParamType(MethodParameter methodParameter) {
+ return methodParameter.getType();
}
protected String getPathParamName(MethodParameter methodParameter) {
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/connection/RestConfiguration.java b/src/main/java/de/fearnixx/jeak/service/http/connection/RestConfiguration.java
similarity index 97%
rename from src/main/java/de/fearnixx/jeak/service/controller/connection/RestConfiguration.java
rename to src/main/java/de/fearnixx/jeak/service/http/connection/RestConfiguration.java
index cfb14f48..ac90c9e2 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/connection/RestConfiguration.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/connection/RestConfiguration.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.controller.connection;
+package de.fearnixx.jeak.service.http.connection;
import de.fearnixx.jeak.reflect.Config;
import de.fearnixx.jeak.reflect.Inject;
@@ -84,8 +84,8 @@ private Optional getValueFromHttpsConfig(String value, Class type) {
return Optional.ofNullable(getConfig().getNode(HTTPS_CONFIG).optValueMap(type).get(value));
}
- public Optional isHttpsEnabled() {
- return getValueFromHttpsConfig(HTTPS_ENABLED, Boolean.class);
+ public boolean isHttpsEnabled() {
+ return getValueFromHttpsConfig(HTTPS_ENABLED, Boolean.class).orElse(false);
}
public Optional isBehindSslProxy() {
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/controller/ControllerContainer.java b/src/main/java/de/fearnixx/jeak/service/http/controller/ControllerContainer.java
similarity index 88%
rename from src/main/java/de/fearnixx/jeak/service/controller/controller/ControllerContainer.java
rename to src/main/java/de/fearnixx/jeak/service/http/controller/ControllerContainer.java
index 10adc3ce..a8a1eb26 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/controller/ControllerContainer.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/controller/ControllerContainer.java
@@ -1,7 +1,8 @@
-package de.fearnixx.jeak.service.controller.controller;
+package de.fearnixx.jeak.service.http.controller;
-import de.fearnixx.jeak.reflect.RequestMapping;
-import de.fearnixx.jeak.reflect.RestController;
+import de.fearnixx.jeak.reflect.http.RequestMapping;
+import de.fearnixx.jeak.reflect.http.RestController;
+import de.fearnixx.jeak.service.http.ControllerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,7 +52,7 @@ public Optional getAnnotation(Class annotationClass
private String extractControllerRoute(Object o) {
RestController annotation = o.getClass().getAnnotation(RestController.class);
- return annotation.pluginId().concat(annotation.endpoint());
+ return ControllerUtil.joinWithSlash(annotation.pluginId(), annotation.path());
}
/**
@@ -67,7 +68,7 @@ public Object invoke(ControllerMethod controllerMethod, Object... methodParamete
return controllerMethod.invoke(controllerObject, methodParameters);
}
- public Object getControllerObject() {
+ public Object getControllerInstance() {
return controllerObject;
}
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/controller/ControllerMethod.java b/src/main/java/de/fearnixx/jeak/service/http/controller/ControllerMethod.java
similarity index 95%
rename from src/main/java/de/fearnixx/jeak/service/controller/controller/ControllerMethod.java
rename to src/main/java/de/fearnixx/jeak/service/http/controller/ControllerMethod.java
index 8a42a901..75452004 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/controller/ControllerMethod.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/controller/ControllerMethod.java
@@ -1,6 +1,6 @@
-package de.fearnixx.jeak.service.controller.controller;
+package de.fearnixx.jeak.service.http.controller;
-import de.fearnixx.jeak.service.controller.RequestMethod;
+import de.fearnixx.jeak.service.http.RequestMethod;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/controller/IncapableDummyAdapter.java b/src/main/java/de/fearnixx/jeak/service/http/controller/IncapableDummyAdapter.java
similarity index 83%
rename from src/main/java/de/fearnixx/jeak/service/controller/controller/IncapableDummyAdapter.java
rename to src/main/java/de/fearnixx/jeak/service/http/controller/IncapableDummyAdapter.java
index 3e8d152e..5921b72c 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/controller/IncapableDummyAdapter.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/controller/IncapableDummyAdapter.java
@@ -1,8 +1,8 @@
-package de.fearnixx.jeak.service.controller.controller;
+package de.fearnixx.jeak.service.http.controller;
import de.fearnixx.jeak.Main;
-import de.fearnixx.jeak.service.controller.connection.HttpServer;
-import de.fearnixx.jeak.service.controller.connection.RestConfiguration;
+import de.fearnixx.jeak.service.http.connection.HttpServer;
+import de.fearnixx.jeak.service.http.connection.RestConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/controller/MethodParameter.java b/src/main/java/de/fearnixx/jeak/service/http/controller/MethodParameter.java
similarity index 91%
rename from src/main/java/de/fearnixx/jeak/service/controller/controller/MethodParameter.java
rename to src/main/java/de/fearnixx/jeak/service/http/controller/MethodParameter.java
index fd30f85f..fcfa907c 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/controller/MethodParameter.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/controller/MethodParameter.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.controller.controller;
+package de.fearnixx.jeak.service.http.controller;
import java.lang.annotation.Annotation;
@@ -48,10 +48,11 @@ public boolean hasAnnotation(Class extends Annotation> clazz) {
* @return The {@link Annotation} if the parameter is marked by the provided annotation,
* {@code Optional.empty()} otherwise.
*/
- public Optional extends Annotation> getAnnotation(Class extends Annotation> clazz) {
+ public Optional getAnnotation(Class clazz) {
return annotations.stream()
.filter(o -> o.annotationType().equals(clazz))
- .findFirst();
+ .findFirst()
+ .map(clazz::cast);
}
/**
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/controller/SparkAdapter.java b/src/main/java/de/fearnixx/jeak/service/http/controller/SparkAdapter.java
similarity index 69%
rename from src/main/java/de/fearnixx/jeak/service/controller/controller/SparkAdapter.java
rename to src/main/java/de/fearnixx/jeak/service/http/controller/SparkAdapter.java
index dbb898ab..66c8e920 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/controller/SparkAdapter.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/controller/SparkAdapter.java
@@ -1,12 +1,19 @@
-package de.fearnixx.jeak.service.controller.controller;
+package de.fearnixx.jeak.service.http.controller;
import de.fearnixx.jeak.Main;
-import de.fearnixx.jeak.reflect.*;
-import de.fearnixx.jeak.service.controller.RequestMethod;
-import de.fearnixx.jeak.service.controller.ResponseEntity;
-import de.fearnixx.jeak.service.controller.connection.HttpServer;
-import de.fearnixx.jeak.service.controller.connection.IConnectionVerifier;
-import de.fearnixx.jeak.service.controller.connection.RestConfiguration;
+import de.fearnixx.jeak.reflect.http.RequestMapping;
+import de.fearnixx.jeak.reflect.http.RestController;
+import de.fearnixx.jeak.reflect.http.params.PathParam;
+import de.fearnixx.jeak.reflect.http.params.QueryParam;
+import de.fearnixx.jeak.reflect.http.params.RequestBody;
+import de.fearnixx.jeak.reflect.http.params.RequestContext;
+import de.fearnixx.jeak.service.http.RequestMethod;
+import de.fearnixx.jeak.service.http.ResponseEntity;
+import de.fearnixx.jeak.service.http.connection.HttpServer;
+import de.fearnixx.jeak.service.http.connection.RestConfiguration;
+import de.fearnixx.jeak.service.http.request.IRequestContext;
+import de.fearnixx.jeak.service.http.request.SparkRequestContext;
+import de.fearnixx.jeak.service.http.request.auth.token.TokenAuthService;
import org.eclipse.jetty.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,6 +27,8 @@
import java.util.Map;
import java.util.Optional;
+import static de.fearnixx.jeak.service.http.request.IRequestContext.Attributes;
+
public class SparkAdapter extends HttpServer {
private static final Logger logger = LoggerFactory.getLogger(SparkAdapter.class);
@@ -27,11 +36,12 @@ public class SparkAdapter extends HttpServer {
public static final int MAX_THREADS = Main.getProperty("jeak.sparkadapter.maxpoolsize", 8);
public static final int MIN_THREADS = Main.getProperty("jeak.sparkadapter.minpoolsize", 3);
public static final int TIMEOUT_MILLIS = 30000;
- private IConnectionVerifier connectionVerifier;
+
+ private final TokenAuthService tokenAuthService;
private Map headers;
private Service service;
- public SparkAdapter(IConnectionVerifier connectionVerifier, RestConfiguration restConfiguration) {
+ public SparkAdapter(RestConfiguration restConfiguration, TokenAuthService tokenAuthService) {
super(restConfiguration);
service = Service.ignite();
// only use NUM_THREADS, if it was configured
@@ -40,7 +50,7 @@ public SparkAdapter(IConnectionVerifier connectionVerifier, RestConfiguration re
} else {
service.threadPool(MAX_THREADS, MIN_THREADS, TIMEOUT_MILLIS);
}
- this.connectionVerifier = connectionVerifier;
+ this.tokenAuthService = tokenAuthService;
}
/**
@@ -151,10 +161,12 @@ private Object[] extractParameters(List methodParameterList, Re
Object retrievedParameter = null;
if (methodParameter.hasAnnotation(PathParam.class)) {
retrievedParameter = transformRequestOption(request.params(getPathParamName(methodParameter)), request, methodParameter);
- } else if (methodParameter.hasAnnotation(RequestParam.class)) {
+ } else if (methodParameter.hasAnnotation(QueryParam.class)) {
retrievedParameter = transformRequestOption(request.queryMap(getRequestParamName(methodParameter)).value(), request, methodParameter);
} else if (methodParameter.hasAnnotation(RequestBody.class)) {
retrievedParameter = transformRequestOption(request.body(), request, methodParameter);
+ } else if (methodParameter.hasAnnotation(RequestContext.class)) {
+ retrievedParameter = transformRequestContext(request, methodParameter);
}
methodParameters[methodParameter.getPosition()] = retrievedParameter;
}
@@ -162,23 +174,64 @@ private Object[] extractParameters(List methodParameterList, Re
return methodParameters;
}
+ protected Object transformRequestContext(Request request, MethodParameter methodParameter) {
+ RequestContext annotation = methodParameter.getAnnotation(RequestContext.class).orElseThrow();
+ var attributeIdent = annotation.attribute();
+
+ if (attributeIdent.isBlank()) {
+ return request.attribute(IRequestContext.Attributes.REQUEST_CONTEXT);
+ } else {
+ var storedValue = request.attribute(attributeIdent);
+
+ if (annotation.required() && storedValue == null) {
+ switch (attributeIdent) {
+ case IRequestContext.Attributes.AUTHENTICATION_USER:
+ if (request.attribute(IRequestContext.Attributes.AUTHENTICATION_TOKEN) != null) {
+ // #halt throws RuntimeException!
+ service.halt(HttpStatus.FORBIDDEN_403, "Only users are allowed to use this endpoint.");
+ } else {
+ // #halt throws RuntimeException!
+ service.halt(HttpStatus.UNAUTHORIZED_401);
+ }
+ return null;
+ case IRequestContext.Attributes.AUTHENTICATION_TOKEN:
+ // #halt throws RuntimeException!
+ service.halt(HttpStatus.UNAUTHORIZED_401);
+ return null;
+ default:
+ var msg = String.format("Required context attribute \"%s\" is unset!", attributeIdent);
+ throw new IllegalStateException(msg);
+ }
+ } else if (storedValue == null) {
+ return null;
+ } else {
+ if (!methodParameter.getType().isAssignableFrom(storedValue.getClass())) {
+ var msg = String.format("Context attribute is not compatible with parameter type: \"%s\" vs. \"%s\"", storedValue.getClass(), methodParameter.getType());
+ throw new IllegalStateException(msg);
+ }
+ return storedValue;
+ }
+ }
+ }
+
private void addBeforeHandlingCheck(String path, ControllerContainer controllerContainer, ControllerMethod controllerMethod) {
service.before(path, (request, response) -> {
controllerContainer.getAnnotation(RestController.class).ifPresent(restController -> {
if (getRestConfiguration().rejectUnencryptedTraffic().orElse(RestConfiguration.DEFAULT_HTTPS_REJECT_UNENCRYPTED) && !isProtocolHttps(request)) {
- logger.debug("HTTPS enforcement enabled, non HTTPS request for {} blocked", path);
+ logger.info("HTTPS enforcement enabled, non HTTPS request for {} blocked", path);
service.halt(426, "{\"errors\": [\"Use of HTTPS is mandatory for this endpoint\"]}");
}
});
- controllerMethod.getAnnotation(RequestMapping.class).ifPresent(requestMapping -> {
- if (requestMapping.isSecured()) {
- boolean isAuthorized = connectionVerifier.verifyRequest(path, request.headers("Authorization"));
- if (!isAuthorized) {
- logger.debug("Authorization for Request to {} failed", path);
- service.halt(401);
- }
+
+ if (!tokenAuthService.attemptAuthentication(request)) {
+ var mapping = controllerMethod.getAnnotation(RequestMapping.class);
+ if (mapping.isPresent()) {
+ logger.info("Unauthenticated HTTP request blocked.");
+ service.halt(HttpStatus.UNAUTHORIZED_401, "{\"errors\": [\"Authentication is mandatory for this endpoint!\"]");
}
- });
+ }
+
+ request.attribute(Attributes.REQUEST_CONTEXT, new SparkRequestContext(request));
});
}
@@ -193,15 +246,12 @@ private boolean isProtocolHttps(Request request) {
@Override
public void start() {
getRestConfiguration().getPort().ifPresent(service::port);
- getRestConfiguration().isHttpsEnabled().ifPresent(isHttpsEnabled -> {
- if (Boolean.TRUE.equals(isHttpsEnabled)) {
- logger.info("Https enabled");
- initHttps();
- } else {
- logger.info("HTTPS disabled");
- }
- });
-
+ if (getRestConfiguration().isHttpsEnabled()) {
+ logger.info("HTTPS enabled");
+ initHttps();
+ } else {
+ logger.info("HTTPS disabled");
+ }
}
private void initHttps() {
diff --git a/src/main/java/de/fearnixx/jeak/service/http/request/SparkRequestContext.java b/src/main/java/de/fearnixx/jeak/service/http/request/SparkRequestContext.java
new file mode 100644
index 00000000..ae2d81c5
--- /dev/null
+++ b/src/main/java/de/fearnixx/jeak/service/http/request/SparkRequestContext.java
@@ -0,0 +1,28 @@
+package de.fearnixx.jeak.service.http.request;
+
+import spark.Request;
+
+import java.util.Objects;
+import java.util.Optional;
+
+public class SparkRequestContext implements IRequestContext {
+
+ private final Request sparkRequest;
+
+ public SparkRequestContext(Request sparkRequest) {
+ this.sparkRequest = sparkRequest;
+ }
+
+ @Override
+ public Optional optAttribute(String name, Class hint) {
+ Objects.requireNonNull(name, "Attribute name may not be null!");
+ Objects.requireNonNull(hint, "Attribute type hint may not be null!");
+ var sparkValue = sparkRequest.attribute(name);
+
+ if (sparkValue == null || !hint.isAssignableFrom(sparkValue.getClass())) {
+ return Optional.empty();
+ } else {
+ return Optional.of(hint.cast(sparkValue));
+ }
+ }
+}
diff --git a/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/AuthenticationToken.java b/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/AuthenticationToken.java
new file mode 100644
index 00000000..2703b899
--- /dev/null
+++ b/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/AuthenticationToken.java
@@ -0,0 +1,35 @@
+package de.fearnixx.jeak.service.http.request.auth.token;
+
+import de.fearnixx.jeak.service.http.request.token.IAuthenticationToken;
+import de.fearnixx.jeak.teamspeak.data.IUser;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+public class AuthenticationToken implements IAuthenticationToken {
+
+ private final String tokenStr;
+ private final IUser tokenOwner;
+ private final ZonedDateTime tokenExpiry;
+
+ public AuthenticationToken(String tokenStr, IUser tokenOwner, ZonedDateTime tokenExpiry) {
+ this.tokenStr = tokenStr;
+ this.tokenOwner = tokenOwner;
+ this.tokenExpiry = tokenExpiry;
+ }
+
+ @Override
+ public String getTokenString() {
+ return tokenStr;
+ }
+
+ @Override
+ public IUser getTokenOwner() {
+ return tokenOwner;
+ }
+
+ @Override
+ public Optional getExpiry() {
+ return Optional.ofNullable(tokenExpiry);
+ }
+}
diff --git a/src/main/java/de/fearnixx/jeak/service/token/RandomString.java b/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/RandomString.java
similarity index 96%
rename from src/main/java/de/fearnixx/jeak/service/token/RandomString.java
rename to src/main/java/de/fearnixx/jeak/service/http/request/auth/token/RandomString.java
index e3d458e8..c73dfe78 100644
--- a/src/main/java/de/fearnixx/jeak/service/token/RandomString.java
+++ b/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/RandomString.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.token;
+package de.fearnixx.jeak.service.http.request.auth.token;
import java.security.SecureRandom;
import java.util.Locale;
diff --git a/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/TokenAuthService.java b/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/TokenAuthService.java
new file mode 100644
index 00000000..5acc584a
--- /dev/null
+++ b/src/main/java/de/fearnixx/jeak/service/http/request/auth/token/TokenAuthService.java
@@ -0,0 +1,236 @@
+package de.fearnixx.jeak.service.http.request.auth.token;
+
+import de.fearnixx.jeak.Main;
+import de.fearnixx.jeak.event.bot.IBotStateEvent;
+import de.fearnixx.jeak.reflect.Config;
+import de.fearnixx.jeak.reflect.Inject;
+import de.fearnixx.jeak.reflect.Listener;
+import de.fearnixx.jeak.reflect.LocaleUnit;
+import de.fearnixx.jeak.service.command.ICommandExecutionContext;
+import de.fearnixx.jeak.service.command.ICommandService;
+import de.fearnixx.jeak.service.http.request.IRequestContext;
+import de.fearnixx.jeak.service.http.request.token.IAuthenticationToken;
+import de.fearnixx.jeak.service.http.request.token.ITokenAuthService;
+import de.fearnixx.jeak.service.locale.ILocalizationUnit;
+import de.fearnixx.jeak.service.teamspeak.IUserService;
+import de.fearnixx.jeak.teamspeak.data.IUser;
+import de.fearnixx.jeak.util.Configurable;
+import de.mlessmann.confort.api.IConfig;
+import de.mlessmann.confort.api.IConfigNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import spark.Request;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+import static de.fearnixx.jeak.service.command.spec.Commands.commandSpec;
+import static de.fearnixx.jeak.service.command.spec.Commands.paramSpec;
+
+public class TokenAuthService extends Configurable implements ITokenAuthService {
+
+ private static final Logger logger = LoggerFactory.getLogger(TokenAuthService.class);
+ private static final int TOKEN_LENGTH = Main.getProperty("jeak.http.auth.token_length", 128);
+ private static final String AUTHENTICATION_TOKEN_PERMISSION = "jeak.command.http.authToken";
+ public static final String AUTHENTICATION_TOKEN_PERMISSION_BASE = AUTHENTICATION_TOKEN_PERMISSION + ".base";
+ public static final String AUTHENTICATION_TOKEN_PERMISSION_OTHER = AUTHENTICATION_TOKEN_PERMISSION + ".other";
+ private static final String MSG_TOKEN_GENERATED = "http.auth.token.generated";
+
+ private static final String NO_EXPIRY_VALUE = "never";
+ public static final String EXPIRY_NODE_NAME = "expiry";
+ public static final DateTimeFormatter EXPIRY_FORMAT = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+
+ private static final Pattern HEADER_EXTRACTION_PATTERN = Pattern.compile("Token (.+)$");
+
+ private final RandomString tokenGenerator = new RandomString(TOKEN_LENGTH);
+
+ @Inject
+ @Config(category = "rest", id = "token-auth")
+ private IConfig tokenConfig;
+
+ @Inject
+ private IUserService userService;
+
+ @Inject
+ private ICommandService commandService;
+
+ @Inject
+ @LocaleUnit(value = "jeak.http", defaultResource = "localization/http.json")
+ private ILocalizationUnit localizationUnit;
+
+ public TokenAuthService() {
+ super(TokenAuthService.class);
+ }
+
+ @Override
+ protected IConfig getConfigRef() {
+ return tokenConfig;
+ }
+
+ protected IConfigNode getTokensNode() {
+ return getConfig().getNode("tokens");
+ }
+
+ public synchronized boolean attemptAuthentication(Request request) {
+ var authHeader = request.headers("Authorization");
+ if (authHeader == null || authHeader.isBlank()) {
+ logger.debug("Request without Auth-Header.");
+ return false;
+ }
+
+ var matcher = HEADER_EXTRACTION_PATTERN.matcher(authHeader);
+ if (!matcher.find()) {
+ logger.debug("Token scheme not found in header: {}", authHeader);
+ return false;
+ }
+
+ String token = matcher.group(1);
+ var optTokenMapEntry = getTokensNode().optMap()
+ .orElseGet(Collections::emptyMap)
+ .entrySet()
+ .stream()
+ .filter(pair -> !pair.getValue().getNode(token).isVirtual())
+ .findAny();
+ if (optTokenMapEntry.isEmpty()) {
+ logger.debug("No match found for token: {}", token);
+ return false;
+ }
+
+ String userUID = optTokenMapEntry.get().getKey();
+ var tokenExpiryStr = optTokenMapEntry.get().getValue().getNode(token, EXPIRY_NODE_NAME).optString("");
+ ZonedDateTime expiry = null;
+ if (!NO_EXPIRY_VALUE.equals(tokenExpiryStr)) {
+ try {
+ expiry = ZonedDateTime.parse(tokenExpiryStr, EXPIRY_FORMAT);
+ if (ZonedDateTime.now().isAfter(expiry)) {
+ logger.warn("Access with expired token: '{}' -> '{}'.", userUID, token);
+ return false;
+ }
+
+ } catch (DateTimeParseException e) {
+ logger.warn("Cannot read expiry value of token: '{}' -> '{}'", userUID, token, e);
+ return false;
+ }
+ }
+
+ var user = userService.findUserByUniqueID(userUID)
+ .stream()
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("Token owner of token " + token + " could not be found! (" + userUID + ")"));
+ AuthenticationToken tokenInstance = new AuthenticationToken(token, user, expiry);
+ request.attribute(IRequestContext.Attributes.AUTHENTICATION_TOKEN, tokenInstance);
+ request.attribute(IRequestContext.Attributes.AUTHENTICATION_USER, user);
+ return true;
+ }
+
+ @Override
+ public synchronized IAuthenticationToken generateToken(IUser tokenOwner) {
+ Objects.requireNonNull(tokenOwner, "Token owner may not be null!");
+ var tokenStr = tokenGenerator.nextString();
+ var tokenInstance = new AuthenticationToken(tokenStr, tokenOwner, null);
+
+ var ownerNode = getTokensNode().getNode(tokenOwner.getClientUniqueID());
+ var tokenNode = ownerNode.getNode(tokenStr);
+ tokenNode.getNode(EXPIRY_NODE_NAME).setString(NO_EXPIRY_VALUE);
+
+ logger.info("Generated new authentication token for subject '{}': {}", tokenOwner, tokenStr);
+ return tokenInstance;
+ }
+
+ @Override
+ public synchronized void setTokenExpiry(IAuthenticationToken token, ZonedDateTime expiryValue) {
+ Objects.requireNonNull(token, "Provided token cannot be null.");
+
+ var tokenNode = getTokensNode().getNode(token.getTokenString());
+ if (tokenNode.isVirtual()) {
+ throw new IllegalArgumentException("Token '" + token.getTokenString() + "' does not exist!");
+ }
+ if (expiryValue == null) {
+ tokenNode.getNode(EXPIRY_NODE_NAME).setString(NO_EXPIRY_VALUE);
+ logger.debug("Set token expiry of '{}' to: {}", token.getTokenString(), NO_EXPIRY_VALUE);
+ } else if (ZonedDateTime.now().isAfter(expiryValue)) {
+ logger.warn("Revoking token '{}' as expiry was set to past.", token.getTokenString());
+ revokeToken(token);
+ } else {
+ tokenNode.getNode(EXPIRY_NODE_NAME).setString(expiryValue.format(EXPIRY_FORMAT));
+ }
+ }
+
+ @Override
+ public synchronized void revokeToken(IAuthenticationToken token) {
+ var optEntry = getTokensNode().optMap().orElseGet(Collections::emptyMap)
+ .entrySet()
+ .stream()
+ .filter(e -> !e.getValue().getNode(token.getTokenString()).isVirtual())
+ .findFirst();
+
+ if (optEntry.isEmpty()) {
+ logger.debug("Cannot remove token '{}'. Not found!", token.getTokenString());
+ } else {
+ var subject = optEntry.get().getKey();
+ logger.info("Revoking token '{}' of subject '{}'.", token.getTokenString(), subject);
+ getTokensNode().getNode(subject).remove(token.getTokenString());
+ }
+ }
+
+ @Override
+ public synchronized void revokeTokens(IUser tokenOwner) {
+ revokeTokens(tokenOwner.getClientUniqueID());
+ }
+
+ protected synchronized void revokeTokens(String ts3uid) {
+ logger.info("Revoking tokens of subject '{}'", ts3uid);
+ getTokensNode().remove(ts3uid);
+ }
+
+ @Listener(order = Listener.Orders.EARLY)
+ public synchronized void onInitialize(IBotStateEvent.IInitializeEvent event) {
+ if (!loadConfig()) {
+ event.cancel();
+ }
+
+ // Register commands
+ commandService.registerCommand(
+ commandSpec("auth-token", "http:auth-token", "http:authenticationToken")
+ .permission(AUTHENTICATION_TOKEN_PERMISSION_BASE)
+ .parameters(paramSpec().optional(paramSpec("user", IUser.class)))
+ .executor(this::onUserRequestedPermission)
+ .build());
+ }
+
+ @Listener(order = Listener.Orders.EARLIER)
+ public synchronized void onShutdown(IBotStateEvent.IPreShutdown shutdownEvent) {
+ if (!saveConfig()) {
+ logger.error("Configuration save failed, see preceding error.");
+ }
+ }
+
+ protected void onUserRequestedPermission(ICommandExecutionContext execCtx) {
+ var optTarget = execCtx.getOne("user", IUser.class);
+ if (optTarget.isPresent() && !execCtx.getSender().hasPermission(AUTHENTICATION_TOKEN_PERMISSION_OTHER)) {
+ execCtx.getSender().sendMessage(String.format("You're not allowed to request tokens for others (%s)", AUTHENTICATION_TOKEN_PERMISSION_OTHER));
+ return;
+ }
+
+ var generatedToken = generateToken(optTarget.orElseGet(execCtx::getSender));
+ String notifyMessage = localizationUnit.getContext(execCtx.getSender())
+ .getMessage(MSG_TOKEN_GENERATED, Map.of("token", generatedToken.getTokenString()));
+ execCtx.getSender().sendMessage(notifyMessage);
+ }
+
+ @Override
+ protected boolean populateDefaultConf(IConfigNode root) {
+ root.getNode("tokens").setMap();
+ return true;
+ }
+
+ @Override
+ protected String getDefaultResource() {
+ return null;
+ }
+}
diff --git a/src/main/java/de/fearnixx/jeak/service/token/ITokenService.java b/src/main/java/de/fearnixx/jeak/service/token/ITokenService.java
deleted file mode 100644
index aaea392c..00000000
--- a/src/main/java/de/fearnixx/jeak/service/token/ITokenService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package de.fearnixx.jeak.service.token;
-
-import java.util.Set;
-
-public interface ITokenService {
- /**
- * Verify a given token.
- *
- * @param endpoint The endpoint to verify the token for.
- * @param token The token as {@link String} to verify.
- * @return true if the token could be verified,
- * false otherwise
- */
- boolean verifyToken(String endpoint, String token);
-
- /**
- * Generate a new token for the verification of requests.
- *
- * @param endpointSet The endpoints to register the token for. The List needs to have at least one item.
- * @return The generated token.
- */
- String generateToken(Set endpointSet);
-
- /**
- * Revoke a token.
- *
- * @param token The token to be revoked.
- * @return true if the token was successfully revoked,
- * false otherwise
- */
- boolean revokeToken(String token);
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/token/TokenConfiguration.java b/src/main/java/de/fearnixx/jeak/service/token/TokenConfiguration.java
deleted file mode 100644
index 5d3c6a70..00000000
--- a/src/main/java/de/fearnixx/jeak/service/token/TokenConfiguration.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package de.fearnixx.jeak.service.token;
-
-import de.fearnixx.jeak.reflect.Config;
-import de.fearnixx.jeak.reflect.Inject;
-import de.fearnixx.jeak.util.Configurable;
-import de.mlessmann.confort.api.IConfig;
-import de.mlessmann.confort.api.IConfigNode;
-import de.mlessmann.confort.node.ConfigNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.text.MessageFormat;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-public class TokenConfiguration extends Configurable {
- private static final Logger logger = LoggerFactory.getLogger(TokenConfiguration.class);
- private static final String DEFAULT_TOKEN_CONFIG = "/restService/token/defaultToken.json";
-
- @Inject
- @Config(id = "tokens")
- private IConfig configRef;
-
- public TokenConfiguration() {
- super(TokenConfiguration.class);
- }
-
- @Override
- public boolean loadConfig() {
- return super.loadConfig();
- }
-
- @Override
- protected void onDefaultConfigLoaded() {
- saveConfig();
- }
-
- @Override
- protected IConfig getConfigRef() {
- return configRef;
- }
-
- @Override
- protected String getDefaultResource() {
- return DEFAULT_TOKEN_CONFIG;
- }
-
- @Override
- protected boolean populateDefaultConf(IConfigNode root) {
- return false;
- }
-
- /**
- * Check for the {@link TokenScope} of a given token.
- *
- * @param token The token to find the scopes for.
- * @return An instance of {@link TokenScope} with the scopes of the token.
- */
- public TokenScope getTokenScopes(String token) {
- logger.debug(MessageFormat.format("reading token {0}", token));
- IConfigNode tokenNode = getConfig().getNode(token);
- Set tokenScopes = new HashSet<>();
- if (!tokenNode.isVirtual()) {
- Optional> iConfigNodes = tokenNode.optList();
- iConfigNodes.ifPresent(localIConfigNodes ->
- localIConfigNodes.stream()
- .map(IConfigNode::optString)
- .filter(Optional::isPresent)
- .map(Optional::get)
- .forEach(tokenScopes::add)
- );
- }
- return new TokenScope(tokenScopes);
- }
-
- /**
- * Save the provided token for the provided scope.
- *
- * @param token The token as {@link String} to save.
- * @param tokenScope The scope as {@link TokenScope} the token is valid for.
- */
- public void saveToken(String token, TokenScope tokenScope) {
- ConfigNode child = new ConfigNode();
- child.setList();
- child.appendValue(tokenScope.getScopeSet());
- getConfig().put(token, child);
- }
-
- /**
- * Delete the provided token. Deleting a not existing token is considered a successful removal,
- * since the token isn't existing after the {@link TokenConfiguration#deleteToken} method call.
- *
- * @param token The token as {@link String} to be deleted.
- * @return true if the token was successfully deleted or doesn't exist,
- * false otherwise
- */
- public boolean deleteToken(String token) {
- boolean deleteSuccessful = true;
- if (!getConfig().getNode(token).isVirtual()) {
- deleteSuccessful = getConfig().remove(token) != null;
- }
- return deleteSuccessful;
- }
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/token/TokenScope.java b/src/main/java/de/fearnixx/jeak/service/token/TokenScope.java
deleted file mode 100644
index 158420e5..00000000
--- a/src/main/java/de/fearnixx/jeak/service/token/TokenScope.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package de.fearnixx.jeak.service.token;
-
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * pluginId/
- * pluginId/Controller/
- * pluginId/Controller/method1
- */
-public class TokenScope {
- private Set scopeSet;
-
- public TokenScope(Set scopeSet) {
- this.scopeSet = scopeSet;
- }
-
- /**
- *
- * @param scope
- * @return
- */
- public boolean isInScope(String scope) {
- String[] splittedStrings = splitScope(scope);
- for (int i = 0; i < splittedStrings.length; i++) {
- Optional optionalShortenedScope = combineStrings(Arrays.copyOf(splittedStrings, splittedStrings.length - i));
- if (optionalShortenedScope.isPresent()) {
- String localScope = optionalShortenedScope.get();
- if (scopeSet.contains(localScope)) {
- return true;
- }
- } else {
- return false;
- }
- }
- return false;
- }
-
- public Set getScopeSet() {
- return scopeSet;
- }
-
- private Optional combineStrings(String ... strings) {
- return Arrays.stream(strings)
- .reduce((s, s2) -> s+"/"+s2);
- }
-
- private String[] splitScope(String scope) {
- return scope.split("/");
- }
-}
diff --git a/src/main/java/de/fearnixx/jeak/service/token/TokenService.java b/src/main/java/de/fearnixx/jeak/service/token/TokenService.java
deleted file mode 100644
index 646dec2b..00000000
--- a/src/main/java/de/fearnixx/jeak/service/token/TokenService.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package de.fearnixx.jeak.service.token;
-
-import de.fearnixx.jeak.event.bot.IBotStateEvent;
-import de.fearnixx.jeak.reflect.FrameworkService;
-import de.fearnixx.jeak.reflect.IInjectionService;
-import de.fearnixx.jeak.reflect.Inject;
-import de.fearnixx.jeak.reflect.Listener;
-import de.fearnixx.jeak.except.InvokationBeforeInitializationException;
-
-import java.util.Set;
-
-@FrameworkService(serviceInterface = ITokenService.class)
-public class TokenService implements ITokenService {
-
- @Inject
- private IInjectionService injectionService;
-
- private TokenConfiguration tokenConfiguration;
-
- @Override
- public boolean verifyToken(String endpoint, String token) {
- if (tokenConfiguration == null) {
- throw new InvokationBeforeInitializationException("The TokenConfiguration is not initialized");
- }
- boolean isVerified = false;
- TokenScope tokenScopes = tokenConfiguration.getTokenScopes(token);
- if (tokenScopes.isInScope(endpoint)) {
- isVerified = true;
- }
- return isVerified;
- }
-
- @Override
- public String generateToken(Set endpointSet) {
- if (tokenConfiguration == null) {
- throw new InvokationBeforeInitializationException("The TokenConfiguration is not initialized");
- }
- String token = createToken();
- tokenConfiguration.saveToken(token, new TokenScope(endpointSet));
- return token;
- }
-
- @Override
- public boolean revokeToken(String token) {
- if (tokenConfiguration == null) {
- throw new InvokationBeforeInitializationException("The TokenConfiguration is not initialized");
- }
- return tokenConfiguration.deleteToken(token);
- }
-
- @Listener
- public void onPreInit(IBotStateEvent.IPreInitializeEvent preInitializeEvent) {
- tokenConfiguration = new TokenConfiguration();
- injectionService.injectInto(tokenConfiguration);
- tokenConfiguration.loadConfig();
- }
-
- private String createToken() {
- RandomString tickets = new RandomString(30);
- return tickets.nextString();
- }
-}
diff --git a/src/main/resources/localization/http.json b/src/main/resources/localization/http.json
new file mode 100644
index 00000000..c42bae4d
--- /dev/null
+++ b/src/main/resources/localization/http.json
@@ -0,0 +1,10 @@
+{
+ "langs": {
+ "en": {
+ "http.auth.token.generated": "Auth token generated: %[token]"
+ },
+ "de": {
+ "http.auth.token.generated": "Auth-Token erstellt: %[token]"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/restService/token/defaultToken.json b/src/main/resources/restService/token/defaultToken.json
deleted file mode 100644
index 38f6e230..00000000
--- a/src/main/resources/restService/token/defaultToken.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "Hallo": [
- "/api/testPluginId/test/hello",
- "/api/testPluginId/test/info",
- "/api/testPluginId/test/body"
- ]
-}
\ No newline at end of file
diff --git a/src/main/java/de/fearnixx/jeak/service/controller/testImpls/DummyObject.java b/src/test/java/de/fearnixx/jeak/test/plugin/http/DummyObject.java
similarity index 90%
rename from src/main/java/de/fearnixx/jeak/service/controller/testImpls/DummyObject.java
rename to src/test/java/de/fearnixx/jeak/test/plugin/http/DummyObject.java
index f95cb268..deeabe11 100644
--- a/src/main/java/de/fearnixx/jeak/service/controller/testImpls/DummyObject.java
+++ b/src/test/java/de/fearnixx/jeak/test/plugin/http/DummyObject.java
@@ -1,4 +1,4 @@
-package de.fearnixx.jeak.service.controller.testImpls;
+package de.fearnixx.jeak.test.plugin.http;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/src/test/java/de/fearnixx/jeak/test/plugin/ControllerTestPlugin.java b/src/test/java/de/fearnixx/jeak/test/plugin/http/HTTPTestPlugin.java
similarity index 61%
rename from src/test/java/de/fearnixx/jeak/test/plugin/ControllerTestPlugin.java
rename to src/test/java/de/fearnixx/jeak/test/plugin/http/HTTPTestPlugin.java
index 761ee979..5cfb7bab 100644
--- a/src/test/java/de/fearnixx/jeak/test/plugin/ControllerTestPlugin.java
+++ b/src/test/java/de/fearnixx/jeak/test/plugin/http/HTTPTestPlugin.java
@@ -1,23 +1,21 @@
-package de.fearnixx.jeak.test.plugin;
+package de.fearnixx.jeak.test.plugin.http;
import de.fearnixx.jeak.event.bot.IBotStateEvent;
import de.fearnixx.jeak.reflect.Inject;
import de.fearnixx.jeak.reflect.JeakBotPlugin;
import de.fearnixx.jeak.reflect.Listener;
-import de.fearnixx.jeak.service.controller.IRestControllerService;
-import de.fearnixx.jeak.service.controller.exceptions.RegisterControllerException;
-import de.fearnixx.jeak.service.controller.testImpls.SecondTestController;
-import de.fearnixx.jeak.service.controller.testImpls.TestController;
+import de.fearnixx.jeak.service.http.IControllerService;
+import de.fearnixx.jeak.service.http.exceptions.RegisterControllerException;
import de.fearnixx.jeak.test.AbstractTestPlugin;
-@JeakBotPlugin(id = "controllertestplugin")
-public class ControllerTestPlugin extends AbstractTestPlugin {
+@JeakBotPlugin(id = "httptestplugin")
+public class HTTPTestPlugin extends AbstractTestPlugin {
@Inject
- IRestControllerService restControllerService;
+ IControllerService restControllerService;
- public ControllerTestPlugin() {
+ public HTTPTestPlugin() {
super();
addTest("register_successful");
addTest("register_duplicated_controller");
@@ -32,6 +30,5 @@ public void onInitialize(IBotStateEvent.IInitializeEvent event) {
} catch (RegisterControllerException e) {
success("register_duplicated_controller");
}
-
}
}
diff --git a/src/test/java/de/fearnixx/jeak/test/plugin/http/SecondTestController.java b/src/test/java/de/fearnixx/jeak/test/plugin/http/SecondTestController.java
new file mode 100644
index 00000000..22d05aea
--- /dev/null
+++ b/src/test/java/de/fearnixx/jeak/test/plugin/http/SecondTestController.java
@@ -0,0 +1,26 @@
+package de.fearnixx.jeak.test.plugin.http;
+
+import de.fearnixx.jeak.reflect.http.RequestMapping;
+import de.fearnixx.jeak.reflect.http.RestController;
+import de.fearnixx.jeak.reflect.http.params.QueryParam;
+import de.fearnixx.jeak.reflect.http.params.RequestBody;
+import de.fearnixx.jeak.service.http.RequestMethod;
+
+@RestController(path = "/test", pluginId = "httptestplugin")
+public class SecondTestController {
+
+ @RequestMapping(endpoint = "/hello")
+ public DummyObject hello() {
+ return new DummyObject("second", 20);
+ }
+
+ @RequestMapping(endpoint = "/info/:name")
+ public String returnSentInfo(@QueryParam(name = "name") String name) {
+ return "second" + name;
+ }
+
+ @RequestMapping(method = RequestMethod.POST, endpoint = "/body")
+ public String sendBody(@RequestBody String string) {
+ return "second body " + string;
+ }
+}
diff --git a/src/test/java/de/fearnixx/jeak/test/plugin/http/TestController.java b/src/test/java/de/fearnixx/jeak/test/plugin/http/TestController.java
new file mode 100644
index 00000000..faece67c
--- /dev/null
+++ b/src/test/java/de/fearnixx/jeak/test/plugin/http/TestController.java
@@ -0,0 +1,44 @@
+package de.fearnixx.jeak.test.plugin.http;
+
+import de.fearnixx.jeak.reflect.http.RequestMapping;
+import de.fearnixx.jeak.reflect.http.RestController;
+import de.fearnixx.jeak.reflect.http.params.QueryParam;
+import de.fearnixx.jeak.reflect.http.params.RequestBody;
+import de.fearnixx.jeak.service.http.IResponseEntity;
+import de.fearnixx.jeak.service.http.RequestMethod;
+import de.fearnixx.jeak.service.http.ResponseEntity;
+import org.eclipse.jetty.http.HttpStatus;
+
+@RestController(pluginId = "testPluginId", path = "/test")
+public class TestController {
+
+ @RequestMapping(endpoint = "/hello")
+ public ResponseEntity hello() {
+ return new ResponseEntity.Builder<>(new DummyObject("Finn", 20))
+ .withHeader("Cache-Control", "max-age=0")
+ .withStatus(HttpStatus.OK_200)
+ .build();
+ }
+
+ @RequestMapping(endpoint = "/info")
+ public String returnSentInfo(@QueryParam(name = "name") String name) {
+ return "received " + name;
+ }
+
+ @RequestMapping(method = RequestMethod.POST, endpoint = "/body")
+ public String sendBody(@RequestBody String string) {
+ return "this is the body " + string;
+ }
+
+ @RequestMapping(endpoint = "/int")
+ public String sendStuff(@QueryParam(name = "num") Integer num) {
+ return "received" + num;
+ }
+
+ @RequestMapping(endpoint = "/hallo")
+ public IResponseEntity hallo() {
+ return new ResponseEntity.Builder()
+ .withHeader("some-header", "GET")
+ .build();
+ }
+}