/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.redhat.com/browse/TEIID-3663
package org.teiid.jboss;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import java.io.Serializable;
import java.security.Principal;

import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.List;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.LoginException;

import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.jboss.as.server.CurrentServiceContainer;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.dqp.service.SessionServiceException;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.security.Credentials;
import org.teiid.security.GSSResult;
import org.teiid.security.SecurityHelper;
import org.teiid.security.TeiidLoginContext;
import org.teiid.services.SessionServiceImpl;
import org.wildfly.extension.elytron.capabilities.CredentialSecurityFactory;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.authz.RoleMapper;
import org.wildfly.security.authz.Roles;
import org.wildfly.security.credential.GSSKerberosCredential;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.evidence.PasswordGuessEvidence;

public class JBossSecurityHelper implements SecurityHelper, Serializable {
	private static final long serialVersionUID = 3598997061994110254L;
	public static final String AT = "@"; //$NON-NLS-1$

	@Override
	public SecurityIdentity getSecurityIdentityInContext(String securityDomain) {
		SecurityDomain securityDomainContext = getSecurityDomainContext(securityDomain);
		if (securityDomainContext != null) {
			SecurityIdentity securityIdentity = securityDomainContext.getCurrentSecurityIdentity();
			if (securityIdentity != null && !securityIdentity.isAnonymous())
				return securityIdentity;
		}
		return null;
	}

	public SecurityIdentity createSecurityIdentity(SecurityDomain securityDomain, String username, Evidence evidence) throws LoginException {
		try {
			SecurityIdentity securityIdentity = securityDomain.authenticate(username, evidence);
			return securityIdentity;
		} catch (Exception e) {
			LogManager.logError(LogConstants.CTX_SECURITY, e);
			throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50072, username));
		}
	}

	@Override
	public boolean sameSubject(String securityDomain, SecurityIdentity subject) {
		SecurityDomain domain = getSecurityDomainContext(securityDomain);
		if (domain == null) {
			throw new TeiidRuntimeException(IntegrationPlugin.Event.TEIID50090, IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50090));
		}
		SecurityIdentity current = domain.getCurrentSecurityIdentity();
		if (current != null && subject != null) {
			Principal currentUser = current.getPrincipal();
			if (subject.getPrincipal().equals(currentUser)) {
				return true;
			}
		}
		return false;
	}

    @Override
    public TeiidLoginContext authenticate(String domain, String userName, Credentials credentials, String applicationName)
            throws LoginException {

        // If username specifies a domain (user@domain) only that domain is authenticated against.
        // If username specifies no domain, then all domains are tried in order.
            // this is the configured login for teiid
	    SecurityDomain securityDomain = getSecurityDomainContext(domain);
	    if (securityDomain != null) {
		    PasswordGuessEvidence guessEvidence = new PasswordGuessEvidence(credentials.getCredentialsAsCharArray());
		    SecurityIdentity securityIdentity = createSecurityIdentity(securityDomain, userName, guessEvidence);
		    LogManager.logDetail(LogConstants.CTX_SECURITY, new Object[] {"Logon successful for \"", userName, "\""}); //$NON-NLS-1$ //$NON-NLS-2$
                    String qualifiedUserName = userName+AT+domain;
		    return new TeiidLoginContext(qualifiedUserName, securityIdentity, domain);
                }
        throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50072, userName ));
            }

	@Override
	public SecurityIdentity createAdHocIdentity(String securityDomain, Principal principal, List<String> roles) {
		SecurityDomain domain = getSecurityDomainContext(securityDomain);
		if (domain != null) {
			SecurityIdentity subject = domain.createAdHocIdentity(principal);
			Roles mRoles = null;
			if (roles != null) {
				for (String roleName : roles) {
					if (mRoles == null)
						mRoles = Roles.of(roleName);
					else
						mRoles = mRoles.or(Roles.of(roleName));
        }
			}
			if (mRoles != null) {
				subject = subject.withDefaultRoleMapper(RoleMapper.constant(mRoles));
			}
			return subject;
		}
		return null;
	}

	@Override
	public SecurityIdentity createAdHocIdentity(String securityDomain, String username, List<String> roles) {
		return createAdHocIdentity(securityDomain, new NamePrincipal(username), roles);
	}
    
    @Override
    public GSSResult neogitiateGssLogin(String securityDomain, byte[] serviceTicket, String krbSecurityFactory) throws LoginException {
		SecurityDomain securityDomainContext = getSecurityDomainContext(securityDomain);
        if (securityDomainContext != null) {
			if (krbSecurityFactory == null)
				throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.DVCORE94220));
	        CredentialSecurityFactory securityFactory = getServiceValue("org", "wildfly", "security", "security-factory", "credential", krbSecurityFactory);
			if (securityFactory == null)
	            throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.DVCORE94221, krbSecurityFactory));
	        try {
		        GSSKerberosCredential credential = (GSSKerberosCredential) securityFactory.create();
				if (credential != null) {
					// this part of code was taken partially from the SpnegoAuthenticationMechanism WildFly Elytron class
					GSSManager gssManager = GSSManager.getInstance();
					GSSCredential gssCredential = credential.getGssCredential();
					GSSContext gssContext = gssManager.createContext(gssCredential);
					KerberosTicket kerberosTicket = credential.getKerberosTicket();
					Subject subject = new Subject(true, Collections.emptySet(), Collections.emptySet(), kerberosTicket != null ? Collections.singleton(kerberosTicket) : Collections.emptySet());
					byte[] responseToken;
					try {
						responseToken = Subject.doAs(subject, (PrivilegedExceptionAction<byte[]>) () -> gssContext.acceptSecContext(serviceTicket, 0, serviceTicket.length));
					} catch (PrivilegedActionException e) {
						LogManager.logError(LogConstants.CTX_SECURITY, e);
						throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.DVCORE94222));
					}

					GSSResult result = responseToken != null ? new GSSResult(responseToken, gssContext.isEstablished(), gssCredential) : new GSSResult(gssContext.isEstablished(), gssCredential);
					if (gssContext.isEstablished()) {
						final GSSName srcName;
                try {
							srcName = gssContext.getSrcName();
							if (srcName == null) {
								throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.DVCORE94223));
                        }
						} catch (GSSException e) {
							LogManager.logError(LogConstants.CTX_SECURITY, e);
							throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.DVCORE94224));
                    }
						String clientName = srcName.toString();
						clientName = SessionServiceImpl.getBaseUsername(clientName);
						List<String> roles = (List<String>) DVCaller.callWithThrow(SessionServiceException.class, 237, clientName, null);
						result.setSecurityContext(createAdHocIdentity(securityDomain, clientName, roles));
						result.setUserName(clientName);
					} else {
						throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.DVCORE94225));
                        }

					return result;
                    }
	        } catch (Exception e) {
		        LogManager.logError(LogConstants.CTX_SECURITY, e);
		        throw new LoginException(e.getMessage());
                }
            }
        throw new LoginException(IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50072, "GSS Auth" )); //$NON-NLS-1$
    }

    @Override
	public SecurityDomain getSecurityDomainContext(String securityDomain) {
		if (securityDomain != null && !securityDomain.isEmpty()) {
			return getServiceValue("org", "wildfly", "security", "security-domain", securityDomain);
            }
		return null;
            }

	private <T> T getServiceValue(String... parts) {
		ServiceName name = ServiceName.of(parts);
		ServiceController<T> controller = (ServiceController<T>) CurrentServiceContainer.getServiceContainer().getService(name);
		if (controller != null) {
			return controller.getService().getValue();
		}
        return null;
    }
}
