/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 * 
 *************************************************************/



#include "cppuhelper/factory.hxx"

#include "com/sun/star/lang/XMultiServiceFactory.hpp"
#include "com/sun/star/task/NoMasterException.hpp"
#include "com/sun/star/task/XInteractionHandler.hpp"
#include "com/sun/star/task/XMasterPasswordHandling.hpp"
#include "com/sun/star/task/XPasswordContainer.hpp"
#include "com/sun/star/task/XUrlContainer.hpp"
#include "com/sun/star/ucb/AuthenticationRequest.hpp"
#include "com/sun/star/ucb/URLAuthenticationRequest.hpp"
#include "com/sun/star/ucb/XInteractionSupplyAuthentication.hpp"
#include "com/sun/star/ucb/XInteractionSupplyAuthentication2.hpp"

#include "passwordcontainer.hxx"

using namespace com::sun::star;

namespace {

//=========================================================================
bool fillContinuation(
    bool bUseSystemCredentials,
    const ucb::AuthenticationRequest & rRequest,
    const task::UrlRecord & aRec,
    const uno::Reference< ucb::XInteractionSupplyAuthentication > &
        xSupplyAuthentication,
    const uno::Reference< ucb::XInteractionSupplyAuthentication2 > &
        xSupplyAuthentication2,
    bool bCanUseSystemCredentials,
    bool bCheckForEqualPasswords )
{
    if ( bUseSystemCredentials )
    {
        // "use system creds" record found.
        // Wants client that we use it?
        if ( xSupplyAuthentication2.is() && bCanUseSystemCredentials )
        {
            xSupplyAuthentication2->setUseSystemCredentials( sal_True );
            return true;
        }
        return false;
    }
    else if (aRec.UserList.getLength() != 0)
    {
        if (aRec.UserList[0].Passwords.getLength() == 0)
        {
            // Password sequence can be empty, for instance if master
            // password was not given (e.g. master pw dialog canceled)
            // pw container does not throw NoMasterException in this case.
            // bug???
            return false;
        }

        // "user/pass" record found.
        if (!bCheckForEqualPasswords || !rRequest.HasPassword
            || rRequest.Password != aRec.UserList[0].Passwords[0]) // failed login attempt?
        {
            if (xSupplyAuthentication->canSetUserName())
                xSupplyAuthentication->
                    setUserName(aRec.UserList[0].UserName.getStr());

            if (xSupplyAuthentication->canSetPassword())
                xSupplyAuthentication->
                    setPassword(aRec.UserList[0].Passwords[0].getStr());
            if (aRec.UserList[0].Passwords.getLength() > 1)
            {
                if (rRequest.HasRealm)
                {
                    if (xSupplyAuthentication->canSetRealm())
                        xSupplyAuthentication->
                            setRealm(aRec.UserList[0].Passwords[1].
                                getStr());
                }
                else if (xSupplyAuthentication->canSetAccount())
                    xSupplyAuthentication->
                        setAccount(aRec.UserList[0].Passwords[1].
                            getStr());
            }

            if ( xSupplyAuthentication2.is() && bCanUseSystemCredentials )
                xSupplyAuthentication2->setUseSystemCredentials( sal_False );

            return true;
        }
    }
    return false;
}

} // namespace

