Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ p_rayno_cas_auth:
```
Note : the xml_namespace and options parameters are optionals

If you want any to access user attributes from the CAS response, inject the session into the user-provider in services.yml :
```yaml
services:
prayno.cas_user_provider:
class: PRayno\CasAuthBundle\Security\User\CasUserProvider
arguments: ['@session']
```

Modify your security.xml with the following values (the provider in the following settings should not be used as it's just a very basic example) :
```yaml
security:
Expand Down
16 changes: 13 additions & 3 deletions Security/CasAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use PRayno\CasAuthBundle\Security\User\CasUserCredentialStoreInterface;

class CasAuthenticator extends AbstractGuardAuthenticator
{
Expand All @@ -21,12 +22,14 @@ class CasAuthenticator extends AbstractGuardAuthenticator
protected $query_ticket_parameter;
protected $query_service_parameter;
protected $options;
protected $client;

/**
* Process configuration
* @param array $config
* @param optional Client $client
*/
public function __construct($config)
public function __construct($config, Client $client = null)
{
$this->server_login_url = $config['server_login_url'];
$this->server_validation_url = $config['server_validation_url'];
Expand All @@ -35,6 +38,11 @@ public function __construct($config)
$this->query_service_parameter = $config['query_service_parameter'];
$this->query_ticket_parameter = $config['query_ticket_parameter'];
$this->options = $config['options'];
if (is_null($client)) {
$this->client = new Client();
} else {
$this->client = $client;
}
}

/**
Expand All @@ -49,8 +57,7 @@ public function getCredentials(Request $request)
$request->get($this->query_ticket_parameter).'&'.
$this->query_service_parameter.'='.urlencode($this->removeCasTicket($request->getUri()));

$client = new Client();
$response = $client->request('GET', $url, $this->options);
$response = $this->client->request('GET', $url, $this->options);

$string = $response->getBody()->getContents();

Expand All @@ -73,6 +80,9 @@ public function getCredentials(Request $request)
public function getUser($credentials, UserProviderInterface $userProvider)
{
if (isset($credentials[$this->username_attribute])) {
if ($userProvider instanceof CasUserCredentialStoreInterface) {
$userProvider->storeUserCredentials($credentials);
}
return $userProvider->loadUserByUsername($credentials[$this->username_attribute]);
} else {
return null;
Expand Down
30 changes: 27 additions & 3 deletions Security/User/CasUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,29 @@
namespace PRayno\CasAuthBundle\Security\User;

use Symfony\Component\Security\Core\User\UserInterface;
use PRayno\CasAuthBundle\Security\User\CasUserAttributesInterface;

class CasUser implements UserInterface
class CasUser implements UserInterface, CasUserAttributesInterface
{
private $username;
private $password;
private $salt;
private $roles;
private $attributes;

/**
* @param $username
* @param $password
* @param $salt
* @param array $roles
*/
public function __construct($username, $password, $salt, array $roles)
public function __construct($username, $password, $salt, array $roles, array $attributes = array())
{
$this->username = $username;
$this->password = $password;
$this->salt = $salt;
$this->roles = $roles;
$this->attributes = $attributes;
}

/**
Expand Down Expand Up @@ -65,4 +68,25 @@ public function eraseCredentials()
{

}
}

/**
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}

/**
* @return mixed string, array, or null if not found.
*/
public function getAttribute($name)
{
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
} else {
return null;
}

}
}
18 changes: 18 additions & 0 deletions Security/User/CasUserAttributesInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace PRayno\CasAuthBundle\Security\User;

interface CasUserAttributesInterface
{

/**
* @return array
*/
public function getAttributes();

/**
* @return mixed string, array, or null if not found.
*/
public function getAttribute($name);

}
13 changes: 13 additions & 0 deletions Security/User/CasUserCredentialStoreInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace PRayno\CasAuthBundle\Security\User;

interface CasUserCredentialStoreInterface
{

/**
* @param array $credentials
*/
public function storeUserCredentials(array $credentials);

}
153 changes: 150 additions & 3 deletions Security/User/CasUserProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,38 @@
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;

