Enhancement(機能追加・改善) #4014
未完了member_configに格納しているメールアドレスを独立したテーブルに分離する
0%
説明
Overview (概要)¶
OpenPNE3 では SNS メンバーのメールアドレスを member_config テーブルに格納しており、下記の name を割り当てている。
(「設定が完了していない」状態とは #3829-3 で指摘しているような「所有者確認が済んでいないメールアドレス」を指す)
- pc_address
- PCメールアドレス
- mobile_address
- 携帯メールアドレス
- pc_address_pre
- 設定が完了していないPCメールアドレス
- mobile_address_pre
- 設定が完了していない携帯メールアドレス
これらのメールアドレスに関する設定は下記のような問題点を抱えている。
問題点1: ユニーク制約を設定できないテーブル構造¶
メールアドレスは SNS メンバーのログインおよびパスワード再発行にも使用される重要な役割を担っている。また、その用途から自明なとおり 1 つのメールアドレスに対応する SNS メンバーが 2 人以上存在してはならない。
しかし、メールアドレスを格納している member_config テーブルに同じメールアドレスが複数含まれないようにユニーク制約を設定することはできない。member_config はメールアドレス以外にも様々な設定を扱っており、value に対してユニーク制約を設定してしまうと他のメンバーと設定が重複しうる別の設定項目にも影響してしまうためである。
そのため現在に至るまで pc_address, mobile_address には DB スキーマ上のユニーク制約を設けることができないまま、アプリケーション側でメールアドレスの重複が生じないように対処し続けてきた。
しかし #1816 のように一度でも重複チェックに漏れが生じると member_config テーブルには重複したメールアドレスが残り続けてしまう。その後の修正も #1816 自体の修正だけでは足らず、 #2340 のように既に重複したメールアドレスが存在していても正しく動作させるためにレコードの更新日時をチェックするといった修正も行われることになる。
もしユニーク制約が設定できる設計であったならば、仮に #1816 のような不具合が発生しても DB にレコードが追加される前に異常終了するため、その不具合のみ修正されれば十分だったはずである。
問題点2: pc_address と mobile_address の区別¶
PCメールアドレスと携帯メールアドレスの違いは opToolkit::isMobileEmailAddress()
によって携帯メールアドレスと判断されるか否かの違いであり、いわゆる キャリアメール が携帯メールアドレスとして扱われている。PCメールアドレスと携帯メールアドレスは利用できる機能にも下記のような違いがある。
- 「メール投稿」関連の機能は携帯メールアドレスからのみ利用できる
- ログイン用メールアドレスに「PCメールアドレスと携帯メールアドレスのどちらか一方を受け付ける」を設定しているSNSでは、PCメールアドレスではPC版のみ、携帯メールアドレスでは携帯版のみにログインできる
- この設定は後にデフォルトではなくなった (master, 3.6.x および 3.8.x の最新バージョン)
スマートフォンから OpenPNE を利用する場合は携帯 IP アドレス帯域制限により mobile_frontend は使用できないが、そのような携帯端末でも新規登録にキャリアメールを使用すると mobile_address として登録されるためログインできなくなるという問題も過去に生じていた (#3155)。
また、Gmail などとキャリアメールの双方を扱える携帯端末が増えたことで pc_address, mobile_address の区別が形骸化しつつあり、携帯 (スマートフォン) で Gmail を使用しているユーザーが「携帯メールアドレス」の設定画面に @gmail.com
のメールアドレスを入力するとエラーになるといった不親切な状態も招いている。
Spec (仕様)¶
これらの問題点を解決するために、下記のような email_address テーブルを新たに追加する。
Member:
actAs: [Timestampable, opActivateBehavior]
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(64), default: "", notnull: true }
invite_member_id: { type: integer(4) }
is_login_rejected: { type: boolean, notnull: true, default: false }
primary_email_address_id: { type: integer(4) } # 追加
relations:
Member: { local: invite_member_id, foreign: id, onDelete: set null }
PrimaryEmailAddress: { class: EmailAddress, local: primary_email_id, onDelete: set null } # 追加
# 新規テーブル
EmailAddress:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
email_address: { type: string(255), notnull: true }
member_id: { type: integer(4), notnull: true }
verified: { type: boolean, notnull: true }
relations:
Member: { onDelete: cascade }
indexes:
email_address_UNIQUE:
fields: [email_address]
type: unique
DB スキーマの詳細¶
- メンバーは 0 個以上のメールアドレスを持つことができる
- 0 個を許容するのは OpenID や LDAP などメールアドレスを用いない認証方式を考慮するため
- 2 個以上を許容するのは既に pc_address, mobile_address の両方にメールアドレスを設定しているメンバーを考慮するため
- メールアドレスに紐付くメンバーは常に 1 人のみ存在する
- メールアドレスは pc, mobile の区別を行わずに格納する
- メールアドレス変更や新規登録などにおける、所有者確認が済んでいないメールアドレスは
email_address.verified = 0
で表す{pc,mobile}_address_pre
に相当する- #4012 における対応と異なり、これらのメールアドレスについても一切の重複を認めない
- メンバーは自ら設定しているメールアドレスのうち 1 つを
member.primary_email_address_id
に設定できる
互換性に関する考慮¶
- メールテンプレートなど pc, mobile の区別が必要な場面ではその都度
opToolkit::isMobileEmailAddress()
を使用して判別する - member_config テーブルの pc_address, mobile_address は引き続き参照できる状態にする
- pc_address, mobile_address を直接参照しているプラグインとの互換性を保つため
- メンバーの PrimaryEmailAddress が変更されると、設定されたメールアドレスが pc_address, mobile_address のいずれか一方にも設定される
- PrimaryEmailAddress のメールアドレスが pc_address に設定された場合、mobile_address のレコードが存在すれば削除される (常にどちらか一方のみ存在する状態になる)