/*
 * Copyright (C) 2005-2010 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ifsoft.jmxweb.plugin;

import java.io.IOException;
import java.io.Serializable;
import java.security.Principal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.security.auth.Subject;

import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.security.*;

import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.admin.AdminManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.XMPPServer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A login service that uses Openfire to authenticate users
 *
 */

public class OpenfireLoginService extends AbstractLifeCycle implements LoginService
{
    private static final Logger Log = LoggerFactory.getLogger(OpenfireLoginService.class);
    public static final ConcurrentHashMap<String, AuthToken> authTokens = new ConcurrentHashMap<String, AuthToken>();
    public static final ConcurrentHashMap<String, UserIdentity> identities = new ConcurrentHashMap<String, UserIdentity>();

    private IdentityService _identityService=new DefaultIdentityService();
    private String _name;
    private UserManager userManager = XMPPServer.getInstance().getUserManager();

    protected OpenfireLoginService()
    {

    }

    public OpenfireLoginService(String name)
    {
        setName(name);
    }

    public String getName()
    {
        return _name;
    }

    public IdentityService getIdentityService()
    {
        return _identityService;
    }

    public void setIdentityService(IdentityService identityService)
    {
        if (isRunning())
            throw new IllegalStateException("Running");
        _identityService = identityService;
    }

    public void setName(String name)
    {
        if (isRunning())
            throw new IllegalStateException("Running");
        _name = name;
    }


    @Override
    protected void doStart() throws Exception
    {
        super.doStart();
    }

    @Override
    protected void doStop() throws Exception
    {
        super.doStop();
    }

    public void logout(UserIdentity identity)
    {
        Log.debug("logout {}",identity);

        identities.remove(identity.getUserPrincipal().getName());
    }

    @Override
    public String toString()
    {
        return this.getClass().getSimpleName()+"["+_name+"]";
    }

    public UserIdentity login(String userName, Object credential)
    {
		UserIdentity identity = null;

		if (identities.containsKey(userName))
		{
			identity = identities.get(userName);

			if (authTokens.containsKey(userName) == false)
			{
				Log.debug("UserIdentity login " + userName + " ");

				try {

					if (AdminManager.getInstance().isUserAdmin(userName, true))
					{
						AuthToken authToken = AuthFactory.authenticate( userName, (String) credential);
						authTokens.put(userName, authToken);

					} else {
						Log.error( "access denied, not admin user " + userName );
						return null;
					}

				} catch ( UnauthorizedException e ) {
					Log.error( "access denied, bad password " + userName );
					return null;

				} catch ( Exception e ) {
					Log.error( "access denied " + userName );
					return null;
				}
			}

		} else {

			Log.debug("UserIdentity login " + userName + " ");

			try {
				userManager.getUser(userName);
			}

			catch (UserNotFoundException e) {
				//Log.error( "user not found " + userName, e );
				return null;
			}

			try {
				if (AdminManager.getInstance().isUserAdmin(userName, true))
				{
					AuthToken authToken = AuthFactory.authenticate( userName, (String) credential);
					authTokens.put(userName, authToken);

				} else {
					Log.error( "access denied, not admin user " + userName );
					return null;
				}

			} catch ( UnauthorizedException e ) {
				Log.error( "access denied, bad password " + userName );
				return null;

			} catch ( Exception e ) {
				Log.error( "access denied " + userName );
				return null;
			}

			Principal userPrincipal = new KnownUser(userName, credential);
			Subject subject = new Subject();
			subject.getPrincipals().add(userPrincipal);
			subject.getPrivateCredentials().add(credential);
			subject.getPrincipals().add(new RolePrincipal("jmxweb"));
			subject.setReadOnly();

			identity = _identityService.newUserIdentity(subject, userPrincipal, new String[] {"jmxweb"});
			identities.put(userName, identity);
		}

		return identity;
    }

    public boolean validate(UserIdentity user)
    {
		return true;
    }

    public static class KnownUser implements UserPrincipal, Serializable
    {
        private static final long serialVersionUID = -6226920753748399662L;
        private final String _name;
        private final Object _credential;

        public KnownUser(String name, Object credential)
        {
            _name=name;
            _credential=credential;
        }

        public boolean authenticate(Object credentials)
        {
            return true;
        }

        public String getName()
        {
            return _name;
        }

        public boolean isAuthenticated()
        {
            return true;
        }

        @Override public String toString()
        {
            return _name;
        }
    }

    public interface UserPrincipal extends Principal,Serializable
    {
        boolean authenticate(Object credentials);
        public boolean isAuthenticated();
    }

    public static class RolePrincipal implements Principal,Serializable
    {
        private static final long serialVersionUID = 2998397924051854402L;
        private final String _roleName;

        public RolePrincipal(String name)
        {
            _roleName=name;
        }
        public String getName()
        {
            return _roleName;
        }
    }
}