namespace uui {

//=========================================================================
PasswordContainerHelper::PasswordContainerHelper(
    uno::Reference< lang::XMultiServiceFactory > const & xServiceFactory )
{
    OSL_ENSURE(xServiceFactory.is(), "no service factory given!");
    if (xServiceFactory.is())
        try
        {
            m_xPasswordContainer
                = uno::Reference< task::XPasswordContainer >(
                      xServiceFactory->
                          createInstance(
                              rtl::OUString(
                                  RTL_CONSTASCII_USTRINGPARAM(
                                     "com.sun.star.task.PasswordContainer"))),
                      uno::UNO_QUERY);
        }
        catch (uno::Exception const &)
        {}
    OSL_ENSURE(m_xPasswordContainer.is(),
               "unable to instanciate password container service");
}

//=========================================================================
bool PasswordContainerHelper::handleAuthenticationRequest(
    ucb::AuthenticationRequest const & rRequest,
    uno::Reference< ucb::XInteractionSupplyAuthentication > const &
        xSupplyAuthentication,
    rtl::OUString const & rURL,
    uno::Reference< task::XInteractionHandler > const & xIH )
        SAL_THROW((uno::RuntimeException))
{
    // Is continuation even a XInteractionSupplyAuthentication2, which
    // is derived from XInteractionSupplyAuthentication?
    uno::Reference< ucb::XInteractionSupplyAuthentication2 >
        xSupplyAuthentication2(xSupplyAuthentication, uno::UNO_QUERY);

    sal_Bool bCanUseSystemCredentials = sal_False;
    if (xSupplyAuthentication2.is())
    {
        sal_Bool bDefaultUseSystemCredentials;
        bCanUseSystemCredentials
            =  xSupplyAuthentication2->canUseSystemCredentials(
                bDefaultUseSystemCredentials );
    }

    uno::Reference< task::XPasswordContainer > xContainer(
        m_xPasswordContainer );
    uno::Reference< task::XUrlContainer > xUrlContainer(
        m_xPasswordContainer, uno::UNO_QUERY );
    OSL_ENSURE( xUrlContainer.is(), "Got no XUrlContainer!" );

    if ( !xContainer.is() || !xUrlContainer.is() )
        return false;

    if ( bCanUseSystemCredentials )
    {
        // Runtime / Persistent info avail for current auth request?

        rtl::OUString aResult = xUrlContainer->findUrl(
            rURL.getLength() ? rURL : rRequest.ServerName );
        if ( aResult.getLength() > 0 )
        {
            if ( fillContinuation( true,
                                   rRequest,
                                   task::UrlRecord(),
                                   xSupplyAuthentication,
                                   xSupplyAuthentication2,
                                   bCanUseSystemCredentials,
                                   false ) )
            {
                return true;
            }
        }
    }

    // xContainer works with userName passwdSequences pairs:
    if (rRequest.HasUserName && rRequest.HasPassword)
    {
        try
        {
            if (rRequest.UserName.getLength() == 0)
            {
                task::UrlRecord aRec;
                if ( rURL.getLength() )
                    aRec = xContainer->find(rURL, xIH);

                if ( aRec.UserList.getLength() == 0 )
                {
                    // compat: try server name.
                    aRec = xContainer->find(rRequest.ServerName, xIH);
                }

                if ( fillContinuation( false,
                                       rRequest,
                                       aRec,
                                       xSupplyAuthentication,
                                       xSupplyAuthentication2,
                                       bCanUseSystemCredentials,
                                       false ) )
                {
                    return true;
                }
            }
            else
            {
                task::UrlRecord aRec;
                if ( rURL.getLength() )
                    aRec = xContainer->findForName(
                        rURL, rRequest.UserName, xIH);

                if ( aRec.UserList.getLength() == 0 )
                {
                    // compat: try server name.
                    aRec = xContainer->findForName(
                        rRequest.ServerName, rRequest.UserName, xIH);
                }

                if ( fillContinuation( false,
                                       rRequest,
                                       aRec,
                                       xSupplyAuthentication,
                                       xSupplyAuthentication2,
                                       bCanUseSystemCredentials,
                                       true ) )
                {
                    return true;
                }
            }
        }
        catch (task::NoMasterException const &)
        {} // user did not enter master password
    }
    return false;
}

//=========================================================================
bool PasswordContainerHelper::addRecord(
    rtl::OUString const & rURL,
    rtl::OUString const & rUsername,
    uno::Sequence< rtl::OUString > const & rPasswords,
    uno::Reference< task::XInteractionHandler > const & xIH,
    bool bPersist )
        SAL_THROW((uno::RuntimeException))
{
    try
    {
        if ( rUsername.getLength() )
        {
            OSL_ENSURE( m_xPasswordContainer.is(),
                        "Got no XPasswordContainer!" );
            if ( !m_xPasswordContainer.is() )
                return false;

            if ( bPersist )
            {
                uno::Reference< task::XMasterPasswordHandling > xMPH(
                    m_xPasswordContainer, uno::UNO_QUERY_THROW );

                // If persistent storing of passwords is not yet
                // allowed, enable it.
                if ( !xMPH->isPersistentStoringAllowed() )
                    xMPH->allowPersistentStoring( sal_True );

                m_xPasswordContainer->addPersistent( rURL,
                                                     rUsername,
                                                     rPasswords,
                                                     xIH );
            }
            else
                m_xPasswordContainer->add( rURL,
                                           rUsername,
                                           rPasswords,
                                           xIH );
        }
        else
        {
            uno::Reference< task::XUrlContainer >
                xContainer( m_xPasswordContainer, uno::UNO_QUERY );
            OSL_ENSURE( xContainer.is(), "Got no XUrlContainer!" );
            if ( !xContainer.is() )
                return false;

            xContainer->addUrl( rURL, bPersist );
        }
    }
    catch ( task::NoMasterException const & )
    {
        // user did not enter master password
        return false;
    }
    return true;
}

//=========================================================================
//=========================================================================
//=========================================================================

PasswordContainerInteractionHandler::PasswordContainerInteractionHandler(
    const uno::Reference< lang::XMultiServiceFactory >& xSMgr )
: m_aPwContainerHelper( xSMgr )
{
}

//=========================================================================
// virtual
PasswordContainerInteractionHandler::~PasswordContainerInteractionHandler()
{
}

//=========================================================================
//
// XServiceInfo methods.
//
//=========================================================================

// virtual
::rtl::OUString SAL_CALL
PasswordContainerInteractionHandler::getImplementationName()
    throw ( uno::RuntimeException )
{
    return getImplementationName_Static();
}

//=========================================================================
// virtual
sal_Bool SAL_CALL
PasswordContainerInteractionHandler::supportsService(
        const ::rtl::OUString& ServiceName )
    throw ( uno::RuntimeException )
{
    uno::Sequence< rtl::OUString > aSNL = getSupportedServiceNames();
    const rtl::OUString * pArray = aSNL.getConstArray();
    for ( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
    {
        if ( pArray[ i ] == ServiceName )
            return sal_True;
    }
    return sal_False;
}

//=========================================================================
// virtual
uno::Sequence< ::rtl::OUString > SAL_CALL
PasswordContainerInteractionHandler::getSupportedServiceNames()
    throw ( uno::RuntimeException )
{
    return getSupportedServiceNames_Static();
}

//=========================================================================
// static
rtl::OUString
PasswordContainerInteractionHandler::getImplementationName_Static()
{
    return rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
        "com.sun.star.comp.uui.PasswordContainerInteractionHandler" ) );
}

