Skip to content

Commit

Permalink
Merge branch 'release/1.5.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarosanchez committed Aug 19, 2015
2 parents d681aae + af5b608 commit 0933b01
Show file tree
Hide file tree
Showing 31 changed files with 648 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ grails-app/i18n

#Generated by test script
foo/
/out
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
language: groovy

sudo: false

cache:
directories:
- $HOME/.m2
- $HOME/.gradle
- $HOME/.grails

jdk:
- oraclejdk8

Expand All @@ -23,6 +31,7 @@ notifications:
on_success: change
on_failure: always
on_start: false

env:
global:
- secure: NDX1oObVIHhG5zUZzhmL8m/601SFB2pE29dci1EcgKrYhdZf+50+Va/b0/7lNzYXnT76CwQmdkbsJmwqBh6+YO+SXcJFE13zudyGfZhLOwY+/oueQB8Up/gk2WbkY4FdIlo0XP/jl+atz4gR9EoAnKCYbdcjYj2lQnJ8ocDpi5U=
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Contributors
* [Andrew Wharton](https://github.com/andrew-wharton).
* [Alonso Torres](https://github.com/Alotor).
* [Bartek Gawel](https://github.com/bgawel).
* [Bob Finch](https://github.com/rbfinch).
* [Bobby Warner](https://github.com/bobbywarner).
* [Burt Beckwith](https://github.com/burtbeckwith).
* [Conall Laverty](https://github.com/conalllaverty).
Expand Down
29 changes: 21 additions & 8 deletions SpringSecurityRestGrailsPlugin.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import grails.plugin.springsecurity.SecurityFilterPosition
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.rest.*
import grails.plugin.springsecurity.rest.authentication.DefaultRestAuthenticationEventPublisher
import grails.plugin.springsecurity.rest.authentication.NullRestAuthenticationEventPublisher
import grails.plugin.springsecurity.rest.credentials.DefaultJsonPayloadCredentialsExtractor
import grails.plugin.springsecurity.rest.credentials.RequestParamsCredentialsExtractor
import grails.plugin.springsecurity.rest.oauth.DefaultOauthUserDetailsService
Expand Down Expand Up @@ -31,7 +33,7 @@ import javax.servlet.http.HttpServletResponse

class SpringSecurityRestGrailsPlugin {

String version = "1.5.1"
String version = "1.5.2"
String grailsVersion = "2.0 > *"
List loadAfter = ['springSecurityCore']
List pluginExcludes = [
Expand Down Expand Up @@ -89,6 +91,7 @@ class SpringSecurityRestGrailsPlugin {
endpointUrl = conf.rest.login.endpointUrl
tokenGenerator = ref('tokenGenerator')
tokenStorageService = ref('tokenStorageService')
authenticationEventPublisher = ref('authenticationEventPublisher')
}

def paramsClosure = {
Expand Down Expand Up @@ -160,9 +163,7 @@ class SpringSecurityRestGrailsPlugin {
authenticationSuccessHandler = ref('restAuthenticationSuccessHandler')
authenticationFailureHandler = ref('restAuthenticationFailureHandler')
restAuthenticationProvider = ref('restAuthenticationProvider')
if (conf.useSecurityEventListener) {
authenticationEventPublisher = ref('authenticationEventPublisher')
}
authenticationEventPublisher = ref('authenticationEventPublisher')
}

restExceptionTranslationFilter(ExceptionTranslationFilter, ref('restAuthenticationEntryPoint'), ref('restRequestCache')) {
Expand Down Expand Up @@ -219,18 +220,18 @@ class SpringSecurityRestGrailsPlugin {
expiration = conf.rest.token.storage.redis.expiration
}
} else if (conf.rest.token.storage.useJwt) {
keyProvider(DefaultRSAKeyProvider)

jwtService(JwtService) {
keyProvider = ref('keyProvider')
jwtSecret = conf.rest.token.storage.jwt.secret
}

tokenStorageService(JwtTokenStorageService) {
jwtService = ref('jwtService')
}

if (conf.rest.token.storage.jwt.useEncryptedJwt) {
jwtService(JwtService) {
keyProvider = ref('keyProvider')
jwtSecret = conf.rest.token.storage.jwt.secret
}
tokenGenerator(EncryptedJwtTokenGenerator) {
jwtTokenStorageService = ref('tokenStorageService')
keyProvider = ref('keyProvider')
Expand All @@ -243,6 +244,8 @@ class SpringSecurityRestGrailsPlugin {
privateKeyPath = conf.rest.token.storage.jwt.privateKeyPath
publicKeyPath = conf.rest.token.storage.jwt.publicKeyPath
}
} else {
keyProvider(DefaultRSAKeyProvider)
}

} else {
Expand All @@ -266,6 +269,16 @@ class SpringSecurityRestGrailsPlugin {
/* oauthUserDetailsService */
oauthUserDetailsService(DefaultOauthUserDetailsService) {
userDetailsService = ref('userDetailsService')
preAuthenticationChecks = ref('preAuthenticationChecks')
}

// SecurityEventListener
if (conf.useSecurityEventListener) {
restSecurityEventListener(RestSecurityEventListener)

authenticationEventPublisher(DefaultRestAuthenticationEventPublisher)
} else {
authenticationEventPublisher(NullRestAuthenticationEventPublisher)
}

if (printStatusMessages) {
Expand Down
1 change: 1 addition & 0 deletions grails-app/conf/BuildConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ grails.project.dependency.resolution = {
compile 'com.google.guava:guava-io:r03'
compile 'org.pac4j:pac4j-core:1.6.0'
compile 'org.pac4j:pac4j-oauth:1.6.0'
compile 'org.pac4j:pac4j-cas:1.6.0'
compile 'com.nimbusds:nimbus-jose-jwt:3.9'

build("com.lowagie:itext:2.0.8") { excludes "bouncycastle:bcprov-jdk14:138", "org.bouncycastle:bcprov-jdk14:1.38" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ import grails.plugin.springsecurity.rest.token.rendering.AccessTokenJsonRenderer
import grails.plugin.springsecurity.rest.token.storage.TokenStorageService
import org.apache.commons.codec.binary.Base64
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.pac4j.core.client.BaseClient
import org.pac4j.core.client.RedirectAction
import org.pac4j.core.context.J2EContext
import org.pac4j.core.context.WebContext
import org.pac4j.oauth.client.BaseOAuthClient
import org.springframework.http.HttpStatus
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UsernameNotFoundException
Expand All @@ -51,7 +51,7 @@ class RestOauthController {
* allows the frontend application to define the frontend callback URL on demand.
*/
def authenticate(String provider, String callback) {
BaseOAuthClient client = restOauthService.getClient(provider)
BaseClient client = restOauthService.getClient(provider)
WebContext context = new J2EContext(request, response)

RedirectAction redirectAction = client.getRedirectAction(context, true, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
*/
package grails.plugin.springsecurity.rest

import grails.plugin.springsecurity.rest.authentication.RestAuthenticationEventPublisher
import grails.plugin.springsecurity.rest.oauth.OauthUser
import grails.plugin.springsecurity.rest.oauth.OauthUserDetailsService
import grails.plugin.springsecurity.rest.token.AccessToken
import grails.plugin.springsecurity.rest.token.generation.TokenGenerator
import grails.plugin.springsecurity.rest.token.storage.TokenStorageService
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.codehaus.groovy.grails.web.mapping.LinkGenerator
import org.pac4j.core.client.BaseClient
import org.pac4j.core.context.WebContext
import org.pac4j.core.credentials.Credentials
import org.pac4j.core.profile.CommonProfile
Expand All @@ -44,18 +46,20 @@ class RestOauthService {
GrailsApplication grailsApplication
LinkGenerator grailsLinkGenerator
OauthUserDetailsService oauthUserDetailsService
RestAuthenticationEventPublisher authenticationEventPublisher


BaseOAuthClient getClient(String provider) {
BaseClient getClient(String provider) {
log.debug "Creating OAuth client for provider: ${provider}"
def providerConfig = grailsApplication.config.grails.plugin.springsecurity.rest.oauth."${provider}"
def ClientClass = providerConfig.client

BaseOAuthClient client
BaseClient client
if (ClientClass?.toString()?.endsWith("CasOAuthWrapperClient")) {
client = ClientClass.newInstance(providerConfig.key, providerConfig.secret, providerConfig.casOAuthUrl)
} else {
} else if (BaseOAuthClient.class.isAssignableFrom(ClientClass)) {
client = ClientClass.newInstance(providerConfig.key, providerConfig.secret)
} else {
client = ClientClass.newInstance()
}

String callbackUrl = grailsLinkGenerator.link controller: 'restOauth', action: 'callback', params: [provider: provider], mapping: 'oauth', absolute: true
Expand All @@ -64,12 +68,13 @@ class RestOauthService {

if (providerConfig.scope) client.scope = providerConfig.scope
if (providerConfig.fields) client.fields = providerConfig.fields
if (providerConfig.casLoginUrl) client.casLoginUrl = providerConfig.casLoginUrl

return client
}

String storeAuthentication(String provider, WebContext context) {
BaseOAuthClient client = getClient(provider)
BaseClient client = getClient(provider)
Credentials credentials = client.getCredentials context

log.debug "Querying provider to fetch User ID"
Expand All @@ -86,6 +91,8 @@ class RestOauthService {
log.debug "Storing token on the token storage"
tokenStorageService.storeToken(accessToken.accessToken, userDetails)

authenticationEventPublisher.publishTokenCreation(accessToken)

SecurityContextHolder.context.setAuthentication(accessToken)

return accessToken.accessToken
Expand Down
1 change: 1 addition & 0 deletions src/docs/guide/events.gdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Spring Security Rest plugin fires events exactly like Spring Security Core does.
16 changes: 16 additions & 0 deletions src/docs/guide/events/eventNotification.gdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
You can set up event notifications in two ways. The sections that follow describe each approach in more detail.

* Register an event listener, ignoring events that do not interest you. Spring allows only partial event subscription; you use generics to register the class of events that interest you, and you are notified of that class and all subclasses.
* Register one or more callback closures in @grails-app/conf/Config.groovy@ that take advantage of the plugin's @grails.plugin.springsecurity.rest.RestSecurityEventListener@. The listener does the filtering for you.

h4. AuthenticationEventPublisher

Spring Security Rest publishes events using an [AuthenticationEventPublisher|http://docs.spring.io/spring-security/site/docs/3.2.x/apidocs/org/springframework/security/authentication/AuthenticationEventPublisher.html] which in turn fire events using the [ApplicationEventPublisher|http://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/context/ApplicationEventPublisher.html]. By default no events are fired since the @AuthenticationEventPublisher@ instance registered is a @grails.plugin.springsecurity.rest.authentication.NullRestAuthenticationEventPublisher@. But you can enable event publishing by setting @grails.plugin.springsecurity.useSecurityEventListener = true@ in @grails-app/conf/Config.groovy@.

You can use the @useSecurityEventListener@ setting to temporarily disable and enable the callbacks, or enable them per-environment.

h4. Token Creation

Currently the Spring Security Rest plugin supports a single event in addition to the default spring security events. The event is fired whenever a new token is created. See @grails.plugin.springsecurity.rest.RestTokenCreationEvent@

*Note:* Every time a token is successfully submitted, an @AuthenticationSuccessEvent@ will be fired.
13 changes: 13 additions & 0 deletions src/docs/guide/events/registeringCallbackClosures.gdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Alternatively, enable events with @grails.plugin.springsecurity.useSecurityEventListener = true@ and register one or more callback closure(s) in @grails-app/conf/Config.groovy@ and let @SecurityEventListener@ do the filtering.

Implement the event handlers that you need, for example:

{code}
grails.plugin.springsecurity.useSecurityEventListener = true

grails.plugin.springsecurity.onRestTokenCreationEvent = { e, appCtx ->
// handle RestTokenCreationEvent
}
{code}

None of these closures are required; if none are configured, nothing will be called. Just implement the event handlers that you need.
26 changes: 26 additions & 0 deletions src/docs/guide/events/registeringEventListener.gdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Enable events with @grails.plugin.springsecurity.useSecurityEventListener = true@ and create one or more Groovy or Java classes, for example:

{code}
package com.foo.bar

import org.springframework.context.ApplicationListener
import grails.plugin.springsecurity.rest.RestTokenCreationEvent

class MySecurityEventListener
implements ApplicationListener<RestTokenCreationEvent> {

void onApplicationEvent(RestTokenCreationEvent event) {
// The access token is a delegate of the event, so you have access to an instance of @grails.plugin.springsecurity.rest.token.AccessToken@
}
}
{code}

Register the class in @grails-app/conf/spring/resources.groovy@:

{code}
import com.foo.bar.MySecurityEventListener

beans = {
mySecurityEventListener(MySecurityEventListener)
}
{code}
4 changes: 3 additions & 1 deletion src/docs/guide/introduction.gdoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ future if there is enough community interest / love :)
{note}

{note}
This plugin is only for Grails 2.x
This plugin is only for Grails 2.x, and it requires at least Java 7.
{note}

The default behaviour of Spring Security is to store the authenticated principal in the HTTP session. However, in a
Expand Down Expand Up @@ -36,6 +36,8 @@ h4. Release History

You can view all releases at https://github.com/alvarosanchez/grails-spring-security-rest/releases.

* 19 August 2015
** [1.5.2|https://github.com/alvarosanchez/grails-spring-security-rest/issues?q=milestone%3A1.5.2+is%3Aclosed]
* 6 May 2015
** [1.5.1|https://github.com/alvarosanchez/grails-spring-security-rest/issues?q=milestone%3A1.5.1+is%3Aclosed]
* 6 May 2015
Expand Down
14 changes: 12 additions & 2 deletions src/docs/guide/oauth.gdoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ can use any OAuth 2.0 provider they support. This includes at the time of writin

Note that OAuth 1.0a providers also work, like Twitter.

The plugin also supports [CAS (Central Authentication Service)|https://www.apereo.org/projects/cas] using the OAuth
authentication flow. See [CAS Authentication|guide:oauth:cas] for details.

To start the OAuth authentication flow, from your frontend application, generate a link to
@<YOUR_GRAILS_APP>/oauth/authenticate/<provider>@. The user clicking on that link represents step 4 in the previous
diagram.
Expand Down Expand Up @@ -79,20 +82,27 @@ class DefaultOauthUserDetailsService implements OauthUserDetailsService {
@Delegate
UserDetailsService userDetailsService

OauthUser loadUserByUserProfile(OAuth20Profile userProfile, Collection<GrantedAuthority> defaultRoles)
UserDetailsChecker preAuthenticationChecks

OauthUser loadUserByUserProfile(CommonProfile userProfile, Collection<GrantedAuthority> defaultRoles)
throws UsernameNotFoundException {
UserDetails userDetails
OauthUser oauthUser

try {
log.debug "Trying to fetch user details for user profile: ${userProfile}"
userDetails = userDetailsService.loadUserByUsername userProfile.id

log.debug "Checking user details with ${preAuthenticationChecks.class.name}"
preAuthenticationChecks?.check(userDetails)

Collection<GrantedAuthority> allRoles = userDetails.authorities + defaultRoles
oauthUser = new OauthUser(userDetails.username, userDetails.password, allRoles, userProfile)
} catch (UsernameNotFoundException unfe) {
log.debug "User not found. Creating a new one with default roles: ${defaultRoles}"
oauthUser = new OauthUser(userProfile.id, 'N/A', defaultRoles, userProfile)
}

return oauthUser
}

Expand Down Expand Up @@ -125,4 +135,4 @@ parameter will be empty, and both @error@ and @message@ params will be appended:
http://your-frontend-app.com/auth-success.html?token=&error=403&message=User+with+email+jimmy%40gmail.com+now+allowed.+Only+%40example.com+accounts+are+allowed
{code}

Below are some examples on how to configure it for Google, Facebook and Twitter.
Below are some examples on how to configure it for Google, Facebook and Twitter.
26 changes: 26 additions & 0 deletions src/docs/guide/oauth/cas.gdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Define the following block in your @Config.groovy@:

{code}
grails {
plugin {
springsecurity {

rest {

oauth {

frontendCallbackUrl = { String tokenValue -> "http://my.frontend-app.com/welcome#token=${tokenValue}" }

cas {

client = org.pac4j.cas.client.CasClient
casLoginUrl = "https://my.cas-server.com/cas/login"
}
}
}
}
}
}
{code}

Set @casLoginUrl@ to the login URL of your CAS server.
8 changes: 7 additions & 1 deletion src/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ introduction: Introduction to the Spring Security REST plugin
whatsNew15: What's new in 1.5?
whatsNew14: What's new in 1.4?
configuration: Configuration
events:
title: Events
eventNotification: Event Notification
registeringEventListener: Registering an Event Listener
registeringCallbackClosures: Registering Callback Closures
authentication:
title: Authentication Endpoint
logout: Logout Endpoint
Expand All @@ -21,5 +26,6 @@ oauth:
google: Google
facebook: Facebook
twitter: Twitter
cas: CAS (Central Authentication Service)
debugging: Debugging
faq: Frequently Asked Questions
faq: Frequently Asked Questions
4 changes: 4 additions & 0 deletions src/docs/guide/tokenValidation.gdoc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ If you still want to have full access and read the token from a different part o
[TokenReader|http://alvarosanchez.github.io/grails-spring-security-rest/latest/docs/gapi/grails/plugin/springsecurity/rest/token/reader/TokenReader.html]
and register it in your @resources.groovy@ as @tokenReader@.

{note}
You must disable bearer token support to register your own @tokenReader@ implementation.
{note}

h2. Anonymous access

If you want to enable anonymous access to URL's where this plugin's filters are applied, you need to:
Expand Down
Loading

0 comments on commit 0933b01

Please sign in to comment.