View Javadoc
1   /*******************************************************************************
2    *   Gisgraphy Project 
3    * 
4    *   This library is free software; you can redistribute it and/or
5    *   modify it under the terms of the GNU Lesser General Public
6    *   License as published by the Free Software Foundation; either
7    *   version 2.1 of the License, or (at your option) any later version.
8    * 
9    *   This library is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   *   Lesser General Public License for more details.
13   * 
14   *   You should have received a copy of the GNU Lesser General Public
15   *   License along with this library; if not, write to the Free Software
16   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
17   * 
18   *  Copyright 2008  Gisgraphy project 
19   *  David Masclet <davidmasclet@gisgraphy.com>
20   *  
21   *  
22   *******************************************************************************/
23  package com.gisgraphy.service;
24  
25  import java.lang.reflect.Method;
26  import java.util.HashSet;
27  import java.util.Set;
28  
29  import org.springframework.security.AccessDeniedException;
30  import org.springframework.security.Authentication;
31  import org.springframework.security.AuthenticationTrustResolver;
32  import org.springframework.security.AuthenticationTrustResolverImpl;
33  import org.springframework.security.GrantedAuthority;
34  import org.springframework.security.context.SecurityContext;
35  import org.springframework.security.context.SecurityContextHolder;
36  import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
37  import org.springframework.security.userdetails.UserDetails;
38  import org.apache.commons.collections.CollectionUtils;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.springframework.aop.AfterReturningAdvice;
42  import org.springframework.aop.MethodBeforeAdvice;
43  
44  import com.gisgraphy.Constants;
45  import com.gisgraphy.model.Role;
46  import com.gisgraphy.model.User;
47  
48  /**
49   * This advice is responsible for enforcing security and only allowing
50   * administrators to modify users. Users are allowed to modify themselves.
51   * 
52   * @author mraible
53   */
54  public class UserSecurityAdvice implements MethodBeforeAdvice,
55  	AfterReturningAdvice {
56      /**
57       * Default "Access Denied" error message (not i18n-ized).
58       */
59      public static final String ACCESS_DENIED = "Access Denied: Only administrators are allowed to modify other users.";
60  
61      private final Log log = LogFactory.getLog(UserSecurityAdvice.class);
62  
63      /**
64       * Method to enforce security and only allow administrators to modify users.
65       * Regular users are allowed to modify themselves.
66       * 
67       * @param method
68       *                the name of the method executed
69       * @param args
70       *                the arguments to the method
71       * @param target
72       *                the target class
73       * @throws Throwable
74       *                 thrown when args[0] is null or not a User object
75       */
76      public void before(Method method, Object[] args, Object target)
77  	    throws Throwable {
78  	SecurityContext ctx = SecurityContextHolder.getContext();
79  
80  	if (ctx.getAuthentication() != null) {
81  	    Authentication auth = ctx.getAuthentication();
82  	    boolean administrator = false;
83  	    GrantedAuthority[] roles = auth.getAuthorities();
84  	    for (GrantedAuthority role1 : roles) {
85  		if (role1.getAuthority().equals(Constants.ADMIN_ROLE)) {
86  		    administrator = true;
87  		    break;
88  		}
89  	    }
90  
91  	    User user = (User) args[0];
92  
93  	    AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
94  	    // allow new users to signup - this is OK b/c Signup doesn't allow
95  	    // setting of roles
96  	    boolean signupUser = resolver.isAnonymous(auth);
97  
98  	    if (!signupUser) {
99  		User currentUser = getCurrentUser(auth);
100 
101 		if (user.getId() != null
102 			&& !user.getId().equals(currentUser.getId())
103 			&& !administrator) {
104 		    log
105 			    .warn("Access Denied: '"
106 				    + currentUser.getUsername()
107 				    + "' tried to modify '"
108 				    + user.getUsername() + "'!");
109 		    throw new AccessDeniedException(ACCESS_DENIED);
110 		} else if (user.getId() != null
111 			&& user.getId().equals(currentUser.getId())
112 			&& !administrator) {
113 		    // get the list of roles the user is trying add
114 		    Set<String> userRoles = new HashSet<String>();
115 		    if (user.getRoles() != null) {
116 			for (Object o : user.getRoles()) {
117 			    Role role = (Role) o;
118 			    userRoles.add(role.getName());
119 			}
120 		    }
121 
122 		    // get the list of roles the user currently has
123 		    Set<String> authorizedRoles = new HashSet<String>();
124 		    for (GrantedAuthority role : roles) {
125 			authorizedRoles.add(role.getAuthority());
126 		    }
127 
128 		    // if they don't match - access denied
129 		    // regular users aren't allowed to change their roles
130 		    if (!CollectionUtils.isEqualCollection(userRoles,
131 			    authorizedRoles)) {
132 			log.warn("Access Denied: '" + currentUser.getUsername()
133 				+ "' tried to change their role(s)!");
134 			throw new AccessDeniedException(ACCESS_DENIED);
135 		    }
136 		}
137 	    } else {
138 		if (log.isDebugEnabled()) {
139 		    log.debug("Registering new user '" + user.getUsername()
140 			    + "'");
141 		}
142 	    }
143 	}
144     }
145 
146     /**
147      * After returning, grab the user, check if they've been modified and reset
148      * the SecurityContext if they have.
149      * 
150      * @param returnValue
151      *                the user object
152      * @param method
153      *                the name of the method executed
154      * @param args
155      *                the arguments to the method
156      * @param target
157      *                the target class
158      * @throws Throwable
159      *                 thrown when args[0] is null or not a User object
160      */
161     public void afterReturning(Object returnValue, Method method,
162 	    Object[] args, Object target) throws Throwable {
163 	User user = (User) args[0];
164 
165 	if (user.getVersion() != null) {
166 	    // reset the authentication object if current user
167 	    Authentication auth = SecurityContextHolder.getContext()
168 		    .getAuthentication();
169 	    AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
170 	    // allow new users to signup - this is OK b/c Signup doesn't allow
171 	    // setting of roles
172 	    boolean signupUser = resolver.isAnonymous(auth);
173 	    if (auth != null && !signupUser) {
174 		User currentUser = getCurrentUser(auth);
175 		if (currentUser.getId().equals(user.getId())) {
176 		    auth = new UsernamePasswordAuthenticationToken(user, user
177 			    .getPassword(), user.getAuthorities());
178 		    SecurityContextHolder.getContext().setAuthentication(auth);
179 		}
180 	    }
181 	}
182     }
183 
184     private User getCurrentUser(Authentication auth) {
185 	User currentUser;
186 	if (auth.getPrincipal() instanceof UserDetails) {
187 	    currentUser = (User) auth.getPrincipal();
188 	} else if (auth.getDetails() instanceof UserDetails) {
189 	    currentUser = (User) auth.getDetails();
190 	} else {
191 	    throw new AccessDeniedException("User not properly authenticated.");
192 	}
193 	return currentUser;
194     }
195 }