diff --git a/README b/README deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6fec23 --- /dev/null +++ b/README.md @@ -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/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": { diff --git a/src/SilexOpauth/Security/OpauthListener.php b/src/SilexOpauth/Security/OpauthListener.php new file mode 100644 index 0000000..d0db85f --- /dev/null +++ b/src/SilexOpauth/Security/OpauthListener.php @@ -0,0 +1,83 @@ +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..dbce984 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthProvider.php @@ -0,0 +1,51 @@ +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..1d4e581 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthResult.php @@ -0,0 +1,97 @@ +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..a19e6a7 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthSilexProvider.php @@ -0,0 +1,99 @@ +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..f9a6965 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthToken.php @@ -0,0 +1,36 @@ +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..5f94406 --- /dev/null +++ b/src/SilexOpauth/Security/OpauthUserProviderInterface.php @@ -0,0 +1,15 @@ +