/*******************************************************************************
 * Copyright (c) 2001, 2009 IBM Corporation and others.
 *
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.useradmin;

import java.security.*;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.log.LogService;
import org.osgi.service.prefs.*;

/*
 *  UserAdminStore is responsible for managing the persistence data of the useradmin
 *  service.  It uses the PersistenceNode service as its underlying storage.
 */

public class UserAdminStore {

	static protected final String propertiesNode = "properties"; //$NON-NLS-1$
	static protected final String credentialsNode = "credentials"; //$NON-NLS-1$
	static protected final String membersNode = "members"; //$NON-NLS-1$
	static private final String typesNode = "types"; //$NON-NLS-1$
	static protected final String basicString = "basic"; //$NON-NLS-1$
	static protected final String requiredString = "required"; //$NON-NLS-1$
	static protected final String typeString = "type"; //$NON-NLS-1$
	static protected final String persistenceUserName = "UserAdmin"; //$NON-NLS-1$

	protected ServiceReference prefsRef;
	protected ServiceRegistration userAdminListenerReg;
	protected UserAdmin useradmin;
	protected LogTracker log;
	protected Preferences rootNode;
	protected PreferencesService preferencesService;

	protected UserAdminStore(PreferencesService preferencesService, UserAdmin useradmin, LogTracker log) {
		this.preferencesService = preferencesService;
		this.useradmin = useradmin;
		this.log = log;
	}

