Slide 1

Slide 1 text

iOSDCを支えるDrupal8 Drupal 8 - backend of iOSDC 長谷川智希 HASEGAWA Tomoki

Slide 2

Slide 2 text

この資料は後ほど公開します This slide will be published later.

Slide 3

Slide 3 text

ライフワーク: Web / iOSアプリ開発, ビール, 電子工作,
 サッカー観戦, レンタルカートレース, … 長谷川 智希 Web / iOS App Development, Beer, IoT, Watch soccer match, Rental Kart Racing, … デジタルサーカス株式会社 副団長CTO Digital Circus, Inc. Vice-master CTO Tokyo, Japan Hobby: @tomzoh

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

WE ARE HIRING!! Web Development Mobile App Development ( ) (iOS, Android) http://www.dgcircus.com Omotesando, Tokyo

Slide 7

Slide 7 text

今日のテーマ Drupal 8 - backend of iOSDC iOSDCを支えるDrupal8 Today’s theme

Slide 8

Slide 8 text

iOSDC https://iosdc.jp

Slide 9

Slide 9 text

技術カンファレンス • スピーカーを募集して技術トークをする。 • チケットを販売。 • スポンサーを募集して快適度アップ。 • 運営は公募したボランティアスタッフ。 • 開催情報は公式サイトにて告知。

Slide 10

Slide 10 text

技術カンファレンス • スピーカーを募集して技術トークをする。 • チケットを販売。 • スポンサーを募集して快適度アップ。 • 運営は公募したボランティアスタッフ。 • 開催情報は公式サイトにて告知。

Slide 11

Slide 11 text

技術カンファレンス • スピーカーを募集して技術トークをする。 • チケットを販売。 • スポンサーを募集して快適度アップ。 • 運営は公募したボランティアスタッフ。 • 開催情報は公式サイトにて告知。

Slide 12

Slide 12 text

技術カンファレンス • スピーカーを募集して技術トークをする。 • チケットを販売。 • スポンサーを募集して快適度アップ。 • 運営は公募したボランティアスタッフ。 • 開催情報は公式サイトにて告知。

Slide 13

Slide 13 text

技術カンファレンス • スピーカーを募集して技術トークをする。 • チケットを販売。 • スポンサーを募集して快適度アップ。 • 運営は公募したボランティアスタッフ。 • 開催情報は公式サイトにて告知。

Slide 14

Slide 14 text

募集

Slide 15

Slide 15 text

数多くのフォーム

Slide 16

Slide 16 text

トーク応募フォーム

Slide 17

Slide 17 text

スポンサー申込フォーム

Slide 18

Slide 18 text

当日スタッフ募集フォーム

Slide 19

Slide 19 text

スピーカーディナー参加フォーム

Slide 20

Slide 20 text

フォームが増えるたびに いちいちプログラムを作るのか…

Slide 21

Slide 21 text

Slide 22

Slide 22 text

ではどうするか

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

iOSDCを支える技術

Slide 26

Slide 26 text

iOSDCを支える技術

Slide 27

Slide 27 text

今日のテーマ Drupal 8 - backend of iOSDC iOSDCを支えるDrupal8 Today’s theme

Slide 28

Slide 28 text

Googleフォーム & スプレッドシート

Slide 29

Slide 29 text

Google フォーム • フォームを作るサービス。 • いくつかの入力形式。 • テキスト入力 • チェックボックス • ラジオボタン • 複数ページ対応なども可能。 • アンケート的な集計も可。

Slide 30

Slide 30 text

Google スプレッドシート • Excel • 複数人での同時編集可。 • Googleフォームの入力内容を 流し込める • APIからアクセスできる。

Slide 31

Slide 31 text

• 開催が決まる • 各種募集を行う • コアスタッフ • トーク • スポンサー • デザインができる • Webサイトができる カンファレンス × CMS

Slide 32

Slide 32 text

• 開催が決まる • 各種募集を行う • コアスタッフ • トーク • スポンサー • デザインができる • Webサイトができる カンファレンス × CMS

Slide 33

Slide 33 text

• 開催が決まる • 各種募集を行う • コアスタッフ • トーク • スポンサー • デザインができる • Webサイトができる カンファレンス × CMS オンラインコラボレーション

Slide 34

Slide 34 text

• 開催が決まる • 各種募集を行う • コアスタッフ • トーク • スポンサー • デザインができる • Webサイトができる カンファレンス × CMS オンラインコラボレーション 情報公開

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録

Slide 37

Slide 37 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募

Slide 38

Slide 38 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募 購入者データ

Slide 39

Slide 39 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募 購入者データ 参加者 アイコン

Slide 40

Slide 40 text

Case 1: Googleフォーム → Drupal

