<commit_before>package org.nasdanika.cdo.web;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Principal;
import java.util.Collections;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.eresource.CDOResourceNode;
import org.eclipse.emf.cdo.util.ObjectNotFoundException;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.nasdanika.cdo.CDOViewContext;
import org.nasdanika.cdo.CDOViewContextProvider;
import org.nasdanika.cdo.CDOViewContextSubject;
import org.nasdanika.cdo.security.Realm;
import org.nasdanika.cdo.util.NasdanikaCDOUtil;
import org.nasdanika.cdo.web.routes.CDOViewSessionModuleGenerator;
import org.nasdanika.core.Context;
import org.nasdanika.core.ContextProvider;
import org.nasdanika.web.Action;
import org.nasdanika.web.HttpServletRequestContext;
import org.nasdanika.web.RequestMethod;
import org.nasdanika.web.Route;
import org.nasdanika.web.RoutingServlet;
import org.nasdanika.web.ValueAction;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.util.tracker.ServiceTracker;

@SuppressWarnings("serial")
public abstract class CDOViewRoutingServletBase<V extends CDOView, CR, C extends CDOViewContext<V, CR>> extends RoutingServlet {
	
	protected ServiceTracker<CDOViewContextProvider<V,CR,C>, CDOViewContextProvider<V,CR,C>> cdoViewContextProviderServiceTracker;
	private String sessionWebSocketServletPath;
	private String userNameHeader;
	private InetAddress userNameHeaderHost;
	
	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		userNameHeader = config.getInitParameter("user-name-header");
		String userNameHeaderHostStr = config.getInitParameter("user-name-header-host");
		try {
			userNameHeaderHost = userNameHeaderHostStr == null ? InetAddress.getLocalHost() : InetAddress.getByName(userNameHeaderHostStr);
		} catch (UnknownHostException e) {
			throw new ServletException("Unknown user-name-header-host", e);
		}
		
		sessionWebSocketServletPath = config.getInitParameter("ws-session-path");
		
		BundleContext bundleContext = getBundleContext();
		if (bundleContext==null) {
			bundleContext = FrameworkUtil.getBundle(RoutingServlet.class).getBundleContext();
		}
		if (bundleContext==null) {
			throw new IllegalStateException("Bundle context is not available, make sure that bundle "+FrameworkUtil.getBundle(RoutingServlet.class).getSymbolicName()+" is activated");
		}
		