	protected void init() throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {

				public Object run() throws BackingStoreException {
					rootNode = preferencesService.getUserPreferences(persistenceUserName);
					loadRoles();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {

			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void addRole(final org.osgi.service.useradmin.Role role) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences node = rootNode.node(role.getName());
					node.putInt(typeString, role.getType());
					node.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void removeRole(final org.osgi.service.useradmin.Role role) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences node = rootNode.node(role.getName());
					node.removeNode();
					rootNode.node("").flush(); //$NON-NLS-1$
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void clearProperties(final org.osgi.service.useradmin.Role role) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences propertyNode = rootNode.node(role.getName() + "/" + propertiesNode); //$NON-NLS-1$
					propertyNode.clear();
					if (propertyNode.nodeExists(typesNode))
						propertyNode.node(typesNode).removeNode();
					propertyNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void addProperty(final org.osgi.service.useradmin.Role role, final String key, final Object value) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences propertyNode = rootNode.node(role.getName() + "/" + propertiesNode); //$NON-NLS-1$
					Preferences propertyTypesNode = propertyNode.node(typesNode);
					if (value instanceof String) {
						propertyNode.put(key, (String) value);
						propertyTypesNode.putBoolean(key, true);
					} else //must be a byte array, then
					{
						propertyNode.putByteArray(key, (byte[]) value);
						propertyTypesNode.putBoolean(key, false);
					}
					propertyNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void removeProperty(final org.osgi.service.useradmin.Role role, final String key) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences propertyNode = rootNode.node(role.getName() + "/" + propertiesNode); //$NON-NLS-1$
					propertyNode.remove(key);
					if (propertyNode.nodeExists(typesNode))
						propertyNode.node(typesNode).remove(key);
					propertyNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void clearCredentials(final org.osgi.service.useradmin.Role role) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences credentialNode = rootNode.node(role.getName() + "/" + credentialsNode); //$NON-NLS-1$
					credentialNode.clear();
					if (credentialNode.nodeExists(typesNode))
						credentialNode.node(typesNode).removeNode();
					credentialNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void addCredential(final org.osgi.service.useradmin.Role role, final String key, final Object value) throws BackingStoreException {

		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences credentialNode = rootNode.node(role.getName() + "/" + credentialsNode); //$NON-NLS-1$
					Preferences credentialTypesNode = credentialNode.node(typesNode);
					if (value instanceof String) {
						credentialNode.put(key, (String) value);
						credentialTypesNode.putBoolean(key, true);
					} else //assume it is a byte array
					{
						credentialNode.putByteArray(key, (byte[]) value);
						credentialTypesNode.putBoolean(key, false);
					}
					credentialNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, NLS.bind(UserAdminMsg.Backing_Store_Write_Exception, new Object[] {NLS.bind(UserAdminMsg.adding_Credential_to__15, role.getName())}), ex);
			throw ((BackingStoreException) ex.getException());
		}

	}

	protected void removeCredential(final org.osgi.service.useradmin.Role role, final String key) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences credentialNode = rootNode.node(role.getName() + "/" + credentialsNode); //$NON-NLS-1$
					credentialNode.remove(key);
					if (credentialNode.nodeExists(typesNode))
						credentialNode.node(typesNode).remove(key);
					credentialNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void addMember(final Group group, final Role role) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences memberNode = rootNode.node(group.getName() + "/" + membersNode); //$NON-NLS-1$
					memberNode.put(role.getName(), basicString);
					memberNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, NLS.bind(UserAdminMsg.Backing_Store_Write_Exception, new Object[] {NLS.bind(UserAdminMsg.adding_member__18, role.getName(), group.getName())}), ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void addRequiredMember(final Group group, final Role role) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences memberNode = rootNode.node(group.getName() + "/" + membersNode); //$NON-NLS-1$
					memberNode.put(role.getName(), requiredString);
					memberNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, NLS.bind(UserAdminMsg.Backing_Store_Write_Exception, new Object[] {NLS.bind(UserAdminMsg.adding_required_member__21, role.getName(), group.getName())}), ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void removeMember(final Group group, final Role role) throws BackingStoreException {
		try {
			AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws BackingStoreException {
					Preferences memberNode = rootNode.node(group.getName() + "/" + membersNode); //$NON-NLS-1$
					memberNode.remove(role.getName());
					memberNode.flush();
					return (null);
				}
			});
		} catch (PrivilegedActionException ex) {
			log.log(LogService.LOG_ERROR, NLS.bind(UserAdminMsg.Backing_Store_Write_Exception, new Object[] {NLS.bind(UserAdminMsg.removing_member__24, role.getName(), group.getName())}), ex);
			throw ((BackingStoreException) ex.getException());
		}
	}

	protected void loadRoles() throws BackingStoreException {
		synchronized (this) {
			createAnonRole();

			String[] children = rootNode.node("").childrenNames(); //$NON-NLS-1$

			for (int i = 0; i < children.length; i++) {
				if (useradmin.getRole(children[i]) == null) //check to see if it is already loaded
				{ //(we may have had to load some roles out of
					loadRole(rootNode.node(children[i]), null); // order due to dependencies) 
					//modified to solve defect 95982
				}
			}
		}
	}

	/* modified to solve defect 95982 */
	protected void loadRole(Preferences node, Role role) throws BackingStoreException {
		int type = node.getInt(typeString, Integer.MIN_VALUE);

		if (type == Integer.MIN_VALUE) {
			String errorString = NLS.bind(UserAdminMsg.Backing_Store_Read_Exception, new Object[] {NLS.bind(UserAdminMsg.Unable_to_load_role__27, node.name())});
			BackingStoreException ex = new BackingStoreException(errorString);
			log.log(LogService.LOG_ERROR, errorString, ex);
			throw (ex);
		}
		if (role == null) {
			role = (Role) useradmin.createRole(node.name(), type, false);
		}
		Preferences propsNode = node.node(propertiesNode);
		String[] keys = propsNode.keys();
		UserAdminHashtable properties = (UserAdminHashtable) role.getProperties();
		Object value;

		//load properties
		Preferences propsTypesNode = propsNode.node(typesNode);
		for (int i = 0; i < keys.length; i++) {
			if (propsTypesNode.getBoolean(keys[i], true))
				value = propsNode.get(keys[i], null);
			else
				value = propsNode.getByteArray(keys[i], null);
			properties.put(keys[i], value, false);
		}

		//load credentials
		if (type == org.osgi.service.useradmin.Role.USER || type == org.osgi.service.useradmin.Role.GROUP) {
			Object credValue;
			Preferences credNode = node.node(credentialsNode);
			Preferences credTypesNode = credNode.node(UserAdminStore.typesNode);
			keys = credNode.keys();
			UserAdminHashtable credentials = (UserAdminHashtable) ((User) role).getCredentials();
			for (int i = 0; i < keys.length; i++) {
				if (credTypesNode.getBoolean(keys[i], true))
					credValue = credNode.get(keys[i], null);
				else
					credValue = credNode.getByteArray(keys[i], null);
				credentials.put(keys[i], credValue, false);
			}
		}

		//load group members
		if (type == org.osgi.service.useradmin.Role.GROUP) {
			Preferences memberNode = node.node(membersNode);
			keys = memberNode.keys();
			for (int i = 0; i < keys.length; i++) {
				value = memberNode.get(keys[i], null);
				Role member = (Role) useradmin.getRole(keys[i]);
				if (member == null) //then we have not loaded this one yet, so load it
				{
					loadRole(rootNode.node(keys[i]), null); // modified to solve defect 95982
					member = (Role) useradmin.getRole(keys[i]);
				}
				if (value.equals(requiredString)) {
					((Group) role).addRequiredMember(member, false);
				} else {
					((Group) role).addMember(member, false);
				}
			}
		}
	}

	protected void destroy() {
		try {
			rootNode.flush();
			rootNode = null;
			preferencesService = null;
		} catch (BackingStoreException ex) {
			log.log(LogService.LOG_ERROR, UserAdminMsg.Backing_Store_Write_Exception, ex);
		}
	}

	private void createAnonRole() throws BackingStoreException {
		Role role = null;
		if (!rootNode.nodeExists(Role.anyoneString)) {
			//If the user.anyone role is not present, create it
			role = (Role) useradmin.createRole(Role.anyoneString, org.osgi.service.useradmin.Role.ROLE, true);
		}
		/* modified to solve defect 95982 */
		if (role != null)
			loadRole(rootNode.node(Role.anyoneString), role);
		else
			loadRole(rootNode.node(Role.anyoneString), null);
	}

}
