Skip to content
Browse files

initial code

  • Loading branch information...
1 parent 94662c6 commit 8be93f56b71d7dcd4c591af922bc9b4db8d10437 @burtbeckwith burtbeckwith committed Mar 22, 2010
Showing with 6,451 additions and 0 deletions.
  1. +17 −0 .classpath
  2. +4 −0 .gitignore
  3. +19 −0 .project
  4. +201 −0 LICENSE.txt
  5. +799 −0 SpringSecurityCoreGrailsPlugin.groovy
  6. +7 −0 application.properties
  7. +90 −0 build.xml
  8. +49 −0 grails-app/conf/BuildConfig.groovy
  9. +25 −0 grails-app/conf/Config.groovy
  10. +14 −0 grails-app/conf/DataSource.groovy
  11. +197 −0 grails-app/conf/DefaultSecurityConfig.groovy
  12. +15 −0 grails-app/domain/test/TestRequestmap.groovy
  13. +14 −0 grails-app/domain/test/TestRole.groovy
  14. +24 −0 grails-app/domain/test/TestUser.groovy
  15. +47 −0 grails-app/domain/test/TestUserRole.groovy
  16. +179 −0 grails-app/services/grails/plugins/springsecurity/SpringSecurityService.groovy
  17. +113 −0 grails-app/taglib/grails/plugins/springsecurity/SecurityTagLib.groovy
  18. BIN lib/easymock.jar
  19. +161 −0 scripts/S2Quickstart.groovy
  20. +9 −0 scripts/_Install.groovy
  21. 0 scripts/_Uninstall.groovy
  22. 0 scripts/_Upgrade.groovy
  23. +118 −0 src/groovy/org/codehaus/groovy/grails/plugins/springsecurity/GormUserDetailsService.groovy
  24. +88 −0 src/groovy/org/codehaus/groovy/grails/plugins/springsecurity/ReflectionUtils.groovy
  25. +238 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/AbstractFilterInvocationDefinition.java
  26. +160 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/AjaxAwareAccessDeniedHandler.java
  27. +64 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/AjaxAwareAuthenticationEntryPoint.java
  28. +279 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/AnnotationFilterInvocationDefinition.java
  29. +115 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/AuthenticatedVetoableDecisionManager.java
  30. +42 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/GrailsUserDetailsService.java
  31. +58 −0 .../org/codehaus/groovy/grails/plugins/springsecurity/InterceptUrlMapFilterInvocationDefinition.java
  32. +183 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/IpAddressFilter.java
  33. +96 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/LogoutFilterFactoryBean.java
  34. +36 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/NullSaltSource.java
  35. +44 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/RequestHolderAuthenticationFilter.java
  36. +72 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/RequestmapFilterInvocationDefinition.java
  37. +41 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/Secured.java
  38. +30 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/SecurityConfigType.java
  39. +106 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/SecurityEventListener.java
  40. +44 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/SecurityFilterPosition.java
  41. +67 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/SecurityRequestHolder.java
  42. +294 −0 src/java/org/codehaus/groovy/grails/plugins/springsecurity/SpringSecurityUtils.java
  43. +14 −0 src/templates/Authority.groovy.template
  44. +90 −0 src/templates/LoginController.groovy.template
  45. +12 −0 src/templates/LogoutController.groovy.template
  46. +17 −0 src/templates/Person.groovy.template
  47. +48 −0 src/templates/PersonAuthority.groovy.template
  48. +16 −0 src/templates/Requestmap.groovy.template
  49. +79 −0 src/templates/auth.gsp.template
  50. +10 −0 src/templates/denied.gsp.template
  51. +135 −0 .../integration/org/codehaus/groovy/grails/plugins/springsecurity/GormUserDetailsServiceTests.groovy
  52. +143 −0 ...ntegration/org/grails/plugins/springsecurity/service/SpringSecurityServiceIntegrationTests.groovy
  53. +208 −0 test/integration/org/grails/plugins/springsecurity/taglib/SecurityTagLibTests.groovy
  54. +83 −0 ...t/org/codehaus/groovy/grails/plugins/springsecurity/AjaxAwareAuthenticationEntryPointTests.groovy
  55. +295 −0 ...rg/codehaus/groovy/grails/plugins/springsecurity/AnnotationFilterInvocationDefinitionTests.groovy
  56. +91 −0 ...rg/codehaus/groovy/grails/plugins/springsecurity/AuthenticatedVetoableDecisionManagerTests.groovy
  57. +138 −0 test/unit/org/codehaus/groovy/grails/plugins/springsecurity/IpAddressFilterTests.groovy
  58. +106 −0 test/unit/org/codehaus/groovy/grails/plugins/springsecurity/LogoutFilterFactoryBeanTests.groovy
  59. +32 −0 test/unit/org/codehaus/groovy/grails/plugins/springsecurity/NullSaltSourceTests.groovy
  60. +161 −0 ...rg/codehaus/groovy/grails/plugins/springsecurity/RequestmapFilterInvocationDefinitionTests.groovy
  61. +138 −0 test/unit/org/codehaus/groovy/grails/plugins/springsecurity/SecurityEventListenerTests.groovy
  62. +70 −0 test/unit/org/codehaus/groovy/grails/plugins/springsecurity/SecurityRequestHolderTests.groovy
  63. +75 −0 test/unit/org/codehaus/groovy/grails/plugins/springsecurity/SecurityTestUtils.groovy
  64. +224 −0 test/unit/org/codehaus/groovy/grails/plugins/springsecurity/SpringSecurityUtilsTests.groovy
  65. +107 −0 test/unit/org/grails/plugins/springsecurity/service/SpringSecurityServiceTests.groovy
