読者です 読者をやめる 読者になる 読者になる

Buno Journals

It's what I do that defines me.

ESLint + Airbnb JavaScript Style Guide でエラー Configuration for rule "jsx-a11y/anchor-has-content" is invalid

現在の職場で、SPA実装のためにJS記述量が増えてきてESLintを導入しました。

eslint.org

Reactも使っているので、Airbnb JavaScript Style Guideを使いました。

github.com

そこで遭遇したエラーの話です。

ESLintインストール

Web上に情報はたくさんあると思います。以下のあたり参照してもらえれば。

ESLint 最初の一歩 - Qiita

ESLint をグローバルにインストールせずに使う - Qiita

私はプロジェクトローカルにインストールしました。

npm install --save-dev eslint

eslint --initでairbnbスタイルガイド適用した場合、エラーになる

ESLintで使う設定ファイルが.eslintrc.jsonで、これはeslint --initコマンドで作成することもできます。

コマンド実行して、ターミナルに表示される質問に対し希望の回答を選択していけば、.eslintrc.jsonが作成されます。

私は以下のように答えました。

$ eslint --init
? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? Airbnb
? Do you use React? Yes
? What format do you want your config file to be in? JSON

以下のような.eslintrc.jsonが作成されました。

{
    "extends": "airbnb",
    "plugins": [
        "react",
        "jsx-a11y",
        "import"
    ]
} 

ここでeslintを実行してみるとエラーに遭遇しました。

$ eslint test.js
/...path_to_your_project.../node_modules/eslint-config-airbnb/rules/react-a11y.js:
    Configuration for rule "jsx-a11y/anchor-has-content" is invalid:
    Value "" is the wrong type.

the airbnb config requires 2.x of eslint-plugin-jsx-a11y (NOT 3.x)

'eslint --init' installs wrong versions of depencies for AirBnB · Issue #7338 · eslint/eslint · GitHub

で報告されている現象でした。

eslint --initairbnbスタイルを適用した場合、

依存関係でインストールされるプラグインeslint-plugin-jsx-a11yのバージョンが正しくならないようです。

GitHub - evcohen/eslint-plugin-jsx-a11y: Static AST checker for a11y rules on JSX elements.

ljharbさんのコメントが参考になります。

https://github.com/eslint/eslint/issues/7338#issuecomment-252785808

https://github.com/airbnb/javascript/issues/1172#issuecomment-259643396

https://github.com/evcohen/eslint-plugin-jsx-a11y/issues/119#issuecomment-259574657

airbnb style guideを適用するには、2.x系が必要なのに、eslint --initで適用した場合 3.x系が入ってしまい動かないということらしいです。

https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb

の記述通り、以下のように実行すれば私の環境では解決しました。

Linux/OSX users can simply run

(
  export PKG=eslint-config-airbnb;
  npm info "$PKG@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save-dev "$PKG@latest"
)

f:id:bunoacts:20161229001316p:plain

2.x系がインストールされ、ESLint + Airbnb JavaScript Style Guideで実行できるようになりました。

minitest-reporters : rake test の実行結果を色付きで出力する

minitest-reporters

Rails4標準のminitestのテストをrake test(rake のみでも同じ)で動かしても出力結果に色は付かない。

f:id:bunoacts:20160828174758p:plain

やはり、成功は緑、失敗は赤、スキップ等は黄色で表示したい時、

minitest-reporters というgemを使えば良い。

github.com

Gemfileに追記してインストールしたら、Usage の通り、

test_helper.rb に、

require "minitest/reporters"
Minitest::Reporters.use!

と記述すれば良い。

f:id:bunoacts:20160828174836p:plain

参考

http://railstutorial.jp/chapters/static_pages?version=4.2#sec-minitest_reporters

github.com

qiita.com

provide() : Template間で値を渡すヘルパーメソッド

ドキュメントは以下

http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-provide

よくある使い方の一つは、各アクションのViewで、

<% provide(:title, "Home") %>

として、 シンボル :title に 文字列 "Home" を関連付ける。

