/*******************************************************************************
 * Copyright (c) 1997, 2008 by ProSyst Software GmbH
 * http://www.prosyst.com
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    ProSyst Software GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.ip.provider.http;

import java.util.*;
import javax.servlet.http.*;
import org.eclipse.equinox.internal.ip.ProvisioningInfoProvider;
import org.eclipse.equinox.internal.ip.impl.Log;
import org.eclipse.equinox.internal.ip.impl.ProvisioningAgent;
import org.eclipse.equinox.internal.ip.provider.BaseProvider;
import org.osgi.framework.*;
import org.osgi.service.http.HttpService;
import org.osgi.service.provisioning.ProvisioningService;
import org.osgi.service.useradmin.*;

/**
 * Implements ProvisioningInfoProvider. Allows remote data loading through HTTP.
 * Acts as dynamic provider. On <I>load(Dictionary)</I> it just get reference
 * to provisioning data.
 * 
 * @author Avgustin Marinov
 * @author Pavlin Dobrev
 * @version 1.0
 */
public class HttpProvider extends BaseProvider implements ProvisioningInfoProvider, ServiceListener {

	/** Alias this servlet will be registered if remote configuration is allowed. */
	public static final String ALIAS = "/rminit";
	/**
	 * This is a key for property that points if remote setting of info is
	 * allowed.
	 */
	public static final String HTTP_SUPPORT = "equinox.provisioning.http.provider.allowed";
	/**
	 * This system property is used to be determined if to accepts only
	 * HttpServletRequest-s with scheme "https".
	 */
	public static final String SECURE = "equinox.provisioning.http.provider.secure";
	/** This system property determines if to requires authentication. */
	public static final String REQUIRE_AUTH = "equinox.provisioning.require.auth";

	/** If to accepts only HttpServletRequests with scheme "https". */
	static boolean secure;
	/** Reference to provisioning service. */
	ProvisioningService prvSrv;
	/** Bundle context reference. */
	private BundleContext bc;
	/** HTTP service that is used for registration of servlet. */
	private HttpService http;
	/** Servlet that is registered. */
	private HttpServlet servlet = new HttpServletImpl();

	/**
	 * @see org.eclipse.equinox.internal.ip.impl.provider.BaseProvider#start(org.osgi.framework.BundleContext)
	 */
	public void start(BundleContext bc) throws Exception {
		boolean httpsupport = true;
		if (bc.getProperty(HTTP_SUPPORT) != null)
			if (bc.getProperty(HTTP_SUPPORT).equals("false"))
				httpsupport = false;
		if (!httpsupport) {
			Log.debug(this + " is not an allowed provider.");
			return;
		}

		this.bc = bc;
		super.start(bc);
	}

	/**
	 * Stops provider. Unregister servlet.
	 * 
	 * @see org.eclipse.equinox.internal.ip.impl.provider.BaseProvider#stop(org.osgi.framework.BundleContext)
	 */
	public void stop(BundleContext bc) {
		super.stop(bc);
		if (http != null) {
			try {
				http.unregister(ALIAS);
			} catch (Exception e) {
				// Not fatal
			}
			http = null;
		}
	}

	/**
	 * @see org.eclipse.equinox.internal.ip.ProvisioningInfoProvider#init(org.osgi.service.provisioning.ProvisioningService)
	 */
	public Dictionary init(ProvisioningService prvSrv) {
		this.prvSrv = prvSrv;
		secure = false;
		if (ProvisioningAgent.bc.getProperty(SECURE) != null)
			if (ProvisioningAgent.bc.getProperty(SECURE).equals("true"))
				secure = true;
		try {
			bc.addServiceListener(this, '(' + "objectClass" + '=' + HttpService.class.getName() + ')');
		} catch (Exception e) {
			Log.debug(e);
		}

		synchronized (this) {
			if (http == null) {
				ServiceReference sref = bc.getServiceReference(HttpService.class.getName());
				if (sref != null) {
					http = (HttpService) bc.getService(sref);
					registerServlet();
				}
			}
		}
		return null; // Do not loads
	}

	/**
	 * @see org.eclipse.equinox.internal.ip.ProvisioningInfoProvider#get(java.lang.Object)
	 */
	public Object get(Object key) {
		return null;
	}

