雑記

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|
2007|01|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|09|11|
2009|02|03|05|06|07|08|10|11|12|
2010|01|03|04|05|06|07|08|09|10|
2011|05|06|09|10|
2012|03|07|09|12|
2013|01|02|04|05|07|08|10|11|
2014|04|05|08|10|12|
2015|01|05|
2016|09|
2024|06|
2025|01|

2010-03-06 [長年日記]

[Rails] select_datetimeにタイムゾーン選択オプションを追加するプラグイン

を作ったので公開しました。Viewに

<p><%= select_datetime Time.now, { :include_timezone => true } %></p>
<p><%= select_datetime Time.now, { :include_seconds => true, :include_timezone => true } %></p>
<p><%= select_timezone %></p>

のように書くと、

タイムゾーン付フォーム

といったフォームが表示され、入力された値はconfig/environment.rbで設定されたタイムゾーン等に従って適当に変換されます。

ActiveRecordとの連携はこれでいいのですが、検索とかに使う場合はコントローラ側で値を取りだしたくなります。そのためのメソッドが、get_multiparameter_attributes()とget_multiparameter_time()です。 get_multiparameter_attributes()は、Multiparameter attributeを配列に変換します。

% script/console
Loading development environment (Rails 2.3.5)
>>    params = {
?>        :datetime => {
?>            "label(1i)" => "2010", "label(2i)" => "1", "label(3i)" => "1",
?>            "label(4i)" => "0", "label(5i)" => "0", "label(6i)" => "0",
?>            "label(7i)" => "+0900"
>>        }
>>    }
=> {:datetime=>{"label(3i)"=>"1", "label(4i)"=>"0", "label(5i)"=>"0", "label(6i)"=>"0", "label(7i)"=>"+0900", "label(1i)"=>"2010", "label(2i)"=>"1"}}
>> ActionController::Base.new.get_multiparameter_attributes(params[:datetime], "label")
=> [2010, 1, 1, 0, 0, 0, 900]

また、get_multiparameter_attributes()はdatetimeに限らず任意の Multiparameter attributeを配列に変換できます。変換ルールはActiveRecord::Baseのtype_cast_attribute_valueメソッドに準拠しています。

>>     input1 = {
?>       "test(1i)" => "10", "test(2)" => "string", "test(3f)" => "3.14", "test(4i)" => "3.14",
?>       "test(5i)" => "g", "test(6f)" => "g", "test(7i)" => "1g", "test(8f)" => "3.14g"
>>     }
=> {"test(8f)"=>"3.14g", "test(1i)"=>"10", "test(2)"=>"string", "test(4i)"=>"3.14", "test(7i)"=>"1g", "test(5i)"=>"g", "test(6f)"=>"g", "test(3f)"=>"3.14"}
>> ActionController::Base.new.get_multiparameter_attributes(input1, "test")
=> [10, "string", 3.14, 3, 0, 0.0, 1, 3.14]

get_multiparameter_time()はより直接的に、View内のselect_datetimeなどから得られた入力をRailsのTime型に変換した物を取得します。

% script/console
Loading development environment (Rails 2.3.5)
>>    params = {
?>        :datetime => {
?>            "label(1i)" => "2010", "label(2i)" => "1", "label(3i)" => "1",
?>            "label(4i)" => "0", "label(5i)" => "0", "label(6i)" => "0",
?>            "label(7i)" => "+0900"
>>        }
>>    }
=> {:datetime=>{"label(3i)"=>"1", "label(4i)"=>"0", "label(5i)"=>"0", "label(6i)"=>"0", "label(7i)"=>"+0900", "label(1i)"=>"2010", "label(2i)"=>"1"}}
>> ActionController::Base.new.get_multiparameter_time(params[:datetime], "label")
=> Fri, 01 Jan 2010 00:00:00 JST +09:00

注意点として、これらを使ってデータベース内のタイムスタンプ等を検索する場合、得られたTime型の変数をデータベース内で保存されるタイムゾーンに変換する必要があります。手元のRails 2.3.5+sqlite3という環境ではRailsのconfig.time_zoneの設定によらず、データベースに対してはUTCで保存されていたので、findを掛けるときにutcメソッドで変換する必要がありました。

