How can I get Symfony2 switch_user / impersonating function to work with my NTLM user authentication

I am working on an intranet site that will use symfony 2.6.

I was able to create my own security provider / listener / etc to provide NTLM authentication (based on Windows login). I also managed to combine this with getting roles from the database.

I am now working on the switch_user feature, and I think my authentication conflicts with the impersonation process as shown in the logs alongside.

I am USER1 (from NTLM) and wish to impersonate USER2, the log entry with ** has been revised for clarity

[2015-07-18 22:17:47] request.INFO: Matched route "homepage_conception" (parameters: "mode": "conception", "_controller": "AppBundle\Controller\MainController::indexAction", "_route": "homepage_conception") [] []
[2015-07-18 22:17:48] security.DEBUG: Read SecurityContext from the session [] []
[2015-07-18 22:17:48] security.DEBUG: Reloading user from user provider. [] []
[2015-07-18 22:17:48] doctrine.DEBUG: ** SELECT query for USER1 (based on id) ** []
[2015-07-18 22:17:48] security.DEBUG: Username "USER1" was reloaded from user provider. [] []
[2015-07-18 22:17:48] app.DEBUG: NTLM Authentication - Handshake Step 1 [] []
[2015-07-18 22:17:48] app.DEBUG: NTLM Authentication - Requesting from client - WWW-Authenticate: NTLM [] []

      

During NTLM authentication, the request is resent 3 times (here is the 2nd round)

[2015-07-18 22:17:48] request.INFO: Matched route "homepage_conception" (parameters: "mode": "conception", "_controller": "AppBundle\Controller\MainController::indexAction", "_route": "homepage_conception") [] []
[2015-07-18 22:17:49] security.DEBUG: Read SecurityContext from the session [] []
[2015-07-18 22:17:49] security.DEBUG: Reloading user from user provider. [] []
[2015-07-18 22:17:49] doctrine.DEBUG: ** SELECT query for USER1 (based on id) ** []
[2015-07-18 22:17:49] security.DEBUG: Username "USER1" was reloaded from user provider. [] []
[2015-07-18 22:17:49] app.DEBUG: NTLM Authentication - Handshake Step 2 [] []
[2015-07-18 22:17:49] app.DEBUG: NTLM Authentication - Requesting from client - WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAAAICAgAAAAAAAAAAAAAAAA== [] []

      

Third round of NTLM

[2015-07-18 22:17:49] request.INFO: Matched route "homepage_conception" (parameters: "mode": "conception", "_controller": "AppBundle\Controller\MainController::indexAction", "_route": "homepage_conception") [] []
[2015-07-18 22:17:49] security.DEBUG: Read SecurityContext from the session [] []
[2015-07-18 22:17:49] security.DEBUG: Reloading user from user provider. [] []
[2015-07-18 22:17:49] doctrine.DEBUG: ** SELECT query for USER1 (based on id) ** []
[2015-07-18 22:17:49] security.DEBUG: Username "USER1" was reloaded from user provider. [] []
[2015-07-18 22:17:49] app.DEBUG: NTLM Authentication - Handshake Step 3 [] []
[2015-07-18 22:17:49] app.DEBUG: NTLM Authentication - Got user info, creating token with username USER1 [] []
[2015-07-18 22:17:49] doctrine.DEBUG: ** SELECT query to get full user object (based on username) ** ["USER1"] []
[2015-07-18 22:17:49] doctrine.DEBUG: ** SELECT query to know role for USER1 id from mapping table (based on id) ** ** []
[2015-07-18 22:17:50] doctrine.DEBUG: ** SELECT query to have role object (based on role id) ** [96] []
[2015-07-18 22:17:50] doctrine.DEBUG: ** SELECT query to have role object (based on role id) ** [97] []

      

I'm in! Attempting to switch user ...

