【Django1.4】django-social-authで"AuthAlreadyAssociated Exception"の解決の仕方【django-social-auth】

問題な場面

  • django-social-authを使う
  • Facebookアカウントでログイン後、ログアウト
  • Twitterアカウントでログイン後、ログアウトせずにFacebookアカウントでログインするurlにアクセス
  • Facebookで認証後自分のサイトに戻った時に"AuthAlreadyAssociated Exception"が投げられる

どこで"AuthAlreadyAssociated Exception"が投げられるか?

  • django-social-authはデフォルトで以下のpipelineを読み込む
(
   'social_auth.backends.pipeline.social.social_auth_user',
    #'social_auth.backends.pipeline.associate.associate_by_email',
    'social_auth.backends.pipeline.user.get_username',
    'social_auth.backends.pipeline.user.create_user',
    'social_auth.backends.pipeline.social.associate_user',
    'social_auth.backends.pipeline.social.load_extra_data',
    'social_auth.backends.pipeline.user.update_user_details'
)
  • 読み込むpipelineはsettings.pyでSOCIAL_AUTH_PIPELINEを与えることにより変更できる
  • AuthAlreadyAssociated Exceptionがraiseされるのはsocial_auth.backends.pipeline.social.social_auth_user
  • 試しに以下にすると(確か)"DatabaseError"となってAuthAlreadyAssociatedを投げてこない。
SOCIAL_AUTH_PIPELINE = (
    #'social_auth.backends.pipeline.social.social_auth_user',                                                           
    #'social_auth.backends.pipeline.associate.associate_by_email',
    'social_auth.backends.pipeline.user.get_username',
    'social_auth.backends.pipeline.user.create_user',
    'social_auth.backends.pipeline.social.associate_user',
    'social_auth.backends.pipeline.social.load_extra_data',
    'social_auth.backends.pipeline.user.update_user_details'
)   

social_auth.backends.pipeline.social.social_auth_userの代わりとなるpipelineを作る

def social_auth_user(backend, uid, user=None, *args, **kwargs):
    """Return UserSocialAuth account for backend/uid pair or None if it
    doesn't exists.

    Raise AuthAlreadyAssociated if UserSocialAuth entry belongs to another
    user.
    """
    social_user = UserSocialAuth.get_social_auth(backend.name, uid)
    if social_user:
        if user and social_user.user != user:
            msg = ugettext('This %(provider)s account is already in use.')
            raise AuthAlreadyAssociated(backend, msg % {
                'provider': backend.name
            })
        elif not user:
            user = social_user.user
    return {'social_user': social_user, 'user': user}

  • どうやら"if user and social_user.user != user:"の部分でraise AuthAlreadyAssociatedしている
  • そこで適当なアプリを作るなりしてそこにmysocial.pyなど適当な名前のモジュールを作成
  • 今回は startapp myappし、projectname/myapp/mysocial.py とする
  • mysocial.social_auth_userではAuthAlreadyAssociatedを投げる代わりにHttpResponseRedirectする
  • mysocial.py
 from social_auth.models import UserSocialAuth
 from django.http import HttpResponseRedirect


def social_auth_user(backend, uid, user=None, *args, **kwargs):
    """Return UserSocialAuth account for backend/uid pair or None if it
    doesn't exists.

    Raise AuthAlreadyAssociated if UserSocialAuth entry belongs to another
    user.
    """
    social_user = UserSocialAuth.get_social_auth(backend.name, uid)
    if social_user:
        if user and social_user.user != user:
            #msg = ugettext('This %(provider)s account is already in use.')
            #raise AuthAlreadyAssociated(backend, msg % {
                #'provider': backend.name
            #})
            return HttpResponseRedirect('/accounts/error/')

        elif not user:
            user = social_user.user
    return {'social_user': social_user, 'user': user}
  • settings.pyで設定して終わり
SOCIAL_AUTH_PIPELINE = (
    #'social_auth.backends.pipeline.social.social_auth_user',
    'myapp.mysocial.social_auth_user',                                                        
    #'social_auth.backends.pipeline.associate.associate_by_email',
    'social_auth.backends.pipeline.user.get_username',
    'social_auth.backends.pipeline.user.create_user',
    'social_auth.backends.pipeline.social.associate_user',
    'social_auth.backends.pipeline.social.load_extra_data',
    'social_auth.backends.pipeline.user.update_user_details'
)   
  • AuthAlreadyAssociatedが投げられるタイミングで/accounts/error/に飛んだ