فهرست منبع

Merge remote-tracking branch 'stof/github-login'

Conflicts:
	composer.json
	composer.lock
Jordi Boggiano 12 سال پیش
والد
کامیت
661d7bb3c2

+ 1 - 0
app/AppKernel.php

@@ -20,6 +20,7 @@ class AppKernel extends Kernel
             new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
             new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
             new FOS\UserBundle\FOSUserBundle(),
+            new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),
             new Snc\RedisBundle\SncRedisBundle(),
             new Packagist\WebBundle\PackagistWebBundle(),
             new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),

+ 3 - 0
app/Resources/FOSUserBundle/views/Profile/show.html.twig

@@ -7,6 +7,9 @@
         <h1>{{ user.username }} (that's you!)</h1>
         <p><a href="{{ path('fos_user_profile_edit') }}">Edit your information</a></p>
         <p><a href="{{ path('fos_user_change_password') }}">Change your password</a></p>
+        {% if not user.githubId %}
+        <p><a href="{{ path('hwi_oauth_connect_service', {'service': 'github'}) }}">Connect your github account</a></p>
+        {% endif %}
         <p><a href="{{ path('user_profile', {'name':app.user.username}) }}">View your public profile</a></p>
 
         {% if app.user.apiToken %}

+ 1 - 1
app/Resources/FOSUserBundle/views/Security/login.html.twig

@@ -5,7 +5,7 @@
     <div>{{ error }}</div>
 {% endif %}
 
-<form action="{{ path("fos_user_security_check") }}" method="post">
+<form action="{{ path('login_check') }}" method="post">
     <div>
         <label for="username">{{ 'security.login.username'|trans({}, 'FOSUserBundle') }}</label>
         <input type="text" id="username" name="_username" value="{{ last_username }}" />

+ 35 - 0
app/Resources/HWIOAuthBundle/views/Connect/login.html.twig

@@ -0,0 +1,35 @@
+{% extends 'HWIOAuthBundle::layout.html.twig' %}
+
+{% block hwi_oauth_content %}
+    {% if error %}
+        <div>{{ error }}</div>
+    {% endif %}
+    {% for owner in hwi_oauth_resource_owners() %}
+    <a class="submit" href="{{ hwi_oauth_login_url(owner) }}">Login with {{ owner | trans({}, 'HWIOAuthBundle') }}</a> <br />
+    {% endfor %}
+
+    {# HWIOAuthBundle uses the same template for the login and the connect functionality currently
+       so we need to check if the user is already authenticated. #}
+    {% if not app.user %}
+    <form action="{{ path('login_check') }}" method="post">
+        <div>
+            <label for="username">{{ 'security.login.username'|trans({}, 'FOSUserBundle') }}</label>
+            <input type="text" id="username" name="_username" />
+        </div>
+
+        <div>
+            <label for="password">{{ 'security.login.password'|trans({}, 'FOSUserBundle') }}</label>
+            <input type="password" id="password" name="_password" />
+        </div>
+
+        <div>
+            <input type="checkbox" id="remember_me" name="_remember_me" value="on" checked="checked" />
+            <label for="remember_me">{{ 'security.login.remember_me'|trans({}, 'FOSUserBundle') }}</label>
+        </div>
+
+        <input type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}" />
+    </form>
+
+    <a href="{{ path('fos_user_resetting_request') }}">Forgot password?</a>
+    {% endif %}
+{% endblock hwi_oauth_content %}

+ 7 - 0
app/Resources/HWIOAuthBundle/views/layout.html.twig

@@ -0,0 +1,7 @@
+{% extends 'PackagistWebBundle::layout.html.twig' %}
+
+{% block content %}
+    <div class="box clearfix">
+        {% block hwi_oauth_content %}{% endblock %}
+    </div>
+{% endblock %}

+ 12 - 0
app/config/config.yml

@@ -74,6 +74,18 @@ fos_user:
         form:
             handler: packagist.form.handler.registration
 
+hwi_oauth:
+    firewall_name: main
+    connect:
+        account_connector: packagist.user_provider
+        registration_form_handler: packagist.oauth.registration_form_handler
+        registration_form: packagist.oauth.registration_form
+    resource_owners:
+        github:
+            type:          github
+            client_id:     %github.client_id%
+            client_secret: %github.client_secret%
+
 nelmio_solarium:
     adapter: ~
 

+ 4 - 1
app/config/parameters.yml.dist

@@ -23,4 +23,7 @@ parameters:
     remember_me.secret: CHANGE_ME_IN_PROD
 
     google_analytics:
-        ga_key:
+        ga_key:
+
+    github.client_id:
+    github.client_secret:

+ 23 - 3
app/config/routing.yml

@@ -2,9 +2,6 @@ _packagist:
     resource: "@PackagistWebBundle/Controller"
     type:     annotation
 
-fos_user_security:
-    resource: "@FOSUserBundle/Resources/config/routing/security.xml"
-
 fos_user_profile:
     resource: "@FOSUserBundle/Resources/config/routing/profile.xml"
     prefix: /profile
@@ -25,3 +22,26 @@ fos_user_resetting:
 
 fos_user_change_password:
     resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"
+
+
+hwi_oauth_connect:
+    resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"
+    prefix:   /connect
+
+# overrides the fosub /login page
+hwi_oauth_login:
+    resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
+    prefix:   /login
+
+hwi_oauth_redirect:
+    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
+    prefix:   /login
+
+github_check:
+    pattern: /login/check-github
+
+logout:
+    pattern: /logout
+
+login_check:
+    pattern: /login_check

+ 8 - 1
app/config/security.yml

@@ -26,6 +26,13 @@ security:
                 lifetime: 31104000 # 1y
             logout:       true
             anonymous:    true
+            oauth:
+                resource_owners:
+                    github: "/login/check-github"
+                login_path:        /login
+                failure_path:      /login
+                oauth_user_provider:
+                    service: packagist.user_provider
             switch_user:
                 provider: packagist
 
@@ -52,4 +59,4 @@ security:
         ROLE_EDIT_PACKAGES: ~
 
         ROLE_ADMIN:       [ ROLE_USER, ROLE_UPDATE_PACKAGES, ROLE_EDIT_PACKAGES, ROLE_DELETE_PACKAGES ]
-        ROLE_SUPERADMIN:  [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
+        ROLE_SUPERADMIN:  [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]

+ 1 - 0
composer.json

@@ -40,6 +40,7 @@
 
         "composer/composer": "dev-master",
         "friendsofsymfony/user-bundle": "2.0.*",
+        "hwi/oauth-bundle": "dev-master",
         "nelmio/solarium-bundle": "dev-master",
         "predis/predis": "0.7.*",
         "snc/redis-bundle": "dev-master",

+ 123 - 1
composer.lock

@@ -1,5 +1,5 @@
 {
-    "hash": "b50cdf1c09be1b7696e6ca2f95a9e5e2",
+    "hash": "ba9c0437196577b6006030095f3a42d9",
     "packages": [
         {
             "name": "composer/composer",
@@ -407,6 +407,82 @@
                 "User management"
             ]
         },
+        {
+            "name": "hwi/oauth-bundle",
+            "version": "dev-master",
+            "target-dir": "HWI/Bundle/OAuthBundle",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/hwi/HWIOAuthBundle",
+                "reference": "8397d4b0dd878bbac4abd60ab22480ce9ca7bb28"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://github.com/hwi/HWIOAuthBundle/zipball/8397d4b0dd878bbac4abd60ab22480ce9ca7bb28",
+                "reference": "8397d4b0dd878bbac4abd60ab22480ce9ca7bb28",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2",
+                "symfony/framework-bundle": ">=2.0,<2.2-dev",
+                "symfony/security-bundle": ">=2.0,<2.2-dev",
+                "kriswallsmith/buzz": "0.7"
+            },
+            "require-dev": {
+                "doctrine/orm": "*",
+                "friendsofsymfony/user-bundle": "*",
+                "symfony/twig-bundle": "*",
+                "symfony/validator": ">=2.0,<2.2-dev"
+            },
+            "suggest": {
+                "friendsofsymfony/user-bundle": "to connect FOSUB with this bundle",
+                "symfony/twig-bundle": "to use the Twig hwi_oauth_* functions",
+                "doctrine/doctrine-bundle": "to use Doctrine user provider"
+            },
+            "time": "1346424865",
+            "type": "symfony-bundle",
+            "installation-source": "source",
+            "autoload": {
+                "psr-0": {
+                    "HWI\\Bundle\\OAuthBundle": ""
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Geoffrey Bachelet",
+                    "email": "geoffrey.bachelet@gmail.com",
+                    "homepage": "https://github.com/ubermuda"
+                },
+                {
+                    "name": "Alexander",
+                    "email": "alexander@hardware.info"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/hwi/HWIOAuthBundle/contributors"
+                }
+            ],
+            "description": "Support for authenticating users via oauth in Symfony2.",
+            "homepage": "http://github.com/hwi/HWIOAuthBundle",
+            "keywords": [
+                "security",
+                "oauth",
+                "oauth2",
+                "github",
+                "twitter",
+                "firewall",
+                "google",
+                "facebook",
+                "linkedin",
+                "windows live",
+                "vkontakte",
+                "oauth1",
+                "sensio connect"
+            ]
+        },
         {
             "name": "jms/aop-bundle",
             "version": "1.0.0",
@@ -789,6 +865,51 @@
                 "minification"
             ]
         },
+        {
+            "name": "kriswallsmith/buzz",
+            "version": "v0.7",
+            "source": {
+                "type": "git",
+                "url": "http://github.com/kriswallsmith/Buzz.git",
+                "reference": "v0.7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://github.com/kriswallsmith/Buzz/zipball/v0.7",
+                "reference": "v0.7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "suggest": {
+                "ext-curl": "*"
+            },
+            "time": "2012-06-25 19:01:38",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-0": {
+                    "Buzz": "lib/"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Kris Wallsmith",
+                    "email": "kris.wallsmith@gmail.com",
+                    "homepage": "http://kriswallsmith.net/"
+                }
+            ],
+            "description": "Lightweight HTTP client",
+            "homepage": "https://github.com/kriswallsmith/Buzz",
+            "keywords": [
+                "curl",
+                "http client"
+            ]
+        },
         {
             "name": "monolog/monolog",
             "version": "dev-master",
@@ -1992,6 +2113,7 @@
     "minimum-stability": "dev",
     "stability-flags": {
         "composer/composer": 20,
+        "hwi/oauth-bundle": 20,
         "nelmio/solarium-bundle": 20,
         "snc/redis-bundle": 20,
         "white-october/pagerfanta-bundle": 20

+ 27 - 1
src/Packagist/WebBundle/Entity/User.php

@@ -50,6 +50,12 @@ class User extends BaseUser
      */
     private $apiToken;
 
+    /**
+     * @ORM\Column(type="string", length=255, nullable=true)
+     * @var string
+     */
+    private $githubId;
+
     public function __construct()
     {
         $this->packages = new ArrayCollection();
@@ -145,4 +151,24 @@ class User extends BaseUser
     {
         return $this->apiToken;
     }
-}
+
+    /**
+     * Get githubId.
+     *
+     * @return string
+     */
+    public function getGithubId()
+    {
+        return $this->githubId;
+    }
+
+    /**
+     * Set githubId.
+     *
+     * @param string $githubId
+     */
+    public function setGithubId($githubId)
+    {
+        $this->githubId = $githubId;
+    }
+}

+ 101 - 0
src/Packagist/WebBundle/Form/Handler/OAuthRegistrationFormHandler.php

@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Form\Handler;
+
+use FOS\UserBundle\Model\UserManagerInterface;
+use FOS\UserBundle\Util\TokenGeneratorInterface;
+use HWI\Bundle\OAuthBundle\Form\RegistrationFormHandlerInterface;
+use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
+use HWI\Bundle\OAuthBundle\OAuth\Response\AdvancedUserResponseInterface;
+use Symfony\Component\Form\Form;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * OAuthRegistrationFormHandler
+ *
+ * @author Alexander <iam.asm89@gmail.com>
+ */
+class OAuthRegistrationFormHandler implements RegistrationFormHandlerInterface
+{
+    private $userManager;
+    private $tokenGenerator;
+
+    /**
+     * Constructor.
+     *
+     * @param UserManagerInterface $userManager
+     * @param TokenGeneratorInterface $tokenGenerator
+     */
+    public function __construct(UserManagerInterface $userManager, TokenGeneratorInterface $tokenGenerator)
+    {
+        $this->tokenGenerator = $tokenGenerator;
+        $this->userManager = $userManager;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function process(Request $request, Form $form, UserResponseInterface $userInformation)
+    {
+        $user = $this->userManager->createUser();
+
+        // Try to get some properties for the initial form when coming from github
+        if ('GET' === $request->getMethod()) {
+            $user->setUsername($this->getUniqueUsername($userInformation->getNickname()));
+
+            if ($userInformation instanceof AdvancedUserResponseInterface) {
+                $user->setEmail($userInformation->getEmail());
+            }
+        }
+
+        $form->setData($user);
+
+        if ('POST' === $request->getMethod()) {
+            $form->bind($request);
+
+            if ($form->isValid()) {
+                $randomPassword = $this->tokenGenerator->generateToken();
+                $user->setPlainPassword($randomPassword);
+                $user->setEnabled(true);
+
+                $apiToken = substr($this->tokenGenerator->generateToken(), 0, 20);
+                $user->setApiToken($apiToken);
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Attempts to get a unique username for the user.
+     *
+     * @param string $name
+     *
+     * @return string Name, or empty string if it failed after 10 times
+     *
+     * @see HWI\Bundle\OAuthBundle\Form\FOSUBRegistrationHandler
+     */
+    protected function getUniqueUserName($name)
+    {
+        $i = 0;
+        $testName = $name;
+
+        do {
+            $user = $this->userManager->findUserByUsername($testName);
+        } while ($user !== null && $i < 10 && $testName = $name.++$i);
+
+        return $user !== null ? '' : $testName;
+    }
+}

+ 42 - 0
src/Packagist/WebBundle/Form/Type/OAuthRegistrationFormType.php

@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+
+class OAuthRegistrationFormType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $builder
+            ->add('username', null, array('label' => 'form.username', 'translation_domain' => 'FOSUserBundle'))
+            ->add('email', 'email', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'))
+        ;
+    }
+
+    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    {
+        $resolver->setDefaults(array(
+            'data_class' => 'Packagist\WebBundle\Entity\User',
+            'intention'  => 'registration',
+            'validation_groups' => array('Default', 'Profile'),
+        ));
+    }
+
+    public function getName()
+    {
+        return 'packagist_oauth_user_registration';
+    }
+}

+ 16 - 0
src/Packagist/WebBundle/Resources/config/services.yml

@@ -32,3 +32,19 @@ services:
     fos_user.util.user_manipulator:
         class: Packagist\WebBundle\Util\UserManipulator
         arguments: [@fos_user.user_manager, @fos_user.util.token_generator]
+
+    packagist.oauth.registration_form_handler:
+        class: Packagist\WebBundle\Form\Handler\OAuthRegistrationFormHandler
+        arguments: [@fos_user.user_manager, @fos_user.util.token_generator]
+
+    packagist.oauth.registration_form_type:
+        class: Packagist\WebBundle\Form\Type\OAuthRegistrationFormType
+        tags:
+            - { name: form.type, alias: packagist_oauth_user_registration }
+
+    packagist.oauth.registration_form:
+        factory_method: create
+        factory_service: form.factory
+        class: Symfony\Component\Form\Form
+        arguments:
+            - 'packagist_oauth_user_registration'

+ 2 - 2
src/Packagist/WebBundle/Resources/views/Web/index.html.twig

@@ -64,9 +64,9 @@ require 'vendor/autoload.php';
                 <h2>Commit The File</h2>
                 <p>You surely don't need help with that.</p>
                 <h2>Publish It</h2>
-                <p><a href="{{ path('fos_user_security_login') }}">Login</a> or <a href="{{ path('fos_user_registration_register') }}">register</a> on this site, then hit the big fat green button above that says <a href="{{ path('submit') }}">submit</a>.</p>
+                <p><a href="{{ path('hwi_oauth_connect') }}">Login</a> or <a href="{{ path('fos_user_registration_register') }}">register</a> on this site, then hit the big fat green button above that says <a href="{{ path('submit') }}">submit</a>.</p>
                 <p>Once you entered your public repository URL in there, your package will be automatically crawled periodically. You just have to make sure you keep the composer.json file up to date.</p>
             </div>
         </section>
     </div>
-{% endblock %}
+{% endblock %}

+ 4 - 4
src/Packagist/WebBundle/Resources/views/layout.html.twig

@@ -38,11 +38,11 @@
         <div class="container">
             <div class="user">
                 {% if app.user %}
-                    <a href="{{ path('fos_user_profile_show') }}">{{ app.user.username }}</a> | <a href="{{ path('fos_user_security_logout') }}">Logout</a>
+                    <a href="{{ path('fos_user_profile_show') }}">{{ app.user.username }}</a> | <a href="{{ path('logout') }}">Logout</a>
                 {% else %}
                     <a href="{{ path('fos_user_registration_register') }}">Create a new account</a>
                     |
-                    <a href="{{ path('fos_user_security_login') }}">Login</a>
+                    <a href="{{ path('hwi_oauth_connect') }}">Login</a>
                 {% endif %}
             </div>
 
@@ -86,10 +86,10 @@
             <ul>
                 {% if app.user %}
                     <li><a href="{{ path('fos_user_profile_show') }}">{{ 'menu.profile'|trans }}</a></li>
-                    <li><a href="{{ path('fos_user_security_logout') }}">{{ 'menu.logout'|trans }}</a></li>
+                    <li><a href="{{ path('logout') }}">{{ 'menu.logout'|trans }}</a></li>
                 {% else %}
                     <li><a href="{{ path('fos_user_registration_register') }}">{{ 'menu.register'|trans }}</a></li>
-                    <li><a href="{{ path('fos_user_security_login') }}">{{ 'menu.login'|trans }}</a></li>
+                    <li><a href="{{ path('hwi_oauth_connect') }}">{{ 'menu.login'|trans }}</a></li>
                 {% endif %}
             </ul>
             <ul>

+ 45 - 1
src/Packagist/WebBundle/Security/Provider/UserProvider.php

@@ -13,11 +13,15 @@
 namespace Packagist\WebBundle\Security\Provider;
 
 use FOS\UserBundle\Model\UserManagerInterface;
+use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
+use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
+use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException;
+use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface;
 use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
 use Symfony\Component\Security\Core\User\UserProviderInterface;
 use Symfony\Component\Security\Core\User\UserInterface;
 
-class UserProvider implements UserProviderInterface
+class UserProvider implements OAuthAwareUserProviderInterface, UserProviderInterface
 {
     /**
      * @var UserManagerInterface
@@ -32,6 +36,46 @@ class UserProvider implements UserProviderInterface
         $this->userManager = $userManager;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function connect($user, UserResponseInterface $response)
+    {
+        $username = $response->getUsername();
+
+        $previousUser = $this->userManager->findUserBy(array('githubId' => $username));
+
+        // The account is already connected. Do nothing
+        if ($previousUser === $user) {
+            return;
+        }
+
+        // 'disconnect' a previous account
+        if (null !== $previousUser) {
+            $previousUser->setGithubId(null);
+            $this->userManager->updateUser($previousUser);
+        }
+
+        $user->setGithubId($username);
+
+        $this->userManager->updateUser($user);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
+    {
+        $username = $response->getUsername();
+        $user = $this->userManager->findUserBy(array('githubId' => $username));
+
+        if (!$user) {
+            throw new AccountNotLinkedException(sprintf('No user with github username "%s" was found.', $username));
+        }
+
+        return $user;
+    }
+
     /**
      * {@inheritDoc}
      */