レイアウトViewで、

<%= yield(:title) %>

のように参照する、という使い方だ。

なお、Railsチュートリアルに以下のような注がある。

Railsでの開発経験者であれば、この時点でcontent_forの使用を検討すると思いますが、残念ながらAsset Pipelineと併用すると正常に動作しないことがあります。provide関数はcontent_forの代替です。

参考

http://railstutorial.jp/chapters/static_pages?version=4.2#sec-layouts_and_embedded_ruby

[Rails]provideヘルパでlayoutにデータを引き渡す - Qiita

Railsで rails generate / rake db:migrate を「元に戻す」方法

destroy: rails generate を元に戻す

rails generate(省略形 rails g)でcontrollerやmodelを生成する場合、複数のファイルが一括で生成され、ルーティングの config/routes.rb も修正されるので、これを取り消すためのコマンドがrails destroyである。

例えば、

$ rails generate controller StaticPages home help

として実行した結果を取り消すなら

$ rails destroy  controller StaticPages home help

とコマンドを打つ。

rollback: rake db:migrate を元に戻す

マイグレーションのコマンド rake db:migrate を取り消すためのコマンドが rake db:rollback だ。このコマンドで一つ前の状態に戻すことができる。

マイグレーションは逐次的に実行され、各マイグレーションに対しバージョン番号が付与されているので、この番号を指定することで指定したバージョンの状態に戻すことができる。

最初の状態に戻す場合は、以下コマンドになる。

$ rake db:migrate VERSION=0

参考

Railsチュートリアルの「コラム3.1 元に戻す方法」より

http://railstutorial.jp/chapters/static_pages?version=4.2#aside-undoing_things

PHPでセッション管理のメモ

以下、PHPのセッション管理についての簡単なメモだ。

セッション管理とクッキー

セッション管理

HTTP自体はステートレスなプロトコルでサーバ側で状態を保持しない。 しかし、ログイン後の認証状態、ECサイトのショッピングカートなど、アプリケーションによっては状態を保持しておく必要がある。 このように、ユーザ毎にサーバ側での状態を覚えておくことセッション管理という。

クッキー

セッション管理をHTTPで実現するための仕組みがクッキー(cookie)だ。

サーバ側からの指示に従い、ブラウザは「名前=変数」の組を記憶する

セッション開始

以下、動作確認しながら説明する。

次のファイルは、session_start() 関数でセッションを開始するサンプルである。

sessiontest1.php

<?php
    session_start();
?>

<html>
<head><title>PHP TEST</title></head>
<body>

<?php

    if (!isset($_COOKIE["PHPSESSID"])){
        print('初回の訪問です。セッションを開始します。');
    }else{
        print('セッションは開始しています。<br>');
        print('セッションIDは '.$_COOKIE["PHPSESSID"].' です。');
    }

?>

</body>
</html>

初めてこのファイルにHTTP GETでアクセスした場合、 レスポンスにはSet-Cookieヘッダがあり、 この情報をブラウザに記憶するように指示している。

f:id:bunoacts:20160709155543p:plain

ちなみにChromeの場合はフォームに chrome://settings/cookies と入力すれば

記憶しているCookieを確認することができる。

f:id:bunoacts:20160709155743p:plain

そして、再度このページにHTTP GETでアクセスする時は、 リクエストのCookieヘッダに記憶した情報が付与されている。

f:id:bunoacts:20160709160158p:plain

ここでやり取りされていたPHPSESSID=****という情報はセッションIDといい、 サーバに保存されている全てのユーザのセッション情報の中で、ユーザを特定するためのIDである。

なお、セキュリティの観点から、認証(ログイン)後、セッションIDを更新した方がよい

ちなみに、PHPSESSIDはセッション名といい、phpの場合はphp.iniファイルでデフォルト値として設定されており、 変更することも可能だ。 そして、この値は session_name() 関数で取得できる。 つまり、クッキーに保存されたセッションIDは$_COOKIE[session_name()]と書いても取得できる。