Slide 41

Slide 41 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募 購入者データ 参加者 アイコン

Slide 42

Slide 42 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募 購入者データ 参加者 アイコン Google API + CRON

Slide 43

Slide 43 text

Googleスプレッドシートアクセス • 2通りの方法: • Google Developers Consoleで認証情報を設定して、OSSの PHP用モジュールをインストール • データ取得用のURLからデータ取得 • iOSDCでは後者を使用。 • データが読めれば良かった。(書き込みは不要) • 後者は認証が不要でラク。 • どちらにしても値のアクセスはやや面倒。

Slide 44

Slide 44 text

function get_cfps(){ $url = 'https://spreadsheets.google.com/feeds/cells/xxx/yyy/public/values?alt=json'; $json = file_get_contents($url); $data = json_decode($json, true); // CfP parser used as generator function sheet_parser($data){ $row_cursor = 1; $cols = []; foreach ($data['feed']['entry'] as $cell){ $c = $cell['gs$cell']; $row = intval($c['row']); if ($row !== $row_cursor){ yield $cols; $cols = []; $row_cursor = $row; } $cols[] = trim($c['$t']); } } $rows = sheet_parser($data); $cfps = []; foreach ($rows as $row){ if (! trim(implode('', $row))){ break; } $cfps[] = $row; } array_shift($cfps); return $cfps; }

Slide 45

Slide 45 text

function get_cfps(){ $url = 'https://spreadsheets.google.com/feeds/cells/xxx/yyy/public/values?alt=json'; $json = file_get_contents($url); $data = json_decode($json, true); // CfP parser used as generator function sheet_parser($data){ $row_cursor = 1; $cols = []; foreach ($data['feed']['entry'] as $cell){ $c = $cell['gs$cell']; $row = intval($c['row']); if ($row !== $row_cursor){ yield $cols; $cols = []; $row_cursor = $row; } $cols[] = trim($c['$t']); } } $rows = sheet_parser($data); $cfps = []; foreach ($rows as $row){ if (! trim(implode('', $row))){ break; } $cfps[] = $row; } array_shift($cfps); return $cfps; } JSONでデータ取得

Slide 46

Slide 46 text

function get_cfps(){ $url = 'https://spreadsheets.google.com/feeds/cells/xxx/yyy/public/values?alt=json'; $json = file_get_contents($url); $data = json_decode($json, true); // CfP parser used as generator function sheet_parser($data){ $row_cursor = 1; $cols = []; foreach ($data['feed']['entry'] as $cell){ $c = $cell['gs$cell']; $row = intval($c['row']); if ($row !== $row_cursor){ yield $cols; $cols = []; $row_cursor = $row; } $cols[] = trim($c['$t']); } } $rows = sheet_parser($data); $cfps = []; foreach ($rows as $row){ if (! trim(implode('', $row))){ break; } $cfps[] = $row; } array_shift($cfps); return $cfps; } JSONでデータ取得 セルが1つずつJSONデータに 入っているのですべてループし て取り出す。

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

列: 1, 行: 1, データ形式: テキ スト, データ: “タイムスタンプ”

Slide 49

Slide 49 text

列: 2, 行: 1, データ形式: テキ スト, データ: “トーク時間”

Slide 50

Slide 50 text

列: 3, 行: 1, データ形式: テキ スト, データ: “トークタイトル”

Slide 51

Slide 51 text

列: 3, 行: 1, データ形式: テキ スト, データ: “トークタイトル” TSURAI

Slide 52

Slide 52 text

Drupalのcronタスク get('cfp_importer.cron_last_run'); Drupal::logger('cfp_importer')->debug( 'Now: '.date('Y/m/d H:i:s', REQUEST_TIME)."\n". 'Last run : '.date('Y/m/d H:i:s', $last_run)."\n". 'Latest hour: '.date('Y/m/d H:i:s', $the_hour)); // Skip this cron if cron run after latest hour. if (($last_run) and ($last_run > $the_hour)){ //Drupal::logger('cfp_importer')->debug('Skip cron'); //return; } (略) // Update cron_last_run to next time. Drupal::state()->set('cfp_importer.cron_last_run', REQUEST_TIME); }

Slide 53

Slide 53 text

Case 2: PassMarket (CSV) → Drupal

Slide 54

Slide 54 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募 購入者データ 参加者 アイコン

Slide 55

Slide 55 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募 購入者データ 参加者 アイコン + モジュール

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

PassMarket(CSV) → Drupal • 参加者データ(CSV)ダウンロードにパスワード を要求される。 • TSURAI • Guzzleとかcurlとかで頑張ってやれば出来なくは ない。 • 時限でしか使わないモノで「頑張る」のも
 TSURAI。

Slide 60

Slide 60 text

頑張らない

Slide 61

Slide 61 text

こうした • 手動でCSVダウンロード • 諸般の事情で複数のイベントがあるので6回ほど同じ手順を繰 り返してダウンロード • Dropboxで本番サーバに同期 • Dropbox for Linuxを使う。 • ローカルPCに置いたデータが本番サーバに同期される • 自作モジュールでインポート • Drupalユーザを作成する • 特定のディレクトリのCSVを全部開いてインポート

Slide 62

Slide 62 text

こうした • 手動でCSVダウンロード • 諸般の事情で複数のイベントがあるので6回ほど同じ手順を繰 り返してダウンロード • Dropboxで本番サーバに同期 • Dropbox for Linuxを使う。 • ローカルPCに置いたデータが本番サーバに同期される • 自作モジュールでインポート • Drupalユーザを作成する • 特定のディレクトリのCSVを全部開いてインポート 職人の手による暖かみのある手作業

Slide 63

Slide 63 text

: foreach ($lines as $line){ if (! $line){ continue; } $cols = explode(",", $line); $email = substr($cols[6], 1, -1); $order_no = substr($cols[0], 1, -1); $nids_order_no = \Drupal::entityQuery('user') ->condition('field_ticket_order_no', $order_no) ->execute(); $nids_email = \Drupal::entityQuery('user') ->condition('mail', $email) ->execute(); if ((count($nids_order_no) > 0) or (count($nids_email) > 0)){ // Existing user : } else { // New user. : } } :

Slide 64

Slide 64 text

: foreach ($lines as $line){ if (! $line){ continue; } $cols = explode(",", $line); $email = substr($cols[6], 1, -1); $order_no = substr($cols[0], 1, -1); $nids_order_no = \Drupal::entityQuery('user') ->condition('field_ticket_order_no', $order_no) ->execute(); $nids_email = \Drupal::entityQuery('user') ->condition('mail', $email) ->execute(); if ((count($nids_order_no) > 0) or (count($nids_email) > 0)){ // Existing user : } else { // New user. : } } : ファイルを開いて 行でループ

Slide 65

Slide 65 text

: foreach ($lines as $line){ if (! $line){ continue; } $cols = explode(",", $line); $email = substr($cols[6], 1, -1); $order_no = substr($cols[0], 1, -1); $nids_order_no = \Drupal::entityQuery('user') ->condition('field_ticket_order_no', $order_no) ->execute(); $nids_email = \Drupal::entityQuery('user') ->condition('mail', $email) ->execute(); if ((count($nids_order_no) > 0) or (count($nids_email) > 0)){ // Existing user : } else { // New user. : } } : 繰り返し実行するので 存在チェックが必要。 ファイルを開いて 行でループ

Slide 66

Slide 66 text

Case 3: Drupal → Local PC

Slide 67

Slide 67 text

iOSDCサイト •応募されたトーク •採用されたトーク •チケットを購入したユーザ •写真登録 トーク応募 購入者データ 参加者 アイコン API + CRON + モジュール

Slide 68

Slide 68 text

Drupal → Local PC • ユーザにフィールドタイプ「画像」のフィールドを 追加。 • 画像ファイルのアップロード先からDropbox対象の ディレクトリにシンボリックリンク。 • アップロードされた画像が何もしなくてもローカルPCに! • 何も考えずに作るとファイル名=アップロードされ たファイル名。これでは誰だかわからない

Slide 69

Slide 69 text

アップロードファイル名を変更する • ユーザが画像ファイルをアップロードした時に ユーザにユニークなファイル名にリネームする。 function user_image_rename_file_insert($file){ $parts = pathinfo($file->getFilename()); $order_no = get_order_no_by_user_id($file->getOwnerId()); $filename = $order_no.".".$parts['extension']; $uri = 'public://avatars/' . $filename; $file = file_move($file, $uri); }

Slide 70

Slide 70 text

アップロードファイル名を変更する • ユーザが画像ファイルをアップロードした時に ユーザにユニークなファイル名にリネームする。 function user_image_rename_file_insert($file){ $parts = pathinfo($file->getFilename()); $order_no = get_order_no_by_user_id($file->getOwnerId()); $filename = $order_no.".".$parts['extension']; $uri = 'public://avatars/' . $filename; $file = file_move($file, $uri); } フック

Slide 71

Slide 71 text

まとめ • iOSDCはDrupalが支えている • サイトが無い状態でデータが発生する場合: • Googleフォーム & スプレッドシートを使うと、
 後が(比較的)ラク。 • モジュールを少し作るのがお勧め。難易度低め。 • Drupal 8はもう使えるか。 • ふつうに使える。 • Drupal 7の知識は半分使える感じでは。

Slide 72

Slide 72 text

Thanks WE ARE HIRING @tomzoh

Slide 73

Slide 73 text

Slide 74

Slide 74 text

おや…?

Slide 75

Slide 75 text

Drupalの様子が…?

Slide 76

Slide 76 text

!!

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

SA-CONTRIB-2016-039 • バリデーションが不十分でPHPコードが実行され る可能性がある。 • モジュールが有効化されていなくても影響を受け る。

Slide 79

Slide 79 text

SA-CONTRIB-2016-039 • バリデーションが不十分でPHPコードが実行され る可能性がある。 • モジュールが有効化されていなくても影響を受け る。

Slide 80

Slide 80 text

どういう修正が当たっているか • coder_upgrade/scripts/coder_upgrade.run.php にこの対策としてパッチが当たっている。 • 実行環境がCLI(Command Line Interface)
 でなければ終了する。 • つまりApacheから実行されると都合が悪い。

Slide 81

Slide 81 text

パッチが当たっていないとどうなるか

Slide 82

Slide 82 text

パッチが当たっていないとどうなるか ここに http://example.com/poison.txt とかを突っ込める

Slide 83

Slide 83 text

どこだ!? $parameters = unserialize(file_get_contents($path)); : // Extract individual array items by key. foreach ($parameters as $key => $variable) { $$key = $variable; } : // Load coder_upgrade bootstrap code. $path = $_coder_upgrade_modules_base . '/coder/coder_upgrade'; $files = array( 'coder_upgrade.inc', 'includes/main.inc', 'includes/utility.inc', ); : foreach ($files as $file) { require_once DRUPAL_ROOT . '/' . $path . "/$file"; }

Slide 84

Slide 84 text

どこだ!? $parameters = unserialize(file_get_contents($path)); : // Extract individual array items by key. foreach ($parameters as $key => $variable) { $$key = $variable; } : // Load coder_upgrade bootstrap code. $path = $_coder_upgrade_modules_base . '/coder/coder_upgrade'; $files = array( 'coder_upgrade.inc', 'includes/main.inc', 'includes/utility.inc', ); : foreach ($files as $file) { require_once DRUPAL_ROOT . '/' . $path . "/$file"; } 既存のクラスのデストラクタが動く

Slide 85

Slide 85 text

どこだ!? $parameters = unserialize(file_get_contents($path)); : // Extract individual array items by key. foreach ($parameters as $key => $variable) { $$key = $variable; } : // Load coder_upgrade bootstrap code. $path = $_coder_upgrade_modules_base . '/coder/coder_upgrade'; $files = array( 'coder_upgrade.inc', 'includes/main.inc', 'includes/utility.inc', ); : foreach ($files as $file) { require_once DRUPAL_ROOT . '/' . $path . "/$file"; } 既存のクラスのデストラクタが動く うまく文字列を作ればローカルファイルを実行できる

Slide 86

Slide 86 text

デモ IUUQESVQBMTJUFTBMMNPEVMFTDPEFSDPEFS@VQHSBEF TDSJQUTDPEFS@VQHSBEFSVOQIQ pMFIUUQ"' 'ESVQBM'QPJTPOUYU IUUQESVQBMTJUFTBMMNPEVMFTDPEFSDPEFS@VQHSBEF TDSJQUTDPEFS@VQHSBEFSVOQIQ

Slide 87

Slide 87 text

もう1つありましたね。

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

まくらばなし • 太古の昔から、UNIXでは環境変数にシステムの設 定を入れるという習慣があった。 • システムのプロキシ設定は環境変数 HTTP_PROXY に入れることになっていた。 • UNIXにCGIの仕組みが出来たときに、HTTPリクエ ストの各種パラメタを環境変数としてプログラム に受け渡すことになった。

Slide 90

Slide 90 text

httpoxyは なぜヤバい? • 外部から、HTTPリクエストヘッダに Proxy ヘッダ を付けてリクエストすると、Apacheは環境変数 HTTP_PROXY にその中身を展開してプログラムを 実行する。 • 攻撃者が指定したプロキシを使わせることができ る。

Slide 91

Slide 91 text

攻撃対象 決済サービス

Slide 92

Slide 92 text

攻撃対象 攻撃者 決済サービス

Slide 93

Slide 93 text

攻撃対象 攻撃者 Proxy: x.x.x.x 決済サービス

Slide 94

Slide 94 text

攻撃対象 攻撃者 Proxy: x.x.x.x 決済サービス

Slide 95

Slide 95 text

攻撃対象 攻撃者 Proxy: x.x.x.x 決済サービス 攻撃者の設置した プロキシ x.x.x.x

Slide 96

Slide 96 text

Thanks WE ARE HIRING @tomzoh