This commit is contained in:
Jonathan Cook
2019-10-23 15:01:44 +02:00
parent db85c8f275
commit 684ec0d2e3
20486 changed files with 1642483 additions and 0 deletions
@@ -0,0 +1,74 @@
package restx.demo;
import restx.config.ConfigLoader;
import restx.config.ConfigSupplier;
import restx.factory.Provides;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableSet;
import restx.security.*;
import restx.factory.Module;
import restx.factory.Provides;
import javax.inject.Named;
import java.nio.file.Paths;
@Module
public class AppModule {
@Provides
public SignatureKey signatureKey() {
return new SignatureKey("restx-demo -447494532235718370 restx-demo 801c9eaf-4116-48f2-906b-e979fba72757".getBytes(Charsets.UTF_8));
}
@Provides
@Named("restx.admin.password")
public String restxAdminPassword() {
return "4780";
}
@Provides
public ConfigSupplier appConfigSupplier(ConfigLoader configLoader) {
// Load settings.properties in restx.demo package as a set of config entries
return configLoader.fromResource("restx/demo/settings");
}
@Provides
public CredentialsStrategy credentialsStrategy() {
return new BCryptCredentialsStrategy();
}
@Provides
public BasicPrincipalAuthenticator basicPrincipalAuthenticator(
SecuritySettings securitySettings, CredentialsStrategy credentialsStrategy,
@Named("restx.admin.passwordHash") String defaultAdminPasswordHash, ObjectMapper mapper) {
return new StdBasicPrincipalAuthenticator(new StdUserService<>(
// use file based users repository.
// Developer's note: prefer another storage mechanism for your users if you need real user management
// and better perf
new FileBasedUserRepository<>(
StdUser.class, // this is the class for the User objects, that you can get in your app code
// with RestxSession.current().getPrincipal().get()
// it can be a custom user class, it just need to be json deserializable
mapper,
// this is the default restx admin, useful to access the restx admin console.
// if one user with restx-admin role is defined in the repository, this default user won't be
// available anymore
new StdUser("admin", ImmutableSet.<String>of("*")),
// the path where users are stored
Paths.get("data/users.json"),
// the path where credentials are stored. isolating both is a good practice in terms of security
// it is strongly recommended to follow this approach even if you use your own repository
Paths.get("data/credentials.json"),
// tells that we want to reload the files dynamically if they are touched.
// this has a performance impact, if you know your users / credentials never change without a
// restart you can disable this to get better perfs
true),
credentialsStrategy, defaultAdminPasswordHash),
securitySettings);
}
}
@@ -0,0 +1,32 @@
package restx.demo;
import com.google.common.base.Optional;
import restx.server.WebServer;
import restx.server.Jetty8WebServer;
/**
* This class can be used to run the app.
*
* Alternatively, you can deploy the app as a war in a regular container like tomcat or jetty.
*
* Reading the port from system env PORT makes it compatible with heroku.
*/
public class AppServer {
public static final String WEB_INF_LOCATION = "src/main/webapp/WEB-INF/web.xml";
public static final String WEB_APP_LOCATION = "src/main/webapp";
public static void main(String[] args) throws Exception {
int port = Integer.valueOf(Optional.fromNullable(System.getenv("PORT")).or("8080"));
WebServer server = new Jetty8WebServer(WEB_INF_LOCATION, WEB_APP_LOCATION, port, "0.0.0.0");
/*
* load mode from system property if defined, or default to dev
* be careful with that setting, if you use this class to launch your server in production, make sure to launch
* it with -Drestx.mode=prod or change the default here
*/
System.setProperty("restx.mode", System.getProperty("restx.mode", "dev"));
System.setProperty("restx.app.package", "restx.demo");
server.startAndAwait();
}
}
+10
View File
@@ -0,0 +1,10 @@
package restx.demo;
/**
* A list of roles for the application.
*
* We don't use an enum here because it must be used inside an annotation.
*/
public final class Roles {
public static final String HELLO_ROLE = "hello";
}
@@ -0,0 +1,21 @@
package restx.demo.domain;
public class Message {
private String message;
public String getMessage() {
return message;
}
public Message setMessage(final String message) {
this.message = message;
return this;
}
@Override
public String toString() {
return "Message{" +
"message='" + message + '\'' +
'}';
}
}
@@ -0,0 +1,64 @@
package restx.demo.rest;
import restx.demo.domain.Message;
import restx.demo.Roles;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import restx.annotations.GET;
import restx.annotations.POST;
import restx.annotations.RestxResource;
import restx.factory.Component;
import restx.security.PermitAll;
import restx.security.RolesAllowed;
import restx.security.RestxSession;
import javax.validation.constraints.NotNull;
@Component @RestxResource
public class HelloResource {
/**
* Say hello to currently logged in user.
*
* Authorized only for principals with Roles.HELLO_ROLE role.
*
* @return a Message to say hello
*/
@GET("/message")
@RolesAllowed(Roles.HELLO_ROLE)
public Message sayHello() {
return new Message().setMessage(String.format(
"hello %s, it's %s",
RestxSession.current().getPrincipal().get().getName(),
DateTime.now(DateTimeZone.UTC).toString("HH:mm:ss")));
}
/**
* Say hello to anybody.
*
* Does not require authentication.
*
* @return a Message to say hello
*/
@GET("/hello")
@PermitAll
public Message helloPublic(String who) {
return new Message().setMessage(String.format(
"hello %s, it's %s",
who, DateTime.now(DateTimeZone.UTC).toString("HH:mm:ss")));
}
public static class MyPOJO {
@NotNull
String value;
public String getValue(){ return value; }
public void setValue(String value){ this.value = value; }
}
@POST("/mypojo")
@PermitAll
public MyPOJO helloPojo(MyPOJO pojo){
pojo.setValue("hello "+pojo.getValue());
return pojo;
}
}
+94
View File
@@ -0,0 +1,94 @@
<configuration>
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>true</resetJUL>
</contextListener>
<property name="LOGS_FOLDER" value="${logs.base:-logs}" />
<appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOGS_FOLDER}/errors.log</File>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOGS_FOLDER}/errors.%d.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<if condition='p("restx.mode").equals("prod")'>
<then>
<!-- production mode -->
<appender name="appLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOGS_FOLDER}/app.log</File>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOGS_FOLDER}/app.%d.log</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
</appender>
<appender name="debugFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOGS_FOLDER}/debug.log</File>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${LOGS_FOLDER}/debug.%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>50MB</maxFileSize>
</triggeringPolicy>
</appender>
<root level="INFO">
<appender-ref ref="debugFile" />
<appender-ref ref="appLog" />
</root>
</then>
<else>
<!-- not production mode -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="appLog" class="ch.qos.logback.core.FileAppender">
<File>${LOGS_FOLDER}/app.log</File>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="appLog" />
</root>
</else>
</if>
<!-- clean up container logs -->
<logger name="org.eclipse.jetty.server.AbstractConnector" level="WARN" />
<logger name="org.eclipse.jetty.server.handler.ContextHandler" level="WARN" />
<logger name="org.eclipse.jetty.webapp.StandardDescriptorProcessor" level="WARN" />
<logger name="org.hibernate.validator.internal.engine.ConfigurationImpl" level="WARN" />
<logger name="org.reflections.Reflections" level="WARN" />
<logger name="restx.factory.Factory" level="WARN" />
<!-- app logs - set DEBUG level, in prod it will go to a dedicated file -->
<logger name="restx.demo" level="DEBUG" />
<root level="INFO">
<appender-ref ref="errorFile" />
</root>
</configuration>
@@ -0,0 +1 @@
app.name=restx-demo
+15
View File
@@ -0,0 +1,15 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="true">
<servlet>
<servlet-name>restx</servlet-name>
<servlet-class>restx.servlet.RestxMainRouterServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restx</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
@@ -0,0 +1,23 @@
package restx.demo.rest;
import org.junit.runner.RunWith;
import restx.tests.FindSpecsIn;
import restx.tests.RestxSpecTestsRunner;
@RunWith(RestxSpecTestsRunner.class)
@FindSpecsIn("specs/hello")
public class HelloResourceSpecUnitTest {
/**
* Useless, thanks to both @RunWith(RestxSpecTestsRunner.class) & @FindSpecsIn()
*
* @Rule
* public RestxSpecRule rule = new RestxSpecRule();
*
* @Test
* public void test_spec() throws Exception {
* rule.runTest(specTestPath);
* }
*/
}
@@ -0,0 +1,10 @@
title: should admin say hello
given:
- time: 2013-08-28T01:18:00.822+02:00
- uuids: [ "e2b4430f-9541-4602-9a3a-413d17c56a6b" ]
wts:
- when: |
GET message
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"admin","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
then: |
{"message":"hello admin, it's 23:18:00"}
@@ -0,0 +1,8 @@
title: should admin say hello
given:
- time: 2013-08-28T01:18:00.822+02:00
wts:
- when: |
GET hello?who=xavier
then: |
{"message":"hello xavier, it's 23:18:00"}
@@ -0,0 +1,17 @@
title: should missing post value triggers a validation error
given:
- time: 2013-08-28T01:18:00.822+02:00
- uuids: [ "e2b4430f-9541-4602-9a3a-413d17c56a6b" ]
wts:
- when: |
POST mypojo
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"user1","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
{}
then: |
400
- when: |
POST mypojo
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"user1","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
{"value":"world"}
then: |
{"value":"hello world"}
@@ -0,0 +1,10 @@
title: should user1 say hello
given:
- time: 2013-08-28T01:18:00.822+02:00
- uuids: [ "e2b4430f-9541-4602-9a3a-413d17c56a6b" ]
wts:
- when: |
GET message
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"user1","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
then: |
{"message":"hello user1, it's 23:18:00"}
@@ -0,0 +1,10 @@
title: should user2 not say hello
given:
- time: 2013-08-28T01:19:44.770+02:00
- uuids: [ "56f71fcc-42d3-422f-9458-8ad37fc4a0b5" ]
wts:
- when: |
GET message
$RestxSession: {"_expires":"2013-09-27T01:19:44.770+02:00","principal":"user2","sessionKey":"56f71fcc-42d3-422f-9458-8ad37fc4a0b5"}
then: |
403