セッションの管理と終了

サーバ側で管理したいセッション情報は、$_SESSION連想配列を使って管理する。 このセッション情報はWebサーバ上のファイル保存される。

そして、ログアウト時には、以下のような処理が必要になる。

  • サーバ側で保存しているセッション情報を削除
  • クライアント(ブラウザ)側のCookie情報の削除

よって、phpでは以下のような処理を行う。

<?php

// 空の配列を代入して、全てのセッション変数をクリアする。
$_SESSION = array();

// ブラウザのクッキーを削除。実際には、クッキーの有効期限に過去の時間をセットしている。
setcookie("PHPSESSID", '', time() - 1800, '/');

// セッションファイルを削除する
session_destroy();

?>

session_destroy();でセッションファイルを破棄すれば、 $_SESSION = array();のようなセッション変数を初期化する処理は不要ではないのか、と思ってしまったが、 私が動作確認したところ、同一スクリプト内ではsession_destroy();実行後であっても$_SESSIONのセッション変数はクリアされておらず、 アクセス可能だった。 やはり、セッション変数の初期化とセッションファイルの削除を両方やっておくべきのようだ。

クッキーの属性

コッキーを発行する際、様々なオプション属性を設定することができる。

属性 意味
Domain ブラウザがクッキー値を送信するサーバのドメイン
Path ブラウザがクッキー値を送信するURLのディレクト
Expires クッキー値の有効期限。指定しない場合はブラウザ閉じるまで
Secure SSLの場合のみクッキーを送信
HttpOnly JavaScriptからこのクッキーにはアクセス不可

Domain属性

デフォルトでは、クッキーはセットされた元のサーバのみに送信する。セキュリティ的には、これが安全なので原則としてDomain属性は設定しない

Domain属性を指定するのは、複数のサーバに送信されるクッキーを生成したい場合だ。 例えば、Domain=example.jpとして生成したクッキーは a.example.jp にも b.example.jp にも送信される。

仮に、a.example.jpサーバがSet-CookieヘッダでDomain=example.comとしても、このクッキーはブラウザに無視される。 セッションIDの固定化攻撃の手段にならないように、異なるドメインに対するクッキーは設定できないようになっている。

HttpOnly属性

クッキーに格納されたセッションIDを盗み出す手段として、クロスサイトスクリプティングによりJavaScriptを悪用してクッキーを盗み出すというものがある。 この属性を設定することで攻撃を難しくすることはできる。 phpの場合は、php.iniファイルで以下のように設定を追加する。

session.cookie_httponly = On

参考

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践

PHP: $_SESSION - Manual

$_SESSION = array() と session_destroy(); の関係性。 - PHP 解決済 | 教えて!goo

セッション管理 - PHP入門

ChromeのCookieデータの場所と覗く方法

10日で覚えるPHPのキソ 第 10 回 セッション(SESSION) | バシャログ。

PHPでBasic認証サンプル

Web開発に携わっていれば、Basic認証に触れる機会は必ずある。今回、基本的な仕組みを整理してみた。

Basic認証

サーバがクライアント(ブラウザ)にBasic認証を要求する場合、

のレスポンスを返す。

それを受けて、ブラウザは、Base64エンコードされたユーザ名とパスワードを、Authorizationヘッダに付与してリクエストを送る。

Authorization: Basic ('ユーザ名:パスワード'をBase64でエンコード)

PHPの実装サンプル

以下は、phpでの簡単なBasic認証の実装例だ。 あくまで動作確認のため、ユーザ名、パスワードに何かしら入力があれば認証を通す、という簡単なものだ。 なお今回、phpを動かすwebサーバ環境の構築については触れない。

basic_authentification_sample.php

<?php
  $user = @$_SERVER['PHP_AUTH_USER'];
  $pass = @$_SERVER['PHP_AUTH_PW'];

  if (! $user || ! $pass) {
    header('HTTP/1.1 401 Unauthorized');
    header('WWW-Authenticate: Basic realm="Basic Authentication Sample"');
    echo "ユーザ名とパスワードが必要です";
    exit;
  }
