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 clazz) { * @return The {@link Annotation} if the parameter is marked by the provided annotation, * {@code Optional.empty()} otherwise. */ - public Optional getAnnotation(Class 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(); + } +}