		String serviceFilter = config.getInitParameter("cdo-view-context-provider-service-filter");
		// TODO - bundle is still null???
		if (serviceFilter==null || serviceFilter.trim().length()==0) {
			cdoViewContextProviderServiceTracker = new ServiceTracker<>(bundleContext, getProviderType().getName(), null);
		} else {
			String theFilter = "(&(" + Constants.OBJECTCLASS + "=" + getProviderType().getName() + ")"+serviceFilter+")";
			try {
				cdoViewContextProviderServiceTracker = new ServiceTracker<>(bundleContext, bundleContext.createFilter(theFilter), null);
			} catch (InvalidSyntaxException e) {
				throw new ServletException("Invalid service filter ("+e+"): "+serviceFilter, e);
			}
		}
		cdoViewContextProviderServiceTracker.open();
	}

	@SuppressWarnings("rawtypes")
	protected abstract Class<? extends CDOViewContextProvider> getProviderType();
	
	@Override
	public void destroy() {
		if (cdoViewContextProviderServiceTracker!=null) {
			cdoViewContextProviderServiceTracker.close();
		}
		super.destroy();
	}
	
	/**
	 * Forbidden for Guest is the same as unauthorized - unauthorized requests are redirected to the login page.
	 * @throws Exception 
	 */
	@Override
	protected Action filterAction(HttpServletRequest req, HttpServletResponse resp, HttpServletRequestContext context, Action action) throws Exception {
		if (action == Action.FORBIDDEN) {
			@SuppressWarnings("unchecked")
			C ctx = (C) context;
			Realm<CR> securityRealm = ctx.getSecurityRealm();
			List<org.nasdanika.cdo.security.Principal> principals = ctx.getPrincipals();
			if (securityRealm != null && principals.size() == 1 && principals.get(0) == securityRealm.getGuest()) {
				return Action.UNAUTHORIZED;
			}
			
		}
		return super.filterAction(req, resp, context, action);
	}
	
	private static final CDOViewSessionModuleGenerator cdoViewSessionModuleGenerator = new CDOViewSessionModuleGenerator();	

	@Override
	protected HttpServletRequestContext createContext(String[] path, final HttpServletRequest req, HttpServletResponse resp, String reqUrl, Context... chain) throws Exception {
		CDOViewContextProvider<V,CR,C> provider = cdoViewContextProviderServiceTracker.getService();
		if (provider==null) {
			throw new ServletException("View provider not found");
		}
		
		HttpSessionCDOViewContextSubject<V, CR> subject = new HttpSessionCDOViewContextSubject<V, CR>(req.getSession(), getPrincipalNames(req));
		C viewContext = provider.createContext(subject);
		HttpServletRequestContext compositeContext = createCompositeContext(path, req, resp, reqUrl, viewContext, chain);
		compositeContext.getRootObjectsPaths().put(viewContext.getView(), reqUrl);
		return compositeContext;
	}

	/**
	 * Retrieves principals names from the request.
	 * Returns singleton list with user principal name if user principal is not null. Otherwise returns singleton list with the value of the header specified in ``user-name-header`` init parameter, if any.
	 * Override to derive principal name(s) by some other means, e.g. decryption of authentication token and/or pulling principal roles from LDAP or by iterating over a list of roles and invoking isUserInRole().  
	 * @param req
	 * @return
	 */
	protected List<String> getPrincipalNames(HttpServletRequest req) {
		Principal userPrincipal = req.getUserPrincipal();
		if (userPrincipal!=null) {
			return Collections.singletonList(userPrincipal.getName());
		}
		if (userNameHeader == null) {
			return Collections.emptyList();
		}
		String principalName = req.getHeader(userNameHeader);
		if (principalName == null) {
			return Collections.emptyList();
		}
		// Validating correctness of the remote address
		try {
			InetAddress remoteAddr = InetAddress.getByName(req.getRemoteAddr());
			if (!userNameHeaderHost.equals(remoteAddr)) {
				return Collections.emptyList();				
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
			return Collections.emptyList();
		}		
				
		return Collections.singletonList(principalName);
	}
	
	protected abstract HttpServletRequestContext createCompositeContext(String[] path, final HttpServletRequest req, HttpServletResponse resp, String reqUrl, C viewContext, Context[] chain) throws Exception;
	
	@Override<commit_msg>org.nasdanika.cdo.web.userNameHeaderHost property to specify SSO host.<commit_after>package org.nasdanika.cdo.web;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Principal;
import java.util.Collections;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.eresource.CDOResourceNode;
import org.eclipse.emf.cdo.util.ObjectNotFoundException;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.nasdanika.cdo.CDOViewContext;
import org.nasdanika.cdo.CDOViewContextProvider;
import org.nasdanika.cdo.CDOViewContextSubject;
import org.nasdanika.cdo.security.Realm;
import org.nasdanika.cdo.util.NasdanikaCDOUtil;
import org.nasdanika.cdo.web.routes.CDOViewSessionModuleGenerator;
import org.nasdanika.core.Context;
import org.nasdanika.core.ContextProvider;
import org.nasdanika.web.Action;
import org.nasdanika.web.HttpServletRequestContext;
import org.nasdanika.web.RequestMethod;
import org.nasdanika.web.Route;
import org.nasdanika.web.RoutingServlet;
import org.nasdanika.web.ValueAction;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.util.tracker.ServiceTracker;

@SuppressWarnings("serial")
public abstract class CDOViewRoutingServletBase<V extends CDOView, CR, C extends CDOViewContext<V, CR>> extends RoutingServlet {
	
	/**
	 * Name of the host which passes user name header for SSO.  * matches any host. If not set then 
	 * defaults to localhost.
	 */
	public static final String USER_NAME_HEADER_HOST_PROPERTY = "org.nasdanika.cdo.web.userNameHeaderHost";
	
	protected ServiceTracker<CDOViewContextProvider<V,CR,C>, CDOViewContextProvider<V,CR,C>> cdoViewContextProviderServiceTracker;
	private String sessionWebSocketServletPath;
	private String userNameHeader;
	private InetAddress userNameHeaderHost;
	
	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		userNameHeader = config.getInitParameter("user-name-header");
		String userNameHeaderHostStr = System.getProperty(USER_NAME_HEADER_HOST_PROPERTY);
		try {
			if (userNameHeaderHostStr == null) {
				userNameHeaderHostStr = "127.0.0.1";
			}
			
			if ("*".equals(userNameHeaderHostStr)) {
				System.out.println("Accepting user name header " +userNameHeader + " from any host");				
			} else {
				userNameHeaderHost = InetAddress.getByName(userNameHeaderHostStr);
				System.out.println("Accepting user name header " +userNameHeader + " from "+userNameHeaderHost);
			}
		} catch (UnknownHostException e) {
			throw new ServletException("Unknown user-name-header-host", e);
		}
		
		sessionWebSocketServletPath = config.getInitParameter("ws-session-path");
		
		BundleContext bundleContext = getBundleContext();
		if (bundleContext==null) {
			bundleContext = FrameworkUtil.getBundle(RoutingServlet.class).getBundleContext();
		}
		if (bundleContext==null) {
			throw new IllegalStateException("Bundle context is not available, make sure that bundle "+FrameworkUtil.getBundle(RoutingServlet.class).getSymbolicName()+" is activated");
		}
		
		String serviceFilter = config.getInitParameter("cdo-view-context-provider-service-filter");
		// TODO - bundle is still null???
		if (serviceFilter==null || serviceFilter.trim().length()==0) {
			cdoViewContextProviderServiceTracker = new ServiceTracker<>(bundleContext, getProviderType().getName(), null);
		} else {
			String theFilter = "(&(" + Constants.OBJECTCLASS + "=" + getProviderType().getName() + ")"+serviceFilter+")";
			try {
				cdoViewContextProviderServiceTracker = new ServiceTracker<>(bundleContext, bundleContext.createFilter(theFilter), null);
			} catch (InvalidSyntaxException e) {
				throw new ServletException("Invalid service filter ("+e+"): "+serviceFilter, e);
			}
		}
		cdoViewContextProviderServiceTracker.open();
	}

	@SuppressWarnings("rawtypes")
	protected abstract Class<? extends CDOViewContextProvider> getProviderType();
	
	@Override
	public void destroy() {
		if (cdoViewContextProviderServiceTracker!=null) {
			cdoViewContextProviderServiceTracker.close();
		}
		super.destroy();
	}
	
	/**
	 * Forbidden for Guest is the same as unauthorized - unauthorized requests are redirected to the login page.
	 * @throws Exception 
	 */
	@Override
	protected Action filterAction(HttpServletRequest req, HttpServletResponse resp, HttpServletRequestContext context, Action action) throws Exception {
		if (action == Action.FORBIDDEN) {
			@SuppressWarnings("unchecked")
			C ctx = (C) context;
			Realm<CR> securityRealm = ctx.getSecurityRealm();
			List<org.nasdanika.cdo.security.Principal> principals = ctx.getPrincipals();
			if (securityRealm != null && principals.size() == 1 && principals.get(0) == securityRealm.getGuest()) {
				return Action.UNAUTHORIZED;
			}
			
		}
		return super.filterAction(req, resp, context, action);
	}
	
	private static final CDOViewSessionModuleGenerator cdoViewSessionModuleGenerator = new CDOViewSessionModuleGenerator();	

	@Override
	protected HttpServletRequestContext createContext(String[] path, final HttpServletRequest req, HttpServletResponse resp, String reqUrl, Context... chain) throws Exception {
		CDOViewContextProvider<V,CR,C> provider = cdoViewContextProviderServiceTracker.getService();
		if (provider==null) {
			throw new ServletException("View provider not found");
		}
		
		HttpSessionCDOViewContextSubject<V, CR> subject = new HttpSessionCDOViewContextSubject<V, CR>(req.getSession(), getPrincipalNames(req));
		C viewContext = provider.createContext(subject);
		HttpServletRequestContext compositeContext = createCompositeContext(path, req, resp, reqUrl, viewContext, chain);
		compositeContext.getRootObjectsPaths().put(viewContext.getView(), reqUrl);
		return compositeContext;
	}

	/**
	 * Retrieves principals names from the request.
	 * Returns singleton list with user principal name if user principal is not null. Otherwise returns singleton list with the value of the header specified in ``user-name-header`` init parameter, if any.
	 * Override to derive principal name(s) by some other means, e.g. decryption of authentication token and/or pulling principal roles from LDAP or by iterating over a list of roles and invoking isUserInRole().  
	 * @param req
	 * @return
	 */
	protected List<String> getPrincipalNames(HttpServletRequest req) {
		Principal userPrincipal = req.getUserPrincipal();
		if (userPrincipal!=null) {
			return Collections.singletonList(userPrincipal.getName());
		}
		if (userNameHeader == null) {
			return Collections.emptyList();
		}
		String principalName = req.getHeader(userNameHeader);
		if (principalName == null) {
			return Collections.emptyList();
		}
		// Validating correctness of the remote address
		if (userNameHeaderHost!= null) {
			try {
				InetAddress remoteAddr = InetAddress.getByName(req.getRemoteAddr());
				if (!userNameHeaderHost.equals(remoteAddr)) {
					return Collections.emptyList();				
				}
			} catch (UnknownHostException e) {
				e.printStackTrace();
				return Collections.emptyList();
			}
		}
				
		return Collections.singletonList(principalName);
	}
	
	protected abstract HttpServletRequestContext createCompositeContext(String[] path, final HttpServletRequest req, HttpServletResponse resp, String reqUrl, C viewContext, Context[] chain) throws Exception;
	
	@Override
{'fraction_non_alphanumeric': 0.10273282750285369, 'fraction_numerical': 0.0008057476666890486, 'mean_word_length': 5.655049151027703, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1092023', 'n_tokens_mistral': 4584, 'n_tokens_neox': 4227, 'n_words': 966}