?>
<body>
認証しました<BR>
ユーザ名:<?php echo htmlspecialchars($user, ENT_NOQUOTES, 'UTF-8'); ?><BR>
パスワード:<?php echo htmlspecialchars($pass, ENT_NOQUOTES, 'UTF-8'); ?> <BR>
</body>

phpは、authorizationヘッダに付与されたユーザ名、パスワードにそれぞれ $_SERVER['PHP_AUTH_USER']$_SERVER['PHP_AUTH_PW']で取得している。

動作の流れ

まず、上記phpファイルにHTTP GETリクエストでアクセスする。 この時、リクエストヘッダにAuthorizationヘッダはないため、ステータスコード401 Unauthorizedが返ってくる。

401 Unauthorized

認証が必要である。Basic認証やDigest認証などを行うときに使用される。 たいていのブラウザはこのステータスを受け取ると、認証ダイアログを表示する。

HTTPステータスコード - Wikipedia

f:id:bunoacts:20160705182454p:plain

ブラウザが表示したダイアログにユーザ名、パスワードを入力して送信すると、ブラウザは再びGETリクエストを送信する。

この時はAuthorizationヘッダにBase64エンコードされたユーザ名、パスワードが付与されている。

f:id:bunoacts:20160705183003p:plain

前述のPHPのコードはサーバ変数でユーザ名、パスワードにアクセスしhtml上に表示する。

f:id:bunoacts:20160705183219p:plain

ステートレス

これ以降、同じURLにアクセスしても認証ダイアログは表示されないが、それはブラウザが記憶したauthorizationヘッダを毎回自動的に付与してリクエストを送っているからである。HTTPもBasic認証もステートレスで、ログインという状態が保存されている訳ではない。

参考

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践

HTTP クライアントを作ってみよう(5) - Basic 認証編 -

PHP: $_SERVER - Manual

JavaScript: 変数宣言の巻き上げ

変数宣言と初期化

jsでは通常、以下のように変数を宣言し、値を代入する(初期化)

var str; // 宣言
str = 'hello';  // 値を代入(初期化)

一行で書くこともできる。

var str = 'hello';

宣言の巻き上げ

jsでは変数の宣言は、関数スコープの先頭に巻き上げられる。

例えば、以下のようなコードがあったとする。

function sayHello() {
  console.log(str);
  var str = 'hello';
}

これは、次のように動作する。

function sayHello() {
  var str; // 宣言、undefinedの値が割り当てられる
  console.log(str);  // 出力: undefined
  str = 'hello'; // 代入(初期化)
}

つまり、

  • 変数宣言は、関数スコープの先頭に巻き上げられ、その変数にはundefinedの値が割り当てられる。
  • 変数の代入(初期化)の位置は変わらない。

スコープチェーンの上昇

JavaScriptエンジンは、変数宣言をローカルスコープからグローバルスコープに向かって調べていく

以下のコードの実行を考える。

var str = 'hello';
function sayHello() {
  console.log(str); // 出力: 'hello'
}
sayHello();

関数sayHelloを実行して、console.log()が変数strを要求すると、JavaScriptエンジンはまず変数strがローカルスコープで宣言されているかを調べる。 この場合、変数strはローカルスコープで宣言されていないので、次にグローバルスコープを調べて、宣言を見つけて値を返す。

では、次のコードはどうなるか。

var str = 'hello';
function sayHello() {
  console.log(str); // 出力: undefined
  var str;  // ローカルスコープでも変数宣言
}
sayHello();

変数strがローカルスコープでも宣言されている。 この宣言は関数先頭に巻き上げられるので、グローバルスコープを調べる前に、この宣言を見つけて出力する。 ローカルスコープでは宣言のみにで初期化はされていないので、出力はundefinedだ。

参考

シングルページWebアプリケーション ―Node.js、MongoDBを活用したJavaScript SPA

シングルページWebアプリケーション ―Node.js、MongoDBを活用したJavaScript SPA