From 603881859013eafaf0ff9738849c08d460b38ae7 Mon Sep 17 00:00:00 2001 From: Rafal Lindemann Date: Mon, 22 Jul 2013 18:30:06 +0200 Subject: [PATCH 1/3] Security classes --- README | 75 +++++++++++++++ src/SilexOpauth/Security/OpauthListener.php | 75 +++++++++++++++ src/SilexOpauth/Security/OpauthProvider.php | 47 +++++++++ src/SilexOpauth/Security/OpauthResult.php | 96 +++++++++++++++++++ .../Security/OpauthSilexProvider.php | 95 ++++++++++++++++++ src/SilexOpauth/Security/OpauthToken.php | 32 +++++++ .../Security/OpauthUserProviderInterface.php | 16 ++++ 7 files changed, 436 insertions(+) create mode 100644 src/SilexOpauth/Security/OpauthListener.php create mode 100644 src/SilexOpauth/Security/OpauthProvider.php create mode 100644 src/SilexOpauth/Security/OpauthResult.php create mode 100644 src/SilexOpauth/Security/OpauthSilexProvider.php create mode 100644 src/SilexOpauth/Security/OpauthToken.php create mode 100644 src/SilexOpauth/Security/OpauthUserProviderInterface.php diff --git a/README b/README index e69de29..a6fec23 100644 --- a/README +++ b/README @@ -0,0 +1,75 @@ +# Usage + +## Simple, event driven silex extension + +```php + // Configure opauth + $app['opauth'] = array( + 'login' => '/auth', + 'callback' => '/auth/callback', + 'config' => array( + 'security_salt' => '...your salt...', + 'Strategy' => array( + 'Facebook' => array( + 'app_id' => '...', + 'app_secret' => '...' + ), + ) + ) + ); + + // Enable extension + $app->register(new OpauthExtension()); + + // Listen for events + $app->dispatcher->addListener(OpauthExtension::EVENT_ERROR, function($e) { + $this->log->error('Auth error: ' . $e['message'], ['response' => $e->getSubject()]); + $e->setArgument('result', $this->redirect('/')); + }); + + $app->dispatcher->addListener(OpauthExtension::EVENT_SUCCESS, function($e) { + $response = $e->getSubject(); + + /* + find/create a user, oauth response is in $response and it's already validated! + store the user in the session + */ + + $e->setArgument('result', $this->redirect('/')); + }); +``` + +## Advanced, symfony security listener+provider + +Note, that you can use it in symfony2 projects too! + +To login using opauth use /login/PROVIDER, or use `opauth_default_login` route with `provider` parameter. + +```php + + $app->register(new OpauthSilexProvider()); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'pattern' => '^/.*', + 'opauth' => array( + // 'check_path' => '/login/opauth', //default + 'opauth' => [ + // 'path' => '/login', //default + 'security_salt' => '...your salt...', + 'Strategy' => [ + // your opauth strategies go here + ] + ] + ), + 'anonymous' => true, + ), + ) + ); + +``` + +By default, users will be looked up by username "provider:uid". + +You should extend your user provider to handle OPauth results correctly by implementing `OpauthUserProviderInterface`. + diff --git a/src/SilexOpauth/Security/OpauthListener.php b/src/SilexOpauth/Security/OpauthListener.php new file mode 100644 index 0000000..76e2178 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthListener.php @@ -0,0 +1,75 @@ +httpUtils->checkRequestPath($request, 'opauth_'.($this->providerKey).'_login')) { + return true; + } + + return parent::requiresAuthentication($request); + } + + + protected function attemptAuthentication(Request $request) + { + + $config = array_merge( + array( + 'callback_url' => $this->options['check_path'], + 'callback_transport' => 'post' // Won't work with silex session + ), + $this->options['opauth'] + ); + + + if (parent::requiresAuthentication($request)) { + if (!isset($_POST['opauth'])) { + throw new AuthenticationException('opauth post parameter is missing'); + } + // check_path + $opauth = new Opauth($config, false); + + $response = unserialize(base64_decode($_POST['opauth'])); + + $failureReason = null; + /** + * Check if it's an error callback + */ + if (array_key_exists('error', $response)) { + throw new AuthenticationException($response['error']); + } else { + if (empty($response['auth']) || empty($response['timestamp']) || empty($response['signature']) || empty($response['auth']['provider']) || empty($response['auth']['uid'])) { + throw new AuthenticationException('Missing key auth response components'); + } elseif (!$opauth->validate(sha1(print_r($response['auth'], true)), $response['timestamp'], $response['signature'], $failureReason)) { + throw new AuthenticationException($failureReason); + } else { + $token = new OpauthToken(new OpauthResult($response['auth'])); + return $this->authenticationManager->authenticate($token); + } + } + + } else { + // this should redirect or print, it's an opauth thing... + new Opauth($config); + // we need to exit now unfortunately... + exit(); + } + + + } + + +} \ No newline at end of file diff --git a/src/SilexOpauth/Security/OpauthProvider.php b/src/SilexOpauth/Security/OpauthProvider.php new file mode 100644 index 0000000..129129e --- /dev/null +++ b/src/SilexOpauth/Security/OpauthProvider.php @@ -0,0 +1,47 @@ +userProvider = $userProvider; + } + + public function authenticate(TokenInterface $token) + { + /* @var $token OpauthToken */ + if ($this->userProvider instanceof OpauthUserProviderInterface) { + $user = $this->userProvider->loadUserByOpauthResult($token->getOpauthResult()); + } else { + $username = $token->getOpauthResult()->getProvider() . ':' . $token->getOpauthResult()->getUid(); + $user = $this->userProvider->loadUserByUsername($username); + } + + if ($user) { + $authenticatedToken = new OpauthToken($token->getOpauthResult(), $user->getRoles()); + $authenticatedToken->setUser($user); + + return $authenticatedToken; + } + + throw new AuthenticationException('The OPauth authentication failed.'); + } + + + public function supports(TokenInterface $token) + { + return $token instanceof OpauthToken; + } +} \ No newline at end of file diff --git a/src/SilexOpauth/Security/OpauthResult.php b/src/SilexOpauth/Security/OpauthResult.php new file mode 100644 index 0000000..cf34940 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthResult.php @@ -0,0 +1,96 @@ +auth = $auth; + } + + + public function getAuth() + { + return $this->auth; + } + + + public function getUid() + { + return $this->auth['uid']; + } + + + public function getToken() + { + return $this->auth['credentials']['token']; + } + + + public function getExpiry() + { + return strtotime($this->auth['credentials']['expires']); + } + + + public function getInfo() + { + return $this->auth['info']; + } + + + public function getRaw() + { + return $this->auth['info']; + } + + + public function getProvider() + { + return $this->auth['provider']; + } + + + public function getName() + { + return $this->auth['info']['name']; + } + + + public function getEmail() + { + return $this->auth['info']['email']; + } + + + public function getNickname() + { + return isset($this->auth['info']['nickname']) ? $this->auth['info']['nickname'] : null; + } + + public function getPicture() + { + return isset($this->auth['info']['image']) ? $this->auth['info']['image'] : null; + } + + + public function serialize() + { + return serialize($this->auth); + } + + + public function unserialize($serialized) + { + $this->auth = unserialize($serialized); + } + + +} \ No newline at end of file diff --git a/src/SilexOpauth/Security/OpauthSilexProvider.php b/src/SilexOpauth/Security/OpauthSilexProvider.php new file mode 100644 index 0000000..17694c3 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthSilexProvider.php @@ -0,0 +1,95 @@ +registerListener($app); + + } + + protected function registerListener(Application $app) { + $app['security.authentication_listener.factory.opauth'] = $app->protect(function ($name, $options) use ($app) { + + $options = array_merge_recursive($options, array( + 'check_path' => '/login/opauth', + 'opauth' => array( + 'path' => '/login/', + ) + )); + + if (!isset($app['security.authentication.success_handler.'.$name])) { + $app['security.authentication.success_handler.'.$name] = $app['security.authentication.success_handler._proto']($name, $options); + } + + if (!isset($app['security.authentication.failure_handler.'.$name])) { + $app['security.authentication.failure_handler.'.$name] = $app['security.authentication.failure_handler._proto']($name, $options); + } + + // define the authentication provider object + if (!isset($app['security.authentication_provider.'.$name.'.opauth'])) { + $app['security.authentication_provider.'.$name.'.opauth'] = $app->share(function () use ($app) { + return new OpauthProvider($app['security.user_provider.default']); + }); + } + + // define the authentication listener object + if (!isset($app['security.authentication_listener.'.$name.'.opauth'])) { + $app['security.authentication_listener.'.$name.'.opauth'] = $app->share(function () use ($app, $name, $options) { + return new OpauthListener( + $app['security'], + $app['security.authentication_manager'], + isset($app['security.session_strategy.'.$name]) ? $app['security.session_strategy.'.$name] : $app['security.session_strategy'], + $app['security.http_utils'], + $name, + $app['security.authentication.success_handler.'.$name], + $app['security.authentication.failure_handler.'.$name], + $options, + $app['logger'], + $app['dispatcher'] + ); + }); + } + + // routes +// $this->onBoot[] = function() use ($app, $options, $name) { + $bindName = "opauth_{$name}_"; + $app->match($options['check_path'], function() {})->bind($bindName . 'check'); + $app->match($options['opauth']['path'] . '{strategy}/{return}', function() {}) + ->value('return', '') + ->bind($bindName . 'login'); +// }; + + return array( + // the authentication provider id + 'security.authentication_provider.'.$name.'.opauth', + // the authentication listener id + 'security.authentication_listener.'.$name.'.opauth', + // the entry point id + null, + // the position of the listener in the stack + 'pre_auth' + ); + }); + } + + + public function boot(Application $app) { + foreach($this->onBoot as $c) { + call_user_func($c); + } + } + + +} \ No newline at end of file diff --git a/src/SilexOpauth/Security/OpauthToken.php b/src/SilexOpauth/Security/OpauthToken.php new file mode 100644 index 0000000..c32a0a4 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthToken.php @@ -0,0 +1,32 @@ +setAuthenticated(count($roles) > 0); + $this->setAttribute('opauth', $result); + } + + public function getCredentials() + { + return ''; + } + + /** @return OpauthResult */ + public function getOpauthResult() + { + return $this->getAttribute('opauth'); + } + +} \ No newline at end of file diff --git a/src/SilexOpauth/Security/OpauthUserProviderInterface.php b/src/SilexOpauth/Security/OpauthUserProviderInterface.php new file mode 100644 index 0000000..8406c65 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthUserProviderInterface.php @@ -0,0 +1,16 @@ + Date: Mon, 22 Jul 2013 18:39:14 +0200 Subject: [PATCH 2/3] Readme in markdown, updated authors in composer --- README => README.md | 0 composer.json | 5 +++++ 2 files changed, 5 insertions(+) rename README => README.md (100%) diff --git a/README b/README.md similarity index 100% rename from README rename to README.md diff --git a/composer.json b/composer.json index 69a9c77..ecabb04 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,11 @@ "name": "Florin Popa", "email": "pp.flrn@gmail.com", "homepage": "http://florinpopa.net" + }, + { + "name": "Rafal Lindemann", + "email": "rl@stamina.pl", + "homepage": "http://stamina.pl" } ], "autoload": { From 5abadf2505b5dc6d171934d57bc4281a0d3a592a Mon Sep 17 00:00:00 2001 From: Rafal Lindemann Date: Mon, 22 Jul 2013 18:48:15 +0200 Subject: [PATCH 3/3] Unified formatting --- src/SilexOpauth/Security/OpauthListener.php | 36 +++++++++++-------- src/SilexOpauth/Security/OpauthProvider.php | 6 +++- src/SilexOpauth/Security/OpauthResult.php | 3 +- .../Security/OpauthSilexProvider.php | 18 ++++++---- src/SilexOpauth/Security/OpauthToken.php | 6 +++- .../Security/OpauthUserProviderInterface.php | 3 +- 6 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/SilexOpauth/Security/OpauthListener.php b/src/SilexOpauth/Security/OpauthListener.php index 76e2178..d0db85f 100644 --- a/src/SilexOpauth/Security/OpauthListener.php +++ b/src/SilexOpauth/Security/OpauthListener.php @@ -12,13 +12,13 @@ * */ class OpauthListener extends AbstractAuthenticationListener { - + protected function requiresAuthentication(Request $request) { - if ($this->httpUtils->checkRequestPath($request, 'opauth_'.($this->providerKey).'_login')) { + if ($this->httpUtils->checkRequestPath($request, 'opauth_' . ($this->providerKey) . '_login')) { return true; } - + return parent::requiresAuthentication($request); } @@ -28,12 +28,11 @@ protected function attemptAuthentication(Request $request) $config = array_merge( array( - 'callback_url' => $this->options['check_path'], - 'callback_transport' => 'post' // Won't work with silex session - ), - $this->options['opauth'] + 'callback_url' => $this->options['check_path'], + 'callback_transport' => 'post' // Won't work with silex session + ), $this->options['opauth'] ); - + if (parent::requiresAuthentication($request)) { if (!isset($_POST['opauth'])) { @@ -41,7 +40,7 @@ protected function attemptAuthentication(Request $request) } // check_path $opauth = new Opauth($config, false); - + $response = unserialize(base64_decode($_POST['opauth'])); $failureReason = null; @@ -51,24 +50,33 @@ protected function attemptAuthentication(Request $request) if (array_key_exists('error', $response)) { throw new AuthenticationException($response['error']); } else { - if (empty($response['auth']) || empty($response['timestamp']) || empty($response['signature']) || empty($response['auth']['provider']) || empty($response['auth']['uid'])) { + if (empty($response['auth']) + || empty($response['timestamp']) + || empty($response['signature']) + || empty($response['auth']['provider']) + || empty($response['auth']['uid']) + ) { throw new AuthenticationException('Missing key auth response components'); - } elseif (!$opauth->validate(sha1(print_r($response['auth'], true)), $response['timestamp'], $response['signature'], $failureReason)) { + } elseif ( + !$opauth->validate( + sha1(print_r($response['auth'], true)), + $response['timestamp'], + $response['signature'], + $failureReason + ) + ) { throw new AuthenticationException($failureReason); } else { $token = new OpauthToken(new OpauthResult($response['auth'])); return $this->authenticationManager->authenticate($token); } } - } else { // this should redirect or print, it's an opauth thing... new Opauth($config); // we need to exit now unfortunately... exit(); } - - } diff --git a/src/SilexOpauth/Security/OpauthProvider.php b/src/SilexOpauth/Security/OpauthProvider.php index 129129e..dbce984 100644 --- a/src/SilexOpauth/Security/OpauthProvider.php +++ b/src/SilexOpauth/Security/OpauthProvider.php @@ -12,6 +12,7 @@ * */ class OpauthProvider implements AuthenticationProviderInterface { + protected $userProvider; public function __construct(UserProviderInterface $userProvider) @@ -19,6 +20,7 @@ public function __construct(UserProviderInterface $userProvider) $this->userProvider = $userProvider; } + public function authenticate(TokenInterface $token) { /* @var $token OpauthToken */ @@ -28,7 +30,7 @@ public function authenticate(TokenInterface $token) $username = $token->getOpauthResult()->getProvider() . ':' . $token->getOpauthResult()->getUid(); $user = $this->userProvider->loadUserByUsername($username); } - + if ($user) { $authenticatedToken = new OpauthToken($token->getOpauthResult(), $user->getRoles()); $authenticatedToken->setUser($user); @@ -44,4 +46,6 @@ public function supports(TokenInterface $token) { return $token instanceof OpauthToken; } + + } \ No newline at end of file diff --git a/src/SilexOpauth/Security/OpauthResult.php b/src/SilexOpauth/Security/OpauthResult.php index cf34940..1d4e581 100644 --- a/src/SilexOpauth/Security/OpauthResult.php +++ b/src/SilexOpauth/Security/OpauthResult.php @@ -74,7 +74,8 @@ public function getNickname() { return isset($this->auth['info']['nickname']) ? $this->auth['info']['nickname'] : null; } - + + public function getPicture() { return isset($this->auth['info']['image']) ? $this->auth['info']['image'] : null; diff --git a/src/SilexOpauth/Security/OpauthSilexProvider.php b/src/SilexOpauth/Security/OpauthSilexProvider.php index 17694c3..a19e6a7 100644 --- a/src/SilexOpauth/Security/OpauthSilexProvider.php +++ b/src/SilexOpauth/Security/OpauthSilexProvider.php @@ -9,17 +9,20 @@ /** * @author Rafal Lindemann * */ -class OpauthSilexProvider implements ServiceProviderInterface { +class OpauthSilexProvider implements ServiceProviderInterface +{ protected $onBoot = array(); - - public function register(Application $app) { + + public function register(Application $app) + { $this->registerListener($app); - } - protected function registerListener(Application $app) { + protected function registerListener(Application $app) + { + $app['security.authentication_listener.factory.opauth'] = $app->protect(function ($name, $options) use ($app) { $options = array_merge_recursive($options, array( @@ -85,8 +88,9 @@ protected function registerListener(Application $app) { } - public function boot(Application $app) { - foreach($this->onBoot as $c) { + public function boot(Application $app) + { + foreach ($this->onBoot as $c) { call_user_func($c); } } diff --git a/src/SilexOpauth/Security/OpauthToken.php b/src/SilexOpauth/Security/OpauthToken.php index c32a0a4..f9a6965 100644 --- a/src/SilexOpauth/Security/OpauthToken.php +++ b/src/SilexOpauth/Security/OpauthToken.php @@ -1,4 +1,5 @@ setAttribute('opauth', $result); } + public function getCredentials() { return ''; } - + + /** @return OpauthResult */ public function getOpauthResult() { return $this->getAttribute('opauth'); } + } \ No newline at end of file diff --git a/src/SilexOpauth/Security/OpauthUserProviderInterface.php b/src/SilexOpauth/Security/OpauthUserProviderInterface.php index 8406c65..5f94406 100644 --- a/src/SilexOpauth/Security/OpauthUserProviderInterface.php +++ b/src/SilexOpauth/Security/OpauthUserProviderInterface.php @@ -9,8 +9,7 @@ */ interface OpauthUserProviderInterface { - + function loadUserByOpauthResult(OpauthResult $result); - }