ts = get_multiparameter_time(params[:search_keys], "timestamp").utc
Log.find(:first, :conditions => ["ip_addr = ? AND recorded_at <= ?", params[:search_keys][:ip], ts, :order => "recorded_at DESC")

UTC以外のタイムゾーンに変更する場合は、in_time_zoneメソッドを使用します。

ts = get_multiparameter_time(params[:search_keys], "timestamp").in_time_zone('Tokyo')

引数に指定可能なタイムゾーン名は"rake time:zones:all"で調べることができます。

おまけ

選択フォームの代わりにテキストフィールドを使う場合、Viewで

<%= text_field "search_keys", "timestamp_text" %>

とやっておいて

ts = DateTime.parse(params[:search_keys][:timestamp_text]).utc

2010-03-14 [長年日記]

安全なかんたんログインの実現案(キャリア編)

サービス標的型の利用者ID

携帯電話のいわゆる「かんたんログイン」機能における成りすまし問題について、アプリ編はあちこちで書かれているのでキャリア編を書いてみます。まず、問題の概略から。

  • 携帯電話からのアクセスでは各種携帯サービスに利用者IDや端末IDなどと呼ばれる利用者を識別可能なID(以下uidと表記)を渡す方法が用意されている
  • 仕様として、同じ利用者/端末であれば全ての携帯サイトに同じuidが渡される

仕組みとしては図の上のようになり、携帯サービスA,B,Cの全てに対して利用者aはxxxa、利用者bはxxxbという同じuidでアクセスすることになります。このため、

  • 何らかの方法で1箇所からuidが漏れると、他のサービスにおいても成りすましログインできる可能性がある
  • uidとキャリアゲートウェイのIPアドレスチェックを併用するなどの対策が言われているが、ゲートウェイのIPアドレス変更があるたびに追従するなど継続的な対応が必要
  • キャリアのゲートウェイを経由した成りすましには対応できない

などの問題があり、サービス側だけでの完全な対策は厳しいと言えます。

そうした背景もあり、キャリアの方でできる対策はないかと考えたとき、ShibbolethというSSOで使われているStoredIDを応用すると多少は状況がましになるような気がしてきました。これをかんたんログイン機能向けにモディファイした方法を以下に述べます。

この方法では、キャリアゲートウェイが携帯からインターネットへのアクセス口とSSOにおけるIdP(認証サーバ)の役割を兼務します。また、端末から送信されてきたuid情報はすべて廃棄し、アクセスしてきた端末の回線情報から契約者のIDを生成して、それをuidとして送信します。フィールドとしては互換性を持たせるためにiモードID(HTTP_X_DCMGUID)、ez番号(HTTP_X_UP_SUBNO)、およびX_JPHONE_UID(HTTP_X_JPHONE_UID)を使っても良いですし、新しい X_****_TARGETED_UIDのようなフィールドにしても良いでしょう。

このフィールドでは以下のルールでuidが提供されます。

  • ある利用者が特定のサービスにアクセスする時には、常に同じuidが渡される
  • 同じ利用者が異なるサービスにアクセスする時には、それぞれ別のuidが渡される

具体的なuidの例は図の下のようになります。利用者aが携帯サービスAにアクセスするとき、uidとして'xxxa'ではなく、'xxxaA'という値が渡されます。これは利用者aがサービスAにアクセスする時は必ず同じ値です。いっぽうでサービスBにアクセスした時は'xxxaB'、Cでは'xxxaC'というサイトごとに異なるuidが渡されます。利用者bも同様です。こうすることにより、サービスAの提供者に悪意があり、かつキャリアゲートウェイにも穴があって、これを介してサービスCに成りすましアクセスしようとしたとしても、オリジナルやサービスCにおけるuidが分からないため、Cのかんたんログイン機能は使えません。注意点として、uidの表現に必要な名前空間は既存の方法では利用者数分だけで済みましたが、この方法では利用者数×サービス数分が必要となります。

uidは例えば以下のキーを繋げた文字列のSHA-1ハッシュなどにします。

利用者識別子現在の各社uid
サービス識別子各携帯サービスのサーバのfqdnまたはIPアドレス
saltランダムな文字列

おそらく今でも利用者の携帯電話から各サービスに渡すuidを求めるためにテーブルを引いているはずですから、そこにハッシュ関数を1段入れれば比較的容易に実現できそうな気がします。またStoredIDのように一度計算したハッシュをDBに保存すれば、時間のかかるハッシュ関数を使っていても2回目以降のアクセスではレスポンスを向上させることができるでしょう。

ただ、この方法でも信頼できるIdPとしてのキャリアゲートウェイのリストはどうしても必要になります。こうしたリストについては、キャリア側でhttpsなサーバ上にxmlファイルとかを提供してもらい、サービス提供者側は日に1回ぐらいクロールして随時更新する枠組みなどが欲しいところです。

この方法のメリットとしては、以下のようなものが考えられます。

  • サイト横断的な成りすましはほぼ出来なくなる
  • かんたんログインという方法を捨てずに済む
  • 携帯端末からのuid偽装対策はシンプル(基本破棄するだけ)になる
  • スマートフォンの実装も格段に楽になる(おそらく勝手アプリ開放とかしても問題なくなる)
  • 携帯サイト側でもuidの外部漏洩からの成りすまし対策を考えなくてすむようになる
  • スマートフォンからのアクセスポイントは変えるとか面倒な管理がいらなくなる、んじゃないかと思う

逆に問題点もあります。

  • ゲートウェイの負荷が上がる
  • サービス識別子がfqdn/IPだとサーバ分散が難しくなる。認証だけ1台に集約するとかの工夫が必要。

iPhoneによりキャリアとメーカの関係が変わったという話がありましたが、キャリアはいいかげん端末に関しては何でもありな状況を想定し、それでも耐えうる枠組みの構築を真剣に考え、早急に実行する必要があるのではないでしょうか。

.green. ====