[2015-07-18 22:17:50] security.INFO: Attempt to switch to user "USER2" [] []
[2015-07-18 22:17:50] doctrine.DEBUG: ** SELECT query to get full user object (based on username) ** ["USER2"] []
[2015-07-18 22:17:50] doctrine.DEBUG: ** SELECT query to know role for USER2 id from mapping table (based on id) ** [5] []
[2015-07-18 22:17:50] ... various event "kernel..." I can provide them if needed
[2015-07-18 22:17:50] event.DEBUG: Listener "Symfony\Component\Security\Http\Firewall::onKernelRequest" stopped propagation of the event "kernel.request". [] []
[2015-07-18 22:17:50] event.DEBUG: Listener "Symfony\Bundle\AsseticBundle\EventListener\RequestListener::onKernelRequest" was not called for event "kernel.request". [] []
[2015-07-18 22:17:50] security.DEBUG: Write SecurityContext in the session [] []
[2015-07-18 22:17:50] ... various event "kernel..." I can provide them if needed

      

Going again (!?) To NTLM handshake ... (1/3)

[2015-07-18 22:17:50] request.INFO: Matched route "homepage_conception" (parameters: "mode": "conception", "_controller": "AppBundle\Controller\MainController::indexAction", "_route": "homepage_conception") [] []
[2015-07-18 22:17:50] security.DEBUG: Read SecurityContext from the session [] []
[2015-07-18 22:17:50] security.DEBUG: Reloading user from user provider. [] []
[2015-07-18 22:17:50] doctrine.DEBUG: ** SELECT query for USER2 (based on id) ** []
[2015-07-18 22:17:50] security.DEBUG: Username "USER2" was reloaded from user provider. [] []
[2015-07-18 22:17:51] app.DEBUG: NTLM Authentication - Handshake Step 1 [] []
[2015-07-18 22:17:51] app.DEBUG: NTLM Authentication - Requesting from client - WWW-Authenticate: NTLM [] []

      

NTLM 2/3

[2015-07-18 22:17:51] request.INFO: Matched route "homepage_conception" (parameters: "mode": "conception", "_controller": "AppBundle\Controller\MainController::indexAction", "_route": "homepage_conception") [] []
[2015-07-18 22:17:51] security.DEBUG: Read SecurityContext from the session [] []
[2015-07-18 22:17:51] security.DEBUG: Reloading user from user provider. [] []
[2015-07-18 22:17:51] doctrine.DEBUG: ** SELECT query for USER2 (based on id) ** []
[2015-07-18 22:17:51] security.DEBUG: Username "USER2" was reloaded from user provider. [] []
[2015-07-18 22:17:51] app.DEBUG: NTLM Authentication - Handshake Step 2 [] []
[2015-07-18 22:17:51] app.DEBUG: NTLM Authentication - Requesting from client - WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAAAICAgAAAAAAAAAAAAAAAA== [] []

      

NTLM 3/3

[2015-07-18 22:17:52] request.INFO: Matched route "homepage_conception" (parameters: "mode": "conception", "_controller": "AppBundle\Controller\MainController::indexAction", "_route": "homepage_conception") [] []
[2015-07-18 22:17:52] security.DEBUG: Read SecurityContext from the session [] []
[2015-07-18 22:17:52] security.DEBUG: Reloading user from user provider. [] []
[2015-07-18 22:17:52] doctrine.DEBUG: ** SELECT query for USER2 (based on id) ** []
[2015-07-18 22:17:52] security.DEBUG: Username "USER2" was reloaded from user provider. [] []
[2015-07-18 22:17:52] app.DEBUG: NTLM Authentication - Handshake Step 3 [] []
[2015-07-18 22:17:52] app.DEBUG: NTLM Authentication - Got user info, creating token with username USER1 [] []
[2015-07-18 22:17:52] doctrine.DEBUG: ** SELECT query to get full user object (based on username) ** ["USER1"] []
[2015-07-18 22:17:52] doctrine.DEBUG: ** SELECT query to know role for USER1 id from mapping table (based on id) ** []
[2015-07-18 22:17:52] doctrine.DEBUG: ** SELECT query to have role object (based on role id) ** [96] []
[2015-07-18 22:17:52] doctrine.DEBUG: ** SELECT query to have role object (based on role id) ** [97] []
[2015-07-18 22:17:52] ... various event "kernel..." I can provide them if needed

      

