001    /* Copyright 2006-2009 the original author or authors.
002     *
003     * Licensed under the Apache License, Version 2.0 (the "License");
004     * you may not use this file except in compliance with the License.
005     * You may obtain a copy of the License at
006     *
007     *      http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software
010     * distributed under the License is distributed on an "AS IS" BASIS,
011     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012     * See the License for the specific language governing permissions and
013     * limitations under the License.
014     */
015    package org.codehaus.groovy.grails.plugins.springsecurity.facebook;
016    
017    import javax.servlet.http.HttpServletRequest;
018    import javax.servlet.http.HttpServletResponse;
019    import javax.servlet.http.HttpSession;
020    
021    import org.codehaus.groovy.grails.plugins.springsecurity.SecurityRequestHolder;
022    import org.springframework.security.Authentication;
023    import org.springframework.security.AuthenticationException;
024    import org.springframework.security.ui.AbstractProcessingFilter;
025    import org.springframework.security.ui.FilterChainOrder;
026    import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
027    import org.springframework.util.Assert;
028    import org.springframework.util.StringUtils;
029    import org.w3c.dom.Document;
030    
031    import com.google.code.facebookapi.FacebookWebappHelper;
032    
033    /**
034     * Intercepts j_spring_facebook_security_check to trigger Facebook login.
035     *
036     * @author <a href='mailto:beckwithb@studentsonly.com'>Burt Beckwith</a>
037     */
038    public class FacebookAuthenticationProcessingFilter extends AbstractProcessingFilter {
039    
040            private String _apiKey;
041            private String _secretKey;
042            private String _authenticationUrlRoot;
043    
044            /**
045             * {@inheritDoc}
046             * @see org.springframework.security.ui.AbstractProcessingFilter#attemptAuthentication(
047             *      javax.servlet.http.HttpServletRequest)
048             */
049            @Override
050            public Authentication attemptAuthentication(final HttpServletRequest request) throws AuthenticationException {
051    
052                    String authToken = request.getParameter("auth_token");
053                    if (!StringUtils.hasText(authToken)) {
054                            // trigger a redirect to the Facebook login
055                            throw new FacebookAuthenticationRequiredException();
056                    }
057    
058                    FacebookAuthenticationToken token = createToken(
059                                    authToken, request, SecurityRequestHolder.getResponse(),
060                                    _apiKey, _secretKey);
061    
062                    token.setDetails(authenticationDetailsSource.buildDetails(request));
063    
064                    Authentication authentication = getAuthenticationManager().authenticate(token);
065                    if (authentication.isAuthenticated()) {
066                            setLastUsername(token.getUserId(), request);
067                    }
068    
069                    return authentication;
070            }
071    
072            private void setLastUsername(final long userId, final HttpServletRequest request) {
073                    HttpSession session = request.getSession(false);
074                    if (session != null || getAllowSessionCreation()) {
075                            request.getSession().setAttribute(
076                                            AuthenticationProcessingFilter.SPRING_SECURITY_LAST_USERNAME_KEY,
077                                            String.valueOf(userId));
078                    }
079            }
080    
081            /**
082             * Build an authentication from a login <code>auth_token</code>.
083             * @param authToken  the <code>auth_token</code>
084             * @param request  the http request
085             * @param response  the http response
086             * @param apiKey  the API key
087             * @param secretKey  the secret key
088             * @return  the auth token
089             */
090            protected FacebookAuthenticationToken createToken(
091                            final String authToken, final HttpServletRequest request, final HttpServletResponse response,
092                            final String apiKey, final String secretKey) {
093    
094                    try {
095                            FacebookWebappHelper<Document> helper = FacebookWebappHelper.newInstanceXml(
096                                            request, response, apiKey, secretKey);
097    
098                            if (helper.isLogin()) {
099                                    String sessionKey = helper.doGetSession(authToken);
100                                    return new FacebookAuthenticationToken(helper.getUser().longValue(), sessionKey);
101                            }
102    
103                            return new FacebookAuthenticationToken(FacebookAuthenticationToken.Status.failure, null);
104                    }
105                    catch (RuntimeException e) {
106                            return new FacebookAuthenticationToken(FacebookAuthenticationToken.Status.error, e.getMessage());
107                    }
108            }
109    
110            /**
111             * {@inheritDoc}
112             * @see org.springframework.security.ui.AbstractProcessingFilter#determineFailureUrl(
113             *      javax.servlet.http.HttpServletRequest, org.springframework.security.AuthenticationException)
114             */
115            @Override
116            protected String determineFailureUrl(final HttpServletRequest request, final AuthenticationException failed) {
117                    if (failed instanceof FacebookAuthenticationRequiredException) {
118                            return _authenticationUrlRoot + _apiKey;
119                    }
120    
121                    return super.determineFailureUrl(request, failed);
122            }
123    
124            /**
125             * {@inheritDoc}
126             * @see org.springframework.security.ui.AbstractProcessingFilter#getDefaultFilterProcessesUrl()
127             */
128            @Override
129            public String getDefaultFilterProcessesUrl() {
130                    return "/j_spring_facebook_security_check";
131            }
132    
133            /**
134             * {@inheritDoc}
135             * @see org.springframework.security.ui.SpringSecurityFilter#getOrder()
136             */
137            public int getOrder() {
138                    return FilterChainOrder.OPENID_PROCESSING_FILTER + 1;
139            }
140    
141            /**
142             * Dependency injection for the API key.
143             * @param key  the key
144             */
145            public void setApiKey(final String key) {
146                    _apiKey = key;
147            }
148    
149            /**
150             * Dependency injection for the secret key.
151             * @param key  the key
152             */
153            public void setSecretKey(final String key) {
154                    _secretKey = key;
155            }
156    
157            /**
158             * Dependency injection for the Facebook auth url root.
159             * @param authenticationUrlRoot  the url root
160             */
161            public void setAuthenticationUrlRoot(String authenticationUrlRoot) {
162                    _authenticationUrlRoot = authenticationUrlRoot;
163            }
164    
165            /**
166             * {@inheritDoc}
167             * @see org.springframework.security.ui.AbstractProcessingFilter#afterPropertiesSet()
168             */
169            @Override
170            public void afterPropertiesSet() throws Exception {
171                    super.afterPropertiesSet();
172          Assert.notNull(_apiKey, "API key must be specified");
173          Assert.notNull(_secretKey, "Secret key must be specified");
174            }
175    }