	/**
	 * Manages servlet (un)registration.
	 * 
	 * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
	 */
	public synchronized void serviceChanged(ServiceEvent se) {
		if (se.getType() == ServiceEvent.REGISTERED) {
			if (http == null) {
				http = (HttpService) bc.getService(se.getServiceReference());
				registerServlet();
			}
		} else if (se.getType() == ServiceEvent.UNREGISTERING) {
			if (http != null) {
				try {
					http.unregister(ALIAS);
				} catch (Exception e) {
					// Not fatal
				}
				http = null;
				ServiceReference sref = bc.getServiceReference(HttpService.class.getName());
				if (sref != null) {
					http = (HttpService) bc.getService(sref);
					registerServlet();
				}
			}
		}
	}

	// Registers servlet with ALIAS.
	private void registerServlet() {
		try {
			http.registerServlet(ALIAS, servlet, null, Context.getInstance());
			Log.debug("Servlet \"" + ALIAS + "\" registered.");
		} catch (Exception e) {
			// Servlet won't be registered
			Log.debug("Error registering HTTP provider servlet!", e);
		}
	}

	/**
	 * Return name of provider.
	 * 
	 * @return name.
	 */
	public String toString() {
		return "Http";
	}

	/**
	 * Class implements servlet which enables (un)secure remote configuration.
	 */
	class HttpServletImpl extends HttpServlet {

		private static final long serialVersionUID = 1L;

		// If supports remote configuration
		/**
		 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
		 *      javax.servlet.http.HttpServletResponse)
		 */
		public void doGet(HttpServletRequest request, HttpServletResponse response) {
			Log.debug("doGet ...");
			if (request.getHeader("Get-Id") == null) {
				Log.debug("Redirect to doPost.");
				doPost(request, response);
			} else {
				Log.debug("Request for spid.");
				if (secure && !request.getScheme().equals("https")) {
					Log.debug("Request to secure HttpLoader must be via https!");
					response.setHeader("error", "Request to secure HttpLoader must be via https!");
					response.setStatus(HttpServletResponse.SC_OK);
					return;
				}
				String spid = (String) prvSrv.getInformation().get(ProvisioningService.PROVISIONING_SPID);
				if (spid != null && spid.length() != 0) {
					response.setHeader("Gw-Id", spid);
				}
				response.setStatus(HttpServletResponse.SC_OK);
			}
		}

		// If supports remote configuration
		/**
		 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
		 *      javax.servlet.http.HttpServletResponse)
		 */
		public void doPost(HttpServletRequest request, HttpServletResponse response) {
			Log.debug("doPost ...");
			if (secure && !request.getScheme().equals("https")) {
				Log.debug("Request to secure HttpLoader must be via https!");
				response.setHeader("error", "Request to secure HttpLoader must be via https!");
				response.setStatus(HttpServletResponse.SC_OK);
				return;
			}

			String user = request.getParameter("user");
			String pass = request.getParameter("pass");

			boolean req_auth = false;
			if (ProvisioningAgent.bc.getProperty(REQUIRE_AUTH) != null)
				if (ProvisioningAgent.bc.getProperty(REQUIRE_AUTH).equals("true"))
					req_auth = true;

			if (!req_auth && !checkAccount(user, pass)) {
				Log.debug("Incorrect Account!");
				response.setHeader("error", "Incorrect account!");
				return;
			}

			Dictionary info = new Hashtable();
			Log.debug("HttpLoader loads:");
			for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) {
				String name = (String) e.nextElement();
				if (!"user".equals(name) && !"pass".equals(name)) {
					String param = request.getParameter(name);
					if (ProvisioningInfoProvider.GM_HOST.equals(name) && "no".equals(param)) {
						continue;
					}
					Log.debug("  " + name + '=' + param);
					info.put(name, param);
				}
			}

			prvSrv.addInformation(info);
			response.setStatus(HttpServletResponse.SC_OK);
		}
	}

	/*
	 * Returns if account is correct.
	 * 
	 * TODO: add user admin to tracked services
	 */
	boolean checkAccount(String user, String pass) {
		if (user == null || pass == null) {
			return false;
		}

		ServiceReference sref = bc.getServiceReference(UserAdmin.class.getName());
		if (sref == null) {
			return false;
		}
		UserAdmin userAdmin = (UserAdmin) bc.getService(sref);
		if (userAdmin != null) {
			try {
				User userRole = (User) userAdmin.getRole(user);
				if (userRole != null && userRole.hasCredential("password", pass)) {
					Authorization authorization = userAdmin.getAuthorization(userRole);
					return authorization.hasRole("administration");
				}
			} catch (Exception e) {
				Log.debug(e);
			}
		}
		return false;
	}

}
