├── .classpath ├── .gitignore ├── .project ├── .settings └── org.codehaus.groovy.eclipse.preferences.prefs ├── .travis.yml ├── CorsGrailsPlugin.groovy ├── README.md ├── application.properties ├── grails-app └── conf │ ├── BuildConfig.groovy │ ├── Config.groovy │ └── DataSource.groovy ├── grailsw ├── grailsw.bat ├── src └── java │ └── com │ └── brandseye │ └── cors │ ├── CorsCompatibleBasicAuthenticationEntryPoint.java │ └── CorsFilter.java ├── test └── unit │ └── com │ └── brandseye │ └── cors │ ├── CorsCompatibleBasicAuthenticationEntryPointTests.groovy │ └── CorsFilterTest.groovy └── wrapper ├── grails-wrapper-runtime-2.2.1.jar ├── grails-wrapper.properties └── springloaded-core-1.1.1.jar /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | grails 4 | plugin.xml 5 | *.zip 6 | target 7 | web-app 8 | *.log 9 | .settings -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | cors 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.jdt.core.javabuilder 26 | 27 | 28 | 29 | 30 | 31 | org.grails.ide.eclipse.core.nature 32 | org.eclipse.jdt.groovy.core.groovyNature 33 | org.eclipse.jdt.core.javanature 34 | 35 | 36 | 37 | .link_to_grails_plugins 38 | 2 39 | GRAILS_ROOT/grails-cors/target/plugins 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /.settings/org.codehaus.groovy.eclipse.preferences.prefs: -------------------------------------------------------------------------------- 1 | #Created by grails 2 | eclipse.preferences.version=1 3 | groovy.dont.generate.class.files=true 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: groovy 2 | jdk: 3 | - oraclejdk7 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | before_script: 10 | - chmod +x grailsw 11 | 12 | script: ./grailsw refresh-dependencies 13 | && ./grailsw test-app -------------------------------------------------------------------------------- /CorsGrailsPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | import com.brandseye.cors.CorsCompatibleBasicAuthenticationEntryPoint 18 | import com.brandseye.cors.CorsFilter 19 | 20 | class CorsGrailsPlugin { 21 | def version = "1.3.0" 22 | def grailsVersion = "2.0 > *" 23 | def title = "CORS Plugin" 24 | def author = "David Tinker" 25 | def authorEmail = "david.tinker@gmail.com" 26 | def description = 'Installs a servlet filter to set Access-Control-Allow-Origin and other CORS related headers to enable cross site AJAX requests to your Grails application' 27 | def documentation = "https://github.com/davidtinker/grails-cors" 28 | 29 | // Make sure this loads after Spring Security otherwise we'll lose our custom BasicAuthenticationEntryPoint bean 30 | def loadAfter = ['springSecurityCore'] 31 | 32 | def license = "APACHE" 33 | def organization = [ name: "BrandsEye", url: "http://www.brandseye.com/" ] 34 | def issueManagement = [ system: "Github", url: "https://github.com/davidtinker/grails-cors/issues" ] 35 | def scm = [ url: "https://github.com/davidtinker/grails-cors" ] 36 | 37 | def doWithSpring = { 38 | // If using Spring Security's Basic Auth, swap out the BasicAuthenticationEntryPoint bean to prevent 39 | // authenticating on OPTIONS requests. See https://github.com/davidtinker/grails-cors/issues/12 for more info. 40 | if (application.config.grails.plugin.springsecurity?.useBasicAuth) { 41 | basicAuthenticationEntryPoint(CorsCompatibleBasicAuthenticationEntryPoint) { bean -> 42 | realmName = application.config.grails.plugin.springsecurity.basic.realmName 43 | } 44 | } 45 | } 46 | 47 | def doWithWebDescriptor = { xml -> 48 | def cfg = application.config.cors 49 | 50 | if (cfg.containsKey('enabled') && !cfg.enabled) return 51 | 52 | def contextParam = xml.'context-param' 53 | contextParam[contextParam.size() - 1] + { 54 | 'filter' { 55 | 'filter-name'('cors-headers') 56 | 'filter-class'(CorsFilter.name) 57 | if (cfg.allow.origin.regex) { 58 | 'init-param' { 59 | 'param-name'('allow.origin.regex') 60 | 'param-value'(cfg.allow.origin.regex.toString()) 61 | } 62 | } 63 | if (cfg.headers instanceof Map) { 64 | cfg.headers.each { k,v -> 65 | 'init-param' { 66 | 'param-name'('header:' + k) 67 | 'param-value'(v) 68 | } 69 | } 70 | } else if (cfg.headers instanceof String) { 71 | def headerMap = Eval.me(cfg.headers?.toString()) 72 | if(headerMap instanceof Map){ 73 | headerMap.each { k,v -> 74 | 'init-param' { 75 | 'param-name'('header:' + k) 76 | 'param-value'(v) 77 | } 78 | } 79 | } 80 | } 81 | if (cfg.expose.headers) { 82 | 'init-param' { 83 | 'param-name'('expose.headers') 84 | 'param-value'(cfg.expose.headers.toString()) 85 | } 86 | } 87 | if (cfg.enable.logging) { 88 | 'init-param' { 89 | 'param-name'('enable.logging') 90 | 'param-value'(cfg.enable.logging.toString()) 91 | } 92 | } 93 | } 94 | } 95 | 96 | def patterns = cfg.url.pattern ?: '/*' 97 | def urlPatterns = patterns instanceof List ? patterns : patterns.split(',').collect{ it.trim() } 98 | 99 | def filter = xml.'filter' 100 | urlPatterns.each { pattern -> 101 | filter[0] + { 102 | 'filter-mapping'{ 103 | 'filter-name'('cors-headers') 104 | 'url-pattern'(pattern) 105 | } 106 | } 107 | } 108 | } 109 | 110 | def getWebXmlFilterOrder() { 111 | def FilterManager = getClass().getClassLoader().loadClass('grails.plugin.webxml.FilterManager') 112 | // Be before the earliest Resource filter. 113 | ['cors-headers': FilterManager.DEFAULT_POSITION - 400] 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CORS Plugin 2 | =========== 3 | 4 | [![Build Status](https://travis-ci.org/Grails-Plugin-Consortium/grails-cors.svg?branch=master)](https://travis-ci.org/Grails-Plugin-Consortium/grails-cors) 5 | 6 | Grails plugin to add Cross-Origin Resource Sharing (CORS) headers for Grails applications. 7 | These headers make it possible for Javascript code served from a different host to easily make calls to your 8 | application. 9 | 10 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 11 | * http://www.w3.org/TR/access-control/ 12 | 13 | It is not easy to do this in a Grails application due to the following bug: http://jira.grails.org/browse/GRAILS-5531 14 | 15 | Grails 3+ 16 | --------- 17 | 18 | This plugin does not work with Grails 3. To handle CORS in a Grails 3 project you can just create a servlet filter 19 | somewhere under ```src/main/java```: 20 | 21 | @Priority(Integer.MIN_VALUE) 22 | public class CorsFilter extends OncePerRequestFilter { 23 | 24 | public CorsFilter() { } 25 | 26 | @Override 27 | protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) 28 | throws ServletException, IOException { 29 | 30 | String origin = req.getHeader("Origin"); 31 | 32 | boolean options = "OPTIONS".equals(req.getMethod()); 33 | if (options) { 34 | if (origin == null) return; 35 | resp.addHeader("Access-Control-Allow-Headers", "origin, authorization, accept, content-type, x-requested-with"); 36 | resp.addHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS"); 37 | resp.addHeader("Access-Control-Max-Age", "3600"); 38 | } 39 | 40 | resp.addHeader("Access-Control-Allow-Origin", origin == null ? "*" : origin); 41 | resp.addHeader("Access-Control-Allow-Credentials", "true"); 42 | 43 | if (!options) chain.doFilter(req, resp); 44 | } 45 | } 46 | 47 | Reference the filter in ```grails-app/conf/spring/resources.groovy```: 48 | 49 | beans = { 50 | corsFilter(CorsFilter) 51 | } 52 | 53 | Using 54 | ----- 55 | 56 | Add a dependency to BuildConfig.groovy: 57 | 58 | plugins { 59 | runtime ":cors:1.3.0" 60 | ... 61 | } 62 | 63 | The default configuration installs a servlet filter that adds the following headers to all OPTIONS requests: 64 | 65 | Access-Control-Allow-Origin: 66 | Access-Control-Allow-Credentials: true 67 | Access-Control-Allow-Headers: origin, authorization, accept, content-type, x-requested-with 68 | Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS 69 | Access-Control-Max-Age: 3600 70 | 71 | The 'Access-Control-Allow-Origin' and 'Access-Control-Allow-Credentials' headers are also added to non-OPTIONS 72 | requests (GET, POST et al.). If the plugin is configured to produce an 'Access-Control-Expose-Headers' header, 73 | it will be added to non-OPTIONS requests as well. 74 | 75 | These headers permit Javascript code loaded from anywhere to make AJAX calls to your application including specifying 76 | an 'Authorization' header (e.g. for Basic authentication). The browser will cache the results of the OPTIONS request 77 | for 1 hour (3600 seconds). 78 | 79 | Configuration 80 | ------------- 81 | 82 | The CORS plugin is configured through Config.groovy. 83 | 84 | You can limit the URL patterns the filter is applied to: 85 | 86 | cors.url.pattern = '/rest/*' 87 | 88 | This parameter also accepts a list of patterns to match: 89 | 90 | cors.url.pattern = ['/service1/*', '/service2/*'] 91 | 92 | Due to the 'Stringy' nature of external properties files, url patterns can be configured using a comma seperated string such as: 93 | 94 | cors.url.pattern = /api/*, /api-docs/* 95 | 96 | You can override the default values used for the headers by supplying a headers map: 97 | 98 | cors.headers = [ 99 | 'Access-Control-Allow-Origin': 'http://app.example.com', 100 | 'My-Custom-Header': 'some value'] 101 | 102 | Due to the 'Stringy' nature of external properties files, headers can be configured using a single line 'string' map: 103 | 104 | cors.headers = ['Access-Control-Allow-Origin': 'http://app.example.com','My-Custom-Header': 'some value'] 105 | 106 | Note that if you want to specify more than one host for 'Access-Control-Allow-Origin' there are issues with 107 | browser support. The recommended approach is to check the 'Origin' header of the request and echo it back 108 | if you are happy with it. The CORS plugin implements this using a regex to match allowed origins: 109 | 110 | cors.allow.origin.regex = '.*\\.example\\.com' 111 | 112 | If 'Origin' header matches the regex then it is echoed back as 'Access-Control-Allow-Origin' otherwise no CORS 113 | headers are sent back to the client and the browser will deny the request. 114 | 115 | Note that you can always send back '*' instead of echoing the 'Origin' header by including: 116 | 117 | cors.headers = ['Access-Control-Allow-Origin': '*'] 118 | 119 | This can be combined with cors.allow.origin.regex to limit allowed domains. 120 | 121 | You can specify a comma-delimited list of response headers that should be exposed to the client: 122 | 123 | cors.expose.headers = 'X-app-header1,X-app-header2' 124 | 125 | You can accept any Access-Control-Request-Headers as follows: 126 | 127 | cors.headers = ['Access-Control-Allow-Headers': '*'] 128 | 129 | You can enable logging of failed requests: 130 | 131 | cors.enable.logging = true 132 | 133 | You can disable the filter if needed. Note that the filter defaults to enabled. 134 | 135 | cors.enabled = false 136 | 137 | Client Performance Notes 138 | ------------------------ 139 | 140 | The browser only has to make an extra OPTIONS request for a cross-site AJAX GET if additional headers are specified. 141 | So this cross site jQuery AJAX call does not result in an OPTIONS request: 142 | 143 | $.ajax("https://api.notmyserver.com/rest/stuff/123", { 144 | data: data, 145 | dataType: 'json', 146 | type: 'get', 147 | success: callback, 148 | }); 149 | 150 | Whereas this one does (at least the first time): 151 | 152 | $.ajax("https://api.notmyserver.com/rest/stuff/123", { 153 | data: data, 154 | dataType: 'json', 155 | type: 'get', 156 | success: callback, 157 | headers: {Authorization: "Basic ..."} 158 | }); 159 | 160 | So if you can authenticate or whatever using query parameters instead of headers it can reduce latency. 161 | 162 | License 163 | ------- 164 | 165 | Copyright 2013-2022 DataEQ (http://www.dataeq.com/) 166 | 167 | Licensed under the Apache License, Version 2.0 (the "License"); 168 | you may not use this file except in compliance with the License. 169 | You may obtain a copy of the License at 170 | 171 | http://www.apache.org/licenses/LICENSE-2.0 172 | 173 | Unless required by applicable law or agreed to in writing, software 174 | distributed under the License is distributed on an "AS IS" BASIS, 175 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 176 | See the License for the specific language governing permissions and 177 | limitations under the License. 178 | 179 | Changelog 180 | --------- 181 | 1.3.0: 182 | - Added support for Access-Control-Allow-Headers="*" to echo back requested headers (thanks pablitar) 183 | 184 | 1.2.0: 185 | - Added support for logging failed requests (cors.enable.logging=true, thanks arrucard) 186 | 187 | 1.1.9: 188 | - Fixed bug with external config eval 189 | 190 | 1.1.8: 191 | - Adding support for external configuration files and travis.yml file (thanks ctoestreich) 192 | 193 | 1.1.7: 194 | - Merged PR for Spring Security 2 plugin (thanks neoecos). This plugin will no longer work with Spring Security < 2.0. 195 | 196 | 1.1.6: 197 | - Reverted to building plugin with Grails 2.2.1. Version 1.1.5 wasn't working with a Grails 2.0.3 app 198 | 199 | 1.1.5: 200 | - Got rid of deprecated ConfigHolder so plugin works with Grails 2.4 201 | - Removed css and other junk (was causing issues with asset pipeline) 202 | 203 | 1.1.4: 204 | - Fixed issue with Access-Control-Allow-Origin in cors.headers being ignored. If cors.headers does not contain 205 | Access-Control-Allow-Origin then any Origin accepted (any or those matching cors.allow.origin.regex) is echoed 206 | back. If cors.headers does contain Access-Control-Allow-Origin then this value is returned for accepted Origin's 207 | (i.e. you can use this in combination with cors.allow.origin.regex or set it to '*' to always send back '*' instead 208 | of the Origin header, useful if the result is cached by a CDN and the Origin varies). 209 | 210 | 1.1.3: 211 | - Fixed issue with getWebXmlFilterOrder not working in some circumstances (thanks Danilo Tuler) 212 | 213 | 1.1.2: 214 | - The CORS servlet filter now processes requests ahead of the resources filters (thanks Steve Loeppky) 215 | - OPTIONS requests are no longer passed down the filter chain (thanks Marcus Krantz) 216 | 217 | 1.1.1: Now works with Spring Security basic authentication (thanks James Hardwick) 218 | 219 | 1.1.0: 220 | - If 'Access-Control-Allow-Origin' is '*' (the default) then the 'Origin' header is echoed back instead of '*'. This 221 | is potentially a breaking change but is theoretically "more compliant" with the spec 222 | - No CORS headers are sent back if the client does not supply an 'Origin' header 223 | - Added 'Access-Control-Expose-Headers' option via 'cors.expose.headers' Config.groovy setting 224 | - Added 'cors.enabled' Config.groovy setting to explicitly enable/disabled the filter (filter is enabled by default) 225 | 226 | 1.0.4: Added Access-Control-Allow-Credentials: true 227 | 228 | 1.0.3: Bumped version no. to workaround plugin publishing issue 229 | 230 | 1.0.2: Added Access-Control-Allow-Methods header to OPTIONS request 231 | 232 | 1.0.1: Added Content-Type to default Access-Control-Allow-Headers 233 | -------------------------------------------------------------------------------- /application.properties: -------------------------------------------------------------------------------- 1 | #Grails Metadata file 2 | #Fri May 23 17:59:16 SAST 2014 3 | app.grails.version=2.2.1 4 | app.name=grails-cors 5 | app.servlet.version=2.5 6 | -------------------------------------------------------------------------------- /grails-app/conf/BuildConfig.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | grails.project.work.dir = "target" 18 | 19 | grails.project.dependency.resolution = { 20 | inherits "global" 21 | log "warn" 22 | 23 | repositories { 24 | grailsPlugins() 25 | grailsHome() 26 | grailsCentral() 27 | mavenLocal() 28 | mavenCentral() 29 | mavenRepo "https://repo.grails.org/grails/plugins" 30 | } 31 | 32 | dependencies { 33 | //test ":grails-test-suite-base:$grailsVersion" 34 | String springSecurityVersion = '3.0.7.RELEASE' 35 | compile("org.springframework.security:spring-security-core:$springSecurityVersion") { 36 | transitive = false 37 | } 38 | compile("org.springframework.security:spring-security-web:$springSecurityVersion") { 39 | transitive = false 40 | } 41 | test 'org.hamcrest:hamcrest-core:1.3' 42 | } 43 | 44 | plugins { 45 | compile ":webxml:1.4.1" 46 | build (':release:2.2.1', ':rest-client-builder:1.0.3') { 47 | export = false 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /grails-app/conf/Config.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | log4j = { 18 | error 'org.codehaus.groovy.grails', 19 | 'org.springframework', 20 | 'org.hibernate', 21 | 'net.sf.ehcache.hibernate' 22 | } 23 | 24 | // Uncomment and edit the following lines to start using Grails encoding & escaping improvements 25 | 26 | /* remove this line 27 | // GSP settings 28 | grails { 29 | views { 30 | gsp { 31 | encoding = 'UTF-8' 32 | htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping 33 | codecs { 34 | expression = 'html' // escapes values inside null 35 | scriptlet = 'none' // escapes output from scriptlets in GSPs 36 | taglib = 'none' // escapes output from taglibs 37 | staticparts = 'none' // escapes output from static template parts 38 | } 39 | } 40 | // escapes all not-encoded output at final stage of outputting 41 | filteringCodecForContentType { 42 | //'text/html' = 'html' 43 | } 44 | } 45 | } 46 | remove this line */ 47 | -------------------------------------------------------------------------------- /grails-app/conf/DataSource.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | dataSource { 18 | pooled = true 19 | driverClassName = 'org.h2.Driver' 20 | username = 'sa' 21 | password = '' 22 | dbCreate = 'update' 23 | url = 'jdbc:h2:mem:testDb' 24 | } 25 | 26 | hibernate { 27 | cache.use_second_level_cache = false 28 | cache.use_query_cache = false 29 | cache.provider_class = 'org.hibernate.cache.EhCacheProvider' 30 | } 31 | -------------------------------------------------------------------------------- /grailsw: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | ## ## 3 | ## Grails JVM Bootstrap for UN*X ## 4 | ## ## 5 | ############################################################################## 6 | 7 | PROGNAME=`basename "$0"` 8 | DIRNAME=`dirname "$0"` 9 | 10 | # Use the maximum available, or set MAX_FD != -1 to use that 11 | MAX_FD="maximum" 12 | 13 | warn() { 14 | echo "${PROGNAME}: $*" 15 | } 16 | 17 | die() { 18 | warn "$*" 19 | exit 1 20 | } 21 | 22 | earlyInit() { 23 | return 24 | } 25 | lateInit() { 26 | return 27 | } 28 | 29 | GROOVY_STARTUP=~/.groovy/startup 30 | if [ -r "$GROOVY_STARTUP" ]; then 31 | . "$GROOVY_STARTUP" 32 | fi 33 | 34 | earlyInit 35 | 36 | # OS specific support (must be 'true' or 'false'). 37 | cygwin=false; 38 | darwin=false; 39 | case "`uname`" in 40 | CYGWIN*) 41 | cygwin=true 42 | ;; 43 | 44 | Darwin*) 45 | darwin=true 46 | ;; 47 | esac 48 | 49 | # Attempt to set JAVA_HOME if it's not already set 50 | if [ -z "$JAVA_HOME" ]; then 51 | 52 | # Set JAVA_HOME for Darwin 53 | if $darwin; then 54 | 55 | [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && 56 | export JAVA_HOME="/Library/Java/Home" 57 | 58 | [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && 59 | export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home" 60 | 61 | fi 62 | 63 | fi 64 | 65 | # For Cygwin, ensure paths are in UNIX format before anything is touched 66 | if $cygwin ; then 67 | [ -n "$GRAILS_HOME" ] && 68 | GRAILS_HOME=`cygpath --unix "$GRAILS_HOME"` 69 | [ -n "$JAVACMD" ] && 70 | JAVACMD=`cygpath --unix "$JAVACMD"` 71 | [ -n "$JAVA_HOME" ] && 72 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 73 | [ -n "$CP" ] && 74 | CP=`cygpath --path --unix "$CP"` 75 | fi 76 | 77 | # Remove possible trailing slash (after possible cygwin correction) 78 | GRAILS_HOME=`echo $GRAILS_HOME | sed -e 's|/$||g'` 79 | 80 | # Locate GRAILS_HOME if not it is not set 81 | if [ -z "$GRAILS_HOME" -o ! -d "$GRAILS_HOME" ] ; then 82 | # resolve links - $0 may be a link to groovy's home 83 | PRG="$0" 84 | 85 | # need this for relative symlinks 86 | while [ -h "$PRG" ] ; do 87 | ls=`ls -ld "$PRG"` 88 | link=`expr "$ls" : '.*-> \(.*\)$'` 89 | if expr "$link" : '/.*' > /dev/null; then 90 | PRG="$link" 91 | else 92 | PRG=`dirname "$PRG"`"/$link" 93 | fi 94 | done 95 | 96 | SAVED="`pwd`" 97 | cd "`dirname \"$PRG\"`/.." 98 | GRAILS_HOME="`pwd -P`" 99 | cd "$SAVED" 100 | fi 101 | 102 | # Warn the user if JAVA_HOME and/or GRAILS_HOME are not set. 103 | if [ -z "$JAVA_HOME" ] ; then 104 | die "JAVA_HOME environment variable is not set" 105 | elif [ ! -d "$JAVA_HOME" ] ; then 106 | die "JAVA_HOME is not a directory: $JAVA_HOME" 107 | fi 108 | 109 | if [ -z "$GRAILS_HOME" ] ; then 110 | warn "GRAILS_HOME environment variable is not set" 111 | fi 112 | 113 | if [ ! -d "$GRAILS_HOME" ] ; then 114 | die "GRAILS_HOME is not a directory: $GRAILS_HOME" 115 | fi 116 | 117 | # Use default groovy-conf config 118 | if [ -z "$STARTER_CONF" ]; then 119 | STARTER_CONF="$GRAILS_HOME/conf/groovy-starter.conf" 120 | fi 121 | STARTER_CLASSPATH="wrapper/grails-wrapper-runtime-2.2.1.jar:wrapper:." 122 | 123 | # Allow access to Cocoa classes on OS X 124 | if $darwin; then 125 | STARTER_CLASSPATH="$STARTER_CLASSPATH:/System/Library/Java/Support" 126 | fi 127 | 128 | # Create the final classpath 129 | # Setting a classpath using the -cp or -classpath option means not to use 130 | # the global classpath. Groovy behaves then the same as the java 131 | # interpreter 132 | if [ -n "$CP" ] ; then 133 | CP="$CP" 134 | elif [ -n "$CLASSPATH" ] ; then 135 | CP="$CLASSPATH" 136 | fi 137 | 138 | # Determine the Java command to use to start the JVM 139 | if [ -z "$JAVACMD" ]; then 140 | if [ -n "$JAVA_HOME" ]; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="java" 149 | fi 150 | fi 151 | if [ ! -x "$JAVACMD" ]; then 152 | die "JAVA_HOME is not defined correctly; can not execute: $JAVACMD" 153 | fi 154 | 155 | # Increase the maximum file descriptors if we can 156 | if [ "$cygwin" = "false" ]; then 157 | MAX_FD_LIMIT=`ulimit -H -n` 158 | if [ "$MAX_FD_LIMIT" != "unlimited" ]; then 159 | if [ $? -eq 0 ]; then 160 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then 161 | # use the businessSystem max 162 | MAX_FD="$MAX_FD_LIMIT" 163 | fi 164 | 165 | ulimit -n $MAX_FD 166 | if [ $? -ne 0 ]; then 167 | warn "Could not set maximum file descriptor limit: $MAX_FD" 168 | fi 169 | else 170 | warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" 171 | fi 172 | fi 173 | fi 174 | 175 | # Fix the cygwin agent issue 176 | AGENT_GRAILS_HOME=$GRAILS_HOME 177 | if $cygwin ; then 178 | [ -n "$GRAILS_HOME" ] && 179 | AGENT_GRAILS_HOME=`cygpath --windows "$GRAILS_HOME"` 180 | fi 181 | 182 | if [ -z "$GRAILS_AGENT_CACHE_DIR" ]; then 183 | GRAILS_AGENT_CACHE_DIR=~/.grails/2.2.1/ 184 | fi 185 | SPRINGLOADED_PARAMS=profile=grails;cacheDir=$GRAILS_AGENT_CACHE_DIR 186 | if [ ! -d "$GRAILS_AGENT_CACHE_DIR" ]; then 187 | mkdir -p "$GRAILS_AGENT_CACHE_DIR" 188 | fi 189 | 190 | # Process JVM args 191 | AGENT_STRING="-javaagent:wrapper/springloaded-core-1.1.1.jar -noverify -Dspringloaded=\"$SPRINGLOADED_PARAMS\"" 192 | CMD_LINE_ARGS="" 193 | DISABLE_RELOADING=false 194 | 195 | while true; do 196 | if [ "$1" = "-cp" ] || [ "$1" = "-classpath" ]; then 197 | CP=$2 198 | shift 2 199 | break 200 | fi 201 | 202 | if [ "$1" = "-reloading" ]; then 203 | AGENT=$AGENT_STRING 204 | shift 205 | break 206 | fi 207 | 208 | if [ "$1" = "-noreloading" ]; then 209 | DISABLE_RELOADING=true 210 | shift 211 | break 212 | fi 213 | 214 | if [ "$1" = "-debug" ]; then 215 | JAVA_OPTS="$JAVA_OPTS -Xdebug -Xnoagent -Dgrails.full.stacktrace=true -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005" 216 | shift 217 | break 218 | fi 219 | 220 | if [ "$1" != -* ]; then 221 | break 222 | fi 223 | 224 | CMD_LINE_ARGS="$CMD_LINE_ARGS $1" 225 | shift 226 | done 227 | 228 | # Enable agent-based reloading for the 'run-app' command. 229 | if ! $DISABLE_RELOADING; then 230 | for a in "$@"; do 231 | if [ "$a" = "run-app" ]; then 232 | AGENT=$AGENT_STRING 233 | fi 234 | done 235 | 236 | if [ $# = 0 ]; then 237 | AGENT=$AGENT_STRING 238 | fi 239 | fi 240 | 241 | ARGUMENTS="$CMD_LINE_ARGS $@" 242 | 243 | # Setup Profiler 244 | useprofiler=false 245 | if [ "x$PROFILER" != "x" ]; then 246 | if [ -r "$PROFILER" ]; then 247 | . $PROFILER 248 | useprofiler=true 249 | else 250 | die "Profiler file not found: $PROFILER" 251 | fi 252 | fi 253 | 254 | # For Darwin, use classes.jar for TOOLS_JAR 255 | TOOLS_JAR="$JAVA_HOME/lib/tools.jar" 256 | if $darwin; then 257 | JAVA_OPTS="-Xdock:name=Grails -Xdock:icon=$GRAILS_HOME/media/icons/grails.icns $JAVA_OPTS" 258 | # TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Classes/classes.jar" 259 | fi 260 | 261 | # For Cygwin, switch paths to Windows format before running java 262 | if $cygwin; then 263 | GRAILS_HOME=`cygpath --path --mixed "$GRAILS_HOME"` 264 | JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"` 265 | STARTER_CONF=`cygpath --path --mixed "$STARTER_CONF"` 266 | CP=`cygpath --path --mixed "$CP"` 267 | TOOLS_JAR=`cygpath --path --mixed "$TOOLS_JAR"` 268 | STARTER_CLASSPATH=`cygpath --path --mixed "$STARTER_CLASSPATH"` 269 | # We build the pattern for arguments to be converted via cygpath 270 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 271 | SEP="" 272 | for dir in $ROOTDIRSRAW; do 273 | ROOTDIRS="$ROOTDIRS$SEP$dir" 274 | SEP="|" 275 | done 276 | OURCYGPATTERN="(^($ROOTDIRS))" 277 | # Add a user-defined pattern to the cygpath arguments 278 | if [ "$GROOVY_CYGPATTERN" != "" ] ; then 279 | OURCYGPATTERN="$OURCYGPATTERN|($GROOVY_CYGPATTERN)" 280 | fi 281 | # Now convert the arguments 282 | ARGUMENTS="" 283 | for arg in "$@" ; do 284 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 285 | if [ $CHECK -ne 0 ] ; then 286 | convArg=`cygpath --path --ignore --mixed "$arg"` 287 | else 288 | convArg=$arg 289 | fi 290 | ARGUMENTS="$ARGUMENTS $convArg" 291 | done 292 | fi 293 | 294 | STARTER_MAIN_CLASS=org.grails.wrapper.GrailsWrapper 295 | 296 | lateInit 297 | 298 | startGrails() { 299 | CLASS=$1 300 | shift 301 | if [ -n "$GRAILS_OPTS" ] 302 | then 303 | GRAILS_OPTS="$GRAILS_OPTS" 304 | else 305 | GRAILS_OPTS="-server -Xmx768M -Xms64M -XX:PermSize=32m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8" 306 | fi 307 | JAVA_OPTS="$GRAILS_OPTS $JAVA_OPTS $AGENT" 308 | # Start the Profiler or the JVM 309 | if $useprofiler; then 310 | runProfiler 311 | else 312 | if [ $# -eq 0 ] ; then # no argument given 313 | exec "$JAVACMD" $JAVA_OPTS \ 314 | -classpath "$STARTER_CLASSPATH" \ 315 | -Dgrails.home="$GRAILS_HOME" \ 316 | -Dtools.jar="$TOOLS_JAR" \ 317 | $STARTER_MAIN_CLASS \ 318 | --main $CLASS \ 319 | --conf "$STARTER_CONF" \ 320 | --classpath "$CP" 321 | else 322 | exec "$JAVACMD" $JAVA_OPTS \ 323 | -classpath "$STARTER_CLASSPATH" \ 324 | -Dgrails.home="$GRAILS_HOME" \ 325 | -Dtools.jar="$TOOLS_JAR" \ 326 | $STARTER_MAIN_CLASS \ 327 | --main $CLASS \ 328 | --conf "$STARTER_CONF" \ 329 | --classpath "$CP" \ 330 | "${ARGUMENTS}" 331 | fi 332 | fi 333 | } 334 | 335 | startGrails $STARTER_MAIN_CLASS "$@" -------------------------------------------------------------------------------- /grailsw.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem ## 4 | @rem Grails JVM Bootstrap 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 | set CLASS=org.grails.wrapper.GrailsWrapper 12 | 13 | if exist "%USERPROFILE%/.groovy/preinit.bat" call "%USERPROFILE%/.groovy/preinit.bat" 14 | 15 | @rem Determine the command interpreter to execute the "CD" later 16 | set COMMAND_COM="cmd.exe" 17 | if exist "%SystemRoot%\system32\cmd.exe" set COMMAND_COM="%SystemRoot%\system32\cmd.exe" 18 | if exist "%SystemRoot%\command.com" set COMMAND_COM="%SystemRoot%\command.com" 19 | 20 | @rem Use explicit find.exe to prevent cygwin and others find.exe from being used 21 | set FIND_EXE="find.exe" 22 | if exist "%SystemRoot%\system32\find.exe" set FIND_EXE="%SystemRoot%\system32\find.exe" 23 | if exist "%SystemRoot%\command\find.exe" set FIND_EXE="%SystemRoot%\command\find.exe" 24 | 25 | :check_JAVA_HOME 26 | @rem Make sure we have a valid JAVA_HOME 27 | if not "%JAVA_HOME%" == "" goto have_JAVA_HOME 28 | 29 | echo. 30 | echo ERROR: Environment variable JAVA_HOME has not been set. 31 | echo. 32 | echo Please set the JAVA_HOME variable in your environment to match the 33 | echo location of your Java installation. 34 | echo. 35 | goto end 36 | 37 | :have_JAVA_HOME 38 | @rem Remove trailing slash from JAVA_HOME if found 39 | if "%JAVA_HOME:~-1%"=="\" SET JAVA_HOME=%JAVA_HOME:~0,-1% 40 | 41 | @rem Validate JAVA_HOME 42 | %COMMAND_COM% /C DIR "%JAVA_HOME%" 2>&1 | %FIND_EXE% /I /C "%JAVA_HOME%" >nul 43 | if not errorlevel 1 goto check_GRAILS_HOME 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | echo. 51 | goto end 52 | 53 | :check_GRAILS_HOME 54 | @rem Define GRAILS_HOME if not set 55 | if "%GRAILS_HOME%" == "" set GRAILS_HOME=%DIRNAME%.. 56 | 57 | @rem Remove trailing slash from GRAILS_HOME if found 58 | if "%GRAILS_HOME:~-1%"=="\" SET GRAILS_HOME=%GRAILS_HOME:~0,-1% 59 | 60 | :init 61 | 62 | for %%x in ("%HOMEPATH%") do set SHORTHOME=%%~fsx 63 | if "x%GRAILS_AGENT_CACHE_DIR%" == "x" set GRAILS_AGENT_CACHE_DIR=%SHORTHOME%/.grails/2.2.1/ 64 | set SPRINGLOADED_PARAMS="profile=grails;cacheDir=%GRAILS_AGENT_CACHE_DIR%" 65 | if not exist "%GRAILS_AGENT_CACHE_DIR%" mkdir "%GRAILS_AGENT_CACHE_DIR%" 66 | 67 | set AGENT_STRING=-javaagent:wrapper/springloaded-core-1.1.1.jar -noverify -Dspringloaded=\"%SPRINGLOADED_PARAMS%\" 68 | set DISABLE_RELOADING= 69 | if "%GRAILS_OPTS%" == "" set GRAILS_OPTS=-server -Xmx768M -Xms64M -XX:PermSize=32m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8 70 | 71 | @rem Get command-line arguments, handling Windows variants 72 | if "%@eval[2+2]" == "4" goto 4NT_args 73 | 74 | @rem Slurp the command line arguments. 75 | set CMD_LINE_ARGS= 76 | set CP= 77 | set INTERACTIVE=true 78 | 79 | :win9xME_args_slurp 80 | if "x%~1" == "x" goto execute 81 | set CURR_ARG=%~1 82 | if "%CURR_ARG:~0,2%" == "-D" ( 83 | set GRAILS_OPTS=%GRAILS_OPTS% %~1=%~2 84 | shift 85 | shift 86 | goto win9xME_args_slurp 87 | ) 88 | if "x%~1" == "x-cp" ( 89 | set CP=%~2 90 | shift 91 | shift 92 | goto win9xME_args_slurp 93 | ) 94 | if "x%~1" == "x-debug" ( 95 | set JAVA_OPTS=%JAVA_OPTS% -Xdebug -Xnoagent -Dgrails.full.stacktrace=true -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 96 | shift 97 | goto win9xME_args_slurp 98 | ) 99 | if "x%~1" == "x-classpath" ( 100 | set CP=%~2 101 | shift 102 | shift 103 | goto win9xME_args_slurp 104 | ) 105 | if "x%~1" == "x-reloading" ( 106 | set AGENT=%AGENT_STRING% 107 | shift 108 | goto win9xME_args_slurp 109 | ) 110 | if "x%~1" == "xrun-app" ( 111 | set AGENT=%AGENT_STRING% 112 | set INTERACTIVE= 113 | set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1 114 | shift 115 | goto win9xME_args_slurp 116 | ) 117 | if "x%~1" == "x-noreloading" ( 118 | set DISABLE_RELOADING=true 119 | shift 120 | goto win9xME_args_slurp 121 | ) 122 | set INTERACTIVE= 123 | set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1 124 | shift 125 | goto win9xME_args_slurp 126 | 127 | :4NT_args 128 | @rem Get arguments from the 4NT Shell from JP Software 129 | set CMD_LINE_ARGS=%$ 130 | 131 | :execute 132 | @rem Setup the command line 133 | set STARTER_CLASSPATH=wrapper/grails-wrapper-runtime-2.2.1.jar;wrapper;. 134 | 135 | if exist "%USERPROFILE%/.groovy/init.bat" call "%USERPROFILE%/.groovy/init.bat" 136 | 137 | @rem Setting a classpath using the -cp or -classpath option means not to use 138 | @rem the global classpath. Groovy behaves then the same as the java interpreter 139 | 140 | if "x" == "x%CLASSPATH%" goto after_classpath 141 | set CP=%CP%;%CLASSPATH% 142 | :after_classpath 143 | 144 | if "x%DISABLE_RELOADING%" == "xtrue" ( 145 | set AGENT= 146 | ) else ( 147 | if "x%INTERACTIVE%" == "xtrue" ( 148 | set AGENT=%AGENT_STRING% 149 | ) 150 | ) 151 | 152 | set STARTER_MAIN_CLASS=org.grails.wrapper.GrailsWrapper 153 | set STARTER_CONF=%GRAILS_HOME%\conf\groovy-starter.conf 154 | 155 | set JAVA_EXE=%JAVA_HOME%\bin\java.exe 156 | set TOOLS_JAR=%JAVA_HOME%\lib\tools.jar 157 | 158 | set JAVA_OPTS=%GRAILS_OPTS% %JAVA_OPTS% %AGENT% 159 | 160 | set JAVA_OPTS=%JAVA_OPTS% -Dprogram.name="%PROGNAME%" 161 | set JAVA_OPTS=%JAVA_OPTS% -Dgrails.home="%GRAILS_HOME%" 162 | set JAVA_OPTS=%JAVA_OPTS% -Dgrails.version=2.2.1 163 | set JAVA_OPTS=%JAVA_OPTS% -Dbase.dir=. 164 | set JAVA_OPTS=%JAVA_OPTS% -Dtools.jar="%TOOLS_JAR%" 165 | set JAVA_OPTS=%JAVA_OPTS% -Dgroovy.starter.conf="%STARTER_CONF%" 166 | 167 | if exist "%USERPROFILE%/.groovy/postinit.bat" call "%USERPROFILE%/.groovy/postinit.bat" 168 | 169 | @rem Execute Grails 170 | CALL "%JAVA_EXE%" %JAVA_OPTS% -classpath "%STARTER_CLASSPATH%" %STARTER_MAIN_CLASS% --main %CLASS% --conf "%STARTER_CONF%" --classpath "%CP%" "%CMD_LINE_ARGS%" 171 | :end 172 | @rem End local scope for the variables with windows NT shell 173 | if "%OS%"=="Windows_NT" endlocal 174 | 175 | @rem Optional pause the batch file 176 | if "%GROOVY_BATCH_PAUSE%" == "on" pause -------------------------------------------------------------------------------- /src/java/com/brandseye/cors/CorsCompatibleBasicAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | package com.brandseye.cors; 18 | 19 | import org.springframework.security.core.AuthenticationException; 20 | import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; 21 | 22 | import javax.servlet.ServletException; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | import java.io.IOException; 26 | 27 | /** 28 | * Prevents authentication of HTTP OPTIONS preflight requests which would otherwise break CORS in most browsers. 29 | */ 30 | public class CorsCompatibleBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { 31 | 32 | public CorsCompatibleBasicAuthenticationEntryPoint() { 33 | super(); 34 | } 35 | 36 | @Override 37 | public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException { 38 | if(!request.getMethod().equals("OPTIONS")) { 39 | super.commence(request, response, authException); 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/java/com/brandseye/cors/CorsFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | package com.brandseye.cors; 18 | 19 | import javax.servlet.*; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import java.io.IOException; 23 | import java.util.Enumeration; 24 | import java.util.LinkedHashMap; 25 | import java.util.Map; 26 | import java.util.regex.Pattern; 27 | import org.slf4j.LoggerFactory; 28 | import org.slf4j.Logger; 29 | 30 | /** 31 | * Adds CORS headers to requests to enable cross-site AJAX calls. 32 | */ 33 | public class CorsFilter implements Filter { 34 | 35 | private static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; 36 | private static Logger logger = LoggerFactory.getLogger(CorsFilter.class); 37 | 38 | private final Map optionsHeaders = new LinkedHashMap(); 39 | 40 | private Pattern allowOriginRegex; 41 | private String allowOrigin; 42 | private String exposeHeaders; 43 | private boolean enableLogging; 44 | 45 | public void init(FilterConfig cfg) throws ServletException { 46 | optionsHeaders.put(ACCESS_CONTROL_ALLOW_HEADERS, "origin, authorization, accept, content-type, x-requested-with"); 47 | optionsHeaders.put("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS"); 48 | optionsHeaders.put("Access-Control-Max-Age", "3600"); 49 | for (Enumeration i = cfg.getInitParameterNames(); i.hasMoreElements(); ) { 50 | String name = i.nextElement(); 51 | if (name.startsWith("header:")) { 52 | optionsHeaders.put(name.substring(7), cfg.getInitParameter(name)); 53 | } 54 | } 55 | 56 | String regex = cfg.getInitParameter("allow.origin.regex"); 57 | if (regex != null) { 58 | allowOriginRegex = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); 59 | } 60 | allowOrigin = optionsHeaders.remove("Access-Control-Allow-Origin"); 61 | 62 | exposeHeaders = cfg.getInitParameter("expose.headers"); 63 | 64 | String logging = cfg.getInitParameter("enable.logging"); 65 | if(logging != null && logging.equalsIgnoreCase("true")){ 66 | enableLogging = true; 67 | } else { 68 | enableLogging = false; 69 | } 70 | } 71 | 72 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 73 | throws IOException, ServletException { 74 | if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { 75 | HttpServletRequest req = (HttpServletRequest)request; 76 | HttpServletResponse resp = (HttpServletResponse)response; 77 | if ("OPTIONS".equals(req.getMethod())) { 78 | if (checkOrigin(req, resp)) { 79 | String accessControlRequestHeaders = req.getHeader("Access-Control-Request-Headers"); 80 | for (Map.Entry e : optionsHeaders.entrySet()) { 81 | if(ACCESS_CONTROL_ALLOW_HEADERS.equals(e.getKey()) && "*".equals(e.getValue()) && accessControlRequestHeaders != null){ 82 | resp.addHeader(ACCESS_CONTROL_ALLOW_HEADERS, accessControlRequestHeaders); 83 | } else { 84 | resp.addHeader(e.getKey(), e.getValue()); 85 | } 86 | } 87 | 88 | // We need to return here since we don't want the chain to further process 89 | // a preflight request since this can lead to unexpected processing of the preflighted 90 | // request or a 405 - method not allowed in Grails 2.3 91 | return; 92 | 93 | } 94 | } else if (checkOrigin(req, resp)) { 95 | if (exposeHeaders != null) { 96 | resp.addHeader("Access-Control-Expose-Headers", exposeHeaders); 97 | } 98 | } 99 | } 100 | filterChain.doFilter(request, response); 101 | } 102 | 103 | private boolean checkOrigin(HttpServletRequest req, HttpServletResponse resp) { 104 | String origin = req.getHeader("Origin"); 105 | if (origin == null) { 106 | //no origin; per W3C spec, terminate further processing for both preflight and actual requests 107 | if(enableLogging){ 108 | logger.error("No origin header for request"); 109 | logger.error(requestToString(req)); 110 | } 111 | return false; 112 | } 113 | 114 | boolean matches; 115 | if (allowOriginRegex != null) { 116 | matches = allowOriginRegex.matcher(origin).matches(); 117 | } else { 118 | matches = allowOrigin == null || allowOrigin.equals("*") || allowOrigin.equals(origin); 119 | } 120 | 121 | if (matches) { 122 | // if no 'Access-Control-Allow-Origin' specified in cors.headers then echo back Origin 123 | resp.addHeader("Access-Control-Allow-Origin", allowOrigin == null ? origin : allowOrigin); 124 | resp.addHeader("Access-Control-Allow-Credentials", "true"); 125 | return true; 126 | } else { 127 | if(enableLogging){ 128 | logger.error("Origin header is present but does not match to allowed origin"); 129 | logger.error(requestToString(req)); 130 | } 131 | return false; 132 | } 133 | } 134 | 135 | private String requestToString(HttpServletRequest req){ 136 | String reqStr = "==================Request details start=================\r\n"; 137 | reqStr += requestUrl(req); 138 | reqStr += requestHeaders(req); 139 | reqStr += "==================Request details end=================\n"; 140 | return reqStr; 141 | } 142 | 143 | private String requestUrl(HttpServletRequest req) { 144 | String reqUrl = req.getRequestURL().toString(); 145 | String queryString = req.getQueryString(); 146 | if (queryString != null) { 147 | reqUrl += "?"+queryString; 148 | } 149 | return "Url: " + reqUrl + "\r\n"; 150 | } 151 | 152 | private String requestHeaders(HttpServletRequest req) { 153 | String reqHeaders = "Headers:\n"; 154 | Enumeration headerNames = req.getHeaderNames(); 155 | while (headerNames.hasMoreElements()) { 156 | String headerName = headerNames.nextElement(); 157 | reqHeaders += "\t" + headerName + ": "; 158 | String headerValue = req.getHeader(headerName); 159 | reqHeaders += headerValue + "\r\n"; 160 | } 161 | return reqHeaders; 162 | } 163 | 164 | public void destroy() { 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /test/unit/com/brandseye/cors/CorsCompatibleBasicAuthenticationEntryPointTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | package com.brandseye.cors 18 | 19 | import groovy.mock.interceptor.MockFor 20 | import org.springframework.security.core.AuthenticationException 21 | 22 | import javax.servlet.http.HttpServletRequest 23 | import javax.servlet.http.HttpServletResponse 24 | 25 | class CorsCompatibleBasicAuthenticationEntryPointTests extends GroovyTestCase { 26 | 27 | 28 | public void testOptionsPreflight() { 29 | // given 30 | def baEntryPoint = new CorsCompatibleBasicAuthenticationEntryPoint() 31 | def mockRequestContext = new MockFor(HttpServletRequest) 32 | mockRequestContext.demand.getMethod { "OPTIONS" } 33 | def mockRequest = mockRequestContext.proxyDelegateInstance() 34 | def mockResponseContext = new MockFor(HttpServletResponse) 35 | mockResponseContext.demand.sendError(0) 36 | def mockResponse = mockResponseContext.proxyDelegateInstance() 37 | 38 | // when 39 | baEntryPoint.commence(mockRequest, mockResponse, null) 40 | 41 | // then 42 | mockRequestContext.verify mockRequest 43 | mockResponseContext.verify mockResponse 44 | } 45 | 46 | public void testAllOthersNeedAuth() { 47 | methodTester("GET") 48 | methodTester("PUT") 49 | methodTester("DELETE") 50 | methodTester("POST") 51 | } 52 | 53 | private void methodTester(String method) { 54 | // given 55 | def baEntryPoint = new CorsCompatibleBasicAuthenticationEntryPoint() 56 | baEntryPoint.setRealmName("Test Realm") 57 | def mockRequestContext = new MockFor(HttpServletRequest) 58 | mockRequestContext.demand.getMethod { method } 59 | def mockRequest = mockRequestContext.proxyDelegateInstance() 60 | def mockResponseContext = new MockFor(HttpServletResponse) 61 | mockResponseContext.demand.addHeader(1) { String name, String value -> } 62 | mockResponseContext.demand.sendError(1) { int error, String msg -> 63 | assert error == HttpServletResponse.SC_UNAUTHORIZED 64 | } 65 | def mockResponse = mockResponseContext.proxyDelegateInstance() 66 | def authExceptionContext = new MockFor(MockedAuthException) 67 | def mockAuthException = authExceptionContext.proxyDelegateInstance() 68 | 69 | // when 70 | baEntryPoint.commence(mockRequest, mockResponse, mockAuthException) 71 | 72 | // then 73 | mockRequestContext.verify mockRequest 74 | mockResponseContext.verify mockResponse 75 | } 76 | 77 | } 78 | 79 | class MockedAuthException extends AuthenticationException { 80 | public MockedAuthException() { 81 | super("MockedAuthException") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/unit/com/brandseye/cors/CorsFilterTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 BrandsEye (http://www.brandseye.com/) 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 | 17 | package com.brandseye.cors 18 | 19 | import org.springframework.mock.web.MockFilterChain 20 | import org.springframework.mock.web.MockFilterConfig 21 | import org.springframework.mock.web.MockHttpServletRequest 22 | import org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletResponse 23 | 24 | import javax.servlet.http.HttpServletRequest 25 | import javax.servlet.http.HttpServletResponse 26 | 27 | /** 28 | * @author Peter Schneider-Manzell 29 | */ 30 | @SuppressWarnings("GroovyUnusedDeclaration") 31 | class CorsFilterTest extends GroovyTestCase { 32 | 33 | CorsFilter underTest; 34 | def minimalHeadersForAllAllowedRequests = ["Access-Control-Allow-Origin","Access-Control-Allow-Credentials"] 35 | def defaultHeadersForAllowedOptionsRequest = [ 36 | "Access-Control-Allow-Origin", 37 | "Access-Control-Allow-Credentials", 38 | "Access-Control-Allow-Headers", 39 | "Access-Control-Allow-Methods", 40 | "Access-Control-Max-Age" 41 | ] 42 | 43 | public void testDefaultHeadersForOptionsRequestsWithoutOriginRegexp(){ 44 | def expectedHeadersForHttpMethods = [ 45 | 'OPTIONS':defaultHeadersForAllowedOptionsRequest, 46 | 'GET':minimalHeadersForAllAllowedRequests, 47 | 'PUT':minimalHeadersForAllAllowedRequests, 48 | 'DELETE':minimalHeadersForAllAllowedRequests, 49 | 'POST':minimalHeadersForAllAllowedRequests 50 | ] 51 | String origin = "http://www.grails.org" 52 | executeTest([:],expectedHeadersForHttpMethods,origin) 53 | } 54 | 55 | public void testDefaultHeadersForOptionsRequestsWithOriginRegexpAndAllowedOrigin(){ 56 | def expectedHeadersForHttpMethods = [ 57 | 'OPTIONS':defaultHeadersForAllowedOptionsRequest, 58 | 'GET':minimalHeadersForAllAllowedRequests, 59 | 'PUT':minimalHeadersForAllAllowedRequests, 60 | 'DELETE':minimalHeadersForAllAllowedRequests, 61 | 'POST':minimalHeadersForAllAllowedRequests 62 | ] 63 | def filterInitParams = [:] 64 | filterInitParams['allow.origin.regex'] = '.*grails.*' 65 | String origin = "http://www.grails.org" 66 | executeTest(filterInitParams,expectedHeadersForHttpMethods,origin) 67 | } 68 | 69 | public void testDefaultHeadersForOptionsRequestsWithOriginRegexpAndNotAllowedOrigin(){ 70 | def expectedHeadersForHttpMethods = [ 71 | 'OPTIONS':[], 72 | 'GET':[], 73 | 'PUT':[], 74 | 'DELETE':[], 75 | 'POST':[] 76 | ] 77 | def filterInitParams = [:] 78 | filterInitParams['allow.origin.regex'] = '.*grails.*' 79 | String origin = "http://www.google.com" 80 | executeTest(filterInitParams,expectedHeadersForHttpMethods,origin) 81 | } 82 | 83 | public void testDefaultHeadersForOptionsRequestsWithoutOriginRegexpAndNotAllowedOrigin(){ 84 | def expectedHeadersForHttpMethods = [ 85 | 'OPTIONS':[], 86 | 'GET':[], 87 | 'PUT':[], 88 | 'DELETE':[], 89 | 'POST':[] 90 | ] 91 | def filterInitParams = [:] 92 | filterInitParams['header:Access-Control-Allow-Origin'] = 'http://www.grails.org' 93 | String origin = "http://www.google.com" 94 | executeTest(filterInitParams,expectedHeadersForHttpMethods,origin) 95 | } 96 | 97 | public void testExposedHeaders() { 98 | def headers = ["Access-Control-Allow-Origin","Access-Control-Allow-Credentials","Access-Control-Expose-Headers"] 99 | def expectedHeadersForHttpMethods = [ 100 | 'OPTIONS':defaultHeadersForAllowedOptionsRequest, 101 | 'GET':headers, 102 | 'PUT':headers, 103 | 'DELETE':headers, 104 | 'POST':headers 105 | ] 106 | def filterInitParams = [:] 107 | filterInitParams['allow.origin.regex'] = '.*grails.*' 108 | filterInitParams['expose.headers'] = "X-custom-header" 109 | String origin = "http://www.grails.org" 110 | executeTest(filterInitParams,expectedHeadersForHttpMethods,origin) 111 | } 112 | 113 | public void testAllowAllHeaders() { 114 | def headers = ["Access-Control-Allow-Origin","Access-Control-Allow-Credentials","Access-Control-Request-Headers"] 115 | def expectedHeadersForHttpMethods = [ 116 | 'OPTIONS':defaultHeadersForAllowedOptionsRequest, 117 | 'GET':minimalHeadersForAllAllowedRequests, 118 | 'PUT':minimalHeadersForAllAllowedRequests, 119 | 'DELETE':minimalHeadersForAllAllowedRequests, 120 | 'POST':minimalHeadersForAllAllowedRequests 121 | ] 122 | def filterInitParams = [:] 123 | filterInitParams['header:Access-Control-Allow-Headers'] = '*' 124 | String origin = "http://www.grails.org" 125 | executeTest(filterInitParams,expectedHeadersForHttpMethods,origin, ['Access-Control-Request-Headers':'X-custom-header']) 126 | } 127 | 128 | 129 | public void testValueOfExposedHeaders() { 130 | def underTest = new CorsFilter() 131 | def expected = 'X-custom-header1,X-custom-header2' 132 | MockFilterConfig filterConfig = new MockFilterConfig() 133 | filterConfig.addInitParameter('allow.origin.regex','.*grails.*') 134 | filterConfig.addInitParameter('expose.headers',expected) 135 | underTest.init(filterConfig) 136 | 137 | HttpServletRequest req = new MockHttpServletRequest() 138 | req.setMethod("PUT") 139 | req.addHeader("Origin",'http://www.grails.org') 140 | HttpServletResponse res = new GrailsMockHttpServletResponse() 141 | underTest.doFilter(req,res,new MockFilterChain()) 142 | assert res.getHeader('Access-Control-Expose-Headers').equals('X-custom-header1,X-custom-header2') : "Access-Control-Expose-Headers, expected ${expected}, got ${res.getHeader('Access-Control-Expose-Headers')}" 143 | 144 | } 145 | 146 | void testAllowOriginStar() { 147 | def underTest = new CorsFilter() 148 | 149 | MockFilterConfig filterConfig = new MockFilterConfig() 150 | filterConfig.addInitParameter('header:Access-Control-Allow-Origin','*') 151 | underTest.init(filterConfig) 152 | 153 | HttpServletRequest req = new MockHttpServletRequest() 154 | req.setMethod("PUT") 155 | req.addHeader("Origin",'http://www.grails.org') 156 | HttpServletResponse res = new GrailsMockHttpServletResponse() 157 | underTest.doFilter(req, res, new MockFilterChain()) 158 | assert '*' == res.getHeader('Access-Control-Allow-Origin') 159 | } 160 | 161 | void testAllowOriginEchosOrigin() { 162 | def underTest = new CorsFilter() 163 | 164 | underTest.init(new MockFilterConfig()) 165 | 166 | HttpServletRequest req = new MockHttpServletRequest() 167 | req.setMethod("PUT") 168 | req.addHeader("Origin",'http://www.grails.org') 169 | HttpServletResponse res = new GrailsMockHttpServletResponse() 170 | underTest.doFilter(req, res, new MockFilterChain()) 171 | assert 'http://www.grails.org' == res.getHeader('Access-Control-Allow-Origin') 172 | } 173 | 174 | void testAllowOriginStarWorksWithRegex() { 175 | def underTest = new CorsFilter() 176 | 177 | MockFilterConfig filterConfig = new MockFilterConfig() 178 | filterConfig.addInitParameter('header:Access-Control-Allow-Origin', '*') 179 | filterConfig.addInitParameter('allow.origin.regex', '.*grails.*') 180 | underTest.init(filterConfig) 181 | 182 | HttpServletRequest req = new MockHttpServletRequest() 183 | req.setMethod("PUT") 184 | req.addHeader("Origin",'http://www.grails.org') 185 | HttpServletResponse res = new GrailsMockHttpServletResponse() 186 | underTest.doFilter(req, res, new MockFilterChain()) 187 | assert '*' == res.getHeader('Access-Control-Allow-Origin') 188 | 189 | req = new MockHttpServletRequest() 190 | req.setMethod("PUT") 191 | req.addHeader("Origin",'http://www.wibble.com') 192 | res = new GrailsMockHttpServletResponse() 193 | underTest.doFilter(req, res, new MockFilterChain()) 194 | assert null == res.getHeader('Access-Control-Allow-Origin') 195 | } 196 | 197 | private void executeTest(Map filterInitParams,Map> expectedHeadersForHttpMethods,String origin, Map requestHeaders = [:]){ 198 | underTest = new CorsFilter() 199 | MockFilterConfig filterConfig = new MockFilterConfig() 200 | filterInitParams.entrySet().each {filterEntry-> 201 | filterConfig.addInitParameter(filterEntry.key,filterEntry.value) 202 | } 203 | underTest.init(filterConfig) 204 | 205 | 206 | expectedHeadersForHttpMethods.entrySet().each {expectedHeadersForHttpMethod-> 207 | String httpMethod = expectedHeadersForHttpMethod.key 208 | Set expectedHeaders = expectedHeadersForHttpMethod.value 209 | HttpServletRequest req = new MockHttpServletRequest() 210 | req.setMethod(httpMethod) 211 | req.addHeader("Origin",origin) 212 | requestHeaders.entrySet().each { 213 | req.addHeader(it.key, it.value) 214 | } 215 | HttpServletResponse res = new GrailsMockHttpServletResponse() 216 | underTest.doFilter(req,res,new MockFilterChain()) 217 | assert res.getHeaderNames().containsAll(expectedHeaders) : "Not all expected headers for request $httpMethod and origin $origin could be found! (expected: $expectedHeaders, got: ${res.getHeaderNames()})" 218 | assert res.getHeaderNames().size() == expectedHeaders.size() : "Header count for request $httpMethod and origin $origin not the expected header count! (expected: $expectedHeaders, got: ${res.getHeaderNames()})" 219 | 220 | //check that the Access-Control-Allow-Credentials is always single-valued 221 | if (res.getHeaderNames().contains('Access-Control-Allow-Credentials')) { 222 | assert res.headers('Access-Control-Allow-Credentials').size() == 1 223 | } 224 | //check that the Access-Control-Allow-Origin is always single-valued, 225 | //since we always set Access-Control-Allow-Credentials 226 | if (res.getHeaderNames().contains('Access-Control-Allow-Origin')) { 227 | assert res.headers('Access-Control-Allow-Origin').size() == 1 228 | } 229 | 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /wrapper/grails-wrapper-runtime-2.2.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidtinker/grails-cors/e3367cdf51ac6b3480e645ac12b431911fe9b48e/wrapper/grails-wrapper-runtime-2.2.1.jar -------------------------------------------------------------------------------- /wrapper/grails-wrapper.properties: -------------------------------------------------------------------------------- 1 | wrapper.dist.url=http://dist.springframework.org.s3.amazonaws.com/release/GRAILS/ -------------------------------------------------------------------------------- /wrapper/springloaded-core-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidtinker/grails-cors/e3367cdf51ac6b3480e645ac12b431911fe9b48e/wrapper/springloaded-core-1.1.1.jar --------------------------------------------------------------------------------