Browse Source

Allow self-service github disconnect

Jordi Boggiano 6 years ago
parent
commit
26cb5bc371

+ 9 - 1
app/Resources/FOSUserBundle/views/Profile/edit_content.html.twig

@@ -46,8 +46,16 @@
 
     <a href="{{ app.user.githubId ? '#' : hwi_oauth_login_url('github') }}" class="btn btn-block btn-github btn-lg {{ app.user.githubId ? 'disabled' : 'btn-primary' }}">
         <span class="icon-github"></span>
-        {{ (app.user.githubId ? 'profile.accounts_connected' : 'profile.connect_accounts')|trans }}
+        {% set ghUser = app.user.githubUsername %}
+        {{ (app.user.githubId ? 'profile.accounts_connected' : 'profile.connect_accounts')|trans({"%user%": ghUser|default('unknown')}) }}
     </a>
+    {% if app.user.githubId %}
+        {% if ghUser == false %}
+            <p>Unknown GitHub user, token is missing, logout and login again via GitHub to fix it, or <a href="{{ path('user_github_disconnect', {token: csrf_token('unlink_github')}) }}">disconnect your GitHub account</a> entirely to connect another one.</p>
+        {% else %}
+            <p><a href="{{ path('user_github_disconnect', {token: csrf_token('unlink_github')}) }}">Disconnect your GitHub account</a> to connect another one.</p>
+        {% endif %}
+    {% endif %}
 </form>
 
 <div class="col-md-2">

+ 21 - 0
src/Packagist/WebBundle/Controller/UserController.php

@@ -181,6 +181,27 @@ class UserController extends Controller
         return $data;
     }
 
+    /**
+     * @Route("/oauth/github/disconnect", name="user_github_disconnect")
+     */
+    public function disconnectGitHubAction(Request $req)
+    {
+        $user = $this->getUser();
+        $token = $this->get('security.csrf.token_manager')->getToken('unlink_github')->getValue();
+        if (!hash_equals($token, $req->query->get('token', '')) || !$user) {
+            throw new AccessDeniedException('Invalid CSRF token');
+        }
+
+        if ($user->getGithubId()) {
+            $user->setGithubId(null);
+            $user->setGithubToken(null);
+            $user->setGithubScope(null);
+            $this->getDoctrine()->getEntityManager()->flush();
+        }
+
+        return $this->redirectToRoute('fos_user_profile_edit');
+    }
+
     /**
      * @Template()
      * @Route("/users/{name}/favorites/", name="user_favorites")

+ 24 - 0
src/Packagist/WebBundle/Entity/User.php

@@ -212,6 +212,30 @@ class User extends BaseUser
         return $this->githubId;
     }
 
+    /**
+     * Get githubId.
+     *
+     * @return string
+     */
+    public function getGithubUsername()
+    {
+        if ($this->githubId) {
+            if (!$this->githubToken) {
+                return false;
+            }
+
+            $ctxt = ['http' => ['header' => ['User-Agent: packagist.org']]];
+            $res = @file_get_contents('https://api.github.com/user?access_token='.$this->githubToken, false, stream_context_create($ctxt));
+            if (!$res || !($res = json_decode($res, true))) {
+                return false;
+            }
+
+            return $res['login'];
+        }
+
+        return false;
+    }
+
     /**
      * Set githubId.
      *

+ 1 - 1
src/Packagist/WebBundle/Resources/config/services.yml

@@ -1,7 +1,7 @@
 services:
     packagist.twig.extension:
         class: Packagist\WebBundle\Twig\PackagistExtension
-        arguments: [ '@packagist.provider_manager' ]
+        arguments: [ '@packagist.provider_manager', '@security.csrf.token_manager' ]
         tags:
             - { name: twig.extension }
 

+ 1 - 1
src/Packagist/WebBundle/Resources/translations/messages.en.yml

@@ -95,7 +95,7 @@ edit:
 profile:
     your_api_token: Your API Token
     show_api_token: Show API Token
-    accounts_connected: Accounts connected
+    accounts_connected: Account connected to %user%
     connect_accounts: Connect accounts
     notify_on_failure: Notify me of package update failures
     api_token_explain: |

+ 18 - 2
src/Packagist/WebBundle/Twig/PackagistExtension.php

@@ -3,6 +3,7 @@
 namespace Packagist\WebBundle\Twig;
 
 use Packagist\WebBundle\Model\ProviderManager;
+use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
 
 class PackagistExtension extends \Twig_Extension
 {
@@ -10,10 +11,13 @@ class PackagistExtension extends \Twig_Extension
      * @var ProviderManager
      */
     private $providerManager;
+    /** @var CsrfTokenManagerInterface */
+    private $csrfTokenManager;
 
-    public function __construct(ProviderManager $providerManager)
+    public function __construct(ProviderManager $providerManager, CsrfTokenManagerInterface$csrfTokenManager)
     {
         $this->providerManager = $providerManager;
+        $this->csrfTokenManager = $csrfTokenManager;
     }
 
     public function getTests()
@@ -29,7 +33,14 @@ class PackagistExtension extends \Twig_Extension
     {
         return array(
             new \Twig_SimpleFilter('prettify_source_reference', [$this, 'prettifySourceReference']),
-            new \Twig_SimpleFilter('gravatar_hash', [$this, 'generateGravatarHash'])
+            new \Twig_SimpleFilter('gravatar_hash', [$this, 'generateGravatarHash']),
+        );
+    }
+
+    public function getFunctions()
+    {
+        return array(
+            new \Twig_SimpleFunction('csrf_token', [$this, 'getCsrfToken']),
         );
     }
 
@@ -70,4 +81,9 @@ class PackagistExtension extends \Twig_Extension
     {
         return md5(strtolower($email));
     }
+
+    public function getCsrfToken($name)
+    {
+        return $this->csrfTokenManager->getToken($name)->getValue();
+    }
 }