class CasUserProvider implements UserProviderInterface
class CasUserProvider implements UserProviderInterface, CasUserCredentialStoreInterface
{

protected $user_attribute_bag;

/**
* @param SessionInterface $session
*/
public function __construct(SessionInterface $session = null) {
if (!is_null($session)) {
$this->user_attribute_bag = new AttributeBag('PRayno_CasAuthBundle_UserAttributes');
$session->registerBag($this->user_attribute_bag);
}
}

/**
* @param array $credentials
*/
public function storeUserCredentials(array $credentials) {
if (isset($this->user_attribute_bag)) {
if ($credentials['user']) {
$this->user_attribute_bag->set($credentials['user'], $this->getCasAttributes($credentials));
} else {
throw new \InvalidArgumentException('Credentials must contain a user property');
}
}
}

/**
* Provides the authenticated user a ROLE_USER
* @param $username
Expand All @@ -22,7 +51,14 @@ public function loadUserByUsername($username)
$salt = "";
$roles = ["ROLE_USER"];

return new CasUser($username, $password, $salt, $roles);
if (isset($this->user_attribute_bag) && $this->user_attribute_bag->has($username)) {
$attributes = $this->user_attribute_bag->get($username);
} else {
$attributes = array();
}
$user = new CasUser($username, $password, $salt, $roles, $attributes);

return $user;
}

throw new UsernameNotFoundException(
Expand Down Expand Up @@ -55,4 +91,115 @@ public function supportsClass($class)
{
return $class === 'PRayno\CasAuthBundle\Security\User\CasUser';
}
}

/**
* @param $credentials
* @retun array
*/
protected function getCasAttributes($credentials) {
$attras = array();

// "Jasig Style" & CAS 3.0 Attributes:
//
// <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
// <cas:authenticationSuccess>
// <cas:user>jsmith</cas:user>
// <cas:attributes>
// <cas:attraStyle>Jasig</cas:attraStyle>
// <cas:surname>Smith</cas:surname>
// <cas:givenName>John</cas:givenName>
// <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
// <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
// </cas:attributes>
// <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
// </cas:authenticationSuccess>
// </cas:serviceResponse>
//
if (isset($credentials['attributes'])) {
foreach ($credentials['attributes'] as $attribute) {
$name = $attribute->getName();
$value = $attribute->__toString();
// Make an attribute multi-valued on the second one seen.
if (isset($attras[$name]) && !is_array($attras[$name])) {
$tmp = $attras[$name];
$attras[$name] = array($tmp);
}
// Add multi-valued attributes.
if (isset($attras[$name])) {
$attras[$name][] = $value;
}
// Single-valued attributes.
else {
$attras[$name] = $value;
}
}
} else {
// "Name-Value" attributes.
//
// Attribute format from these mailing list thread:
// http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
// Note: This is a less widely used format, but in use by at least two institutions.
//
// <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
// <cas:authenticationSuccess>
// <cas:user>jsmith</cas:user>
//
// <cas:attribute name='attraStyle' value='Name-Value' />
// <cas:attribute name='surname' value='Smith' />
// <cas:attribute name='givenName' value='John' />
// <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
// <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
//
// <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
// </cas:authenticationSuccess>
// </cas:serviceResponse>
//
if (isset($credentials['attribute']) && isset($credentials['attribute'][0]->attributes()['name']) && isset($credentials['attribute'][0]->attributes()['value'])) {
foreach ($credentials['attribute'] as $attribute) {
$name = (string)$attribute->attributes()['name'];
$value = (string)$attribute->attributes()['value'];
// Make an attribute multi-valued on the second one seen.
if (isset($attras[$name]) && !is_array($attras[$name])) {
$tmp = $attras[$name];
$attras[$name] = array($tmp);
}
// Add multi-valued attributes.
if (isset($attras[$name])) {
$attras[$name][] = $value;
}
// Single-valued attributes.
else {
$attras[$name] = $value;
}
}
}
// "RubyCAS Style" attributes
//
// <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
// <cas:authenticationSuccess>
// <cas:user>jsmith</cas:user>
//
// <cas:attraStyle>RubyCAS</cas:attraStyle>
// <cas:surname>Smith</cas:surname>
// <cas:givenName>John</cas:givenName>
// <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
// <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
//
// <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
// </cas:authenticationSuccess>
// </cas:serviceResponse>
//
else {
// Test for elements other than our allowed list know to the protocol.
$skip = array('user', 'proxyGrantingTicket');
foreach ($credentials as $name => $value) {
if (!in_array($name, $skip)) {
$attras[$name] = $value;
}
}
}
}

return $attras;
}
}
7 changes: 7 additions & 0 deletions autoload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
// if library is in dev environement with its own vendor, include its autoload
if(file_exists(__DIR__ . '/vendor'))
require_once __DIR__ . '/vendor/autoload.php';
// if library is in vendor of another project, include the global autolaod
else
require_once __DIR__ . '/../../../../autoload.php';
27 changes: 27 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>

<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit
backupGlobals = "false"
backupStaticAttributes = "false"
colors = "false"
convertErrorsToExceptions = "true"
convertNoticesToExceptions = "true"
convertWarningsToExceptions = "true"
processIsolation = "false"
stopOnFailure = "false"
syntaxCheck = "false"
bootstrap = "autoload.php"
>



<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>



</phpunit>
Loading