Does anyone have any hints on how to make all the points work together?

Here is my NTLMListener code

namespace AppBundle\Security\Firewall;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use AppBundle\Security\Authentication\Token\NtlmUserToken;
use Symfony\Component\HttpKernel\Log\LoggerInterface;


class NtlmListener implements ListenerInterface
{
    protected $securityContext;
    protected $authenticationManager;
    protected $logger;

    public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger)
    {
        $this->securityContext = $securityContext;
        $this->authenticationManager = $authenticationManager;
        $this->logger = $logger;
    }

    private function unAuthorized($msg=null) { 
        $ntlm = 'WWW-Authenticate: NTLM'; 
        if ($msg) { 
            $ntlm .= ' '.$msg; 
        } 
        header('HTTP1.0 401 Unauthorized'); 
        header($ntlm); 

        $this->logger->debug("NTLM Authentication - Requesting from client - ".$ntlm);

        return 1; // NTLM_AUTH_FAILED
    } 

    private function getInfosFromNTLM() {             
        if (!empty($_SERVER['HTTP_VIA'])) { 
            return 2; // NTLM_PROXY
        } 

        $header = apache_request_headers(); 
        $auth = isset($header['Authorization']) ? $header['Authorization'] : null; 

        if (is_null($auth)) { 
            $this->logger->debug("NTLM Authentication - Handshake Step 1");
            return $this->unAuthorized(); 
        } 

        if ($auth && (substr($auth,0,4) == 'NTLM')) { 
            $c64 = base64_decode(substr($auth,5)); 
            $state = ord($c64{8}); 
            switch ($state) { 

                case 1: 
                    $this->logger->debug("NTLM Authentication - Handshake Step 2");
                    $chrs = array(0,2,0,0,0,0,0,0,0,40,0,0,0,1,130,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0);
                    $ret = "NTLMSSP"; 
                    foreach ($chrs as $chr) { 
                        $ret .= chr($chr); 
                    } 
                    return $this->unAuthorized(trim(base64_encode($ret))); 

                case 3: 
                    $this->logger->debug("NTLM Authentication - Handshake Step 3");
                $l = ord($c64{31}) * 256 + ord($c64{30}); 
                $o = ord($c64{33}) * 256 + ord($c64{32}); 
                $domain = str_replace("\0","",substr($c64,$o,$l)); 

                $l = ord($c64{39}) * 256 + ord($c64{38}); 
                $o = ord($c64{41}) * 256 + ord($c64{40}); 
                $user = str_replace("\0","",substr($c64,$o,$l)); 

                return array('domain'=>$domain,'user'=>$user); 
            } 
        } 
        return 3;
    }

    public function handle(GetResponseEvent $event)
    {

        header('WWW-Authenticate: Negociate');
        $infos = $this->getInfosFromNTLM(); 
        switch ($infos) { 
            case 3:
            case 2:

                // NTLM not available ... give up
                return;
            case 1:
                //header sent, client will re-send request (if NTLM aware)                  
                exit;
            default: 
        } 

        $this->logger->debug("NTLM Authentication - Got user info, creating token with username ".$infos["user"]);

        $token = new NtlmUserToken();
        $token->setUser($infos['user']);

        try {
            $authToken = $this->authenticationManager->authenticate($token);

            $this->securityContext->setToken($authToken);
        } catch (AuthenticationException $failed) {

            // Deny authentication with a '403 Forbidden' HTTP response
            $response = new Response($failed);
            $response->setStatusCode(403);
            $event->setResponse($response);

        }
    }
}

      

+3


source to share





All Articles