【Python】python social auth - pipeline の各メソッドの内容を整理した【未完成】

Django REST × React で Google ログインボタンを作成する際、
python social auth の各メソッドに関してしっかり理解する必要があったためまとめてみました

ggってもほとんど情報が出てこなかったので、たぶん使ってる人ほとんどいないっぽいですので、
誰の役にも立たなさそうですが、、、

# 各メソッドの詳細

## create_user

```py
def create_user(strategy, details, backend, user=None, *args, **kwargs):
    if user:
        return {'is_new': False}

    fields = {name: kwargs.get(name, details.get(name))
                  for name in backend.setting('USER_FIELDS', USER_FIELDS)}
    if not fields:
        return

    return {
        'is_new': True,
        'user': strategy.create_user(**fields)
    }
```

- ユーザが見つからなかった場合作成するメソッド
- 引数にuserが存在している場合
  - is_new : False(つまり、新規作成ではない)としてreturnして終了
- fields変数に値を入力
  - おそらく、settings.pyのUSER_FIELDSのリストを取ってきて、そこで指定されているものをforで取得している
  - デフォルトはusernameとemail user.pyの一番上で初期化されている
  - 変数が空の場合何も返さず終了
- fields変数を元にdjango内のcreate_userメソッドを実行し、作成されたuserインスタンスを返す

---

## social_user

```py
def social_user(backend, uid, user=None, *args, **kwargs):
    provider = backend.name
    social = backend.strategy.storage.user.get_social_auth(provider, uid)
    if social:
        if user and social.user != user:
            raise AuthAlreadyAssociated(backend)
        elif not user:
            user = social.user
    return {'social': social,
            'user': user,
            'is_new': user is None,
            'new_association': social is None}

```

- 送られてきたアカウントがすでにdjangoアプリ上に存在するかどうかを検証するメソッド
- provider : 認証バックエンドの名前を指定
- social : backendのget_social_authメソッドを実行してユーザデータを引き抜いてると思う
- social が取得できた場合
  - 引数のuserが存在していて かつ 引数のuserとsocial.userが一致しない場合
    - ユーザーがすでに存在していますのエラーを発砲
  - 引数のuserが存在していない場合
    - social.user を user 変数に入れる
- social, user, is_new, new_association を返す
  - is_new: user is None : 引数のuserが存在しなかった場合Trueとして返す
  - new_association: social is None : socialに値が入らなかった場合Trueとして返す

---

## associate_user

```py
def associate_user(backend, uid, user=None, social=None, *args, **kwargs):
    if user and not social:
        try:
            social = backend.strategy.storage.user.create_social_auth(
                user, uid, backend.name
            )
        except Exception as err:
            if not backend.strategy.storage.is_integrity_error(err):
                raise
            # Protect for possible race condition, those bastard with FTL
            # clicking capabilities, check issue #131:
            #   https://github.com/omab/django-social-auth/issues/131
            result = social_user(backend, uid, user, *args, **kwargs)
            # Check if matching social auth really exists. In case it does
            # not, the integrity error probably had different cause than
            # existing entry and should not be hidden.
            if not result['social']:
                raise
            return result
        else:
            return {'social': social,
                    'user': social.user,
                    'new_association': True}
```

- ソーシャルアカウントと、サイト内のユーザアカウントを結びつけるレコードを作成するメソッド
- 引数のuserが存在していて かつ socialは存在していない場合
  - social変数に,user情報をもとにcreate_social_authメソッドを実行した値を入れる
  - ダメだった場合エラーを発砲

---

## user_detail

```py
def user_details(strategy, details, backend, user=None, *args, **kwargs):
    """Update user details using data from provider."""
    if not user:
        return

    changed = False  # flag to track changes

    # Default protected user fields (username, id, pk and email) can be ignored
    # by setting the SOCIAL_AUTH_NO_DEFAULT_PROTECTED_USER_FIELDS to True
    if strategy.setting('NO_DEFAULT_PROTECTED_USER_FIELDS') is True:
        protected = ()
    else:
        protected = ('username', 'id', 'pk', 'email', 'password',
                     'is_active', 'is_staff', 'is_superuser',)

    protected = protected + tuple(strategy.setting('PROTECTED_USER_FIELDS', []))

    # Update user model attributes with the new data sent by the current
    # provider. Update on some attributes is disabled by default, for
    # example username and id fields. It's also possible to disable update
    # on fields defined in SOCIAL_AUTH_PROTECTED_USER_FIELDS.
    field_mapping = strategy.setting('USER_FIELD_MAPPING', {}, backend)
    for name, value in details.items():
        # Convert to existing user field if mapping exists
        name = field_mapping.get(name, name)
        if value is None or not hasattr(user, name) or name in protected:
            continue

        current_value = getattr(user, name, None)
        if current_value == value:
            continue

        immutable_fields = tuple(strategy.setting('IMMUTABLE_USER_FIELDS', []))
        if name in immutable_fields and current_value:
            continue

        changed = True
        setattr(user, name, value)

    if changed:
        strategy.storage.user.changed(user)
```

- ソーシャルアカウントに変更点があった場合変更点を統合するメソッド
- user が None だったらreturnされて実行されない
- user field の保護設定
  - NO_DEFAULT_PROTECTED_USER_FIELDS : デフォルトで`False`で、usermodelの特定のフィールドに変更が起きないように保護できる
  - NO_DEFAULT_PROTECTED_USER_FIELDS = True を settings.py に入力すると、逆に保護を解除できる(つまり、Socialアカウント側に設定を寄せる)
  - SOCIAL_AUTH_PROTECTED_USER_FIELDS = ('some', 'thing', 'else') として設定すれば、保護したいフィールドを追加できる

## 出てくる謎の引数`strategy`, `backend`, `storage`について

### strategy

おそらく、今回の場合Djangoのこと
strategy.setting で、settings.py の中の設定を参照する
strategy.create_user() で、Django内のcreate_userメソッドを実行する

### backend

認証プロバイダー(今回の場合Google)のこと
詳細が不明だが`backend.something`と出てきたら、GoogleTokenの中身を参照しているはず

## storage

おそらく、認証プロバイダーのアカウントによって作成された情報が格納されたDBのことだと思う
多分、requestボディに入っているtokenIdをデコードして、username,emailとかを一時的に保存しているのか
もしかしたら、直接Google(twitter,facebook,etc)のデータベースにアクセスしているのかもしれない

```py
social = backend.strategy.storage.user.get_social_auth(provider, uid)
return user = social.user
```

user_detailsメソッドに↑のような処理があるが、
これは引数の`user`とstorageから引き出したuser情報を比較して、変更が見つかった場合、
storageから引き出した内容で上書きしてuserオブジェクトを返す処理となっている