//=========================================================================
// static
uno::Sequence< rtl::OUString >
PasswordContainerInteractionHandler::getSupportedServiceNames_Static()
{
    uno::Sequence< rtl::OUString > aSNS( 1 );
    aSNS.getArray()[ 0 ]
        = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
            "com.sun.star.task.PasswordContainerInteractionHandler" ) );
    return aSNS;
}

//=========================================================================
//
// XInteractionHandler methods.
//
//=========================================================================

// virtual
void SAL_CALL
PasswordContainerInteractionHandler::handle(
        const uno::Reference< task::XInteractionRequest >& rRequest )
    throw ( uno::RuntimeException )
{
    if ( !rRequest.is() )
        return;

    uno::Any aAnyRequest( rRequest->getRequest() );

    ucb::AuthenticationRequest aAuthenticationRequest;
    if ( !( aAnyRequest >>= aAuthenticationRequest ) )
        return;

    rtl::OUString aURL;
    ucb::URLAuthenticationRequest aURLAuthenticationRequest;
    if ( aAnyRequest >>= aURLAuthenticationRequest )
        aURL = aURLAuthenticationRequest.URL;

    uno::Sequence< uno::Reference< task::XInteractionContinuation > >
        rContinuations = rRequest->getContinuations();

    uno::Reference< ucb::XInteractionSupplyAuthentication >
        xSupplyAuthentication;

    for ( sal_Int32 i = 0; i < rContinuations.getLength(); ++i )
    {
        xSupplyAuthentication
            = uno::Reference< ucb::XInteractionSupplyAuthentication >(
                rContinuations[i], uno::UNO_QUERY );
        if( xSupplyAuthentication.is() )
            break;
    }

    if ( !xSupplyAuthentication.is() )
        return;

    // Try to obatin credentials from password container.
    if ( m_aPwContainerHelper.
             handleAuthenticationRequest( aAuthenticationRequest,
                                          xSupplyAuthentication,
                                          aURL,
                                          // @@@ FIXME: this not able to
                                          // handle master pw request!
                                          // master pw request is never
                                          // solvable without UI!
                                          this ) )
    {
        // successfully handled
        xSupplyAuthentication->select();
    }
}

//=========================================================================
//
// Service factory implementation.
//
//=========================================================================

static uno::Reference< uno::XInterface > SAL_CALL
PasswordContainerInteractionHandler_CreateInstance(
        const uno::Reference< lang::XMultiServiceFactory> & rSMgr )
    throw( uno::Exception )
{
    lang::XServiceInfo * pX = static_cast< lang::XServiceInfo * >(
        new PasswordContainerInteractionHandler( rSMgr ) );
    return uno::Reference< uno::XInterface >::query( pX );
}

//=========================================================================
// static
uno::Reference< lang::XSingleServiceFactory >
PasswordContainerInteractionHandler::createServiceFactory(
    const uno::Reference< lang::XMultiServiceFactory >& rxServiceMgr )
{
    return uno::Reference< lang::XSingleServiceFactory >(
        cppu::createOneInstanceFactory(
            rxServiceMgr,
            PasswordContainerInteractionHandler::getImplementationName_Static(),
            PasswordContainerInteractionHandler_CreateInstance,
            PasswordContainerInteractionHandler::getSupportedServiceNames_Static() ) );
}

} // namespace uui
