今回は誰でも編集できないようにパスワードをかけます。本当はデータベースを作成するときにパスワードのカラムも作っておくべきだったのですが、あとになってカラムを追加したくなったとき用のコードを書いてみることにします。
⇒ 動作確認はこちらから
ただし削除されては困るのでパスワードをかけています。そのため削除と編集の操作はできません。
Contents
パスワードの取得と保存に関する関数
index.php
1 2 3 4 5 |
// グローバル変数 $db_path = './tasks.sqlite3'; $table_pages = 'table_pages'; $table_tasks = 'table_tasks'; $table_pass = 'table_passwords'; |
パスワードをテーブルに保存したり読み出すために必要な関数を先に示します。
これはパスワードを保存するためのテーブルです。PageIDがページのidになっています。ここに設定されたパスワードを暗号化して保存します。
1 2 3 4 5 6 7 8 9 10 11 |
function CreatePasswordTable($db){ global $table_pass; $text = "CREATE TABLE if not exists {$table_pass} ("; $text .= "id integer primary key autoincrement, "; $text .= "PageID text", ; $text .= "HashedPassword text"; $text .= ");"; $sql = $db->prepare($text); $sql->execute(); } |
CheckSetPassword関数はページIDからそのページにパスワードが設定されているかどうかを調べるためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function CheckSetPassword($id){ global $db_path, $table_pass; $db = new PDO("sqlite:{$db_path}"); CreatePasswordTable($db); $sql = $db->prepare("SELECT * FROM {$table_pass} where PageID = {$id}"); $sql->execute(); $result = $sql->fetch(PDO::FETCH_ASSOC); if($result == null) return false; else return true; } |
ShowPassWordPage関数はパスワードを入力するフォームを表示するためのものです。
1 2 3 4 5 6 7 8 9 |
function ShowPassWordPage(){ $html = '<p>パスワードを入力してください。パスワードを設定していない場合はそのまま実行ボタンをおしてください。</p>' . "\n"; $html .= '<form action="" method="post">' . "\n"; $html .= '<label for="password">password</label>' . "\n"; $html .= '<input type="password" name="password">' . "\n"; $html .= '<button type="submit">実行</button>' . "\n"; $html .= '</form>' . "\n"; echo $html; } |
ShowSetPassWordPage関数はパスワードを設定するためのフォームを表示させるためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function ShowSetPassWordPage($id){ $html = '<p>パスワードを入力してください。パスワードを設定しない場合はそのまま実行ボタンをおしてください。</p>' . "\n"; $html .= '<form action="'. "" .'" method="post">' . "\n"; $html .= '<label for="password">古いパスワード</label>' . "\n"; $html .= '<input type="password" name="old-password"><br>' . "\n"; $html .= '<label for="password">新しいパスワード(1回目)</label>' . "\n"; $html .= '<input type="password" name="new-password-1" required="required"><br>' . "\n"; $html .= '<label for="password">新しいパスワード(2回目)</label>' . "\n"; $html .= '<input type="password" name="new-password-2"><br>' . "\n"; $html .= '<button type="submit">実行</button>' . "\n"; $html .= '</form>' . "\n"; echo $html; } |
GetHashedPassFromId関数はページIDから設定されているパスワードのハッシュを取得します。パスワードが設定されていない場合は空文字列を返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function GetHashedPassFromId($id){ global $db_path, $table_pass; $db = new PDO("sqlite:{$db_path}"); CreatePasswordTable($db); $sql = $db->prepare("SELECT * FROM {$table_pass} where PageID = {$id}"); $sql->execute(); $result = $sql->fetch(PDO::FETCH_ASSOC); $db = null; if($result != null) return $result['HashedPassword']; else return ''; } |
main関数の変更
main関数を少し変更します。
edit-XXにアクセスしたときはOnAccessEditPage関数、change-XXにアクセスしたときは OnAccessChangePasswordPage関数、update-XXにアクセスしたときは OnAccessUpdatePage関数、delete-XXにアクセスしたときは OnAccessDeletePage関数が呼び出されることにします。それからPostFromNewPage関数も変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
function main(){ session_start(); try { $arg = $_GET["arg"]; if($arg == ""){ ShowTopPage(); return; } else { if($arg == "new"){ ShowNewPage(); return; } else if($arg == "post-from-new"){ PostFromNewPage(); return; } else if(strpos($arg, 'page-') === 0) { $id = str_replace('page-',"", $arg); $id = (int) $id; if($id > 0){ ShowPage($id); return; } else{ echo '不正なリクエストです'; return; } } else if(strpos($arg, 'edit-') === 0) { $id = str_replace('edit-', "", $arg); OnAccessEditPage($id); return; } else if(strpos($arg, 'change-') === 0) { $id = str_replace('change-', "", $arg); OnAccessChangePasswordPage($id); return; } else if(strpos($arg, 'update-') === 0) { $id = str_replace('update-',"", $arg); OnAccessUpdatePage($id); return; } else if(strpos($arg, 'delete-') === 0) { $id = str_replace('delete-',"", $arg); OnAccessDeletePage($id); return; } echo $arg . 'は不正なリクエストです'; return; } } catch(Exception $e) { echo '例外<br>'; } } |
新しくチャートを作成する処理
新しくチャートを作成するときにパスワードも設定できるようにします。
template/new.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>新規</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <form action="./post-from-new" method="post"> <label>新規チャート名:</label> <input id = "chart-name" name = "chart-name" type="text" size="24" required="required"> <label for="password">パスワード(1回目)</label> <input type="password" name="new-password-1" required="required"><br> <label for="password">パスワード(2回目)</label> <input type="password" name="new-password-2"><br> <button style="margin-left: 20px;">チャートの追加</button> </form> </body> </html> |
/post-from-newにポストされたら同じパスワードが入力されているか確認してからパスワードをSQLiteのDBに保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
function PostFromNewPage(){ global $db_path, $table_pages, $table_pass; // 直接ブラウザにurlを入力した場合は処理をしない if (!isset($_POST['chart-name']) || !isset($_POST['new-password-1']) || !isset($_POST['new-password-2'])) { echo '不正なリクエストです'; return; } // パスワードが設定されていない場合は処理をしない if($_POST['new-password-1'] == ''){ echo 'パスワードは設定してください'; return; } // 同じパスワードが入力されていない場合は処理をしない if($_POST['new-password-1'] != $_POST['new-password-2']){ echo 'パスワードの設定が間違っています'; return; } $chartName = $_POST["chart-name"]; $chartName = EscapeString($chartName); $db = new PDO("sqlite:{$db_path}"); CreatePasswordTable($db); CreatePagesTable($db); // テーブル $table_pagesに追加 $sql = $db->prepare("INSERT INTO {$table_pages} (PageName) VALUES (?)"); $sql->execute([$chartName]); // 新しく追加されたページのidを取得 $text = "select max(id) from {$table_pages}"; $sql = $db->prepare($text); $sql->execute(); $result = $sql->fetch(PDO::FETCH_ASSOC); $max = (int)$result["max(id)"]; // パスワードを登録 $hashed_password = password_hash($_POST['new-password-1'], PASSWORD_DEFAULT); $sql = $db->prepare("INSERT INTO {$table_pass} (PageID, HashedPassword) VALUES (?, ?)"); $sql->execute([$max, $hashed_password]); $db = null; // 新しく追加されたページへリダイレクト $next = "./page-" . $max; header("Location: $next"); return; } |
編集用のページを表示する処理
/edit-XXにアクセスされたらパスワードが設定されていない場合はそのままタスク編集用のページを表示し、設定されている場合はパスワードを入力するフォームを表示します。
パスワードを入力してボタンをおすとDBに登録されているパスワードと照合され、合っていればタスク編集用のページが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function OnAccessEditPage($id){ global $db_path, $table_pass; $id = (int) $id; if($id > 0){ if(CheckSetPassword($id) == false) // パスワードが設定されていない場合 ShowEditPage($id); else { // パスワードが設定されている場合 $hash_pass = GetHashedPassFromId($id); if(isset($_SESSION["hash_pass"]) && $_SESSION["hash_pass"] == $hash_pass ){ ShowEditPage($id); unset($_SESSION['hash_pass']); return; } else if(isset($_POST['password'])){ if(password_verify($_POST['password'], $hash_pass)){ ShowEditPage($id); } else echo 'パスワードが正しくありません'; return; } else if(isset($_POST['new-password-1'])){ ShowEditPage($id); return; } else{ ShowPassWordPage(); } } } else{ echo '不正なリクエストです'; } } |
パスワードを変更する処理
パスワードの再設定のページで新しくパスワードを設定した場合は新しく入力されたふたつのパスワードが等しいか、古いパスワードがある場合は元のパスワードに入力したものと同じかどうかが調べられ、合っていれば再設定がおこなわれます。このときセッション情報が一時的に保存されます。次にedit-XXにリダイレクトされるのですが、まだパスワードを入力しなければならないのを回避するためです。
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
function OnAccessChangePasswordPage($id){ $id = (int) $id; if($id > 0){ if(isset($_POST['old-password']) && isset($_POST['new-password-1']) && isset($_POST['new-password-2'])){ if($_POST['new-password-1'] != $_POST['new-password-2']){ echo 'パスワードの設定が間違っています'; return; } $newPass = $_POST['new-password-1']; $oldPass = $_POST['old-password']; $hashed_password = password_hash($newPass, PASSWORD_DEFAULT); global $db_path, $table_pass; $db = new PDO("sqlite:{$db_path}"); CreatePasswordTable($db); $sql = $db->prepare("SELECT * FROM {$table_pass} where PageID = {$id}"); $sql->execute(); $result = $sql->fetch(PDO::FETCH_ASSOC); $_SESSION["hash_pass"] = $hashed_password; if($result == null){ // はじめての登録 if($oldPass != null){ // パスワードが設定されていないのに「古いパスワード」を入力した場合はエラー echo '元のパスワードが登録されているものとは違います'; return; } $sql = $db->prepare("INSERT INTO {$table_pass} (PageID, HashedPassword) VALUES (?, ?)"); $sql->execute([$id, $hashed_password]); $_SESSION["hash_pass"] = $hashed_password; // いったんセッション情報を保存する } else { // パスワードの更新 $hashed_old_password = password_hash($oldPass, PASSWORD_DEFAULT); if(password_verify($oldPass, $result['HashedPassword'])){ $sql = $db->prepare("update {$table_pass} set HashedPassword = '{$hashed_password}' where PageID = ${id}"); $sql->execute(); $_SESSION["hash_pass_{$id}"] = $hashed_password; // いったんセッション情報を保存する } else { echo '元のパスワードが登録されているものとは違います'; return; } } $next = "./edit-{$id}"; header("Location: $next"); } else ShowSetPassWordPage($id); } else{ echo '不正なリクエストです'; } } |
チャートを更新する処理
更新と削除の処理ですが、パスワードを入力することを義務づけます。
既存のToReflect関数とDeleteChart関数を削除し、以下のコードをjs/task2.jsかtemplate/edit.htmlのスクリプト部分の下のほうに書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//JS function ToReflect(){ let pathame = location.pathname; let last = pathame.lastIndexOf('-'); let id = pathame.substring(last +1); let html = '<p>パスワードを入力してください。パスワードを設定していない場合はそのまま実行ボタンをおしてください。</p>' + "\n"; html += `<form id = "form" action="./update-${id}" method="post"\n>`; html += '<label for="password">password</label>\n'; html += '<input type="password" name="password">\n'; html += '<button type="submit">実行</button>\n'; html += '</form>' + "\n"; let jsonText = GetResultJsonText(); // document.write(html);を実行する前に取得しておく // パスワードを入力するフォームを表示させる // 見えないところにGetResultJsonText関数で取得した文字列をいれておき、 // パスワード送信ボタンが押されたら一緒にPostできるようにする document.write(html); let newElement = document.createElement('input'); let form = document.getElementById('form'); form.appendChild(newElement); newElement.value = jsonText; newElement.name = 'jsontext'; newElement.style.display = 'none'; } //JS function DeleteChart(){ let check = confirm('このチャートを削除します。よろしいですか?'); if(!check) return; // ./delete-XXへリダイレクトさせる。するとパスワード入力フォームが表示される let pathname = location.pathname; let last = pathname.lastIndexOf('-'); let id = pathname.substring(last +1); window.location.href = `./delete-${id}`; } |
更新処理をするときはパスワードが合っているかチェックしたあと、ページidとPostされたjsonTEXTをUpdatePage関数に渡します。
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function OnAccessUpdatePage($id){ $id = (int) $id; if($id > 0){ if(!isset($_POST['password'])){ echo '不正なリクエストです'; return; } $password = $_POST['password']; $hash_pass = GetHashedPassFromId($id); $jsonText = $_POST["jsontext"]; if($jsonText != null){ if($hash_pass == '' && $password == '') UpdatePage($id, $jsonText); else if($hash_pass != '' && password_verify($password, $hash_pass)) UpdatePage($id, $jsonText); else echo 'パスワードが違います'; } else echo '不正なリクエストです'; } else { echo '不正なリクエストです'; } } |
UpdatePage関数ではJSON文字列をobjectに変換し、$contents[“Tasks”]や$contents[“PageName”]が存在するか確認します。確認できたらいったんタスクに関する情報が保存されているテーブルは削除して作り直します。そのあとSQLiteのDBに保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
function UpdatePage($id, $json) { // JSON文字列をobjectに変換 ⇒ 第2引数をtrueにしないとハマるので注意 $contents = json_decode($json, true); if($contents == null || $contents["Tasks"] == null){ echo '不正なリクエストです'; return; } global $db_path, $table_pages; $db = new PDO("sqlite:{$db_path}"); // table_pages内に登録されているか確認する CreatePagesTable($db); $sql = $db->prepare("SELECT * FROM {$table_pages} where id = {$id}"); $sql->execute(); $result = $sql->fetch(PDO::FETCH_ASSOC); if($result == null){ $db = null; echo 'ページが存在しません'; return; } // テーブルはその都度作り直す // いったん削除 $taskTableName = GetTaskTableName($id); $sql = $db->prepare("drop table if exists {$taskTableName}"); $sql->execute(); // 作り直す CreateTaskTable($db, $id); SaveToSqlite($db, $id, $contents); $db = null; // DBに保存したら./page-XXにリダイレクトする $next = "./page-{$id}"; header("Location: {$next}"); } |
更新処理がおこなわれるときに実際にDBにデータを保存する処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function SaveToSqlite($db, $id, $contents){ global $table_pages; foreach ($contents["Tasks"] as $content) { $categoryName = $content["CategoryName"]; $categoryName = EscapeString($categoryName); $taskName = $content["TaskName"]; $taskName = EscapeString($taskName); $managerName = $content["ManagerName"]; $managerName = EscapeString($managerName); $startDateText = $content["StartDateText"]; $endDateText = $content["EndDateText"]; $taskTableName = GetTaskTableName($id); $sql = $db->prepare("INSERT INTO {$taskTableName} (CategoryName,TaskName,ManagerName,StartDateText,EndDateText) VALUES (?,?,?,?,?)"); $sql->execute([$categoryName, $taskName, $managerName, $startDateText, $endDateText]); $sql = null; echo "<br>" . 'a'; } $pageName = EscapeString($contents["PageName"]); $sql = $db->prepare("update {$table_pages} set PageName = '{$pageName}' where id = ${id}"); $sql->execute(); } |
チャートを削除する処理
削除するときの処理を示します。Postされたパスワードが正しいかどうかチェックして、合っているのであればDeletePage関数を呼び出してDBからデータを削除します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
function OnAccessDeletePage($id){ $id = (int) $id; if($id > 0){ if(!isset($_POST['password'])) ShowPassWordPage(); else{ $password = $_POST['password']; $hash_pass = GetHashedPassFromId($id); if($hash_pass == '' && $password == '') DeletePage($id); else if($hash_pass != '' && password_verify($password, $hash_pass)) DeletePage($id); else echo '不正なリクエストです'; } return; } else { echo '不正なリクエストです'; } } function DeletePage($id){ global $table_pages; global $db_path; $db = new PDO("sqlite:{$db_path}"); $taskTableName = GetTaskTableName($id); // テーブル削除 $sql = $db->prepare("drop table {$taskTableName}"); $sql->execute(); // table_pagesから一致するものを削除 $sql = $db->prepare("delete from ${table_pages} where id = ${id}"); $sql->execute(); $sql = $db->prepare("vacuum"); $sql->execute(); $db = null; header("Location: ./"); } |