/*
 * Copyright (C) 2005 Patrick Huber (phuber at swisstech dot net)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package net.swisstech.portlet.security.interceptor;

import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.acegisecurity.providers.ProviderNotFoundException;
import org.springframework.web.portlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * This interceptor authenticates users based on the Principal provided in the
 * request and based on a textual definition of mode/action combinations and
 * the required role to view such a page.<br/>
 * The basic configuration looks like this:<br/>
 * <code>
 * 	mode:X,action:Y=portlet_user, admin
 * </code>
 * The above configuration will activate if the current PortletMode is X and
 * the action request parameter is Y. If these two criterias match, the user
 * must have the role portlet_user or admin.<br/>
 * mode and action may appear once or never per line. multiple mode or action
 * definitions on the same line are not allowed.<br/>
 * So, to sum this up, both criterias before the '=' sign must match in order
 * to activate the rule. Then, if a user has any of the roles after the '='
 * sign, he is good to proceed.<br/>
 * Other examples:
 * <code>
 * 	// special case, matches everything regardless of mode/action
 * 	*=manager,pluto
 * 	mode:e*=user
 * 	action:doSomething=user
 * </code>
 * @author Patrick Huber (phuber at swisstech dot net)
 */
public class AuthenticationHandlerInterceptor implements HandlerInterceptor {
	
	private String authData = "";
	public String getAuthData() { return authData; }
	public void setAuthData(String authData) { this.authData = authData; }

	public boolean preHandle(PortletRequest request, PortletResponse response, Object handler) throws Exception {

		String requestedMode = request.getPortletMode().toString();
		String requestedAction = request.getParameter("action");
		if (requestedAction == null) {
			requestedAction = "";
		}
		
		// go trough all auth lines
		String[] authDataLines = getAuthData().split("\n");
		for (int i = 0; i < authDataLines.length; i++) {
			
			// read single line, verify it's ok
			String singleLine = authDataLines[i];
			if (singleLine == null) {
				continue;
			}

			// remove garbage
			singleLine = singleLine.trim();
			if (singleLine.length() <= 0) {
				continue;
			}
			
			// split to request-requirements and role-requirements
			String[] lineParts = singleLine.split("=");
			if (lineParts.length != 2) {
				throw new ProviderNotFoundException("Invalid auth line '" + i + "': '" + singleLine + "'");
			}
			
			String reqRequest = lineParts[0].trim();
			String reqRole    = lineParts[1].trim();
			
			// prepare roles
			String[] roles = reqRole.split(",");
			
			// special case
			if (reqRequest.equals("*")) {
				if (checkForRole(request, roles)) {
					return true;
				}
			}
			
			// analyse request requirements
			String[] reqRequestParts = reqRequest.split(",");
			
			if (reqRequestParts.length > 2) {
				throw new ProviderNotFoundException("Invalid first part before '=' sign in line '" + singleLine + "' -- only 1 mode and 1 action attribute allowed");
			}
			
			// all parts must match
			boolean hasMode = false;
			boolean modeMatches = false;
			boolean hasAction = false;
			boolean actionMatches = false;
			
			// check all parts
			for (int j = 0; j < reqRequestParts.length; j++) {
				
				String part = reqRequestParts[j];
				
				String[] def = part.split(":");
				if (def.length != 2) {
					throw new ProviderNotFoundException("Invalid definition '" + part + "'");
				}
				
				String type = def[0];
				String pattern = def[1];
				
				boolean hasWildcard = (pattern.indexOf('*') >= 0);
				if (hasWildcard) {
					pattern = pattern.substring(0, pattern.indexOf('*'));
				}
				
				// check the mode
				if (type.equals("mode")) {
					hasMode = true;
					
					if (hasWildcard) {
						
						if (requestedMode.startsWith(pattern)) {
							modeMatches = true;
						}
					}
					else {
						if (pattern.equals(requestedMode)) {
							modeMatches = true;
						}
					}
					
				}
				// check the action
				else if (type.equals("action")) {
					hasAction = true;
					
					if (hasWildcard) {
						if (requestedAction.startsWith(pattern)) {
							actionMatches = true;
						}
						else {
							if (pattern.equals(requestedAction)) {
								actionMatches = true;
							}
						}
					}
				}
				// unknown token
				else {
					throw new ProviderNotFoundException("Invalid first part before '=' sign in line '" + singleLine + "' -- one part is neiter 'mode:' nor 'action:'");
				}
			}
			
			// check role depending on action/mode constellation
			// has mode & action and both match
			if ((hasMode && modeMatches) && (hasAction && actionMatches)) {
				return checkForRole(request, roles);
			}
			// has mode that matches and no action
			else if ((hasMode && modeMatches) && (!hasAction)) {
				return checkForRole(request, roles);
			}
			// has action that matches and no mode
			else if ((!hasMode) && (hasAction && actionMatches)) {
				return checkForRole(request, roles);
			}
		}
		
		return false;
	}
	
	private boolean checkForRole(PortletRequest request, String[] roles) {
		for (int i = 0; i < roles.length; i++) {
			String role = roles[i].trim();
			if (request.isUserInRole(role)) {
				return true;
			}
		}
		
		return false;
	}

	public void postHandle(RenderRequest request, RenderResponse response, Object handler, ModelAndView modelAndView) throws Exception {
	}

	public void afterCompletion(PortletRequest request, PortletResponse response, Object handler, Exception ex) throws Exception {
	}
}








