├── settings.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── src
├── test
│ ├── resources
│ │ ├── certs
│ │ │ ├── .keystore
│ │ │ ├── keystore.jks
│ │ │ ├── truststore.jks
│ │ │ ├── MyServerCert.cer
│ │ │ ├── README
│ │ │ ├── EquifaxSecureGlobaleBusinessCA-1.crt
│ │ │ └── EquifaxSecureGlobaleBusinessCA-2.crt
│ │ └── truststore.jks
│ └── groovy
│ │ └── grails
│ │ └── plugin
│ │ └── httpbuilderhelper
│ │ └── ssl
│ │ └── SimpleKeyStoreFactoryTests.groovy
└── main
│ └── groovy
│ └── grails
│ └── plugin
│ └── httpbuilderhelper
│ ├── ssl
│ ├── HTTPBuilderSSLHelper.groovy
│ ├── KeyStoreFactory.groovy
│ ├── HTTPBuilderSSLConstants.groovy
│ ├── SimpleHTTPBuilderSSLHelper.groovy
│ └── SimpleKeyStoreFactory.groovy
│ └── HttpBuilderHelperGrailsPlugin.groovy
├── .gitignore
├── grails-app
├── init
│ └── rest
│ │ └── Application.groovy
└── conf
│ ├── logback.groovy
│ └── application.yml
├── gradlew.bat
├── gradlew
├── README.md
└── LICENSE.txt
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'http-builder-helper'
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | grailsVersion=3.2.11
2 | gradleWrapperVersion=3.5
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bobbywarner/grails-http-builder-helper/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/src/test/resources/certs/.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bobbywarner/grails-http-builder-helper/HEAD/src/test/resources/certs/.keystore
--------------------------------------------------------------------------------
/src/test/resources/truststore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bobbywarner/grails-http-builder-helper/HEAD/src/test/resources/truststore.jks
--------------------------------------------------------------------------------
/src/test/resources/certs/keystore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bobbywarner/grails-http-builder-helper/HEAD/src/test/resources/certs/keystore.jks
--------------------------------------------------------------------------------
/src/test/resources/certs/truststore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bobbywarner/grails-http-builder-helper/HEAD/src/test/resources/certs/truststore.jks
--------------------------------------------------------------------------------
/src/test/resources/certs/MyServerCert.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bobbywarner/grails-http-builder-helper/HEAD/src/test/resources/certs/MyServerCert.cer
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /build
3 | .classpath
4 | .project
5 | /.settings
6 | /target-eclipse
7 | /target
8 | /web-app
9 | /bin
10 | plugin.xml
11 | grails-rest-*.zip
12 | grails-rest-*.zip.sha1
13 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Feb 04 17:05:29 CST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip
7 |
--------------------------------------------------------------------------------
/src/test/resources/certs/README:
--------------------------------------------------------------------------------
1 | #Certificates
2 | EquifaxSecureGlobalBusinessCA-1.crt : Cert. from https://dev.java.net
3 | MyServerCert.cer : Just a simple cert built with the JDK keytool.
4 |
5 | #Key Stores...
6 | .keystore : Password changeit
7 | keystore.jks : Password test1234
8 | truststore.jks : Password test1234
9 |
--------------------------------------------------------------------------------
/grails-app/init/rest/Application.groovy:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import grails.boot.GrailsApp
4 | import grails.boot.config.GrailsAutoConfiguration
5 | import grails.plugins.metadata.PluginSource
6 |
7 | @PluginSource
8 | class Application extends GrailsAutoConfiguration {
9 | static void main(String[] args) {
10 | GrailsApp.run(Application, args)
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/groovy/grails/plugin/httpbuilderhelper/ssl/HTTPBuilderSSLHelper.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugin.httpbuilderhelper.ssl
2 |
3 | import groovyx.net.http.HTTPBuilder
4 |
5 | /**
6 | * Contract of any HttBuilderSSLHelper
7 | * @author berngp
8 | */
9 | interface HTTPBuilderSSLHelper {
10 |
11 | /**
12 | * Through this main method, and normaly using a {@link KeyStoreFactory} and a org.apache.http.conn.ssl.SSLSocketFactory,
13 | * it should register an SSL Scheme containing a java.security.KeyStore, loaded with either a keystore and/or a
14 | * truststore, into the Builder.Client's Connection Manager.
15 | * @param config Configuration holder that might be used to register an SSL Scheme in the given client's Connection Manager.
16 | */
17 | HTTPBuilder addSSLSupport(ConfigObject config, HTTPBuilder client)
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/grails-app/conf/logback.groovy:
--------------------------------------------------------------------------------
1 | import grails.util.BuildSettings
2 | import grails.util.Environment
3 |
4 |
5 | // See http://logback.qos.ch/manual/groovy.html for details on configuration
6 | appender('STDOUT', ConsoleAppender) {
7 | encoder(PatternLayoutEncoder) {
8 | pattern = "%level %logger - %msg%n"
9 | }
10 | }
11 |
12 | root(ERROR, ['STDOUT'])
13 |
14 | if(Environment.current == Environment.DEVELOPMENT) {
15 | def targetDir = BuildSettings.TARGET_DIR
16 | if(targetDir) {
17 |
18 | appender("FULL_STACKTRACE", FileAppender) {
19 |
20 | file = "${targetDir}/stacktrace.log"
21 | append = true
22 | encoder(PatternLayoutEncoder) {
23 | pattern = "%level %logger - %msg%n"
24 | }
25 | }
26 | logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/groovy/grails/plugin/httpbuilderhelper/ssl/KeyStoreFactory.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugin.httpbuilderhelper.ssl
2 |
3 | /**
4 | *
5 | * KeyStore Factory contract that should provide a mechanism to create and load a java.security.KeyStore with a
6 | * keystore and/or truststore.
7 | *
8 | * @author berngp
9 | */
10 | interface KeyStoreFactory {
11 |
12 | /**
13 | * Loads a given Key Store returning the following model that most contain.
14 | *
15 | * - keystore: Instance of a java.security.KeyStore that has been configured with a specific Key Store.
16 | * - password: Password used to access such Key Store.
17 | *
18 | */
19 | Map getKeyStoreModel()
20 | /**
21 | * Loads a given Trust Store returning the following model that most contain.
22 | *
23 | * - keystore: Instance of a java.security.KeyStore that has been configured with the specific Trust Store.
24 | *
25 | */
26 | Map getTrustStoreModel()
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/groovy/grails/plugin/httpbuilderhelper/ssl/HTTPBuilderSSLConstants.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugin.httpbuilderhelper.ssl
2 |
3 | /**
4 | * Constants used across the different SSL Support classes.
5 | * @author berngp
6 | */
7 | interface HTTPBuilderSSLConstants {
8 |
9 | /** The URL reqeusted doesn't need to match the URL in the Certificate. */
10 | String CERT_HOSTNAME_VERIFIER_ALLOW_ALL = 'ALLOW_ALL'
11 | /** The URL reqeusted needs to match the URL in the Certificate. */
12 | String CERT_HOSTNAME_VERIFIER_STRICT = 'STRICT'
13 | /** The URL reqeusted must be in the same domain as the one in the Certificate. */
14 | String CERT_HOSTNAME_VERIFIER_BROWSER_COMPATIBLE = 'BROWSER_COMPATIBLE'
15 | /** Certificates Hostnames verfifiers valid options. */
16 | Set CERT_HOSTNAME_VERIFIERS = Collections.unmodifiableSet(
17 | [
18 | CERT_HOSTNAME_VERIFIER_ALLOW_ALL,
19 | CERT_HOSTNAME_VERIFIER_STRICT,
20 | CERT_HOSTNAME_VERIFIER_BROWSER_COMPATIBLE
21 | ] as LinkedHashSet
22 | )
23 |
24 | /** */
25 | String HTTPS = "https"
26 | /** */
27 | int SSL_DEFAULT_PORT = 443
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/resources/certs/EquifaxSecureGlobaleBusinessCA-1.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDLjCCApegAwIBAgIDDBLjMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
3 | MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
4 | aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNzIyMTcwMDI1WhcNMTAxMDIyMjEwNzM0
5 | WjCBuDELMAkGA1UEBhMCVVMxFzAVBgNVBAoUDiouZGV2LmphdmEubmV0MRMwEQYD
6 | VQQLEwpHVDg4MjQ1NjQ1MTEwLwYDVQQLEyhTZWUgd3d3LnJhcGlkc3NsLmNvbS9y
7 | ZXNvdXJjZXMvY3BzIChjKTA5MS8wLQYDVQQLEyZEb21haW4gQ29udHJvbCBWYWxp
8 | ZGF0ZWQgLSBSYXBpZFNTTChSKTEXMBUGA1UEAxQOKi5kZXYuamF2YS5uZXQwgZ8w
9 | DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVWD2FBlAPBrdRbgZ+EBp07+Z6hr046
10 | Ba9LJC6eykjRHXKEFR0finMFzmtmhfDoXPKpSB9pcnl/fMXy7UQX2kEbyPItGZfY
11 | t+YsGez59mUjU8XsdmA6dTaXb+vtP/R6YgMOrufVDRKEqPASYQ26lffakBHxgSJd
12 | z6Uei8kJ72ShAgMBAAGjga4wgaswDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQWBBRB
13 | hXi+DbU3DAN5vodRZyDjT6bp/DA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3Js
14 | Lmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDAfBgNVHSMEGDAWgBRI5mj5
15 | K9KylddH2CMgEE8zmJCf1DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
16 | DQYJKoZIhvcNAQEFBQADgYEAlTBnlfWDgVFdNsKzDI05N38oPaQh2O8JcFtI0vm+
17 | rhwJYj6vjGdeH1bspIJ+f6ZjciOiaADwermT0QyFTsW/6PRpi5rd4Z+iv2tR4ieB
18 | /2ECgo/GQPs39B40mVoXPcZNoDGKWBAhDbYC+ehPe9TmSoqL4J5wchvR0oxlj1L7
19 | kVU=
20 | -----END CERTIFICATE-----
21 |
--------------------------------------------------------------------------------
/src/test/resources/certs/EquifaxSecureGlobaleBusinessCA-2.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDdDCCAt2gAwIBAgIDFIweMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
3 | MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
4 | aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTAwOTEzMDk1MjE3WhcNMTExMTE1MTQzODA5
5 | WjCB4zEpMCcGA1UEBRMgbVkzU1UtRmNzM2hNWkZmN3p4OG8vSWlnWHRjcEhtM20x
6 | CzAJBgNVBAYTAlVTMRcwFQYDVQQKDA4qLmRldi5qYXZhLm5ldDETMBEGA1UECxMK
7 | R1Q4ODI0NTY0NTExMC8GA1UECxMoU2VlIHd3dy5yYXBpZHNzbC5jb20vcmVzb3Vy
8 | Y2VzL2NwcyAoYykxMDEvMC0GA1UECxMmRG9tYWluIENvbnRyb2wgVmFsaWRhdGVk
9 | IC0gUmFwaWRTU0woUikxFzAVBgNVBAMMDiouZGV2LmphdmEubmV0MIGfMA0GCSqG
10 | SIb3DQEBAQUAA4GNADCBiQKBgQC1Vg9hQZQDwa3UW4GfhAadO/meoa9OOgWvSyQu
11 | nspI0R1yhBUdH4pzBc5rZoXw6FzyqUgfaXJ5f3zF8u1EF9pBG8jyLRmX2LfmLBns
12 | +fZlI1PF7HZgOnU2l2/r7T/0emIDDq7n1Q0ShKjwEmENupX32pAR8YEiXc+lHovJ
13 | Ce9koQIDAQABo4HJMIHGMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/U
14 | MA4GA1UdDwEB/wQEAwIE8DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
15 | GQYDVR0RBBIwEIIOKi5kZXYuamF2YS5uZXQwOgYDVR0fBDMwMTAvoC2gK4YpaHR0
16 | cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9zZWN1cmVjYS5jcmwwHQYDVR0OBBYE
17 | FEGFeL4NtTcMA3m+h1FnIONPpun8MA0GCSqGSIb3DQEBBQUAA4GBADHYZ2DalVVh
18 | XKShprHIR7f0U7jz6IrPYkTs72cDfvLDtW14labCKuvP6cL5dYYuZ8tIN91llVKL
19 | ai4QrYUoeb414AVGs+10iYV3OrX2/jTYDP/9KQ1FwjclfTNh9lPjCONkxsI9enm1
20 | 7khiM6v1OJm5aGRiZxaCmJn1XmPXDVLz
21 | -----END CERTIFICATE-----
22 |
--------------------------------------------------------------------------------
/src/test/groovy/grails/plugin/httpbuilderhelper/ssl/SimpleKeyStoreFactoryTests.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugin.httpbuilderhelper.ssl
2 |
3 | import grails.test.mixin.TestMixin
4 | import grails.test.mixin.support.GrailsUnitTestMixin
5 | import spock.lang.Specification
6 |
7 | @TestMixin(GrailsUnitTestMixin)
8 | class SimpleKeyStoreFactoryTests extends Specification {
9 |
10 | void testGetKeyStoreFromConf() {
11 | given:
12 | config.https.keystore.path = 'src/test/resources/certs/keystore.jks'
13 | config.https.keystore.pass = 'test1234'
14 |
15 | when:
16 | SimpleKeyStoreFactory sksf = new SimpleKeyStoreFactory()
17 | def keyStoreModel = sksf.getKeyStoreModel()
18 |
19 | then:
20 | keyStoreModel.keystore != null
21 | }
22 |
23 | void testGetKeyStoreFromDefault() {
24 | given:
25 | SimpleKeyStoreFactory.metaClass.getDefaultKeyStoreHome = {
26 | 'src/test/resources/certs'
27 | }
28 | config.https.keystore.path = ''
29 | config.https.keystore.pass = ''
30 |
31 | when:
32 | SimpleKeyStoreFactory sksf = new SimpleKeyStoreFactory()
33 | def keyStoreModel = sksf.getKeyStoreModel()
34 |
35 | then:
36 | keyStoreModel.keystore != null
37 | keyStoreModel.path == "src/test/resources/certs/.keystore"
38 | }
39 |
40 | void testGetTrustStoreFromConf() {
41 | given:
42 | config.https.truststore.path = 'src/test/resources/certs/truststore.jks'
43 | config.https.truststore.pass = 'test1234'
44 |
45 | when:
46 | SimpleKeyStoreFactory sksf = new SimpleKeyStoreFactory()
47 | def trustStoreModel = sksf.getTrustStoreModel()
48 |
49 | then:
50 | trustStoreModel.keystore != null
51 | }
52 |
53 | void testGetTrustStoreFromDefault() {
54 | given:
55 | SimpleKeyStoreFactory.metaClass.getDefaultTrustStoreHome = {
56 | 'src/test/resources'
57 | }
58 | config.https.truststore.path = ''
59 | config.https.truststore.pass = ''
60 |
61 | when:
62 | SimpleKeyStoreFactory sksf = new SimpleKeyStoreFactory()
63 | def trustStoreModel = sksf.getTrustStoreModel()
64 |
65 | then:
66 | trustStoreModel.keystore != null
67 | trustStoreModel.path == 'src/test/resources/truststore.jks'
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/grails-app/conf/application.yml:
--------------------------------------------------------------------------------
1 | ---
2 | grails:
3 | profile: web-plugin
4 | codegen:
5 | defaultPackage: rest
6 | info:
7 | app:
8 | name: '@info.app.name@'
9 | version: '@info.app.version@'
10 | grailsVersion: '@info.app.grailsVersion@'
11 | spring:
12 | groovy:
13 | template:
14 | check-template-location: false
15 |
16 | ---
17 | grails:
18 | mime:
19 | disable:
20 | accept:
21 | header:
22 | userAgents:
23 | - Gecko
24 | - WebKit
25 | - Presto
26 | - Trident
27 | types:
28 | all: '*/*'
29 | atom: application/atom+xml
30 | css: text/css
31 | csv: text/csv
32 | form: application/x-www-form-urlencoded
33 | html:
34 | - text/html
35 | - application/xhtml+xml
36 | js: text/javascript
37 | json:
38 | - application/json
39 | - text/json
40 | multipartForm: multipart/form-data
41 | pdf: application/pdf
42 | rss: application/rss+xml
43 | text: text/plain
44 | hal:
45 | - application/hal+json
46 | - application/hal+xml
47 | xml:
48 | - text/xml
49 | - application/xml
50 | urlmapping:
51 | cache:
52 | maxsize: 1000
53 | controllers:
54 | defaultScope: singleton
55 | converters:
56 | encoding: UTF-8
57 | views:
58 | default:
59 | codec: html
60 | gsp:
61 | encoding: UTF-8
62 | htmlcodec: xml
63 | codecs:
64 | expression: html
65 | scriptlets: html
66 | taglib: none
67 | staticparts: none
68 | ---
69 | hibernate:
70 | cache:
71 | queries: false
72 | use_second_level_cache: true
73 | use_query_cache: false
74 | region.factory_class: 'org.hibernate.cache.ehcache.EhCacheRegionFactory'
75 |
76 | dataSource:
77 | pooled: true
78 | jmxExport: true
79 | driverClassName: org.h2.Driver
80 | username: sa
81 | password:
82 |
83 | environments:
84 | development:
85 | dataSource:
86 | dbCreate: create-drop
87 | url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
88 | test:
89 | dataSource:
90 | dbCreate: update
91 | url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
92 | production:
93 | dataSource:
94 | dbCreate: update
95 | url: jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
96 | properties:
97 | jmxEnabled: true
98 | initialSize: 5
99 | maxActive: 50
100 | minIdle: 5
101 | maxIdle: 25
102 | maxWait: 10000
103 | maxAge: 600000
104 | timeBetweenEvictionRunsMillis: 5000
105 | minEvictableIdleTimeMillis: 60000
106 | validationQuery: SELECT 1
107 | validationQueryTimeout: 3
108 | validationInterval: 15000
109 | testOnBorrow: true
110 | testWhileIdle: true
111 | testOnReturn: false
112 | jdbcInterceptors: ConnectionState
113 | defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED
114 |
--------------------------------------------------------------------------------
/src/main/groovy/grails/plugin/httpbuilderhelper/ssl/SimpleHTTPBuilderSSLHelper.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugin.httpbuilderhelper.ssl
2 |
3 | import groovyx.net.http.HTTPBuilder
4 | import java.security.KeyManagementException
5 | import java.security.KeyStoreException
6 | import java.security.NoSuchAlgorithmException
7 | import java.security.UnrecoverableKeyException
8 | import org.apache.http.conn.scheme.Scheme
9 | import org.apache.http.conn.ssl.SSLSocketFactory
10 | import static grails.plugin.httpbuilderhelper.ssl.HTTPBuilderSSLConstants.*
11 |
12 | /**
13 | * Through its main method {@link SimpleHTTPBuilderSSLHelper#addSSLSupport} and using a {@link KeyStoreFactory}
14 | * and a org.apache.http.conn.ssl.SSLSocketFactory
15 | * it registers an SSL Scheme containing a java.security.KeyStore, loaded with either a keystore and/or a
16 | * truststore, into the Builder.Client's Connection Manager.
17 | * By default the it will use a {@link SimpleKeyStoreFactory} .
18 | * Please review the {@link SimpleKeyStoreFactory} documentation to be aware of its configuration
19 | * keys hosted inside the ConfigObject.
20 | * @see org.apache.http.conn.ssl.SSLSocketFactory
21 | * @see org.apache.http.conn.scheme.Scheme
22 | * @see org.grails.plugins.rest.ssl.SimpleKeyStoreFactory
23 | * @author berngp
24 | * @since 0.6
25 | */
26 | class SimpleHTTPBuilderSSLHelper implements HTTPBuilderSSLHelper {
27 |
28 | /**
29 | * Defines the KeyStoreFactory used if connecting to an endpoints behind SSL.
30 | * @todo Make sure that there exists a clear mechanism to inject the restPluginKeyStoreFactory through the Spring Context
31 | */
32 | KeyStoreFactory restPluginKeyStoreFactory = new SimpleKeyStoreFactory()
33 |
34 | /**
35 | * If a Key Store and/or a Trust Store are defined it creates a org.apache.http.conn.ssl.SSLSocketFactory attaching
36 | * it to the given builder.client.connectionManager.
37 | * The config:ConfigObject may specify the following value(s) among others used by the KeyStoreFactory:
38 | *
39 | * - https.cert.hostnameVerifier: As defined by
org.apache.http.conn.ssl.SSLSocketFactory, eg.
40 | * https.cert.hostnameVerifier=allow_all. If none is specified the default SSLSocketFactory's strategy will be used.
41 | *
42 | * @see org.apache.http.conn.ssl.SSLSocketFactory
43 | * @see org.apache.http.conn.scheme.Scheme
44 | * @see org.grails.plugins.rest.ssl.SimpleKeyStoreFactory
45 | */
46 | HTTPBuilder addSSLSupport(ConfigObject config, HTTPBuilder builder) {
47 | if (!builder) {
48 | throw new IllegalArgumentException("builder:HTTPBuilder can't be null.")
49 | }
50 |
51 | def keyStoreModel = restPluginKeyStoreFactory.getKeyStoreModel(config)
52 |
53 | def trustStoreModel = restPluginKeyStoreFactory.getTrustStoreModel(config)
54 |
55 | try {
56 | def sslSocketFactory
57 | if (keyStoreModel?.keystore && trustStoreModel?.keystore) {
58 | sslSocketFactory = new SSLSocketFactory(
59 | keyStoreModel.keystore, keyStoreModel.password, trustStoreModel.keystore)
60 |
61 | } else if (trustStoreModel?.keystore) {
62 | sslSocketFactory = new SSLSocketFactory(trustStoreModel.keystore)
63 |
64 | } else if (keyStoreModel?.keystore) {
65 | sslSocketFactory = new SSLSocketFactory(
66 | keyStoreModel.keystore,
67 | keyStoreModel.password,
68 | keyStoreModel.keystore)
69 | }
70 |
71 | if (sslSocketFactory) {
72 | // Set the hostname verifier for the trusted certificates...
73 | if (config?.https?.cert?.hostnameVerifier) {
74 | switch (config.https.cert.hostnameVerifier.toUpperCase()) {
75 | case CERT_HOSTNAME_VERIFIER_STRICT:
76 | sslSocketFactory.hostnameVerifier = SSLSocketFactory.STRICT_HOSTNAME_VERIFIER
77 | break;
78 | case CERT_HOSTNAME_VERIFIER_ALLOW_ALL:
79 | sslSocketFactory.hostnameVerifier = SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
80 | break;
81 | case CERT_HOSTNAME_VERIFIER_BROWSER_COMPATIBLE:
82 | sslSocketFactory.hostnameVerifier = SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
83 | break;
84 | default:
85 | throw new IllegalArgumentException(
86 | "The https.cert.hostnameVerifier doesn't match any of the following ${CERT_HOSTNAME_VERIFIERS.join(', ')}")
87 |
88 | }
89 | }
90 | //finally register the HTTPS Scheme...
91 | builder.client.connectionManager.schemeRegistry.register(
92 | new Scheme(HTTPS, sslSocketFactory, SSL_DEFAULT_PORT)
93 | )
94 | } else if (config?.https?.sslSocketFactory?.enforce) {
95 | throw new IllegalStateException("Unable to load a SSL Socket Factory!")
96 | }
97 |
98 | } catch (NoSuchAlgorithmException e) {
99 | throw new IllegalStateException(e)
100 |
101 | } catch (KeyManagementException e) {
102 | throw new IllegalStateException(e)
103 |
104 | } catch (KeyStoreException e) {
105 | throw new IllegalArgumentException(e)
106 |
107 | } catch (UnrecoverableKeyException e) {
108 | throw new IllegalArgumentException(e)
109 | }
110 |
111 | return builder
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/src/main/groovy/grails/plugin/httpbuilderhelper/HttpBuilderHelperGrailsPlugin.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009-2015 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package grails.plugin.httpbuilderhelper
17 |
18 | import groovyx.net.http.AsyncHTTPBuilder
19 | import groovyx.net.http.HTTPBuilder
20 | import groovyx.net.http.RESTClient
21 |
22 | import java.lang.reflect.InvocationTargetException
23 |
24 | import org.apache.http.impl.conn.ProxySelectorRoutePlanner
25 |
26 | import grails.plugin.httpbuilderhelper.ssl.HTTPBuilderSSLConstants
27 | import grails.plugin.httpbuilderhelper.ssl.HTTPBuilderSSLHelper
28 | import grails.plugin.httpbuilderhelper.ssl.SimpleHTTPBuilderSSLHelper
29 |
30 | import grails.plugins.Plugin
31 |
32 | class HttpBuilderHelperGrailsPlugin extends Plugin {
33 | def grailsVersion = "3.2.0 > *"
34 | def profiles = ['web']
35 | def influences = ['controllers', 'services']
36 | def loadAfter = ['controllers', 'services']
37 |
38 | private HTTPBuilderSSLHelper sslHelper = new SimpleHTTPBuilderSSLHelper()
39 |
40 | @Override
41 | void doWithDynamicMethods() {
42 | processArtifacts(grailsApplication)
43 | }
44 | void onChange(Map event) {
45 | processArtifacts(event.application)
46 | }
47 | void onConfigChange(Map event) {
48 | processArtifacts(event.application)
49 | }
50 |
51 | private void processArtifacts(application) {
52 | def types = application.config.getProperty('grails.rest.injectInto', List, ["Controller", "Service"])
53 | types.each { type ->
54 | application.getArtefacts(type).each { klass -> addDynamicMethods(klass, application) }
55 | }
56 | }
57 |
58 | private void addDynamicMethods(klass, application) {
59 | klass.metaClass.withAsyncHttp = withClient.curry(AsyncHTTPBuilder, klass, application)
60 | klass.metaClass.withHttp = withClient.curry(HTTPBuilder, klass, application)
61 | klass.metaClass.withRest = withClient.curry(RESTClient, klass, application)
62 | }
63 |
64 | private withClient = { Class klass, Object target, application, Map params, Closure closure ->
65 | def client
66 | if (params.id) {
67 | String id = params.remove("id").toString()
68 | if (target.metaClass.hasProperty(target, id)) {
69 | client = target.metaClass.getProperty(target, id)
70 | } else {
71 | client = makeClient(klass, params, application)
72 | target.metaClass."$id" = client
73 | }
74 | } else {
75 | client = makeClient(klass, params, application)
76 | }
77 |
78 | setRoutePlanner(client)
79 |
80 | if (closure) {
81 | closure.delegate = client
82 | closure.resolveStrategy = Closure.DELEGATE_FIRST
83 | closure()
84 | }
85 | }
86 |
87 | private makeClient(Class klass, Map params, application) {
88 | def client
89 | if (klass == AsyncHTTPBuilder) {
90 | client = makeAsyncClient(klass, params)
91 |
92 | } else {
93 | client = makeSyncClient(klass, params)
94 | }
95 |
96 | if (HTTPBuilderSSLConstants.HTTPS == client.uri.toURL().protocol) {
97 | addSSLSupport(client, application)
98 | }
99 |
100 | return client
101 | }
102 |
103 | private makeAsyncClient(Class klass, Map params){
104 | def client
105 | try {
106 | Map args = [:]
107 | [ "threadPool", "poolSize", "uri", "contentType", "timeout" ].each { arg ->
108 | if (params[(arg)] != null) args[(arg)] = params[(arg)]
109 | }
110 | client = klass.newInstance(args)
111 |
112 | } catch (IllegalArgumentException e) {
113 | throw new RuntimeException("Failed to create async http client reason: $e", e)
114 | } catch (InvocationTargetException e) {
115 | throw new RuntimeException("Failed to create async http client reason: $e", e)
116 | }
117 | return client
118 | }
119 |
120 | private makeSyncClient(Class klass, Map params){
121 | def client
122 | try {
123 | client = klass.newInstance()
124 | if (params.uri) client.uri = params.remove("uri")
125 | if (params.contentType) client.contentType = params.remove("contentType")
126 | if (params.charset) client.getEncoder().setCharset(params.remove("charset"))
127 | } catch (IllegalArgumentException e) {
128 | throw new RuntimeException("Failed to create ${(klass == HTTPBuilder ? 'http' : 'rest')} client reason: $e", e)
129 | } catch (InvocationTargetException e) {
130 | throw new RuntimeException("Failed to create ${(klass == HTTPBuilder ? 'http' : 'rest')} client reason: $e", e)
131 | }
132 | return client
133 | }
134 |
135 | private addSSLSupport(client, application){
136 | try {
137 | sslHelper.addSSLSupport(application.config?.rest, client)
138 | } catch (IllegalArgumentException e) {
139 | throw new RuntimeException("Failed to add ssl support : ${e.message}", e)
140 | } catch (IllegalStateException e) {
141 | throw new RuntimeException("Failed to add ssl support : ${e.message}", e)
142 | }
143 | }
144 |
145 | private setRoutePlanner(builder){
146 | builder.client.routePlanner = new ProxySelectorRoutePlanner(
147 | builder.client.connectionManager.schemeRegistry,
148 | ProxySelector.default
149 | )
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Grails HTTP Builder Helper
2 | ==========================
3 |
4 | This plugin used to be called the REST plugin in Grails 1.x & 2.x, but for Grails 3.x it has been renamed to `httpbuilder-helper`. The plugin enables the usage of HTTPBuilder in a Grails application.
5 |
6 | # Description
7 |
8 | The REST plugin enables the usage of HTTPBuilder on a Grails application.
9 |
10 | ## Usage
11 |
12 | The plugins will inject the following dynamic methods:
13 |
14 | * `withHttp(Map params, Closure stmts)` - executes stmts using a `HTTPBuilder`
15 | * `withAsyncHttp(Map params, Closure stmts)` - executes stmts using an `AsyncHTTPBuilder`
16 | * `withRest(Map params, Closure stmts)` - executes stmts using a `RESTClient`
17 |
18 | ## Examples
19 |
20 | Taken from HttpBuilder's Simplified GET Request
21 |
22 | ```groovy
23 | withHttp(uri: "http://www.google.com") {
24 | def html = get(path : '/search', query : [q:'Groovy'])
25 | assert html.HEAD.size() == 1
26 | assert html.BODY.size() == 1
27 | }
28 | ```
29 |
30 | Notice that you can call `HTTPBuilder`'s methods inside `stmts`, the current `HTTPBuilder` is set as the closure's delegate. The same holds true for the other dynamic methods.
31 | `AsyncHTTPBuilder`
32 |
33 | ```groovy
34 | import static groovyx.net.http.ContentType.HTML
35 | withAsyncHttp(poolSize : 4, uri : "http://hc.apache.org", contentType : HTML) {
36 | def result = get(path:'/') { resp, html ->
37 | println ' got async response!'
38 | return html
39 | }
40 | assert result instanceof java.util.concurrent.Future
41 |
42 | while (! result.done) {
43 | println 'waiting...'
44 | Thread.sleep(2000)
45 | }
46 |
47 | /* The Future instance contains whatever is returned from the response
48 | closure above; in this case the parsed HTML data: */
49 | def html = result.get()
50 | assert html instanceof groovy.util.slurpersupport.GPathResult
51 | }
52 | ```
53 |
54 | All dynamic methods will create a new http client when invoked unless you define an id: attribute. When this attribute is supplied the client will be stored as a property on the instance's metaClass. You will be able to access it via regular property access or using the id: again.
55 |
56 | ```groovy
57 | class FooController {
58 | def loginAction = {
59 | withRest(id: "twitter", uri: "http://twitter.com/statuses/") {
60 | auth.basic model.username, model.password
61 | }
62 | }
63 | def queryAction = {
64 | withRest(id: "twitter") {
65 | def response = get(path: "followers.json")
66 | // …
67 | }
68 | /* alternatively
69 | def response twitter.get(path: "followers.json")
70 | */
71 | }
72 | }
73 | ```
74 |
75 | # Configuration
76 |
77 | ## Dynamic method injection
78 |
79 | Dynamic methods will be added to controllers and services by default. You can change this setting by adding a configuration flag `application.yml` or `application.groovy`:
80 |
81 | ```groovy
82 | grails.rest.injectInto = ["Controller", "Service", "Routes"]
83 | ```
84 |
85 | ## Proxy settings
86 |
87 | You can apply proxy settings by calling `setProxy(String host, int port, String scheme)` on the client/builders at any time. You can also take advantage of the `proxy:` shortcut
88 |
89 | ```groovy
90 | withHttp(uri: "http://google.com", proxy: [host: "myproxy.acme.com", port: 8080, scheme: "http"])
91 | ```
92 |
93 | This shortcut has the following defaults
94 |
95 | ```
96 | port: = 80
97 | scheme: = http
98 | ```
99 |
100 | Meaning most of the times you'd only need to define a value for `host:`
101 |
102 | ## SSL Key-Store and Trust-Store support
103 |
104 | If you are connecting to a server through HTTPS you might need to add a Key and or a Trust Store to the underlying SSL Socket Factory. Some examples are…
105 |
106 | The service you are connecting to requires some sort of SSL Cert authentication,
107 | You want to make sure that the server you are connecting to provides a specific certificate.
108 | You do not mind that the certificate that the server provides doesn't match the Domain Name that the server has.
109 | Note, this last option will normally apply to development and test environments.
110 |
111 | ### So how can I add the Key and/or Trust Stores to the underlying SSL Socket Factory?
112 |
113 | The client will try to add a Key and a Trust Store if the URL of the host starts with `https`. By default it will attempt to locate a Key Store through the File System's path `$HOME/.keystore` e.g `/home/berngp/.keystore` and a Trust Store through the JVM Classpath `./truststore.jks`, you can override this by specifying a `rest.https.keystore.path` and/or `rest.https.truststore.path` configuration entries in the `application.yml` or `application.groovy` file.
114 |
115 | Also by default it will try to open the stores using the following passwords '', 'changeit', 'changeme' but you can set a specific password through the `rest.https.keystore.pass` and `rest.https.truststore.pass` configuration entries. If for some reason it is unable to setup the underlying SSL Socket Factory it will fail silently unless the `rest.https.sslSocketFactory.enforce` configuration entry is set to `true`.
116 |
117 |
118 | ### Specifying a Hostname Verification strategy for the Trust Store.
119 |
120 | You can set three different Hostname Verification strategies through the `rest.https.cert.hostnameVerifier` configuration entry.
121 |
122 | `ALLOW_ALL`: The URL requested doesn't need to match the URL in the Certificate.
123 | `STRICT`: The URL requested needs to match the URL in the Certificate.
124 | `BROWSER_COMPATIBLE`: The URL requested must be in the same domain as the one in the Certificate. It accepts wildcards.
125 | Example of a specific setup
126 |
127 | ```groovy
128 | /** SSL truststore configuration key */
129 | rest.https.truststore.path = 'resources/certs/truststore.jks'
130 | /** SSL keystore configuration key */
131 | rest.https.keystore.path='resources/certs/keystore.jks'
132 | /** SSL keystore password configuration key */
133 | rest.https.keystore.pass='changeme'
134 | /** Certificate Hostname Verifier configuration key */
135 | rest.https.cert.hostnameVerifier = 'BROWSER_COMPATIBLE'
136 | /** Enforce SSL Socket Factory */
137 | rest.https.sslSocketFactory.enforce = true
138 | ```
139 |
140 | ### Generating a Key Store
141 |
142 | Generating a Java Key Store is outside the scope of this guide but you can find some useful information through the following links.
143 |
144 | * Official JDK Guide - http://download.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html
145 |
146 | * Importing a Private Key into a KeyStore (JDK 1.6 and above) - http://cunning.sharp.fm/2008/06/importing_private_keys_into_a.html
147 | * KeyMan - An non JDK Key Tool by IBM - http://www.alphaworks.ibm.com/tech/keyman
148 |
--------------------------------------------------------------------------------
/src/main/groovy/grails/plugin/httpbuilderhelper/ssl/SimpleKeyStoreFactory.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugin.httpbuilderhelper.ssl
2 |
3 | import grails.util.Holders
4 |
5 | import java.security.KeyStore
6 | import java.security.NoSuchAlgorithmException
7 | import java.security.cert.CertificateException
8 |
9 | import org.slf4j.Logger
10 | import org.slf4j.LoggerFactory
11 | import org.springframework.core.io.ClassPathResource
12 | import org.springframework.core.io.FileSystemResource
13 |
14 | /**
15 | * Implements a Basic KeyStore Factory to generate a {@link java.security.KeyStore} loaded with either
16 | * a Key Store and/or Trust Store files into it if such store files are available.
17 | * See the specific {@link SimpleKeyStoreFactory#getKeyStoreModel(ConfigObject)}
18 | * and {@link SimpleKeyStoreFactory#getTrustStoreModel(ConfigObject)}
19 | * methods for details.
20 | * @author berngp
21 | */
22 | class SimpleKeyStoreFactory implements KeyStoreFactory {
23 |
24 | private static final Logger log = LoggerFactory.getLogger(this)
25 | /** Default Key Store file. */
26 | private static final String DEFAULT_KEYSTORE = ".keystore"
27 | /** Default Trust Store Classpath file. */
28 | private static final String DEFAULT_TRUSTSTORE = "truststore.jks"
29 | /** Set of common default Key/Trust Store files passwords. */
30 | private static final Set COMMON_PASSWORDS = (['', 'changeit', 'changeme'] as Set).asImmutable()
31 |
32 | /** Loads a resource from the FileSystem given a specific path. */
33 | protected getResourceFromFile = { String path ->
34 | def resource = new FileSystemResource(path)
35 | try {
36 | return resource.URL ? resource : null
37 | } catch (FileNotFoundException e) {
38 | log.trace "Unable to load ${path} through the File System.", e
39 | }
40 | // --
41 | return null
42 | }
43 |
44 | /** Loads a resource from the ClassPath given a specific path. */
45 | protected getResourceFromClassPath = { String path ->
46 | def resource = new ClassPathResource(path)
47 | try {
48 | return resource.URL ? resource : null
49 | } catch (FileNotFoundException e) {
50 | log.trace "Unable to load ${path} through the Class Loader.", e
51 | }
52 | // --
53 | return null
54 | }
55 |
56 | /**
57 | * if a correct password was found that means that we were able to open the resource and add it successfully to our keyStore,
58 | * if so proceed and deliver a model of our Key Store which includes...
59 | *
60 | * - path:Path from for the keystore.
61 | * - URL:Actual URL as given by either the Classi Loader or the File System.
62 | * - password:The password used to open such store.
63 | * - keystore:
java.security.KeyStore.
64 | *
65 | * if not return null.
66 | *
67 | */
68 | private Map getKeyStoreInternal(path, knownPasswd ){
69 | // We need a ref to a Resource
70 | def resource
71 | // lets try first through the path
72 | if (path) {
73 | //lets see if a resource is available through the Classpath.
74 | //resource = getResourceFromClassPath(path)
75 | if (!(resource = getResourceFromClassPath(path))) {
76 | //if not lets check the file system.
77 | resource = getResourceFromFile(path)
78 | }
79 | }
80 | // if we have a Resource
81 | if (resource) {
82 | //obtain an Instance of a javax.security.KeyStore used to host our keystore
83 | def keyStore = KeyStore.getInstance(KeyStore.defaultType)
84 | //and define a set passwords we will use to open such store, if non are defined we will use a default set
85 | def keyStorePasswds = knownPasswd ? [knownPasswd] as LinkedHashSet : COMMON_PASSWORDS
86 |
87 | String correctPasswd
88 | for (String passwd: keyStorePasswds) {
89 | try {
90 | keyStore.load(resource.inputStream, passwd.toCharArray())
91 | correctPasswd = passwd
92 | break
93 | } catch (CertificateException e) {
94 | log.debug e.message,e
95 | } catch (NoSuchAlgorithmException e) {
96 | log.debug e.message,e
97 | } catch (FileNotFoundException e) {
98 | log.debug e.message,e
99 | } catch (IOException e) {
100 | log.debug e.message,e
101 | }
102 | }
103 | return correctPasswd ? [path: path, URL: resource.URL.toString(), keystore: keyStore, password: correctPasswd] : null
104 |
105 | }
106 | // if not
107 | return [ : ]
108 | }
109 |
110 | /**
111 | * Used to specify a getter for the default directory hosting the KeyStore. Currently it is set to the User's Home
112 | * as seen by System.getProperty('user.home').
113 | */
114 | protected getDefaultKeyStoreHome() { return System.getProperty('user.home') }
115 |
116 | /**
117 | * Used to specify a getter for the default directory hosting the TrustStore. Currently it is set to the User's Home
118 | * as seen by System.getProperty('user.home').
119 | */
120 | protected getDefaultTrustStoreHome() { return System.getProperty('user.home') }
121 |
122 | /**
123 | * Looks for the Key Store file, loads it, and generates a Map from it as specified in the {@link #getKeyStoreInternal} method.
124 | * The config:ConfigObject may specify a path for the Key Store through https.keystore.path, if none is specified it
125 | * will use a path defined as "Default Key Store Home ( see {@link #getDefaultKeyStoreHome()} )/( value of {@link #DEFAULT_KEYSTORE} )" .
126 | * In the same manner a password for such Key Store might be specified through https.keystore.pass,
127 | * If none we will try to guess using common default keystore passwords as defined by the {@link #COMMON_PASSWORDS} set.
128 | */
129 | Map getKeyStoreModel() {
130 | def path = Holders.config.https?.keystore?.path ?: "${this.defaultKeyStoreHome}/${DEFAULT_KEYSTORE}"
131 | def passwd = Holders.config.https?.keystore?.pass
132 |
133 | getKeyStoreInternal(path, passwd)
134 | }
135 | /**
136 | * Looks for the Trust Store file, loads it, and generates a Map from it as specified in the {@link #getKeyStoreInternal} method.
137 | * The config:ConfigObject may specify a path for the Trust Store through https.truststore.path, if none is specified it
138 | * will use {@link #DEFAULT_CLASSPATH_TRUSTSTORE} . In the same manner a password for such Trust Store might be specified through https.truststore.pass,
139 | * If none we will try to guess using common default keystore passwords.
140 | */
141 | Map getTrustStoreModel() {
142 |
143 | // Lets try to get a path from the config and if not set it to a default.
144 | def path = Holders.config.https?.truststore?.path ?: "${this.defaultTrustStoreHome}/${DEFAULT_TRUSTSTORE}"
145 | def passwd = Holders.config.https?.truststore?.pass
146 |
147 | getKeyStoreInternal(path, passwd)
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------