View
17 .classpath
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/java"/>
+ <classpathentry kind="src" path="src/groovy"/>
+ <classpathentry kind="src" path="grails-app/conf"/>
+ <classpathentry kind="src" path="grails-app/domain"/>
+ <classpathentry kind="src" path="grails-app/services"/>
+ <classpathentry kind="src" path="grails-app/taglib"/>
+ <classpathentry kind="src" path="test/integration"/>
+ <classpathentry kind="src" path="test/unit"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="var" path="IVY_CACHE/org.springframework.security/org.springframework.security.core/jars/org.springframework.security.core-3.0.2.RELEASE.jar"/>
+ <classpathentry kind="var" path="IVY_CACHE/org.springframework.security/org.springframework.security.web/jars/org.springframework.security.web-3.0.2.RELEASE.jar" sourcepath="/SPRING_SECURITY_SOURCE/spring-security-web-3.0.2.RELEASE-sources.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/grails1.2"/>
+ <classpathentry kind="lib" path="lib/easymock.jar"/>
+ <classpathentry kind="output" path="target/eclipseclasses"/>
+</classpath>
View
4 .gitignore
@@ -0,0 +1,4 @@
+cobertura.ser
+stacktrace.log
+target
+
View
19 .project
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>spring-security-core</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.springsource.sts.grails.core.nature</nature>
+ <nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
View
201 LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
View
799 SpringSecurityCoreGrailsPlugin.groovy
@@ -0,0 +1,799 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import javax.servlet.Filter
+
+import org.springframework.cache.ehcache.EhCacheFactoryBean
+import org.springframework.cache.ehcache.EhCacheManagerFactoryBean
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
+import org.springframework.security.access.vote.AuthenticatedVoter
+import org.springframework.security.access.vote.RoleHierarchyVoter
+import org.springframework.security.authentication.AccountStatusUserDetailsChecker
+import org.springframework.security.authentication.AnonymousAuthenticationProvider
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl
+import org.springframework.security.authentication.ProviderManager
+import org.springframework.security.authentication.RememberMeAuthenticationProvider
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider
+import org.springframework.security.authentication.dao.ReflectionSaltSource
+import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder
+import org.springframework.security.core.context.SecurityContextHolder as SCH
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
+import org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache
+import org.springframework.security.core.userdetails.cache.NullUserCache
+import org.springframework.security.web.DefaultRedirectStrategy
+import org.springframework.security.web.FilterChainProxy
+import org.springframework.security.web.PortMapperImpl
+import org.springframework.security.web.PortResolverImpl
+import org.springframework.security.web.access.ExceptionTranslationFilter
+import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl
+import org.springframework.security.web.access.channel.ChannelProcessingFilter
+import org.springframework.security.web.access.channel.InsecureChannelProcessor
+import org.springframework.security.web.access.channel.RetryWithHttpEntryPoint
+import org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint
+import org.springframework.security.web.access.channel.SecureChannelProcessor
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
+import org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler
+import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
+import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
+import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService
+import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor
+import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
+import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy
+import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy
+import org.springframework.security.web.authentication.switchuser.SwitchUserFilter
+import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
+import org.springframework.security.web.context.SecurityContextPersistenceFilter
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
+import org.springframework.security.web.session.HttpSessionEventPublisher
+import org.springframework.security.web.util.AntUrlPathMatcher
+import org.springframework.security.web.util.RegexUrlPathMatcher
+import org.springframework.web.filter.DelegatingFilterProxy
+
+import org.codehaus.groovy.grails.plugins.springsecurity.AjaxAwareAccessDeniedHandler
+import org.codehaus.groovy.grails.plugins.springsecurity.AjaxAwareAuthenticationEntryPoint
+import org.codehaus.groovy.grails.plugins.springsecurity.AnnotationFilterInvocationDefinition
+import org.codehaus.groovy.grails.plugins.springsecurity.AuthenticatedVetoableDecisionManager
+import org.codehaus.groovy.grails.plugins.springsecurity.GormUserDetailsService
+import org.codehaus.groovy.grails.plugins.springsecurity.InterceptUrlMapFilterInvocationDefinition
+import org.codehaus.groovy.grails.plugins.springsecurity.IpAddressFilter
+import org.codehaus.groovy.grails.plugins.springsecurity.LogoutFilterFactoryBean
+import org.codehaus.groovy.grails.plugins.springsecurity.NullSaltSource
+import org.codehaus.groovy.grails.plugins.springsecurity.RequestmapFilterInvocationDefinition
+import org.codehaus.groovy.grails.plugins.springsecurity.RequestHolderAuthenticationFilter
+import org.codehaus.groovy.grails.plugins.springsecurity.SecurityConfigType
+import org.codehaus.groovy.grails.plugins.springsecurity.SecurityEventListener
+import org.codehaus.groovy.grails.plugins.springsecurity.SecurityFilterPosition
+import org.codehaus.groovy.grails.plugins.springsecurity.SecurityRequestHolder
+import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
+
+class SpringSecurityCoreGrailsPlugin {
+
+ private static final String DEFINITION_SOURCE_PREFIX =
+ 'CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON\n' +
+ 'PATTERN_TYPE_APACHE_ANT\n'
+
+ String version = '0.1'
+ String grailsVersion = '1.2 > *'
+ List observe = ['controllers']
+ Map dependsOn = [:]
+ List loadAfter = ['controllers', 'services', 'hibernate']
+
+ List pluginExcludes = [
+ 'lib/easymock*.jar',
+ 'grails-app/domain/**',
+ 'grails-app/services/**/Test*Service.groovy',
+ 'scripts/_Events.groovy'
+ ]
+
+ String author = 'Burt Beckwith'
+ String authorEmail = 'beckwithb@vmware.com'
+ String title = 'Spring Security Core Plugin'
+ String description = 'Spring Security Core plugin'
+ String documentation = 'http://grails.org/plugin/spring-security-core'
+
+ def doWithWebDescriptor = { xml ->
+
+ def conf = SpringSecurityUtils.securityConfig
+ if (!conf || !conf.active) {
+ return
+ }
+
+ // we add the filter(s) right after the last context-param
+ def contextParam = xml.'context-param'
+
+ // the name of the filter matches the name of the Spring bean that it delegates to
+ contextParam[contextParam.size() - 1] + {
+ 'filter' {
+ 'filter-name'('springSecurityFilterChain')
+ 'filter-class'(DelegatingFilterProxy.name)
+ }
+ }
+
+ // add the filter-mapping after the Spring character encoding filter
+ findMappingLocation.delegate = delegate
+ def mappingLocation = findMappingLocation(xml)
+ mappingLocation + {
+ 'filter-mapping'{
+ 'filter-name'('springSecurityFilterChain')
+ 'url-pattern'('/*')
+ }
+ }
+
+ if (conf.useHttpSessionEventPublisher) {
+ def filterMapping = xml.'filter-mapping'
+ filterMapping[filterMapping.size() - 1] + {
+ 'listener' {
+ 'listener-class'(HttpSessionEventPublisher.name)
+ }
+ }
+ }
+ }
+
+ def doWithSpring = {
+
+ def conf = SpringSecurityUtils.securityConfig
+ if (!conf || !conf.active) {
+ println '\n\nSpring Security is disabled, not loading\n\n'
+ return
+ }
+
+ println '\nConfiguring Spring Security ...'
+
+ createRefList.delegate = delegate
+
+ /** springSecurityFilterChain */
+ configureFilterChain.delegate = delegate
+ configureFilterChain conf
+
+ // logout
+ configureLogout.delegate = delegate
+ configureLogout conf
+
+ /** securityContextPersistenceFilter */
+ securityContextPersistenceFilter(SecurityContextPersistenceFilter)
+
+ /** authenticationProcessingFilter */
+ configureAuthenticationProcessingFilter.delegate = delegate
+ configureAuthenticationProcessingFilter conf
+
+ /** securityContextHolderAwareRequestFilter */
+ securityContextHolderAwareRequestFilter(SecurityContextHolderAwareRequestFilter)
+
+ /** rememberMeAuthenticationFilter */
+ rememberMeAuthenticationFilter(RememberMeAuthenticationFilter) {
+ authenticationManager = ref('authenticationManager')
+ rememberMeServices = ref('rememberMeServices')
+ }
+
+ /** rememberMeServices */
+ rememberMeServices(TokenBasedRememberMeServices) {
+ userDetailsService = ref('userDetailsService')
+ key = conf.rememberMe.key
+ cookieName = conf.rememberMe.cookieName
+ alwaysRemember = conf.rememberMe.alwaysRemember
+ tokenValiditySeconds = conf.rememberMe.tokenValiditySeconds
+ parameter = conf.rememberMe.parameter
+ }
+
+ /** anonymousAuthenticationFilter */
+ anonymousAuthenticationFilter(AnonymousAuthenticationFilter) {
+ key = conf.anon.key // 'foo'
+ userAttribute = conf.anon.userAttribute //'anonymousUser,ROLE_ANONYMOUS'
+ }
+
+ /** exceptionTranslationFilter */
+ exceptionTranslationFilter(ExceptionTranslationFilter) {
+ authenticationEntryPoint = ref('authenticationEntryPoint')
+ accessDeniedHandler = ref('accessDeniedHandler')
+ authenticationTrustResolver = ref('authenticationTrustResolver')
+ requestCache = ref('requestCache')
+ }
+ accessDeniedHandler(AjaxAwareAccessDeniedHandler) {
+ errorPage = conf.adh.errorPage == 'null' ? null : conf.adh.errorPage // '/login/denied' or 403
+ ajaxErrorPage = conf.adh.ajaxErrorPage
+ portResolver = ref('portResolver')
+ authenticationTrustResolver = ref('authenticationTrustResolver')
+ if (conf.ajaxHeader) {
+ ajaxHeader = conf.ajaxHeader //default: X-Requested-With
+ }
+ }
+
+ /** authenticationTrustResolver */
+ authenticationTrustResolver(AuthenticationTrustResolverImpl) {
+ anonymousClass = conf.atr.anonymousClass
+ rememberMeClass = conf.atr.rememberMeClass
+ }
+
+ authenticationEntryPoint(AjaxAwareAuthenticationEntryPoint) {
+ loginFormUrl = conf.auth.loginFormUrl // '/login/auth'
+ forceHttps = conf.auth.forceHttps // 'false'
+ ajaxLoginFormUrl = conf.auth.ajaxLoginFormUrl // '/login/authAjax'
+ useForward = conf.auth.useForward // false
+ if (conf.auth.ajaxHeader) {
+ ajaxHeader = conf.ajaxHeader //default: X-Requested-With
+ }
+ portMapper = ref('portMapper')
+ portResolver = ref('portResolver')
+ }
+
+ /** filterInvocationInterceptor */
+ filterInvocationInterceptor(FilterSecurityInterceptor) {
+ authenticationManager = ref('authenticationManager')
+ accessDecisionManager = ref('accessDecisionManager')
+ objectDefinitionSource = ref('objectDefinitionSource')
+ }
+ if (conf.securityConfigType == SecurityConfigType.Annotation) {
+ objectDefinitionSource(AnnotationFilterInvocationDefinition) {
+ boolean lowercase = conf.controllerAnnotations.matchesLowercase // true
+ if ('ant'.equals(conf.controllerAnnotations.matcher)) {
+ urlMatcher = new AntUrlPathMatcher(lowercase)
+ }
+ else {
+ urlMatcher = new RegexUrlPathMatcher(lowercase)
+ }
+ if (conf.rejectIfNoRule instanceof Boolean) {
+ rejectIfNoRule = conf.rejectIfNoRule
+ }
+ }
+ }
+ else if (conf.securityConfigType == SecurityConfigType.Requestmap) {
+ objectDefinitionSource(RequestmapFilterInvocationDefinition) {
+ urlMatcher = new AntUrlPathMatcher(true)
+ if (conf.rejectIfNoRule instanceof Boolean) {
+ rejectIfNoRule = conf.rejectIfNoRule
+ }
+ }
+ }
+ else if (conf.securityConfigType == SecurityConfigType.InterceptUrlMap) {
+ objectDefinitionSource(InterceptUrlMapFilterInvocationDefinition) {
+ urlMatcher = new AntUrlPathMatcher(true)
+ if (conf.rejectIfNoRule instanceof Boolean) {
+ rejectIfNoRule = conf.rejectIfNoRule
+ }
+ }
+ }
+
+ // voters
+ configureVoters.delegate = delegate
+ configureVoters conf
+
+ /** anonymousAuthenticationProvider */
+ anonymousAuthenticationProvider(AnonymousAuthenticationProvider) {
+ key = conf.anon.key // 'foo'
+ }
+ /** rememberMeAuthenticationProvider */
+ rememberMeAuthenticationProvider(RememberMeAuthenticationProvider) {
+ key = conf.rememberMe.key
+ }
+
+ // authenticationManager
+ configureAuthenticationManager.delegate = delegate
+ configureAuthenticationManager conf
+
+ /** daoAuthenticationProvider */
+ if (conf.dao.reflectionSaltSourceUserProperty) {
+ saltSource(ReflectionSaltSource) {
+ userPropertyToUse = conf.dao.reflectionSaltSourceUserProperty
+ }
+ }
+ else {
+ saltSource(NullSaltSource)
+ }
+ daoAuthenticationProvider(DaoAuthenticationProvider) {
+ userDetailsService = ref('userDetailsService')
+ passwordEncoder = ref('passwordEncoder')
+ userCache = ref('userCache')
+ saltSource = ref('saltSource')
+ }
+
+ /** passwordEncoder */
+ passwordEncoder(MessageDigestPasswordEncoder, conf.password.algorithm) {
+ if (conf.password.encodeHashAsBase64) {
+ encodeHashAsBase64 = true
+ }
+ }
+
+ /** userDetailsService */
+ userDetailsService(GormUserDetailsService) {
+ sessionFactory = ref('sessionFactory')
+ transactionManager = ref('transactionManager')
+ }
+
+ // port mappings for channel security, etc.
+ portMapper(PortMapperImpl) {
+ portMappings = [(conf.portMapper.httpPort.toString()) : conf.portMapper.httpsPort.toString()]
+ }
+ portResolver(PortResolverImpl) {
+ portMapper = portMapper
+ }
+
+ // SecurityEventListener
+ if (conf.useSecurityEventListener) {
+ securityEventListener(SecurityEventListener)
+ }
+
+ // Basic Auth
+ if (conf.useBasicAuth) {
+ configureBasicAuth.delegate = delegate
+ configureBasicAuth conf
+ }
+
+ // Switch User
+ if (conf.useSwitchUserFilter) {
+ switchUserProcessingFilter(SwitchUserFilter) {
+ userDetailsService = ref('userDetailsService')
+ switchUserUrl = conf.switchUser.switchUserUrl
+ exitUserUrl = conf.switchUser.exitUserUrl
+ targetUrl = conf.switchUser.targetUrl
+ authenticationSuccessHandler = ref('authenticationSuccessHandler')
+ authenticationFailureHandler = ref('authenticationFailureHandler')
+ }
+ }
+
+ // X509
+ if (conf.useX509) {
+ configureX509.delegate = delegate
+ configureX509 conf
+ }
+
+ // channel (http/https) security
+ if (useSecureChannel(conf)) {
+ configureChannelProcessingFilter.delegate = delegate
+ configureChannelProcessingFilter conf
+ }
+
+ // IP filter
+ if (conf.ipRestrictions) {
+ configureIpFilter.delegate = delegate
+ configureIpFilter conf
+ }
+
+ // user details cache
+ if (conf.cacheUsers) {
+ userCache(EhCacheBasedUserCache) {
+ cache = ref('securityUserCache')
+ }
+ securityUserCache(EhCacheFactoryBean) {
+ cacheManager = ref('cacheManager')
+ cacheName = 'userCache'
+ }
+ cacheManager(EhCacheManagerFactoryBean)
+ }
+ else {
+ userCache(NullUserCache)
+ }
+ }
+
+ def doWithDynamicMethods = { ctx ->
+
+ def conf = SpringSecurityUtils.securityConfig
+ if (!conf || !conf.active) {
+ return
+ }
+
+ for (controllerClass in application.controllerClasses) {
+ addControllerMethods controllerClass.metaClass, ctx
+ }
+
+ if (conf.securityConfigType == SecurityConfigType.Annotation) {
+ ctx.objectDefinitionSource.initialize conf.controllerAnnotations.staticRules,
+ ctx.grailsUrlMappingsHolder, application.controllerClasses
+ }
+ }
+
+ def doWithApplicationContext = { ctx ->
+
+ def conf = SpringSecurityUtils.securityConfig
+ if (!conf || !conf.active) {
+ return
+ }
+
+ // build filters here to give dependent plugins a chance to register some
+ def filterChain = ctx.springSecurityFilterChain
+ Map<String, List<Filter>> filterChainMap = [:]
+ List<String> filterNames = findFilterChainNames(conf)
+ def allConfiguredFilters = filterNames.collect { name -> ctx.getBean(name) }
+
+ if (conf.filterChain.chainMap) {
+ conf.filterChain.chainMap.each { key, value ->
+ def filters
+ if (value == 'JOINED_FILTERS') {
+ // special case to use either the filters defined by
+ // conf.filterChain.filterNames or the filters defined by config settings
+ filters = allConfiguredFilters
+ }
+ else {
+ filters = value.toString().split(',').collect { name -> ctx.getBean(name) }
+ }
+ filterChainMap[key] = filters
+ }
+ }
+ else {
+ filterChainMap[filterChain.matcher.universalMatchPattern] = allConfiguredFilters // /**
+ }
+
+ filterChain.filterChainMap = filterChainMap
+
+ // build voters list here to give dependent plugins a chance to register some
+ def voters = createBeanList(conf.voterNames, ctx)
+ ctx.accessDecisionManager.decisionVoters = voters
+
+ // build providers list here to give dependent plugins a chance to register some
+ def providers = createBeanList(conf.providerNames, ctx)
+ ctx.authenticationManager.providers = providers
+
+ // TODO uses constructor injection
+ // build handlers list here to give dependent plugins a chance to register some
+// def logoutHandlers = createBeanList(conf.logout.handlerNames, ctx)
+// ctx.logoutFilter.handlers = logoutHandlers
+ }
+
+ def onChange = { event ->
+
+ def conf = SpringSecurityUtils.securityConfig
+ if (!conf || !conf.active) {
+ return
+ }
+
+ if (event.source && application.isControllerClass(event.source)) {
+
+ if (conf.securityConfigType == SecurityConfigType.Annotation) {
+ event.ctx.objectDefinitionSource.initialize conf.controllerAnnotations.staticRules,
+ event.ctx.grailsUrlMappingsHolder, application.controllerClasses
+ }
+
+ addControllerMethods application.getControllerClass(event.source.name).metaClass, event.ctx
+ }
+ }
+
+ def onConfigChange = { event ->
+
+ def conf = SpringSecurityUtils.securityConfig
+ if (!conf || !conf.active) {
+ return
+ }
+
+ if (conf.securityConfigType == SecurityConfigType.Annotation) {
+ // might have changed controllerAnnotations.staticRules
+ event.ctx.objectDefinitionSource.initialize conf.controllerAnnotations.staticRules,
+ event.ctx.grailsUrlMappingsHolder, application.controllerClasses
+ }
+ }
+
+ private void addControllerMethods(MetaClass mc, ctx) {
+ mc.getPrincipal = { -> SCH.context?.authentication?.principal }
+ mc.isLoggedIn = { -> ctx.springSecurityService.isLoggedIn() }
+ }
+
+ private createRefList = { names -> names.collect { name -> ref(name) } }
+
+ private createBeanList(names, ctx) { names.collect { name -> ctx.getBean(name) } }
+
+ private boolean useSecureChannel(conf) {
+ conf.secureChannel.definitionSource || conf.secureChannel.config.secure || conf.secureChannel.config.insecure
+ }
+
+ private configureLogout = { conf ->
+
+ securityContextLogoutHandler(SecurityContextLogoutHandler)
+
+ def logoutHandlers = createRefList(conf.logout.handlerNames)
+
+ /** logoutFilter */
+ logoutFilter(LogoutFilterFactoryBean) {
+ handlers = logoutHandlers
+ logoutSuccessUrl = conf.logout.afterLogoutUrl // '/'
+ filterProcessesUrl = conf.logout.filterProcessesUrl // '/j_spring_security_logout'
+ }
+ }
+
+ private configureBasicAuth = { conf ->
+
+ authenticationEntryPoint(BasicAuthenticationEntryPoint) {
+ realmName = conf.basic.realmName // 'Grails Realm'
+ }
+
+ basicAuthenticationFilter(BasicAuthenticationFilter) {
+ authenticationManager = ref('authenticationManager')
+ authenticationEntryPoint = ref('authenticationEntryPoint')
+ }
+ }
+
+ private configureVoters = { conf ->
+
+ roleHierarchy(RoleHierarchyImpl) {
+ hierarchy = conf.roleHierarchy
+ }
+
+ roleVoter(RoleHierarchyVoter, ref('roleHierarchy'))
+
+ authenticatedVoter(AuthenticatedVoter) {
+ authenticationTrustResolver = ref('authenticationTrustResolver')
+ }
+
+ def voters = createRefList(conf.voterNames)
+
+ /** accessDecisionManager */
+ accessDecisionManager(AuthenticatedVetoableDecisionManager) {
+ allowIfAllAbstainDecisions = false
+ decisionVoters = voters
+ }
+ }
+
+ private configureAuthenticationManager = { conf ->
+ def providerRefs = createRefList(conf.providerNames)
+ /** authenticationManager */
+ authenticationManager(ProviderManager) {
+ providers = providerRefs
+ }
+ }
+
+ private configureFilterChain = { conf ->
+ springSecurityFilterChain(FilterChainProxy) {
+ filterChainMap = [:] // will be set in doWithApplicationContext
+ stripQueryStringFromUrls = conf.filterChain.stripQueryStringFromUrls // true
+ matcher = new AntUrlPathMatcher(true) // make into bean
+ }
+ }
+
+ private List<String> findFilterChainNames(conf) {
+
+ // if the user listed the names, use those
+ def filterNames = conf.filterChain.filterNames
+ if (!filterNames) {
+ def orderedNames = new TreeMap()
+
+ if (useSecureChannel(conf)) {
+ orderedNames[SecurityFilterPosition.CHANNEL_FILTER.order] = 'channelProcessingFilter'
+ }
+
+ // CONCURRENT_SESSION_FILTER
+
+ orderedNames[SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order] = 'securityContextPersistenceFilter'
+
+ orderedNames[SecurityFilterPosition.LOGOUT_FILTER.order] = 'logoutFilter'
+
+ if (conf.ipRestrictions) {
+ orderedNames[SecurityFilterPosition.LOGOUT_FILTER.order + 1] = 'ipAddressFilter'
+ }
+
+ if (conf.useX509) {
+ orderedNames[SecurityFilterPosition.X509_FILTER.order] = 'x509ProcessingFilter'
+ }
+
+ // PRE_AUTH_FILTER
+
+ // CAS_FILTER
+
+ orderedNames[SecurityFilterPosition.FORM_LOGIN_FILTER.order] = 'authenticationProcessingFilter'
+
+ // OPENID_FILTER
+
+ // facebook
+
+ // DIGEST_AUTH_FILTER
+
+ if (conf.useBasicAuth) {
+ orderedNames[SecurityFilterPosition.BASIC_AUTH_FILTER.order] = 'basicAuthenticationFilter'
+ }
+
+ // REQUEST_CACHE_FILTER
+
+ orderedNames[SecurityFilterPosition.SERVLET_API_SUPPORT_FILTER.order] = 'securityContextHolderAwareRequestFilter'
+
+ orderedNames[SecurityFilterPosition.REMEMBER_ME_FILTER.order] = 'rememberMeAuthenticationFilter'
+
+ orderedNames[SecurityFilterPosition.ANONYMOUS_FILTER.order] = 'anonymousAuthenticationFilter'
+
+ // SESSION_MANAGEMENT_FILTER
+
+ orderedNames[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order] = 'exceptionTranslationFilter'
+
+ orderedNames[SecurityFilterPosition.FILTER_SECURITY_INTERCEPTOR.order] = 'filterInvocationInterceptor'
+
+ if (conf.useSwitchUserFilter) {
+ orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order] = 'switchUserProcessingFilter'
+ }
+
+ // add in filters contributed by secondary plugins
+ orderedNames.putAll SpringSecurityUtils.ORDERED_FILTERS
+
+ filterNames = orderedNames.values() as List
+ }
+
+ filterNames
+ }
+
+ private configureChannelProcessingFilter = { conf ->
+
+ retryWithHttpEntryPoint(RetryWithHttpEntryPoint) {
+ portMapper = ref('portMapper')
+ portResolver = ref('portResolver')
+ }
+
+ retryWithHttpsEntryPoint(RetryWithHttpsEntryPoint) {
+ portMapper = ref('portMapper')
+ portResolver = ref('portResolver')
+ }
+
+ secureChannelProcessor(SecureChannelProcessor) {
+ entryPoint = retryWithHttpsEntryPoint
+ }
+
+ insecureChannelProcessor(InsecureChannelProcessor) {
+ entryPoint = retryWithHttpEntryPoint
+ }
+
+ channelDecisionManager(ChannelDecisionManagerImpl) {
+ channelProcessors = [insecureChannelProcessor, secureChannelProcessor]
+ }
+
+ // TODO see if these are still supported
+ String definitionSource
+ if (conf.secureChannel.definitionSource) {
+ // if the entire string is set in the config, use that
+ definitionSource = conf.secureChannel.definitionSource
+ }
+ else {
+ definitionSource = DEFINITION_SOURCE_PREFIX
+ for (pattern in conf.secureChannel.config.secure) {
+ definitionSource += "$pattern=REQUIRES_SECURE_CHANNEL\n"
+ }
+ for (pattern in conf.secureChannel.config.insecure) {
+ definitionSource += "$pattern=REQUIRES_INSECURE_CHANNEL\n"
+ }
+ }
+ channelProcessingFilter(ChannelProcessingFilter) {
+ channelDecisionManager = channelDecisionManager
+ filterInvocationDefinitionSource = definitionSource
+ }
+ }
+
+ private configureIpFilter = { conf ->
+ ipAddressFilter(IpAddressFilter) {
+ ipRestrictions = conf.ipRestrictions
+ }
+ }
+
+ private configureAuthenticationProcessingFilter = { conf ->
+
+ if (conf.useSessionFixation) {
+ sessionAuthenticationStrategy(SessionFixationProtectionStrategy) {
+ migrateSessionAttributes = conf.sessionFixation.migrate
+ alwaysCreateSession = conf.sessionFixation.alwaysCreate
+ }
+ }
+ else {
+ sessionAuthenticationStrategy(NullAuthenticatedSessionStrategy)
+ }
+
+ // TODO ajax aware
+ authenticationFailureHandler(ExceptionMappingAuthenticationFailureHandler) {
+ redirectStrategy = ref('redirectStrategy')
+ defaultFailureUrl = conf.failureHandler.defaultFailureUrl //'/login/authfail?login_error=1'
+ useForward = conf.failureHandler.useForward // false
+// ajaxAuthenticationFailureUrl = conf.failureHandler.ajaxAuthenticationFailureUrl // '/login/authfail?ajax=true'
+ exceptionMappings = conf.failureHandler.exceptionMappings // [:]
+ }
+
+ authenticationProcessingFilter(RequestHolderAuthenticationFilter) {
+ authenticationManager = ref('authenticationManager')
+ sessionAuthenticationStrategy = ref('sessionAuthenticationStrategy')
+ authenticationSuccessHandler = ref('authenticationSuccessHandler')
+ authenticationFailureHandler = ref('authenticationFailureHandler')
+ rememberMeServices = ref('rememberMeServices')
+ authenticationDetailsSource = ref('authenticationDetailsSource')
+ filterProcessesUrl = conf.apf.filterProcessesUrl // '/j_spring_security_check'
+ usernameParameter = conf.apf.usernameParameter // j_username
+ passwordParameter = conf.apf.passwordParameter // j_password
+ continueChainBeforeSuccessfulAuthentication = conf.apf.continueChainBeforeSuccessfulAuthentication // false
+ allowSessionCreation = conf.apf.allowSessionCreation // true
+ postOnly = conf.apf.postOnly // true
+ }
+
+ authenticationDetailsSource(WebAuthenticationDetailsSource) {
+ clazz = conf.authenticationDetails.authClass // WebAuthenticationDetails
+ }
+
+ requestCache(HttpSessionRequestCache) {
+ portResolver = ref('portResolver')
+ justUseSavedRequestOnGet = conf.requestCache.justUseSavedRequestOnGet // false
+ createSessionAllowed = conf.requestCache.createSessionAllowed // true
+ }
+
+ authenticationSuccessHandler(SavedRequestAwareAuthenticationSuccessHandler) {
+ requestCache = ref('requestCache')
+ defaultTargetUrl = conf.successHandler.defaultTargetUrl // '/'
+ alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefaultTargetUrl // false
+ targetUrlParameter = conf.successHandler.targetUrlParameter
+ useReferer = conf.successHandler.useReferer
+ redirectStrategy = ref('redirectStrategy')
+ }
+
+ redirectStrategy(DefaultRedirectStrategy) { // TODO ajax aware
+ contextRelative = conf.redirectStrategy.contextRelative // false
+ }
+ }
+
+ private configureX509 = { conf ->
+
+ x509ProcessingFilter(X509AuthenticationFilter) {
+ principalExtractor = ref('x509PrincipalExtractor')
+ authenticationManager = ref('authenticationManager')
+ authenticationDetailsSource = ref('authenticationDetailsSource')
+ continueFilterChainOnUnsuccessfulAuthentication = conf.x509.continueFilterChainOnUnsuccessfulAuthentication // true
+ checkForPrincipalChanges = conf.x509.checkForPrincipalChanges // false
+ invalidateSessionOnPrincipalChange = conf.x509.invalidateSessionOnPrincipalChange // true
+ }
+
+ x509PrincipalExtractor(SubjectDnX509PrincipalExtractor) {
+ messageSource = ref('messageSource')
+ subjectDnRegex = conf.x509.subjectDnRegex // CN=(.*?),
+ }
+
+ preAuthenticatedUserDetailsService(PreAuthenticatedGrantedAuthoritiesUserDetailsService)
+
+ userDetailsChecker(AccountStatusUserDetailsChecker)
+
+ x509AuthenticationProvider(PreAuthenticatedAuthenticationProvider) {
+ preAuthenticatedUserDetailsService = ref('preAuthenticatedUserDetailsService')
+ userDetailsChecker = ref('userDetailsChecker')
+ throwExceptionWhenTokenRejected = conf.x509.throwExceptionWhenTokenRejected // false
+ }
+
+ authenticationEntryPoint(Http403ForbiddenEntryPoint)
+ }
+
+ private findMappingLocation = { xml ->
+
+ // find the location to insert the filter-mapping; needs to be after the 'charEncodingFilter'
+ // which may not exist. should also be before the sitemesh filter.
+ // thanks to the JSecurity plugin for the logic.
+
+ def mappingLocation = xml.'filter-mapping'.find { it.'filter-name'.text() == 'charEncodingFilter' }
+ if (mappingLocation) {
+ return mappingLocation
+ }
+
+ // no 'charEncodingFilter'; try to put it before sitemesh
+ int i = 0
+ int siteMeshIndex = -1
+ xml.'filter-mapping'.each {
+ if (it.'filter-name'.text().equalsIgnoreCase('sitemesh')) {
+ siteMeshIndex = i
+ }
+ i++
+ }
+ if (siteMeshIndex > 0) {
+ return xml.'filter-mapping'[siteMeshIndex - 1]
+ }
+
+ if (siteMeshIndex == 0 || xml.'filter-mapping'.size() == 0) {
+ def filters = xml.'filter'
+ return filters[filters.size() - 1]
+ }
+
+ // neither filter found
+ def filters = xml.'filter'
+ return filters[filters.size() - 1]
+ }
+}
View
7 application.properties
@@ -0,0 +1,7 @@
+#Grails Metadata file
+#Sat Mar 20 12:28:40 EDT 2010
+app.grails.version=1.2.1
+app.name=spring-security-core
+plugins.code-coverage=1.1.8
+plugins.codenarc=0.5
+plugins.hibernate=1.2.1
View
90 build.xml
@@ -0,0 +1,90 @@
+<project name='spring-security-core' default='package'>
+
+ <!-- Properties -->
+
+ <property file='application.properties' />
+
+ <property environment='env'/>
+
+ <!-- optional properties file for developer overrides -->
+ <property file='build.properties' />
+
+ <property name='grails.home' value='${env.GRAILS_HOME}' />
+
+ <condition property='grails' value='grails.bat'>
+ <os family='windows'/>
+ </condition>
+ <property name='grails' value='grails' />
+
+ <condition property='grails-debug' value='grails-debug.bat'>
+ <os family='windows'/>
+ </condition>
+ <property name='grails-debug' value='grails-debug' />
+
+ <!-- Macrodefs -->
+
+ <macrodef name='grails'>
+ <attribute name='action' />
+ <attribute name='environment' default='dev' />
+ <element name='args' optional='true' />
+ <sequential>
+ <exec executable='${grails}' failonerror='true'>
+ <args />
+ <arg value='@{environment}'/>
+ <arg value='@{action}'/>
+ </exec>
+ </sequential>
+ </macrodef>
+
+ <macrodef name='grails-debug'>
+ <attribute name='action' />
+ <attribute name='environment' default='dev' />
+ <element name='args' optional='true' />
+ <sequential>
+ <exec executable='${grails-debug}' failonerror='true'>
+ <args />
+ <arg value='@{environment}'/>
+ <arg value='@{action}'/>
+ </exec>
+ </sequential>
+ </macrodef>
+
+ <!-- Targets -->
+
+ <target name='init'>
+ <mkdir dir='grails-app/conf/spring'/>
+ </target>
+
+ <target name='clean' description='Cleans a Grails application' depends='init'>
+ <grails action='clean' />
+ <delete><fileset dir='.' includes='*.log*' /></delete>
+ </target>
+
+ <target name='doc' description='Generates docs' depends='init'>
+ <grails action='doc' />
+ </target>
+
+ <target name='test' description='Run unit tests' depends='clean'>
+ <grails action='test-app' environment='test' />
+ </target>
+
+ <target name='package' description='Package the plugin' depends='test'>
+
+ <grails action='package-plugin' />
+
+ <!-- clean up -->
+ <delete dir='grails-app/conf/hibernate'/>
+ <delete dir='grails-app/conf/spring'/>
+ <delete dir='grails-app/controllers'/>
+ <delete dir='grails-app/i18n'/>
+ <delete dir='grails-app/utils'/>
+ <delete dir='grails-app/views'/>
+ <delete dir='web-app/css'/>
+ <delete dir='web-app/images'/>
+ <delete dir='web-app/js'/>
+ <delete dir='web-app/plugins'/>
+ <delete dir='web-app/META-INF'/>
+ <delete dir='web-app/WEB-INF'/>
+ </target>
+
+</project>
View
49 grails-app/conf/BuildConfig.groovy
@@ -0,0 +1,49 @@
+grails.project.class.dir = 'target/classes'
+grails.project.test.class.dir = 'target/test-classes'
+grails.project.test.reports.dir = 'target/test-reports'
+
+grails.project.dependency.resolution = {
+
+ inherits('global')
+
+ log 'warn'
+
+ repositories {
+ grailsPlugins()
+ grailsHome()
+
+ ebr() // SpringSource http://www.springsource.com/repository
+ }
+
+ dependencies {
+ runtime('org.springframework.security:org.springframework.security.core:3.0.2.RELEASE') {
+ excludes 'com.springsource.org.aopalliance',
+ 'com.springsource.org.apache.commons.logging',
+ 'org.springframework.beans',
+ 'org.springframework.context',
+ 'org.springframework.core'
+ }
+
+ runtime('org.springframework.security:org.springframework.security.web:3.0.2.RELEASE') {
+ excludes 'com.springsource.javax.servlet',
+ 'com.springsource.org.aopalliance',
+ 'com.springsource.org.apache.commons.logging',
+ 'org.springframework.aop',
+ 'org.springframework.beans',
+ 'org.springframework.context',
+ 'org.springframework.core',
+ 'org.springframework.web'
+ }
+ }
+}
+
+coverage {
+ enabledByDefault = true
+ sourceInclusions = ['grails-app/conf']
+ exclusionListOverride = [
+ '*GrailsPlugin*',
+ 'DataSource*',
+ '*Config*',
+ 'test/**'
+ ]
+}
View
25 grails-app/conf/Config.groovy
@@ -0,0 +1,25 @@
+// for testing only, not included in plugin zip
+grails {
+ plugins {
+ springsecurity {
+ userLookup {
+ userDomainClassName = 'test.TestUser'
+ usernamePropertyName = 'loginName'
+ enabledPropertyName = 'enabld'
+ passwordPropertyName = 'passwrrd'
+ authoritiesPropertyName = 'roles'
+ }
+
+ requestMap {
+ className = 'test.TestRequestmap'
+ urlField = 'urlPattern'
+ configAttributeField = 'rolePattern'
+ }
+
+ authority {
+ className = 'test.TestRole'
+ nameField = 'auth'
+ }
+ }
+ }
+}
View
14 grails-app/conf/DataSource.groovy
@@ -0,0 +1,14 @@
+dataSource {
+ pooled = true
+ driverClassName = 'org.hsqldb.jdbcDriver'
+ username = 'sa'
+ password = ''
+ dbCreate = 'update'
+ url = 'jdbc:hsqldb:mem:testDb'
+}
+
+hibernate {
+ cache.use_second_level_cache = false
+ cache.use_query_cache = false
+ cache.provider_class = 'org.hibernate.cache.EhCacheProvider'
+}
View
197 grails-app/conf/DefaultSecurityConfig.groovy
@@ -0,0 +1,197 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.springframework.security.authentication.AnonymousAuthenticationToken
+import org.springframework.security.authentication.RememberMeAuthenticationToken
+import org.springframework.security.web.authentication.WebAuthenticationDetails
+import org.codehaus.groovy.grails.plugins.springsecurity.SecurityConfigType
+
+security {
+
+ /** misc properties */
+
+ active = true
+
+ ajaxHeader = 'X-Requested-With'
+
+ // 'strict' mode where an explicit grant is required to access any resource;
+ // if true make sure to allow IS_AUTHENTICATED_ANONYMOUSLY
+ // for /, /js/**, /css/**, /images/**, /login/**, /logout/**, etc.
+ rejectIfNoRule = false
+
+ /** error messages */
+ errors.login.disabled = "Sorry, your account is disabled."
+ errors.login.fail = "Sorry, we were not able to find a user with that username and password."
+ errors.login.no_granted_roles = errors.login.fail
+
+ // hierarchical roles
+ roleHierarchy = ''
+
+ // ip restriction filter
+ ipRestrictions = [:]
+
+ // voters
+ voterNames = ['authenticatedVoter', 'roleVoter']
+
+ // providers
+ providerNames = ['daoAuthenticationProvider',
+ 'anonymousAuthenticationProvider',
+ 'rememberMeAuthenticationProvider']
+
+ // HttpSessionEventPublisher
+ useHttpSessionEventPublisher = false
+
+ // SecurityEventListener
+ useSecurityEventListener = false
+
+ // user caching
+ cacheUsers = false
+
+ // user and role class properties
+ userLookup.userDomainClassName = 'Person'
+ userLookup.usernamePropertyName = 'username'
+ userLookup.enabledPropertyName = 'enabled'
+ userLookup.passwordPropertyName = 'passwd'
+ userLookup.authoritiesPropertyName = 'authorities'
+ authority.className = 'Authority'
+ authority.nameField = 'authority'
+
+ /** authenticationProcessingFilter */
+ apf.filterProcessesUrl = '/j_spring_security_check'
+ apf.usernameParameter = 'j_username'
+ apf.passwordParameter = 'j_password'
+ apf.continueChainBeforeSuccessfulAuthentication = false
+ apf.allowSessionCreation = true
+ apf.postOnly = true
+
+ // failureHandler
+ failureHandler.defaultFailureUrl = '/login/authfail?login_error=1'
+ failureHandler.ajaxAuthenticationFailureUrl = '/login/authfail?ajax=true'
+ failureHandler.exceptionMappings = [:]
+ failureHandler.useForward = false
+
+ // successHandler
+ successHandler.defaultTargetUrl = '/'
+ successHandler.alwaysUseDefaultTargetUrl = false
+ successHandler.targetUrlParameter = 'spring-security-redirect'
+ successHandler.useReferer = false
+
+ // requestCache
+ requestCache.justUseSavedRequestOnGet = false
+ requestCache.createSessionAllowed = true
+
+ // redirectStrategy
+ redirectStrategy.contextRelative = false
+
+ // authenticationDetails
+ authenticationDetails.authClass = WebAuthenticationDetails
+
+ // session fixation
+ useSessionFixation = false
+ sessionFixation.migrate = true
+ sessionFixation.alwaysCreateSession = true
+
+ /** daoAuthenticationProvider **/
+ dao.reflectionSaltSourceUserProperty = null // if null, don't use salt source
+
+ /** anonymousProcessingFilter */
+ anon.key = 'foo'
+ anon.userAttribute = 'anonymousUser,ROLE_ANONYMOUS'
+
+ /** authenticationEntryPoint */
+ auth.loginFormUrl = '/login/auth'
+ auth.forceHttps = 'false'
+ auth.ajaxLoginFormUrl = '/login/authAjax'
+ auth.useForward = false
+
+ /** logoutFilter */
+ logout.afterLogoutUrl = '/'
+ logout.filterProcessesUrl = '/j_spring_security_logout'
+ logout.handlerNames = ['rememberMeServices', 'securityContextLogoutHandler']
+
+ /**
+ * accessDeniedHandler
+ * set errorPage to null to send Error 403 instead of showing error page
+ */
+ adh.errorPage = '/login/denied'
+ adh.ajaxErrorPage = '/login/deniedAjax'
+
+ /** passwordEncoder */
+ // see http://java.sun.com/j2se/1.5.0/docs/guide/security/CryptoSpec.html#AppA
+ password.algorithm = 'SHA-256'
+ password.encodeHashAsBase64 = false
+
+ /** rememberMeServices */
+ rememberMe.cookieName = 'grails_remember_me'
+ rememberMe.alwaysRemember = false
+ rememberMe.tokenValiditySeconds = 1209600 //14 days
+ rememberMe.parameter = '_spring_security_remember_me'
+ rememberMe.key = 'grailsRocks'
+
+ /** URL <-> Role mapping */
+
+ // default to annotation mode
+ securityConfigType = SecurityConfigType.Annotation
+ // whether to use SpEL expressions
+ securityConfig.useExpressions = false
+
+ // use Requestmap domain class to store rules in the database
+ // change securityConfigType to SecurityConfigType.Requestmap
+ requestMap.className = 'Requestmap'
+ requestMap.urlField = 'url'
+ requestMap.configAttributeField = 'configAttribute'
+
+ // use annotations from Controllers to define security rules
+ // change securityConfigType to SecurityConfigType.Annotation
+ controllerAnnotations.matcher = 'ant' // or 'regex'
+ controllerAnnotations.matchesLowercase = true
+ controllerAnnotations.staticRules = [:]
+
+ // use a Map of URL -> roles to define security rules
+ // change securityConfigType to SecurityConfigType.InterceptUrlMap
+ interceptUrlMap = [:]
+
+ /** basic auth */
+ useBasicAuth = false
+ basic.realmName = 'Grails Realm'
+
+ /** use switchUserProcessingFilter */
+ useSwitchUserFilter = false
+ switchUser.switchUserUrl = '/j_spring_security_switch_user'
+ switchUser.exitUserUrl = '/j_spring_security_exit_user'
+ switchUser.targetUrl = '/'
+
+ /** filterChainProxy */
+ filterChain.stripQueryStringFromUrls = true
+
+ // port mappings
+ portMapper.httpPort = 8080
+ portMapper.httpsPort = 8443
+
+ // secure channel filter (http/https)
+ secureChannel.definitionSource = ''
+ secureChannel.config = [secure: [], insecure: []]
+
+ // X509
+ useX509 = false
+ x509.continueFilterChainOnUnsuccessfulAuthentication = true
+ x509.subjectDnRegex = 'CN=(.*?),'
+ x509.checkForPrincipalChanges = false
+ x509.invalidateSessionOnPrincipalChange = true
+ x509.throwExceptionWhenTokenRejected = false
+
+ // authenticationTrustResolver
+ atr.anonymousClass = AnonymousAuthenticationToken
+ atr.rememberMeClass = RememberMeAuthenticationToken
+}
View
15 grails-app/domain/test/TestRequestmap.groovy
@@ -0,0 +1,15 @@
+package test
+
+/**
+ * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
+ */
+class TestRequestmap {
+
+ String urlPattern
+ String rolePattern
+
+ static constraints = {
+ urlPattern blank: false, unique: true
+ rolePattern blank: false
+ }
+}
View
14 grails-app/domain/test/TestRole.groovy
@@ -0,0 +1,14 @@
+package test
+
+/**
+ * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
+ */
+class TestRole {
+
+ String auth
+ String description
+
+ static constraints = {
+ auth blank: false, unique: true
+ }
+}
View
24 grails-app/domain/test/TestUser.groovy
@@ -0,0 +1,24 @@
+package test
+
+/**
+ * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
+ */
+class TestUser {
+
+ static transients = ['pass', 'roleNames']
+
+ String loginName
+ String passwrrd
+ boolean enabld
+
+ String pass = '[secret]'
+
+ Set<TestRole> getRoles() { TestUserRole.findAllByUser(this).collect { it.role } }
+
+ Collection<String> getRoleNames() { roles*.auth }
+
+ static constraints = {
+ loginName blank: false, unique: true
+ passwrrd blank: false
+ }
+}
View
47 grails-app/domain/test/TestUserRole.groovy
@@ -0,0 +1,47 @@
+package test
+
+import org.apache.commons.lang.builder.HashCodeBuilder
+
+class TestUserRole implements Serializable {
+
+ TestUser user
+ TestRole role
+
+ boolean equals(other) {
+ if (!(other instanceof TestUserRole)) {
+ return false
+ }
+
+ other.user.id == user.id && other.role.id == role.id
+ }
+
+ int hashCode() {
+ def builder = new HashCodeBuilder()
+ if (user) builder.append(user.id)
+ if (role) builder.append(role.id)
+ builder.toHashCode()
+ }
+
+ static TestUserRole get(long userId, long roleId) {
+ find 'from TestUserRole where user.id=:userId and role.id=:roleId',
+ [userId: userId, roleId: roleId]
+ }
+
+ static TestUserRole create(TestUser user, TestRole role, boolean flush = false) {
+ new TestUserRole(user: user, role: role).save(flush: flush, insert: true)
+ }
+
+ static boolean remove(TestUser user, TestRole role, boolean flush = false) {
+ TestUserRole instance = TestUserRole.findByUserAndRole(user, role)
+ instance ? instance.delete(flush: flush) : false
+ }
+
+ static void removeAll(TestUser user) {
+ executeUpdate "DELETE FROM TestUserRole WHERE user=:user", [user: user]
+ }
+
+ static mapping = {
+ id composite: ['role', 'user']
+ version false
+ }
+}
View
179 grails-app/services/grails/plugins/springsecurity/SpringSecurityService.groovy
@@ -0,0 +1,179 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package grails.plugins.springsecurity
+
+import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
+import org.codehaus.groovy.grails.plugins.springsecurity.SecurityConfigType
+import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
+
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder as SCH
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource
+
+/**
+ * Utility methods.
+ *
+ * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
+ */
+class SpringSecurityService {
+
+ boolean transactional = false
+
+ /** dependency injection for authenticationTrustResolver */
+ def authenticationTrustResolver
+
+ /** dependency injection for the password encoder */
+ def passwordEncoder
+
+ /** dependency injection for {@link FilterInvocationSecurityMetadataSource} */
+ def objectDefinitionSource
+
+ /** dependency injection for userDetailsService */
+ def userDetailsService
+
+ /** dependency injection for userCache */
+ def userCache
+
+ /**
+ * Get the currently logged in user's principal.
+ * @return the principal or <code>null</code> if not logged in
+ */
+ def getPrincipal() { getAuthentication()?.principal }
+
+ /**
+ * Get the currently logged in user's <code>Authentication</code>.
+ * @return the authentication or <code>null</code> if not logged in
+ */
+ Authentication getAuthentication() { SCH.context?.authentication }
+
+ /**
+ * Encode the password using the configured PasswordEncoder.
+ */
+ String encodePassword(String passwd, salt = null) {
+ passwordEncoder.encodePassword passwd, salt
+ }
+
+ /**
+ * Quick check to see if the current user is logged in.
+ * @return <code>true</code> if the authenticated and not anonymous
+ */
+ boolean isLoggedIn() {
+ def authentication = SCH.context.authentication
+ authentication && !authenticationTrustResolver.isAnonymous(authentication)
+ }
+
+ /**
+ * Call when editing, creating, or deleting a Requestmap to flush the cached
+ * configuration and rebuild using the most recent data.
+ */
+ void clearCachedRequestmaps() {
+ objectDefinitionSource?.reset()
+ }
+
+ /**
+ * Delete a role, and if Requestmap class is used to store roles, remove the role
+ * from all Requestmap definitions. If a Requestmap's config attribute is this role,
+ * it will be deleted.
+ *
+ * @param role the role to delete
+ */
+ void deleteRole(role) {
+ def conf = SpringSecurityUtils.securityConfig
+ String configAttributeName = conf.requestMap.configAttributeField
+ String authorityFieldName = conf.authority.nameField
+
+ role.getClass().withTransaction { status ->
+ if (conf.securityConfigType == SecurityConfigType.Requestmap) {
+ String roleName = role."$authorityFieldName"
+ def requestmaps = findRequestmapsByRole(roleName, role.getClass(), conf)
+ for (rm in requestmaps) {
+ String configAttribute = rm."$configAttributeName"
+ if (configAttribute.equals(roleName)) {
+ rm.delete()
+ }
+ else {
+ List parts = configAttribute.split(',')
+ parts.remove roleName
+ rm."$configAttributeName" = parts.join(',')
+ }
+ }
+ clearCachedRequestmaps()
+ }
+
+ role.delete(flush: true)
+ }
+ }
+
+ /**
+ * Update a role, and if Requestmap class is used to store roles, replace the new role
+ * name in all Requestmap definitions that use it if the name was changed.
+ *
+ * @param role the role to update
+ * @param newProperties the new role attributes ('params' from the calling controller)
+ */
+ boolean updateRole(role, newProperties) {
+
+ def conf = SpringSecurityUtils.securityConfig
+ String configAttributeName = conf.requestMap.configAttributeField
+ String authorityFieldName = conf.authority.nameField
+
+ String oldRoleName = role."$authorityFieldName"
+ role.properties = newProperties
+
+ role.save()
+ if (role.hasErrors()) {
+ return false
+ }
+
+ if (conf.securityConfigType == SecurityConfigType.Requestmap) {
+ String newRoleName = role."$authorityFieldName"
+ if (newRoleName != oldRoleName) {
+ def requestmaps = findRequestmapsByRole(oldRoleName, role.getClass(), conf)
+ for (rm in requestmaps) {
+ rm."$configAttributeName" = rm."$configAttributeName".replace(oldRoleName, newRoleName)
+ }
+ }
+ clearCachedRequestmaps()
+ }
+
+ true
+ }
+
+ /**
+ * Rebuild an Authentication for the given username and register it in the security context.
+ * Typically used after updating a user's authorities or other auth-cached info.
+ *
+ * Also removes the user from the user cache to force a refresh at next login.
+ *
+ * @param username the user's login name
+ * @param password optional
+ */
+ void reauthenticate(String username, String password = null) {
+ def userDetails = userDetailsService.loadUserByUsername(username)
+ SCH.context.authentication = new UsernamePasswordAuthenticationToken(
+ userDetails, password ?: userDetails.password, userDetails.authorities)
+ userCache.removeUserFromCache username
+ }
+
+ private List findRequestmapsByRole(String roleName, domainClass, conf) {
+ String requestmapClassName = conf.requestMap.className
+ String configAttributeName = conf.requestMap.configAttributeField
+ return domainClass.executeQuery(
+ "SELECT rm FROM $requestmapClassName rm " +
+ "WHERE rm.$configAttributeName LIKE :roleName",
+ [roleName: "%$roleName%"])
+ }
+}
View
113 grails-app/taglib/grails/plugins/springsecurity/SecurityTagLib.groovy
@@ -0,0 +1,113 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package grails.plugins.springsecurity
+
+import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
+
+class SecurityTagLib {
+
+ static namespace = 'sec'
+
+ def springSecurityService
+
+ def ifAllGranted = { attrs, body ->
+
+ assertAttribute 'roles', attrs, 'ifAllGranted'
+
+ if (SpringSecurityUtils.ifAllGranted(attrs.roles)) {
+ out << body()
+ }
+ }
+
+ def ifNotGranted = { attrs, body ->
+
+ assertAttribute 'roles', attrs, 'ifNotGranted'
+
+ if (SpringSecurityUtils.ifNotGranted(attrs.roles)) {
+ out << body()
+ }
+ }
+
+ def ifAnyGranted = { attrs, body ->
+
+ assertAttribute 'roles', attrs, 'ifAnyGranted'
+
+ if (SpringSecurityUtils.ifAnyGranted(attrs.roles)) {
+ out << body()
+ }
+ }
+
+ // TODO rename
+ // TODO support 'var' and 'scope' and set the result instead of writing it
+ def loggedInUserInfo = { attrs, body ->
+
+ assertAttribute 'field', attrs, 'loggedInUserInfo'
+
+ def source
+ if (springSecurityService.isLoggedIn()) {
+ source = determineSource()
+ for (pathElement in attrs.field.split('\\.')) {
+ source = source."$pathElement"
+ if (source == null) {
+ break
+ }
+ }
+ }
+
+ if (source) {
+ out << source
+ }
+ else {
+ out << body()
+ }
+ }
+
+ def username = { attrs ->
+ if (springSecurityService.isLoggedIn()) {
+ out << springSecurityService.principal.username
+ }
+ }
+
+ def ifLoggedIn = { attrs, body ->
+ if (springSecurityService.isLoggedIn()) {
+ out << body()
+ }
+ }
+
+ def ifNotLoggedIn = { attrs, body ->
+ if (!springSecurityService.isLoggedIn()) {
+ out << body()
+ }
+ }
+
+ private void assertAttribute(String name, attrs, String tag) {
+ if (!attrs[name]) {
+ throwTagError "Tag [$tag] is missing required attribute [$name]"
+ }
+ }
+
+ // TODO not supporting getDomainClass?
+ private determineSource() {
+ def principal = springSecurityService.principal
+
+ // check to see if it's a GrailsUser/GrailsUserImpl/subclass,
+ // or otherwise has a 'domainClass' property
+ if (principal.metaClass.respondsTo(principal, 'getDomainClass')) {
+ return principal.domainClass
+ }
+
+ principal
+ }
+}
View
BIN lib/easymock.jar
Binary file not shown.
View
161 scripts/S2Quickstart.groovy
@@ -0,0 +1,161 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import groovy.text.SimpleTemplateEngine
+
+import grails.util.GrailsNameUtils
+
+USAGE = """
+Usage: grails s2-quickstart DOMAIN_CLASS_PACKAGE USER_CLASS_NAME ROLE_CLASS_NAME [REQUESTMAP_CLASS_NAME]
+
+Creates a user and role class (and optionally a requestmap class) in the specified package
+
+Example: grails s2-quickstart com.yourapp User Role
+Example: grails s2-quickstart com.yourapp Person Authority Requestmap
+"""
+
+includeTargets << grailsScript('_GrailsBootstrap')
+
+packageName = ''
+userClassName = ''
+roleClassName = ''
+requestmapClassName = ''
+
+templateEngine = new SimpleTemplateEngine()
+
+// TODO ask
+overwrite = true
+
+templateAttributes = [:]
+
+templateDir = "$springSecurityCorePluginDir/src/templates"
+appDir = "$basedir/grails-app"
+
+target('default': 'Creates artifacts for the Spring Security plugin') {
+ depends(checkVersion, configureProxy, packageApp, classpath)
+
+ configure()
+ createDomains()
+ copyControllersAndViews()
+ summarize()
+}
+
+private void configure() {
+
+ def argValues = parseArgs()
+ if (argValues.size() == 4) {
+ (packageName, userClassName, roleClassName, requestmapClassName) = argValues
+ }
+ else {
+ (packageName, userClassName, roleClassName) = argValues
+ }
+
+ templateAttributes = [packageName: packageName,
+ userClassName: userClassName,
+ userClassProperty: GrailsNameUtils.getPropertyName(userClassName),
+ roleClassName: roleClassName,
+ roleClassProperty: GrailsNameUtils.getPropertyName(roleClassName),
+ requestmapClassName: requestmapClassName]
+}
+
+private void createDomains() {
+
+ String dir = packageToDir(packageName)
+ generateFile "$templateDir/Person.groovy.template", "$appDir/domain/${dir}${userClassName}.groovy"
+ generateFile "$templateDir/Authority.groovy.template", "$appDir/domain/${dir}${roleClassName}.groovy"
+ generateFile "$templateDir/PersonAuthority.groovy.template", "$appDir/domain/${dir}${userClassName}${roleClassName}.groovy"
+ if (requestmapClassName) {
+ generateFile "$templateDir/Requestmap.groovy.template", "$appDir/domain/${dir}${requestmapClassName}.groovy"
+ }
+}
+
+private String packageToDir(pkg) {
+ String dir = ''
+ if (pkg) {
+ dir = pkg.replaceAll('\\.', '/') + '/'
+ }
+
+ return dir
+}
+
+private void copyControllersAndViews() {
+ ant.mkdir dir: "$appDir/views/login"
+ copyFile "$templateDir/auth.gsp.template", "$appDir/views/login/auth.gsp"
+ copyFile "$templateDir/denied.gsp.template", "$appDir/views/login/denied.gsp"
+ copyFile "$templateDir/LoginController.groovy.template", "$appDir/controllers/LoginController.groovy"
+ copyFile "$templateDir/LogoutController.groovy.template", "$appDir/controllers/LogoutController.groovy"
+}
+
+private void summarize() {
+ String requestmapFullName = packageName
+ if (packageName) {
+ requestmapClassName
+ }
+
+ println """
+Created domain classes, controllers, and GSPs.
+
+Next you'll need to edit Config.groovy and add the following:
+
+ grails.plugins.springsecurity.userLookup.userDomainClassName = '${packageName}.$userClassName'
+"""
+
+ if (requestmapClassName) {
+ println " grails.plugins.springsecurity.requestMap.className = '${packageName}.$requestmapClassName'"
+ }
+}
+
+generateFile = { String templatePath, String outputPath ->
+
+ File templateFile = new File(templatePath)
+ if (!templateFile.exists()) {
+ println "$templatePath doesn't exist"
+ return
+ }
+
+ File outFile = new File(outputPath)
+ if (outFile.exists() && !overwrite) {
+ println "file *not* generated: $outFile.absolutePath"
+ return
+ }
+
+ // in case it's in a package, create dirs
+ ant.mkdir dir: outFile.parentFile
+
+ outFile.withWriter { writer ->
+ templateEngine.createTemplate(templateFile.text).make(templateAttributes).writeTo(writer)
+ }
+
+ println "generated $outFile.absolutePath"
+}
+
+copyFile = { String from, String to ->
+ ant.copy(file: from, tofile: to, overwrite: overwrite)
+}
+
+private parseArgs() {
+ args = args ? args.split('\n') : []
+ switch (args.size()) {
+ case 3:
+ println "Creating User class ${args[1]} and Role class ${args[2]} in package ${args[0]}"
+ return args
+ case 4:
+ println "Creating User class ${args[1]}, Role class ${args[2]}, and Requestmap class ${args[3]} in package ${args[0]}"
+ return args
+ default:
+ println "Usage:\n$USAGE"
+ System.exit(1)
+ break
+ }
+}
View
9 scripts/_Install.groovy
@@ -0,0 +1,9 @@
+println '''
+*******************************************************
+* You've installed the Spring Security Core plugin. *
+* *
+* Next run the "s2-quickstart" script to initialize *
+* Spring Security and create your domain classes. *
+* *
+*******************************************************
+'''
View
0 scripts/_Uninstall.groovy
No changes.
View
0 scripts/_Upgrade.groovy
No changes.
View
118 src/groovy/org/codehaus/groovy/grails/plugins/springsecurity/GormUserDetailsService.groovy
@@ -0,0 +1,118 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.codehaus.groovy.grails.plugins.springsecurity
+
+import org.apache.log4j.Logger
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.authority.GrantedAuthorityImpl
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+import org.springframework.transaction.PlatformTransactionManager
+import org.springframework.transaction.TransactionStatus
+import org.springframework.transaction.support.TransactionCallback
+import org.springframework.transaction.support.TransactionTemplate
+
+/**
+ * Default implementation of <code>GrailsUserDetailsService</code> that uses domain classes to load users and roles.
+ *
+ * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
+ */
+class GormUserDetailsService implements GrailsUserDetailsService {
+
+ private Logger _log = Logger.getLogger(getClass())
+
+ /**
+ * Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least one role, so
+ * we give a user with no granted roles this one which gets past that restriction but
+ * doesn't grant anything.
+ */
+ static final List NO_ROLES = [new GrantedAuthorityImpl('ROLE_NO_ROLES')]
+
+ /** Dependency injection for Hibernate session factory. */
+ def sessionFactory
+
+ /** Dependency injection for Hibernate transaction manager. */
+ PlatformTransactionManager transactionManager
+
+ /**
+ * {@inheritDoc}
+ * @see org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserDetailsService#loadUserByUsername(
+ * java.lang.String, boolean)
+ */
+ UserDetails loadUserByUsername(String username, boolean loadRoles) throws UsernameNotFoundException {
+ def callback = { TransactionStatus status -> loadUserFromSession(username, sessionFactory.currentSession, loadRoles) }
+ new TransactionTemplate(transactionManager).execute(callback as TransactionCallback)
+ }
+
+ /**
+ * {@inheritDoc}
+ * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
+ */
+ UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ loadUserByUsername username, true
+ }
+
+ protected UserDetails loadUserFromSession(String username, session, boolean loadRoles) {
+ def user = loadUser(username, session)
+ Collection<GrantedAuthority> authorities = loadAuthorities(user, username, loadRoles)
+ createUserDetails user, authorities
+ }
+
+ protected loadUser(String username, session) {
+ String userDomainClassName = ReflectionUtils.getConfigProperty('userLookup', 'userDomainClassName')
+ String usernamePropertyName = ReflectionUtils.getConfigProperty('userLookup', 'usernamePropertyName')
+
+ List<?> users = session.createQuery(
+ "FROM $userDomainClassName WHERE $usernamePropertyName = :username")
+ .setString('username', username)
+ .list()
+
+ if (!users) {
+ log.warn "User not found: $username"
+ throw new UsernameNotFoundException('User not found', username)
+ }
+
+ users[0]
+ }
+
+ protected Collection<GrantedAuthority> loadAuthorities(user, String username, boolean loadRoles) {
+ if (!loadRoles) {
+ return []
+ }
+
+ String authoritiesPropertyName = ReflectionUtils.getConfigProperty('userLookup', 'authoritiesPropertyName')
+ String authorityPropertyName = ReflectionUtils.getConfigProperty('authority', 'nameField')
+
+ Collection<?> userAuthorities = user."$authoritiesPropertyName"
+ def authorities = userAuthorities.collect { new GrantedAuthorityImpl(it."$authorityPropertyName") }
+ authorities ?: NO_ROLES
+ }
+
+ protected UserDetails createUserDetails(user, Collection<GrantedAuthority> authorities) {
+
+ String usernamePropertyName = ReflectionUtils.getConfigProperty('userLookup', 'usernamePropertyName')
+ String enabledPropertyName = ReflectionUtils.getConfigProperty('userLookup', 'enabledPropertyName')
+ String passwordPropertyName = ReflectionUtils.getConfigProperty('userLookup', 'passwordPropertyName')
+
+ String username = user."$usernamePropertyName"
+ String password = user."$passwordPropertyName"
+ boolean enabled = user."$enabledPropertyName"
+
+ new User(username, password, enabled, enabled, enabled, enabled, authorities)
+ }
+
+ protected Logger getLog() { _log }
+}
View
88 src/groovy/org/codehaus/groovy/grails/plugins/springsecurity/ReflectionUtils.groovy
@@ -0,0 +1,88 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.codehaus.groovy.grails.plugins.springsecurity
+
+import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
+import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH
+
+/**
+ * Helper methods in Groovy.
+ *
+ * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
+ */
+class ReflectionUtils {
+
+ private ReflectionUtils() {
+ // static only
+ }
+
+ static getConfigProperty(String name) {
+ SpringSecurityUtils.securityConfig."$name"
+ }
+
+ static getConfigProperty(String... names) {
+ def current = SpringSecurityUtils.securityConfig
+ for (String name in names) {
+ current = current."$name"
+ }
+ current
+ }
+
+ static void setConfigProperty(String name, p) {
+ SpringSecurityUtils.securityConfig."$name" = p
+ }
+
+ static String getRoleAuthority(role) {
+ lookupPropertyValue role, 'authority', 'nameField'
+ }
+
+ static String getRequestmapUrl(requestmap) {
+ lookupPropertyValue requestmap, 'requestMap', 'urlField'
+ }
+
+ static String getRequestmapConfigAttribute(requestmap) {
+ lookupPropertyValue requestmap, 'requestMap', 'configAttributeField'
+ }
+
+ static List loadAllRequestmaps() {
+ String requestMapClassName = SpringSecurityUtils.securityConfig.requestMap.className
+ AH.application.getClassForName(requestMapClassName).list()
+ }
+
+ static List asList(authorities) {
+ authorities ? authorities as List : []
+ }
+
+ static ConfigObject getSecurityConfig() { CH.config.grails.plugins.springsecurity }
+ static void setSecurityConfig(ConfigObject c) { CH.config.grails.plugins.springsecurity = c }
+
+ static Map<String, List<String>> splitMap(Map<String, Object> m) {
+ Map<String, List<String>> split = [:]
+ m.each { String key, value ->
+ if (value instanceof List<?> || value.getClass().array) {
+ split[key] = value*.toString()
+ }
+ else { // String/GString
+ split[key] = [value.toString()]
+ }
+ }
+ split
+ }
+
+ private static lookupPropertyValue(o, String... confPropertyNames) {
+ String fieldName = getConfigProperty(confPropertyNames)
+ o."$fieldName"
+ }
+}
View
238 ...org/codehaus/groovy/grails/plugins/springsecurity/AbstractFilterInvocationDefinition.java
@@ -0,0 +1,238 @@
+/* Copyright 2006-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and