diff --git a/.gitignore b/.gitignore index 8fd5bd8..d424146 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ # Created by .ignore support plugin (hsz.mobi) ### Gradle template .gradle -/build/ - +build # Ignore Gradle GUI config gradle-app.setting @@ -37,6 +36,6 @@ gradle-app.setting # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -/out +out/ .idea *.iml diff --git a/.travis.yml b/.travis.yml index 6a25a6d..a6bea9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,14 @@ language: java - jdk: - - oraclejdk8 +- oraclejdk8 before_install: - - chmod +x gradlew - - chmod +x gradle/wrapper/gradle-wrapper.jar +- chmod +x gradlew +- chmod +x gradle/wrapper/gradle-wrapper.jar +notifications: + email: + on_success: never + on_failure: always + slack: + secure: zbrlGnn3XROnnOnu3QIKb+LHTkGbJrqW3xyUNE/egHZ2F6214IrW3nMQ1UCWcf3BgrcA/t+ATPUGT0DJBUbzRypQrJadkG82KavGllAhd6KUBSKGFEGq4+6ug8g+1OhOs/EyxbMH98dftPO9IABs0bByHARU8EQEvaRTkX+MvaF0I6Ni/3nFoYn2xf6aSEZ8/S20co59BN+1GjLTippJERTREWsj6A5LDlsyH5Dw9REbhp2UXRbR2zlw2hbQu6HxTLSf8FAarEh0NsXI5EeCkO2TJsL1R0CejFznPRIk2WLr4TebgWY6Hdk3EUlwuM/4JqEqgml0yLg8/BpG3oB/iXJaYZOhRW7ZALJ9KFCD2CKI4Kdi1CeryGtabl6FfQene9vPDxGkmolTOFtglW5MoHIpLVE/oni0GTQEaqHhdYqCBJ/gHLx+Mrc433cDgIEd9RNqCa6vNzmfS7h7+yHKH4OxzK+PCDezsTvrwca1VCMhKBtdT9YI+hOys5J76Gf42zW8EpY9BsC05H3g+Pp0pbVIe6rmu5RShgJUne16LlkrsLKr03mZes0XsYOYA7VgnMMLFMwd/urbtcKOe1YClfKiAuBcFrpdEbSS8boqkxLbG0BmfcyXyf35eFhKFghom0dZNLa+BgMQ0os7vk3IE25f46/rzmpZRmAUlsW7aco= + on_success: always + on_failure: always \ No newline at end of file diff --git a/README.md b/README.md index 7647456..c0b2fae 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -![build status image](https://travis-ci.org/Team0x2B/core-service-lib.svg?branch=master) +[![build status image](https://travis-ci.org/Team0x2B/org.x2b.study.core-service-lib.svg?branch=master)](https://travis-ci.org/Team0x2B/core-service-lib) Right now this is just me messing around with GraphQL and Spring until I find a pattern I like. Eventually this will be -the core service library used to start writing a new backend service. +the org.x2b.study.core service library used to start writing a new backend service. # GraphQL Conventions diff --git a/build.gradle b/build.gradle index 79e2a72..905d377 100644 --- a/build.gradle +++ b/build.gradle @@ -1,42 +1,40 @@ -group 'org.x2b.study.core' +group 'org.x2b.study' version '0.1-SNAPSHOT' + +tasks { + task wrapper(type: Wrapper) { + gradleVersion = '4.2' + } +} + buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } maven { url 'http://repo.spring.io/plugins-release' } + } dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.7.RELEASE") + classpath "org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE" classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6" } } -tasks { - task wrapper(type: Wrapper) { - gradleVersion = '4.2' - } -} - -apply plugin: 'java' -apply plugin: 'org.springframework.boot' +allprojects { + apply plugin: 'java' -sourceCompatibility = 1.8 -targetCompatibility = 1.8 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} -repositories { - mavenCentral() +subprojects { + repositories { + mavenCentral() + maven {url 'http://repo.spring.io/libs-release'} + } } -bootRepackage.enabled = false dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - - compile 'org.springframework.boot:spring-boot-starter-web' - - compile 'com.graphql-java:graphql-spring-boot-starter:3.9.2' - - compile 'com.graphql-java:graphiql-spring-boot-starter:3.9.2' - - compile 'com.graphql-java:graphql-java-tools:4.1.2' + compile project(":core-service-lib") + compile project(":core-service-lib-integration") } diff --git a/core-service-lib-integration/build.gradle b/core-service-lib-integration/build.gradle new file mode 100644 index 0000000..a1f5c12 --- /dev/null +++ b/core-service-lib-integration/build.gradle @@ -0,0 +1,18 @@ +group 'org.x2b.study.org.x2b.study.core' +version '0.1-SNAPSHOT' + +apply plugin: 'java' + +apply plugin: 'org.springframework.boot' + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + + testCompile("org.springframework.boot:spring-boot-starter-test") + + compile project(':core-service-lib') +} diff --git a/core-service-lib-integration/src/main/java/org/x2b/studi/core/IntegrationTestService.java b/core-service-lib-integration/src/main/java/org/x2b/studi/core/IntegrationTestService.java new file mode 100644 index 0000000..955a354 --- /dev/null +++ b/core-service-lib-integration/src/main/java/org/x2b/studi/core/IntegrationTestService.java @@ -0,0 +1,47 @@ +package org.x2b.studi.core; + +import graphql.schema.idl.RuntimeWiring; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.mongodb.core.MongoClientFactoryBean; +import org.x2b.studi.core.graphql.fetchers.mutation.createuser.CreateUserFetcher; +import org.x2b.studi.core.graphql.fetchers.mutation.getsecure.SecureTestFetcher; +import org.x2b.studi.core.graphql.fetchers.query.getuser.GetUserFetcher; +import org.x2b.studi.core.security.data.mongodb.AuthorizationRepository; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Base64; + +@SpringBootApplication +@ComponentScan(basePackages = "org.x2b.study.core.*") +@EnableCaching +public class IntegrationTestService extends GraphQLServiceConfigure { + + + public static void main(String[] args) throws IOException { + ConfigurableApplicationContext ctx = SpringApplication.run(IntegrationTestService.class, args); + } + + @Autowired + private AuthorizationRepository authRepo; + + @Override + protected RuntimeWiring createRuntimeWiring() { + return RuntimeWiring.newRuntimeWiring() + .type("MutationRoot", w -> w + .dataFetcher("createUser", new CreateUserFetcher(authRepo)) + ) + .type("QueryRoot", w -> w + .dataFetcher("getUserPermissions", new GetUserFetcher(authRepo)) + .dataFetcher("secureGet", new SecureTestFetcher()) + ) + .build(); + } + + +} diff --git a/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/mutation/createuser/CreateUserFetcher.java b/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/mutation/createuser/CreateUserFetcher.java new file mode 100644 index 0000000..f231122 --- /dev/null +++ b/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/mutation/createuser/CreateUserFetcher.java @@ -0,0 +1,30 @@ +package org.x2b.studi.core.graphql.fetchers.mutation.createuser; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.x2b.studi.core.security.data.mongodb.AuthenticatedUser; +import org.x2b.studi.core.security.data.mongodb.AuthorizationRepository; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; + +public class CreateUserFetcher implements DataFetcher { + + + private AuthorizationRepository authRepo; + + public CreateUserFetcher(AuthorizationRepository authRepo) { + this.authRepo = authRepo; + } + + @Override + public String get(DataFetchingEnvironment environment) { + HashMap input = environment.getArgument("input"); + List permissions = (List) input.get("permissions"); + AuthenticatedUser user = new AuthenticatedUser(UUID.randomUUID(), new HashSet<>(permissions)); + authRepo.save(user); + return user.getUUID().toString(); + } +} diff --git a/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/mutation/getsecure/SecureTestFetcher.java b/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/mutation/getsecure/SecureTestFetcher.java new file mode 100644 index 0000000..63f04bf --- /dev/null +++ b/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/mutation/getsecure/SecureTestFetcher.java @@ -0,0 +1,25 @@ +package org.x2b.studi.core.graphql.fetchers.mutation.getsecure; + +import graphql.schema.DataFetchingEnvironment; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.subject.Subject; +import org.x2b.studi.core.graphql.fetchers.SecureRootFetcher; + +public class SecureTestFetcher extends SecureRootFetcher { + private static final Log log = LogFactory.getLog(SecureTestFetcher.class); + + @Override + public void handleAuthenticationFailure(AuthenticationException e, DataFetchingEnvironment environment) { + log.debug("auth failure!"); + } + + @Override + public Object secureGet(DataFetchingEnvironment environment) { + Subject s = SecurityUtils.getSubject(); + + return s.isPermitted("foo:bar:read"); + } +} diff --git a/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/query/getuser/GetUserFetcher.java b/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/query/getuser/GetUserFetcher.java new file mode 100644 index 0000000..aab486e --- /dev/null +++ b/core-service-lib-integration/src/main/java/org/x2b/studi/core/graphql/fetchers/query/getuser/GetUserFetcher.java @@ -0,0 +1,27 @@ +package org.x2b.studi.core.graphql.fetchers.query.getuser; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.x2b.studi.core.security.data.mongodb.AuthenticatedUser; +import org.x2b.studi.core.security.data.mongodb.AuthorizationRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class GetUserFetcher implements DataFetcher> { + + private AuthorizationRepository authRepo; + + public GetUserFetcher(AuthorizationRepository authRepo) { + this.authRepo = authRepo; + } + + @Override + public List get(DataFetchingEnvironment environment) { + String idString = environment.getArgument("id"); + UUID uuid = UUID.fromString(idString); + AuthenticatedUser user = authRepo.findOne(uuid); + return new ArrayList<>(user.getPermissions()); + } +} diff --git a/core-service-lib-integration/src/main/resources/schema.gql b/core-service-lib-integration/src/main/resources/schema.gql new file mode 100644 index 0000000..515d684 --- /dev/null +++ b/core-service-lib-integration/src/main/resources/schema.gql @@ -0,0 +1,17 @@ +type MutationRoot { + createUser(input: PermissionsInput!): ID! +} + +input PermissionsInput { + permissions: [String!]! +} + +type QueryRoot { + getUserPermissions(id: ID!): [String]! + secureGet: String! +} + +schema { + mutation: MutationRoot + query: QueryRoot +} \ No newline at end of file diff --git a/core-service-lib-integration/src/test/java/org/x2b/studi/core/TestIntegrationService.java b/core-service-lib-integration/src/test/java/org/x2b/studi/core/TestIntegrationService.java new file mode 100644 index 0000000..ee9ff06 --- /dev/null +++ b/core-service-lib-integration/src/test/java/org/x2b/studi/core/TestIntegrationService.java @@ -0,0 +1,60 @@ +package org.x2b.studi.core; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration(classes = IntegrationTestService.class) +@TestPropertySource(properties = { + "studi.security.auth.secret=abcdefg123" +}) +public class TestIntegrationService { + + @Autowired + private WebApplicationContext wac; + private MockMvc mockMvc; + + + + + @Before + public void setup() { + DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac); + this.mockMvc = builder.build(); + } + + + @Test + public void contextLoads() throws Exception { + + } + + public ResultMatcher okMatcher() { + return MockMvcResultMatchers.status().isOk(); + } + + @Test + public void testGraphQlResponds() throws Exception { + ResultMatcher ok = okMatcher(); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/graphiql"); + mockMvc.perform(builder) + .andExpect(ok); + } + + +} diff --git a/core-service-lib-integration/src/test/resources/schema.gql b/core-service-lib-integration/src/test/resources/schema.gql new file mode 100644 index 0000000..47a2e1f --- /dev/null +++ b/core-service-lib-integration/src/test/resources/schema.gql @@ -0,0 +1,16 @@ +type MutationRoot { + createUser(input: PermissionsInput!): ID! +} + +input PermissionsInput { + permissions: [String!]! +} + +type QueryRoot { + getUserPermissions(id: ID!): [String]! +} + +schema { + mutation: MutationRoot + query: QueryRoot +} \ No newline at end of file diff --git a/core-service-lib/build.gradle b/core-service-lib/build.gradle new file mode 100644 index 0000000..ac97f2a --- /dev/null +++ b/core-service-lib/build.gradle @@ -0,0 +1,26 @@ +group 'org.x2b.study.org.x2b.study.core' +version '0.1-SNAPSHOT' + + +apply plugin: 'org.springframework.boot' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +bootRepackage.enabled = false + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + + compile 'org.springframework.boot:spring-boot-starter-web:1.5.7.RELEASE' + + compile 'com.graphql-java:graphql-spring-boot-starter:3.9.2' + + compile 'com.graphql-java:graphiql-spring-boot-starter:3.9.2' + + compile 'org.springframework.data:spring-data-mongodb:2.0.0.RELEASE' + + compile 'org.apache.shiro:shiro-all:1.2.3' + + compile 'com.auth0:java-jwt:3.2.0' +} diff --git a/core-service-lib/gradle/wrapper/gradle-wrapper.properties b/core-service-lib/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..807d49e --- /dev/null +++ b/core-service-lib/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Oct 05 15:56:37 MDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.1-bin.zip diff --git a/core-service-lib/gradlew b/core-service-lib/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/core-service-lib/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/core-service-lib/gradlew.bat b/core-service-lib/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/core-service-lib/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/GraphQLServiceConfigure.java b/core-service-lib/src/main/java/org/x2b/studi/core/GraphQLServiceConfigure.java new file mode 100644 index 0000000..4e3a425 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/GraphQLServiceConfigure.java @@ -0,0 +1,123 @@ +package org.x2b.studi.core; + + +import graphql.schema.GraphQLSchema; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; +import org.apache.shiro.mgt.DefaultSubjectDAO; +import org.apache.shiro.mgt.SubjectDAO; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoClientFactoryBean; +import org.springframework.web.filter.DelegatingFilterProxy; +import org.x2b.studi.core.security.data.mongodb.MongoDbDetailsProvider; +import org.x2b.studi.core.security.jwt.JWTUserTokenVerifier; +import org.x2b.studi.core.security.jwt.SharedSecretProvider; +import org.x2b.studi.core.security.shiro.GenericAuthenticatingRealm; + +import java.io.File; + +@Configuration +public abstract class GraphQLServiceConfigure { + + protected String schemaFileLocation = "schema.gql"; + + @Autowired + protected ApplicationContext applicationContext; + + + @Bean + public GraphQLSchema schema() { + SchemaParser parser = new SchemaParser(); + SchemaGenerator schemaGenerator = new SchemaGenerator(); + TypeDefinitionRegistry tdr = parser.parse(getSchemaFile()); + RuntimeWiring runtimeWiring = createRuntimeWiring(); + if (runtimeWiring == null) { + return null; + } + return schemaGenerator.makeExecutableSchema(tdr, runtimeWiring); + } + + @Bean + public GenericAuthenticatingRealm authenticatingRealm() { + return new GenericAuthenticatingRealm(); + } + + @Bean + public DefaultWebSecurityManager securityManager() { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(authenticatingRealm()); + securityManager.setSubjectDAO(createStatelessSubjectDao()); + return securityManager; + } + + private SubjectDAO createStatelessSubjectDao() { + DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + sessionStorageEvaluator.setSessionStorageEnabled(false); + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); + subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator); + return subjectDAO; + } + + @Bean + public FilterRegistrationBean shrioFilterRegistration() { + FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); + DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy(); + delegatingFilterProxy.setTargetBeanName("shiroFilter"); + filterRegistration.setFilter(delegatingFilterProxy); + filterRegistration.setName("shiroFilter"); + filterRegistration.addInitParameter("targetFilterLifecycle", "true"); + filterRegistration.addUrlPatterns("/*"); + return filterRegistration; + } + + @Bean + public ShiroFilterFactoryBean shiroFilter() { + ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean(); + shiroFilterFactory.setSecurityManager(securityManager()); + return shiroFilterFactory; + } + + @Bean + public SharedSecretProvider jwtAuthKeyProvider() { + return new SharedSecretProvider(); + } + + @Bean + public JWTUserTokenVerifier jwtUserTokenVerifier() { + return new JWTUserTokenVerifier((SharedSecretProvider) applicationContext.getBean("jwtAuthKeyProvider")); + } + + + @Bean + public MongoDbDetailsProvider authDbDetailsProvider() { + return new MongoDbDetailsProvider(); + } + + @Bean + public MongoClientFactoryBean authorizationDatasource() { + MongoClientFactoryBean factory = new MongoClientFactoryBean(); + MongoDbDetailsProvider detailsProvider = authDbDetailsProvider(); + + factory.setHost(detailsProvider.getHost()); + factory.setPort(detailsProvider.getPort()); + + return factory; + } + + private File getSchemaFile() { + return new File(this.getClass().getClassLoader().getResource(schemaFileLocation).getFile()); + } + + + protected abstract RuntimeWiring createRuntimeWiring(); +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/ServiceConstants.java b/core-service-lib/src/main/java/org/x2b/studi/core/ServiceConstants.java new file mode 100644 index 0000000..50b4d1e --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/ServiceConstants.java @@ -0,0 +1,18 @@ +package org.x2b.studi.core; + +public final class ServiceConstants { + + private ServiceConstants() {} + + + public static final String SECURITY_TOKEN_ISSUER = "studi_auth_service"; + public static final String SECURITY_UUID_CLAIM = "uuid"; + public static final String SECURITY_AUTHENTICATION_REALM_NAME = "generic_authentication_realm"; + + public static final String SECURITY_DATA_MONGODB_HOST_PROPERTY = "security.data.mongodb.host"; + public static final String SECURITY_DATA_MONGODB_PORT_PROPERTY = "security.data.mongodb.port"; + + public static final String HTTP_AUTH_HEADER = "Authorization"; + + +} diff --git a/src/main/java/org/x2b/study/core/graphql/errors/UnauthorizedException.java b/core-service-lib/src/main/java/org/x2b/studi/core/graphql/errors/UnauthorizedException.java similarity index 94% rename from src/main/java/org/x2b/study/core/graphql/errors/UnauthorizedException.java rename to core-service-lib/src/main/java/org/x2b/studi/core/graphql/errors/UnauthorizedException.java index 7b2acb3..f9f1819 100644 --- a/src/main/java/org/x2b/study/core/graphql/errors/UnauthorizedException.java +++ b/core-service-lib/src/main/java/org/x2b/studi/core/graphql/errors/UnauthorizedException.java @@ -1,4 +1,4 @@ -package org.x2b.study.core.graphql.errors; +package org.x2b.studi.core.graphql.errors; import graphql.ErrorType; import graphql.GraphQLError; diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/graphql/fetchers/SecureRootFetcher.java b/core-service-lib/src/main/java/org/x2b/studi/core/graphql/fetchers/SecureRootFetcher.java new file mode 100644 index 0000000..c87c553 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/graphql/fetchers/SecureRootFetcher.java @@ -0,0 +1,45 @@ +package org.x2b.studi.core.graphql.fetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.mgt.RealmSecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.subject.Subject; +import org.x2b.studi.core.graphql.util.GraphQLUtils; +import org.x2b.studi.core.ServiceConstants; +import org.x2b.studi.core.security.shiro.JWTAuthenticationToken; + +/** + * Provides a secure edge for GraphQL queries. User this for all root query types to ensure that the user is properly + * logged in + * @param + */ +public abstract class SecureRootFetcher implements DataFetcher { + @Override + public T get(DataFetchingEnvironment environment) { + for (Realm realm : ((RealmSecurityManager) SecurityUtils.getSecurityManager()).getRealms()) + System.out.println(realm.getName()); + Subject currentUser = SecurityUtils.getSubject(); + if (!currentUser.isAuthenticated()) { + AuthenticationToken token = new JWTAuthenticationToken(getAuthTokenFromContext(environment)); + try { + currentUser.login(token); + } catch (AuthenticationException e) { + handleAuthenticationFailure(e, environment); + } + } + return secureGet(environment); + } + + + private String getAuthTokenFromContext(DataFetchingEnvironment environment) { + return GraphQLUtils.getHeader(ServiceConstants.HTTP_AUTH_HEADER, environment); //TODO: make this + } + + public abstract void handleAuthenticationFailure(AuthenticationException e, DataFetchingEnvironment environment); + + public abstract T secureGet(DataFetchingEnvironment environment); +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/graphql/util/GraphQLUtils.java b/core-service-lib/src/main/java/org/x2b/studi/core/graphql/util/GraphQLUtils.java new file mode 100644 index 0000000..9413ad3 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/graphql/util/GraphQLUtils.java @@ -0,0 +1,22 @@ +package org.x2b.studi.core.graphql.util; + +import graphql.schema.DataFetchingEnvironment; +import graphql.servlet.GraphQLContext; + +public final class GraphQLUtils { + + private GraphQLUtils() {} + + /** + * @param name The header name + * @param environment The DataFetchingEnvironment + * @return The value of the header or null if the http request does not exist + */ + public static String getHeader(String name, DataFetchingEnvironment environment) { + GraphQLContext context = environment.getContext(); + if (context.getRequest().isPresent()) { + return context.getRequest().get().getHeader(name); + } + return null; + } +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/User.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/User.java new file mode 100644 index 0000000..f540b46 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/User.java @@ -0,0 +1,7 @@ +package org.x2b.studi.core.security; + +import java.util.UUID; + +public interface User { + UUID getUUID(); +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/AuthenticatedUser.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/AuthenticatedUser.java new file mode 100644 index 0000000..d1c9245 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/AuthenticatedUser.java @@ -0,0 +1,52 @@ +package org.x2b.studi.core.security.data.mongodb; + +import org.springframework.data.annotation.Id; +import org.x2b.studi.core.security.User; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class AuthenticatedUser implements User { + + @Id + private final UUID id; + + private final Set permissions; + + public AuthenticatedUser(UUID id, Set permissions) { + this.id = id; + this.permissions = new HashSet<>(permissions); + } + + public boolean hasPermission(String permission) { + return permission.contains(permission); + } + + public Set getPermissions() { + return permissions; + } + + @Override + public UUID getUUID() { + return id; + } + + @Override + public String toString() { + return String.format("User: %s", id); + } + + @Override + public boolean equals(Object other) { + if (other != null && other instanceof AuthenticatedUser) { + return ((AuthenticatedUser) other).id.equals(id); + } + return false; + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/AuthorizationRepository.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/AuthorizationRepository.java new file mode 100644 index 0000000..2bb0d2c --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/AuthorizationRepository.java @@ -0,0 +1,29 @@ +package org.x2b.studi.core.security.data.mongodb; + +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.UUID; + +public interface AuthorizationRepository extends MongoRepository { + + String AUTHORIZED_USER_CACHE = "authorizedUser"; + + @Override + @Cacheable(AUTHORIZED_USER_CACHE) + AuthenticatedUser findOne(UUID uuid); + + + @Override + @CacheEvict(AUTHORIZED_USER_CACHE) + AuthenticatedUser save(AuthenticatedUser entity); + + @Override + @CacheEvict(AUTHORIZED_USER_CACHE) + AuthenticatedUser insert(AuthenticatedUser user); + + @Override + @CacheEvict(AUTHORIZED_USER_CACHE) + void delete(AuthenticatedUser entity); +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/MongoDbDetailsProvider.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/MongoDbDetailsProvider.java new file mode 100644 index 0000000..242f96a --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/data/mongodb/MongoDbDetailsProvider.java @@ -0,0 +1,24 @@ +package org.x2b.studi.core.security.data.mongodb; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.x2b.studi.core.ServiceConstants; + +@Component +public class MongoDbDetailsProvider { + + @Value("${" + ServiceConstants.SECURITY_DATA_MONGODB_HOST_PROPERTY + ":localhost}") + private String host; + + @Value("${" + ServiceConstants.SECURITY_DATA_MONGODB_PORT_PROPERTY + ":27017}") + private int port; + + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/jwt/JWTUserTokenVerifier.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/jwt/JWTUserTokenVerifier.java new file mode 100644 index 0000000..5f46388 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/jwt/JWTUserTokenVerifier.java @@ -0,0 +1,39 @@ +package org.x2b.studi.core.security.jwt; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.x2b.studi.core.security.User; +import org.x2b.studi.core.ServiceConstants; +import org.x2b.studi.core.security.shiro.JWTAuthenticationToken; + +import java.util.UUID; + +@Component +public class JWTUserTokenVerifier { + + private final JWTVerifier verifier; + + @Autowired + public JWTUserTokenVerifier(SharedSecretProvider authSecretProvider) { + + Algorithm algorithm = Algorithm.HMAC256(authSecretProvider.getKey()); + verifier = JWT.require(algorithm) + .withIssuer(ServiceConstants.SECURITY_TOKEN_ISSUER) + .build(); + } + + public User getUser(JWTAuthenticationToken token) { + //TODO: Eventually this should use getSubject to get a JSON and then deserialize into java + DecodedJWT decodedJWT = verifier.verify(token.getToken()); + Claim uuidClaim = decodedJWT.getClaim(ServiceConstants.SECURITY_UUID_CLAIM); + String uuidString = uuidClaim.asString(); + UUID uuid = UUID.fromString(uuidString); + + return () -> uuid; //TODO: this is not a good place for a lambda + } +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/jwt/SharedSecretProvider.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/jwt/SharedSecretProvider.java new file mode 100644 index 0000000..13cc47c --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/jwt/SharedSecretProvider.java @@ -0,0 +1,21 @@ +package org.x2b.studi.core.security.jwt; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Base64; + +/** + * Reads the shared secret from the proper location + */ +//TODO: not sure this really needs to be it's own class +@Component +public class SharedSecretProvider { + + @Value("${studi.security.auth.secret}") + private String encodedKey; + + public byte[] getKey() { + return Base64.getDecoder().decode(encodedKey); //I think this won't be a performance problem + } +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/shiro/GenericAuthenticatingRealm.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/shiro/GenericAuthenticatingRealm.java new file mode 100644 index 0000000..b6a81f4 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/shiro/GenericAuthenticatingRealm.java @@ -0,0 +1,61 @@ +package org.x2b.studi.core.security.shiro; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.x2b.studi.core.security.User; +import org.x2b.studi.core.security.data.mongodb.AuthenticatedUser; +import org.x2b.studi.core.security.data.mongodb.AuthorizationRepository; +import org.x2b.studi.core.security.jwt.JWTUserTokenVerifier; +import org.x2b.studi.core.ServiceConstants; + +@Component +public class GenericAuthenticatingRealm extends AuthorizingRealm { + + + @Autowired + public AuthorizationRepository repository; + + @Autowired + private JWTUserTokenVerifier jwtUserTokenVerifier; + + public GenericAuthenticatingRealm() { + this.setCachingEnabled(false); //TODO: maybe just don't extend a caching realm + } + + @Override + public String getName() { + return ServiceConstants.SECURITY_AUTHENTICATION_REALM_NAME; + } + + @Override + public boolean supports(AuthenticationToken authenticationToken) { + return authenticationToken instanceof JWTAuthenticationToken; + } + + @Override + public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { + JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) authenticationToken; + + User claimedUser = jwtUserTokenVerifier.getUser(jwtToken); + SimpleAuthenticationInfo authenticationInfo = + new SimpleAuthenticationInfo(claimedUser, authenticationToken.getCredentials(), getName()); + return authenticationInfo; + } + + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + User user = (User) principalCollection.getPrimaryPrincipal(); + AuthenticatedUser authorizedUser = repository.findOne(user.getUUID()); + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + info.setStringPermissions(authorizedUser.getPermissions()); + return info; + } +} diff --git a/core-service-lib/src/main/java/org/x2b/studi/core/security/shiro/JWTAuthenticationToken.java b/core-service-lib/src/main/java/org/x2b/studi/core/security/shiro/JWTAuthenticationToken.java new file mode 100644 index 0000000..fff1538 --- /dev/null +++ b/core-service-lib/src/main/java/org/x2b/studi/core/security/shiro/JWTAuthenticationToken.java @@ -0,0 +1,34 @@ +package org.x2b.studi.core.security.shiro; + +import org.apache.shiro.authc.AuthenticationToken; + +public class JWTAuthenticationToken implements AuthenticationToken{ + + private final String data; + + public JWTAuthenticationToken(String data) { + this.data = data; + } + + @Override + public Object getPrincipal() { + return data; + } + + @Override + public Object getCredentials() { + return data; + } + + public String getToken() { + return data; + } + + @Override + public boolean equals(Object other) { + if (other != null && other.getClass().equals(JWTAuthenticationToken.class)) { + return this.data.equals(((JWTAuthenticationToken) other).data); + } + return false; + } +} diff --git a/src/main/resources/application.yaml b/core-service-lib/src/main/resources/application.yaml similarity index 79% rename from src/main/resources/application.yaml rename to core-service-lib/src/main/resources/application.yaml index d9f29c6..dd86454 100644 --- a/src/main/resources/application.yaml +++ b/core-service-lib/src/main/resources/application.yaml @@ -14,3 +14,8 @@ graphiql: mapping: /graphiql endpoint: /graphql enabled: true + +studi: + security: + auth: + secret: helasecret \ No newline at end of file diff --git a/core-service-lib/src/test/java/org/x2b/studi/core/TestGraphQLServiceConfigure.java b/core-service-lib/src/test/java/org/x2b/studi/core/TestGraphQLServiceConfigure.java new file mode 100644 index 0000000..748e4ef --- /dev/null +++ b/core-service-lib/src/test/java/org/x2b/studi/core/TestGraphQLServiceConfigure.java @@ -0,0 +1,59 @@ +package org.x2b.studi.core; + +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLType; +import graphql.schema.idl.RuntimeWiring; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.data.mongodb.core.MongoClientFactoryBean; + +import java.util.List; + +public class TestGraphQLServiceConfigure { + private class TestConfigure extends GraphQLServiceConfigure { + public class Hello { + + public Hello() { + + } + + public String getValue() { + return "this class is for testing schema creation but does not contain tests"; + } + } + + + @Override + protected RuntimeWiring createRuntimeWiring() { + return RuntimeWiring.newRuntimeWiring() + .type("QueryRoot", w -> w + .dataFetcher("getHello", environment -> { + return new Hello(); + }) + ) + .type("MutationRoot", w -> w + .dataFetcher("logAString", env -> "mutation") + ) + .build(); + } + } + + + @Test + public void testCreateSchema() { + TestConfigure configure = new TestConfigure(); + GraphQLSchema schema = configure.schema(); + Assert.assertNotNull(schema); + + GraphQLType queryRoot = schema.getQueryType(); + Assert.assertNotNull(queryRoot); + Assert.assertEquals("QueryRoot", queryRoot.getName()); + + GraphQLType mutRoot = schema.getMutationType(); + Assert.assertNotNull(mutRoot); + Assert.assertEquals("MutationRoot", mutRoot.getName()); + + List types = schema.getAllTypesAsList(); + Assert.assertEquals(types.toString(), 3 + 10, types.size()); //10 built in + } +} diff --git a/core-service-lib/src/test/resources/schema.gql b/core-service-lib/src/test/resources/schema.gql new file mode 100644 index 0000000..da8688a --- /dev/null +++ b/core-service-lib/src/test/resources/schema.gql @@ -0,0 +1,17 @@ +type Hello { + value: String! +} + +type QueryRoot { + getHello: Hello! +} + +type MutationRoot { + logAString(str: String!): String! +} + + +schema { + query: QueryRoot + mutation: MutationRoot +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index efd0dfe..ccb39a7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ -rootProject.name = 'core-service-lib' +rootProject.name = 'core-service' +include 'core-service-lib' +include 'core-service-lib-integration' diff --git a/src/main/java/org/x2b/study/core/GraphQLServiceConfigure.java b/src/main/java/org/x2b/study/core/GraphQLServiceConfigure.java deleted file mode 100644 index 9be8488..0000000 --- a/src/main/java/org/x2b/study/core/GraphQLServiceConfigure.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.x2b.study.core; - - -import graphql.GraphQL; -import graphql.schema.GraphQLSchema; - -import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.TypeDefinitionRegistry; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; - -import java.io.File; - -@SpringBootApplication -public abstract class GraphQLServiceConfigure { - - @Value("#{graphql.schema.schemaFileLocation}") - public static String schemaFileLocation = "schema.gql"; - - - @Bean - public GraphQLSchema schema() { - graphql.schema.idl.SchemaParser parser = new graphql.schema.idl.SchemaParser(); - SchemaGenerator schemaGenerator = new SchemaGenerator(); - TypeDefinitionRegistry tdr = parser.parse(getSchemaFile()); - return schemaGenerator.makeExecutableSchema(tdr, createRuntimeWiring()); - } - - private File getSchemaFile() { - return new File(this.getClass().getClassLoader().getResource(schemaFileLocation).getFile()); - } - - - protected abstract RuntimeWiring createRuntimeWiring(); -} diff --git a/src/main/resources/schema.gql b/src/main/resources/schema.gql deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/resources/schema.gql b/src/test/resources/schema.gql deleted file mode 100644 index 6a2df83..0000000 --- a/src/test/resources/schema.gql +++ /dev/null @@ -1,22 +0,0 @@ -type Hello { - value: String! - getComplexThing(bar: String!): String! -} - -input HelloInput { - foo: String! -} - -type Query { - hello(input: HelloInput!): Hello! -} - -type Mutation { - logAString(str: String!): String! -} - - -schema { - query: Query - mutation: Mutation -} \ No newline at end of file