Skip to content

Commit

Permalink
Add HTTP Basic Authentication support.
Browse files Browse the repository at this point in the history
  • Loading branch information
ChadSikorra committed Sep 2, 2017
1 parent b7e5b97 commit 80c0e89
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 8 deletions.
3 changes: 3 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ protected function addSecuritySection(ArrayNodeDefinition $node)
->info('Guard specific configuration options.')
->addDefaultsIfNotSet()
->children()
->booleanNode('http_basic')->defaultFalse()->end()
->scalarNode('http_basic_domain')->defaultNull()->end()
->scalarNode('http_basic_realm')->defaultNull()->end()
->scalarNode('login_path')->defaultValue('/login')->end()
->scalarNode('default_target_path')->defaultValue('/')->end()
->booleanNode('always_use_target_path')->defaultFalse()->end()
Expand Down
3 changes: 3 additions & 0 deletions DependencyInjection/LdapToolsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ protected function setGuardConfiguration(ContainerBuilder $container, array $con
'post_only' => $config['post_only'],
'remember_me' => $config['remember_me'],
'login_query_attribute' => $security['login_query_attribute'],
'http_basic' => $config['http_basic'],
'http_basic_domain' => $config['http_basic_domain'],
'http_basic_realm' => $config['http_basic_realm'],
]);
$container->getDefinition('ldap_tools.security.authentication.form_entry_point')
->addArgument(new Reference('security.http_utils'))
Expand Down
22 changes: 22 additions & 0 deletions Resources/doc/Configuration-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,28 @@ Set this to true if you want user roles re-queried on a user refresh from the Ld

These are Guard specific security settings under the `security.guard` section.

------------------
#### http_basic

Whether or not the guard should be used for HTTP basic authentication.

**Default**: `false`

------------------
#### http_basic_domain

A specific domain name to use for HTTP basic authentication. If not set it will use the default domain for LdapTools.

**Default**: `null`

------------------
#### http_basic_realm

A specific realm for HTTP basic authentication prompt. If not set it will first use the `http_basic_domain` value if set.
If that is not set it will use the default domain name from LdapTools.

**Default**: `null`

------------------
#### login_path

Expand Down
46 changes: 46 additions & 0 deletions Resources/doc/LDAP-Authentication-Provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ LDAP Authentication Provider

* [Symfony 2.8+](#symfony-28-use-the-guard-component)
* [Symfony 2.7](#symfony-23-use-ldap_tools_form-custom-authentication-type)
* [HTTP Basic Authentication](#http-basic-authentication)
* [LDAP Authentication Username](#ldap-authentication-username)
* [Mapping LDAP Groups to Roles](#mapping-ldap-groups-to-roles)
* [Mapping LDAP Attributes](#mapping-ldap-attributes)
Expand Down Expand Up @@ -102,6 +103,48 @@ security:
The LDAP provider is used for the purpose of these examples, but other user providers can be substituted. By default the
LDAP user provider provides an extended instance of `\LdapTools\Object\LdapObject`.

### HTTP Basic Authentication

HTTP Basic Authentication can be used by using a specific Guard setting. In your bundle config you would first set it:

```yaml
# app/config/config.yml
ldap_tools:
# Setup your domain like usual...
domains:
example:
domain_name: example.local
servers: [ dc1.example.local ]
use_tls: true
# Tell it you want the guard to do HTTP Basic auth...
security:
guard:
http_basic: true
```

Then make sure to set your security. This would secure the whole site with LDAP:

```yaml
# app/config/security.yml
security:
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
ldap:
id: ldap_tools.security.user.ldap_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
default:
guard:
authenticators:
- ldap_tools.security.ldap_guard_authenticator
```

## LDAP Authentication Username

The following logic is used to determine how to authenticate users against LDAP in the Guard/Authentication Provider of
Expand Down Expand Up @@ -219,6 +262,9 @@ ldap_tools:
failure_forward: false
failure_path_parameter: '_failure_path'
remember_me: false
http_basic: false
http_basic_domain: null
http_basic_realm: null
```

## Guard Redirection
Expand Down
72 changes: 66 additions & 6 deletions Security/LdapGuardAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint;

/**
* LDAP Guard Authenticator.
Expand Down Expand Up @@ -83,6 +84,9 @@ class LdapGuardAuthenticator extends AbstractGuardAuthenticator
'post_only' => false,
'remember_me' => false,
'login_query_attribute' => null,
'http_basic' => false,
'http_basic_realm' => null,
'http_basic_domain' => null,
];

/**
Expand Down Expand Up @@ -115,9 +119,9 @@ public function __construct($hideUserNotFoundExceptions = true, LdapUserChecker
public function getCredentials(Request $request)
{
$credentials = [
'username' => $this->getRequestParameter($this->options['username_parameter'], $request),
'password' => $this->getRequestParameter($this->options['password_parameter'], $request),
'ldap_domain' => $this->getRequestParameter($this->options['domain_parameter'], $request),
'username' => $this->getRequestUsername($request),
'password' => $this->getRequestPassword($request),
'ldap_domain' => $this->getRequestDomain($request),
];
if (empty($credentials['username'])) {
return null;
Expand Down Expand Up @@ -213,7 +217,7 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token,
);
$this->dispatcher->dispatch(AuthenticationHandlerEvent::SUCCESS, $event);

return $event->getResponse();
return $this->options['http_basic'] ? null : $event->getResponse();
}

/**
Expand All @@ -228,16 +232,17 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
);
$this->dispatcher->dispatch(AuthenticationHandlerEvent::FAILURE, $event);

return $event->getResponse();
return $this->options['http_basic'] ? null : $event->getResponse();
}

/**
* {@inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$entryPoint = $this->options['http_basic'] ? new BasicAuthenticationEntryPoint($this->getHttpBasicRealm()) : $this->entryPoint;
$event = new AuthenticationHandlerEvent(
$this->entryPoint->start($request, $authException),
$entryPoint->start($request, $authException),
$request,
$authException
);
Expand Down Expand Up @@ -265,6 +270,45 @@ public function createAuthenticatedToken(UserInterface $user, $providerKey)
return $token;
}

/**
* @param Request $request
* @return null|string
*/
protected function getRequestUsername(Request $request)
{
if ($this->options['http_basic']) {
return $request->server->get('PHP_AUTH_USER');
} else{
return $this->getRequestParameter($this->options['username_parameter'], $request);
}
}

/**
* @param Request $request
* @return null|string
*/
protected function getRequestPassword(Request $request)
{
if ($this->options['http_basic']) {
return $request->server->get('PHP_AUTH_PW');
} else{
return $this->getRequestParameter($this->options['password_parameter'], $request);
}
}

/**
* @param Request $request
* @return null|string
*/
protected function getRequestDomain(Request $request)
{
if ($this->options['http_basic']) {
return $this->options['http_basic_domain'];
} else{
return $this->getRequestParameter($this->options['domain_parameter'], $request);
}
}

/**
* @param string $param
* @param Request $request
Expand All @@ -280,4 +324,20 @@ protected function getRequestParameter($param, Request $request)

return $value;
}

/**
* @return string
*/
protected function getHttpBasicRealm()
{
if ($this->options['http_basic_realm'] !== null) {
$realm = $this->options['http_basic_realm'];
} elseif ($this->options['http_basic_domain'] !== null) {
$realm = $this->options['http_basic_domain'];
} else {
$realm = $this->ldap->getDomainContext();
}

return $realm;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ class LdapToolsExtensionSpec extends ObjectBehavior
'security' => [
'login_query_attribute' => null,
'guard' => [
'remember_me' => true
'remember_me' => true,
'http_basic' => false,
'http_basic_domain' => null,
'http_basic_realm' => null,
]
],
'domains' => [
Expand Down Expand Up @@ -159,6 +162,9 @@ function it_should_load_the_configuration($container, $cacheWarmer, $doctrineEve
"post_only" => false,
'remember_me' => true,
'login_query_attribute' => null,
'http_basic' => false,
'http_basic_domain' => null,
'http_basic_realm' => null,
])->shouldBeCalled();
$container->setParameter("ldap_tools.security.authentication.ldap_authentication_provider.options", [
'login_query_attribute' => null,
Expand Down Expand Up @@ -329,6 +335,9 @@ function it_should_allow_setting_a_specific_ldap_attribute_to_query_for_a_bind_d
"post_only" => false,
'remember_me' => true,
'login_query_attribute' => 'username',
'http_basic' => false,
'http_basic_domain' => null,
'http_basic_realm' => null,
])->shouldBeCalled();
$container->setParameter("ldap_tools.security.authentication.ldap_authentication_provider.options", [
'login_query_attribute' => 'username',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
Expand Down Expand Up @@ -108,7 +109,25 @@ function it_should_get_the_credentials_array()

$this->getCredentials($this->request)->shouldBeEqualTo(['username' => 'foo', 'password' => 'bar', 'ldap_domain' => 'foo.bar']);
}


function it_should_get_the_credentials_array_with_http_basic($ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, $ldapUserProvider)
{
$this->beConstructedWith(false, $this->userChecker, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, ['http_basic' => true], $ldapUserProvider);

$this->request->server->add(['PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar']);

$this->getCredentials($this->request)->shouldBeEqualTo(['username' => 'foo', 'password' => 'bar', 'ldap_domain' => null]);
}

function it_should_get_the_domain_in_the_credentials_array_with_http_basic_if_specified($ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, $ldapUserProvider)
{
$this->beConstructedWith(false, $this->userChecker, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, ['http_basic' => true, 'http_basic_domain' => 'foo.bar'], $ldapUserProvider);

$this->request->server->add(['PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar']);

$this->getCredentials($this->request)->shouldHaveKeyWithValue('ldap_domain', 'foo.bar');
}

function it_should_get_null_when_getting_the_credentials_array_with_no_username_set()
{
$this->getCredentials($this->request)->shouldBeNull();
Expand Down Expand Up @@ -386,4 +405,40 @@ function it_should_prefer_to_use_the_users_DN_on_login_if_available($connection)
$connection->execute(new AuthenticationOperation('cn=foo,dc=foo,dc=bar', 'bar'))->shouldBeCalled()->willReturn(new AuthenticationResponse(true));
$this->checkCredentials($credentials, $user)->shouldReturn(true);
}

function it_should_use_http_basic_authentication_on_start_if_specified($ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, $ldapUserProvider)
{
$this->beConstructedWith(false, $this->userChecker, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, ['http_basic' => true], $ldapUserProvider);

$response = new Response();
$response->headers->set('WWW-Authenticate', 'Basic realm="foo.bar"');
$response->setStatusCode(401);

$this->start($this->request, null)->shouldBeLike($response);
}

function it_should_return_null_when_using_http_basic_on_authentication_success(Request $request, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, $ldapUserProvider, TokenInterface $token)
{
$this->beConstructedWith(false, $this->userChecker, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, ['http_basic' => true], $ldapUserProvider);

$this->onAuthenticationSuccess($request, $token, 'main')->shouldBeNull();
}

function it_should_return_null_when_using_http_basic_on_authentication_failure(Request $request, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, $ldapUserProvider)
{
$this->beConstructedWith(false, $this->userChecker, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, ['http_basic' => true], $ldapUserProvider);

$this->onAuthenticationFailure($request, new AuthenticationException('foo'))->shouldBeNull();
}

function it_should_set_the_http_basic_realm_if_specified($ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, $ldapUserProvider)
{
$this->beConstructedWith(false, $this->userChecker, $ldap, $entryPoint, $dispatcher, $authSuccess, $authFailure, ['http_basic' => true, 'http_basic_realm' => 'Secret Area'], $ldapUserProvider);

$response = new Response();
$response->headers->set('WWW-Authenticate', 'Basic realm="Secret Area"');
$response->setStatusCode(401);

$this->start($this->request, null)->shouldBeLike($response);
}
}

0 comments on commit 80c0e89

Please sign in to comment.