2013年6月21日金曜日

JavaScript でコールバックするときの注意

JavaScript では、this がどのオブジェクトを指しているのか注意が必要です。

例えば、クラスA が クラスB へ非同期処理をお願いして、終わったら渡しておいたコールバックを呼び出してもらうとします。
/**
 * クラスA
 * @class
 * @property {B} b クラスBのインスタンス
 */
var A = function() {
    this.myname = 'A';
    this.b = new B();
};

/**
 * クラスAの処理
 * B にコールバックを渡す
 * 非同期な処理が終わったらコールバックを呼び出してもらう
 * @method
 */
A.prototype.start = function() {
    this.b.start(this.callback);
};

/**
 * コールバック関数
 * @method
 */
A.prototype.callback = function() {
    alert('私の名前は ' + this.myname + ' です');
};

/**
 * クラスB
 * @class
 */
var B = function() {
}

/**
 * 非同期処理を行う
 * 終わったらコールバックを呼び出す
 * @method
 * @param {Function} callback コールバック関数
 */
B.prototype.start = function(callback) {
 setTimeout(function() {
  callback();
 });
};

// 実行
var a = new A();
a.start();    // "私の名前は undefined です"

this が a を指すと思いきや window を指します。
window.myname は未定義なので unndefined が出力されます。


こういう時は callback と一緒に自分自身を渡して、callback 呼び出し時に this として自分自身を指定してもらえば大丈夫です。
this を指定して関数を呼ぶときは call() を使います。
/**
 * クラスA
 * @class
 * @property {B} b クラスBのインスタンス
 */
var A = function() {
 this.myname = 'A';
    this.b = new B();
};

/**
 * クラスAの処理
 * B にコールバックを渡す
 * 非同期な処理が終わったらコールバックを呼び出してもらう
 * @method
 */
A.prototype.start = function() {
    this.b.start(this, this.callback);
};

/**
 * コールバック関数
 * @method
 */
A.prototype.callback = function() {
    alert('私の名前は ' + this.myname + ' です');
};

/**
 * クラスB
 * @class
 */
var B = function() {
}

/**
 * 非同期処理を行う
 * 終わったらコールバックを呼び出す
 * @method
 * @param {A} caller 呼び出し元のインスタンス
 * @param {Function} callback コールバック関数
 */
B.prototype.start = function(caller, callback) {
 setTimeout(function() {
  callback.call(caller);  // this = caller として callback() を実行
 });
};

// 実行
var a = new A();
a.start();    // "私の名前は A です"

2013年6月20日木曜日

Xcode で C 言語

久しぶりに C 言語を書こうと gcc や vi を使おうとしても、普段 Xcode を使ってるとちょっと面倒くさいかったりします。

Xcode で書けないのかなと調べてみたら書けるようですね。

File -> New -> Project

OSX Application -> Command Line Tool



Type で C を指定



これで C 言語のコンソールプログラムが書けます。

2013年6月19日水曜日

ニコ生とC言語

ペアプログラミング気分を味わいたいときはニコ生のプログラミング配信をよく見ています。

 ニコニコ生放送
 http://live.nicovideo.jp


「開発」や「プログラミング」で検索すると、意外と出てきます。
趣味でコードを書き始めたおじさん、Unity でバリバリゲームを作っているお兄さん、「やさしいC」と格闘する中学生まで様々です。
どんなことでも人に見せることを意識するとモチベーションが上がりますよね。

放送中に Stypi などのリアルタイムで共有できるエディタを使ってみんなでわいわいコードを書いたりすると結構楽しいものです。

 Stypi
 https://code.stypi.com/

そこで放送中に C 言語のコンソールゲーム書きました。
wasd で移動してアイテムを集めます。敵に当たったらゲームオーパーです。



C言語を触り始めた頃はよくこんなかんじのゲーム作りましたよね。
/**
 * ダンジョンRPG
 * C言語でダンジョンRPG的なものを作る
 *
 * - 画面の見方 -
 * @ : 主人公
 * X : 敵
 * * : アイテム
 *
 * - ルール -
 * 敵に当たらずにアイテムを集めろ
 *
 * - 操作方法 -
 * w: 上
 * a: 左
 * s: 下
 * d: 右
 */
#include <stdio.h>
#include <stdlib.h>

// プロトタイプ宣言
void showMap();
void showMessage(char message[]);
int getKey();
void clear();
void moveTo(int x, int y);
void setItems();
void initialize();
void gameClear();
void setEnemy();
void pickup();
void moveEnemies();
void gameOver();

// マップサイズ
const int WIDTH = 30;
const int HEIGHT = 10;

// アイテムの数
const int ITEMNUM = 10;

// キー操作
const char UP = 'w';
const char LEFT = 'a';
const char DOWN = 's';
const char RIGHT = 'd';

// 面倒くさいから関数間データ共有はグローバル
char map[HEIGHT][WIDTH];
int playerX = 5;
int playerY = 1;
int collected = 0; // 拾ったアイテムの数
int gameover = 0; // ゲームオーバーになったかどうか

int main(int argc, const char *argv[]) {
    int key;

    // 初期化
    initialize();

    // メインループ
    while(gameover == 0) {
        // 画面の更新
        clear();
        showMap();

     // アイテムを全部拾ったらゲームクリア
        if(collected >= ITEMNUM) {
            gameClear();
            break;
        }

        // メッセージ出力
        showMessage("  コマンド入力後に Enter \n  上:w  左:a  下:s 右:d");

        // プレイヤーの入力
        key = getKey();
        
        // データの更新
        switch(key) {
        case UP:
            moveTo(0, -1);
            break;
        case DOWN:
            moveTo(0, 1);
            break;
        case RIGHT:
            moveTo(1, 0);
            break;
        case LEFT:
            moveTo(-1, 0);
            break;
        default:
            // それ以外のキーが押されたら次の入力をまつ 
            continue;
        }

        // 敵を動かす
        moveEnemies();
    }
}

/**
 * ゲームの初期化
 * @function
 */
void initialize() {
    int i, j;

    // マップ初期化
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            map[i][j] = ' ';
        }
    }

    // 主人公の配置
    map[playerY][playerX] = '@';
    
    // アイテムの配置
    setItems();
}


/**
 * アイテムを拾う
 * ついでに敵が出現する
 * @function
 */
void pickup() {
    collected++;
    setEnemy();
}

/**
 * ゲームクリア
 * @function
 */
void gameClear() {
    showMessage("すべてのアイテムを集めた!\nゲームをクリアした!おめでとう!");
}

/**
 * 敵を1対配置する
 * @function
 */
void setEnemy() {
    int x, y;
    do {
        x = rand() % WIDTH;
        y = rand() % HEIGHT;
    } while(map[y][x] != ' ');
    map[y][x] = 'X';
}

/**
 * 敵を動かす
 * ランダム方向に移動しようとしてアイテムにt回阻まれたら動かない
 * @function
 */
void moveEnemies() {
    int t = 1;
    int i, j, k;
    int dx, dy;
    int count = 0;
    int enemyCount = 0;
    int nowEnemyNumber = 0;
    int duplicated;

    // 既に動いた敵の移動先座標リスト
    // 長さが ITEMNUM なのは敵がアイテム数以上出現しないため
    // 同じ敵が2回以上動くことを防止する
    int movedListX[ITEMNUM];
    int movedListY[ITEMNUM];
    
    // 敵を数える
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            if(map[i][j] == 'X') {
                enemyCount++;
            }
        }
    }

    // 既に動いた敵リストを初期化
    for(i = 0; i < ITEMNUM; i++) {
        movedListX[i] = -1;
        movedListY[i] = -1;
    }

    // 敵を順番に動かす
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            if(map[i][j] == 'X') {
                
                // 既に動かした敵リストに登録されているものは動かさない
                duplicated = 0;
                for(k = 0; k < nowEnemyNumber; k++) {
                    if(i == movedListY[k] && j == movedListX[k]) {
                        duplicated = 1;
                        break;
                    }
                }
                if(duplicated) {
                    continue;
                }
                
                do {
                    dx = (rand() % 2) == 0 ? 1 : -1;
                    dy = (rand() % 2) == 0 ? 1 : -1;

                    if(i + dy < 0 || HEIGHT < i + dy || j + dx < 0 || WIDTH < j + dx) {
                        continue;
                    }

                    count++;
                } while(map[i + dy][j + dx] != ' ' && map[i + dy][j + dx] != '@' && count < t);

                if(map[i + dy][j + dx] == '@') {
                    map[i][j] = ' ';
                    map[i + dy][j + dx] = 'X';
                    movedListX[nowEnemyNumber] = j + dx;
                    movedListY[nowEnemyNumber] = i + dy;
                    gameOver();
                } else if(map[i + dy][j + dx] == ' ') {
                    map[i][j] = ' ';
                    map[i + dy][j + dx] = 'X';
                    movedListX[nowEnemyNumber] = j + dx;
                    movedListY[nowEnemyNumber] = i + dy;
                } else {
                    // 動かない
                }

                // 動かした敵を数えておく
                nowEnemyNumber++;
            }
        }
    }
}

/**
 * 敵に触ってゲームオーバー
 * @function
 */
void gameOver() {
    clear();
    showMap();
    showMessage("敵に襲われてゲームオーバー!");
    gameover = 1;
}

/**
 * アイテムの配置
 * マップ内にランダムにアイテムを配置する
 * @function
 */ 
void setItems() {
    int x, y, i;
    for(i = 0; i < ITEMNUM; i++) {
        x = rand() % WIDTH;
        y = rand() % HEIGHT;
        if(map[y][x] == ' ') {
            map[y][x] = '*';
        } else {
            i--;
            continue;
        }
    }
}

/**
 * プレイヤーを現在地から移動させる
 * @function
 * @param {int} x X方向の移動距離
 * @param {int} y Y方向の移動距離
 */
void moveTo(int x, int y) {
    // 外の世界に出ないようにする
    if(playerX + x < 0 || WIDTH < playerX + x || playerY + y < 0 || HEIGHT < playerY + y) {
        return;
    }
    
    // 移動前の主人公を消す
    map[playerY][playerX] = ' ';
    
    // 座標の更新
    playerX += x;
    playerY += y;
    
 // 移動先にアイテムがあれば拾う
    if(map[playerY][playerX] == '*') {
        pickup();
    }

    // 移動後の主人公を追加
    map[playerY][playerX] = '@';
}

/**
 * プレイヤーからのキー入力を受け付ける
 * @function
 * @returns {int} プレイヤーが押したキー
 */
int getKey() {
    printf("コマンド? => ");
    return getchar();
}

/**
 * 画面を消す
 * @function
 */
void clear() {
    int i;
    for(i = 0; i < 30; i++) {
        putchar('\n');
    }
}

/**
 * 枠線を表示(メッセージ用)
 * @function
 */
void line() {
    int i;
    putchar('+');
    for(i = 0; i < WIDTH - 2; i++) {
        putchar('-');
    }
    putchar('+');
    putchar('\n');    
}

/**
 * 画面下部にメッセージ表示
 * @function
 * @param {char[]} 表示するメッセージ
 */
void showMessage(char message[]) {
    line();
    puts(message);
    printf("  アイテム( %d / %d )\n", collected, ITEMNUM);
    line();
}

/**
 * マップの出力
 * @function
 */
void showMap() {
 int i, j;
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            putchar(map[i][j]);
        }
        putchar('\n');
    }
    map[playerY][playerX] = '@';
}

2013年6月18日火曜日

GAE + Go で Sign in with Twitter (4)

Authentication Header が作れたので、リクエストを送信する部分を作ります。
OAuth 1.0a の通信の流れを簡単に描くと以下の用になります。





まず、リクエストトークンを要求します。事前に Twitter へ登録しているアプリであればリクエストトークンがもらえます。登録されたアプリかどうかの判断は、Authorization ヘッダによって判断されます。登録されたアプリであると判断されたらリクエストトークンが帰ってきます。

次に、データのアクセスについてユーザの同意を得ます。Twitter のログインページヘリダイレクトして、Twitter へのログイン・ユーザデータへのアクセス許可をしてもらいます。リダイレクトする際にリクエストトークン(未認証)を渡すと、アクセス許可された後ににリクエストトークン(認証済)が帰ってきます。

次に、認証済のリクエストトークンとアクセストークンを交換します。アクセストークンとはユーザのデータへアクセスするために使うパスワードのようなものです。Twitter ではアクセストークンと一緒にユーザのID、ユーザ名が返ってくるため、Twitter アカウントを使ってログインするだけならここまでで OK です。

それ以降は、アクセストークンを使ってユーザのデータへアクセスすることができます。今回はアクセスしませんのでこちらはやらないです。


HTTP リクエスト送信処理

まず、HTTP リクエストを送ってレスポンスを得る汎用的なメソッドを書きます。
すべてのリクエストには Authorization Header が含まれるので、先日作った createHeader() メソッドを使います。
/**
 * リクエストを送信してレスポンスを受信する
 * メソッドは POST 固定
 * @method
 * @memberof OAuth
 * @param {string} targetUrl 送信先
 * @param {string} body リクエストボディ
 * @returns {string} レスポンス
 */
func (this *OAuth) request(targetUrl string, body string) string {

    // リクエストごとに変わるパラメータを設定
    this.params["oauth_nonce"] = this.createNonce()
    this.params["oauth_timestamp"] = strconv.Itoa(int(time.Now().Unix()))
    this.params["oauth_signature"] = this.createSignature(targetUrl)
 
    // Authorization Header を作成
    header := this.createHeader()
 
    // リクエストの作成
    var request *http.Request
    var err error
    if body == "" {
        request, err = http.NewRequest("POST", targetUrl, nil)
    } else {
        request, err = http.NewRequest("POST", targetUrl, NewReader(body))
    }
    check(this.context, err)
    request.Header.Add("Authorization", header)
 
    // リクエストの送信とレスポンスの受信
    client := urlfetch.Client(this.context)
    response, err := client.Do(request)
    check(this.context, err)
 
    // レスポンスボディの読み取り
    result := make([]byte, 256)
    response.Body.Read(result)
 
    return string(result)
}

エラーチェックのために OAuth クラスへ context を追加したり細かい修正をしていますが省きます。詳しくは Github のコードをご覧ください。

Go から HTTP リクエストを作成する場合、普通は net/http パッケージを使うのですが、GAE 上で動いている場合には GAE の appengine/urlfetch ライブラリを使うことになっています。

 URL Fetch Go API Overview - Google App Engine
 https://developers.google.com/appengine/docs/go/urlfetch/

また、リクエストボディは io.Reader インタフェースを実装している必要があるため、Read メソッドを実装します。
/**
 * リクエストボディ用のリーダー
 * request() で body を送信するために使う
 * @class
 * @member {[]byte} body 本文
 * @member {int} pointer 何バイト目まで読み込んだか表すポインタ
 */
type Reader struct {
 io.Reader
 body []byte
 pointer int
}

/**
 * Reader のインスタンスを作成する
 * @param {string} body 本文
 * @returns {*Reader} 作成したインスタンス
 */
func NewReader(body string) *Reader {
 reader := new(Reader)
 reader.body = []byte(body)
 reader.pointer = 0
 return reader
}


/**
 * 本文を読み出す
 * 2回目以降は前回の続きから読み出せる
 * @method
 * @memberof *Reader
 * @param {[]byte} p 読みだしたデータの保存先
 * @returns {int} 読みだしたバイト数
 * @returns {error} エラー
 */
func (this *Reader) Read(p []byte) (int, error) {
 var l int
 var err error
 if this.pointer + len(p) < len(this.body) {
  l = len(p)
  err = nil
 } else {
  l = len(this.body) - this.pointer
  err = io.EOF
 }
 
 for i := 0; i < l; i++ {
  p[i] = this.body[i + this.pointer]
 }
 
 this.pointer = l + this.pointer
 
 return l, err
}


このリクエスト送信メソッドを使って実際にリクエストを送る部分を書きます。


リクエストトークンの要求

まず最初に、リクエストトークンの要求です。
Twitter の場合、https://api.twitter.com/oauth/request_token にリクエストを送信します。
/**
 * Twitter へリクエストトークンを要求する
 * @method
 * @memberof OAuth
 * @returns {map[string]string} リクエスト結果
 */
func (this *OAuth) requestToken() map[string]string {
    response := this.request("https://api.twitter.com/oauth/request_token", "")
    datas := strings.Split(response, "&")
    result := make(map[string]string, len(datas))
    for i := 0; i < len(datas); i++ {
        data := strings.Split(datas[i], "=")
        result[data[0]] = data[1]
    }
 
    return result
}

レスポンスボディにリクエストトークンが入っています。
パラメータ名=値&パラメータ名=値
という感じになっているのでパラメータ毎に切り離して返します。


ユーザの許可を得る

次に、ユーザ認証画面へリダイレクトします。
Sign in with Twitter ボタンが押された時にリダイレクトするようにしましょう。
リダイレクト先は
https://api.twitter.com/oauth/authenticate?oauth_token=***
です。***にはリクエストトークンが入ります。

ユーザが Twitter へログインして、データへのアクセスを許可します。
許可が完了したら oauth_callback へリダイレクトされます。
このとき、パラメータとして認証済リクエストトークン(oauth_token) が渡されるので取り出します。

/**
 * OAuthで他のサイトでログインしてから戻ってきた時
 * @method
 * @memberof Controller
 * @param {http.ResponseWriter} w 応答先
 * @param {*http.Request} r リクエスト
 */
func (this *Controller) oauthCallback(w http.ResponseWriter, r *http.Request) {
    token := r.FormValue("oauth_token")
    verifier := r.FormValue("oauth_verifier")
 
    c := appengine.NewContext(r)
    oauth := NewOAuth(c)
    result := oauth.exchangeToken(token, verifier, "https://api.twitter.com/oauth/access_token")
    view := new(View)
    view.login(c, w)
    fmt.Fprintf(w, "あなたのidは %s です
あなたのユーザ名は %s です", result["user_id"], result["screen_name"])
 
    u, _ := user.CurrentOAuth(c, "")
    log.Printf("ユーザ: %#v", u)
}

この中で、認証済みリクエストトークンとアクセストークンを交換しています。
/**
 * リクエストトークンをアクセストークンに変換する
 * @memberof OAuth1
 * @method
 * @param {string} token リクエストトークン
 * @param {string} verifier 認証データ
 * @param {string} targetUrl リクエストの送信先
 * @returns {map[string]string} アクセストークンとユーザデータ
 */
func (this *OAuth1) exchangeToken(token string, verifier string, targetUrl string) map[string]string {
    this.params["oauth_token"] = token
    body := fmt.Sprintf("oauth_verifier=%s", verifier)
    response := this.request(targetUrl, body)
 
    datas := strings.Split(response, "&")
    result := make(map[string]string, len(datas))
    for i := 0; i < len(datas); i++ {
        data := strings.Split(datas[i], "=")
        result[data[0]] = data[1]
    }
    return result
}

アクセストークンと一緒に、ユーザ名(screen_name)、ユーザID(user_id)が帰ってくるので、これをログインデータとして使えばOKです。

2013年6月17日月曜日

GAE + Go で Sign in with Twitter (3)

Go + GAE で OAuth1.0a の Authorization Header を作成します。

ソースコードは GitHub にアップロードしてあります。
https://github.com/yokano/escape3ds_angularjs/blob/master/server/oauth1.go

まず、通信を行うための構造体 OAuth を作成します。
クラスっぽく扱うのでコメントではクラスと書いています。
送信するパラメータなどを保存しておけるようにしておきます。
/**
 * OAuthの通信を行うクラス
 * @class
 */
type OAuth struct {
    params map[string]string
}

インスタンス化する関数を作成します。
/**
 * OAuthクラスのインスタンス化
 * @function
 * @returns {*OAuth} OAuthインスタンス
 */
func NewOAuth() *OAuth {
    params := make(map[string]string, 7)
    params["oauth_callback"] = "http://localhost:8080/oauth_callback"
    params["oauth_consumer_key"] = config["consumer_key"]
    params["oauth_signature_method"] = "HMAC-SHA1"
    params["oauth_version"] = "1.0"
 
    oauth := new(OAuth)
    oauth.params = params
    return oauth
}

初期化する際に、固定できるパラメータを指定しています。

  • oauth_callback
  • oauth_consumer_key
  • oauth_signature_method
  • oauth_version

上の4つはリクエストによらず不変なので初期化時に指定しておきます。

  • oauth_nonce
  • oauth_timestamp
  • oauth_signature

この3つはリクエストごとに異なる値を取ります。
そのため、初期化時に指定せずリクエスト毎に作り直します。


まずは、oauth_nonce からです。
oauth_nonce はリクエストごとに異なるランダムな文字列を指定します。
この値は、クライアントから同じリクエストが過去に送られてきたかどうかをサーバが判断するための値です。
ここでは仕様書に載っている例と同様に生成してみます。
32byte のランダムなデータを Base64 エンコードして記号を削除した文字列を oauth_nonce とします。8byte で同じ事をする関数を別で作ったので、それを4回繰り返しています。
/**
 * oauth_nonce を作成する
 * @method
 * @memberof OAuth
 * @returns {string} 作成したoauth_nonce
 */
func (this *OAuth) createNonce() string {
    nonce := ""
    for i := 0; i < 4; i++ {
        nonce = strings.Join([]string{nonce, string(getRandomizedString())}, "")
    }
    return nonce
}
/**
 * ランダムな文字列を取得する
 * 64bit のランダムデータを Base64 エンコードして記号を抜いたもの
 * @function
 * @returns {string} ランダムな文字列
 */
func getRandomizedString() string {
    r := rand.Int63()
    b := make([]byte, binary.MaxVarintLen64)
    binary.PutVarint(b, int64(r))
    e := base64.StdEncoding.EncodeToString(b)
    e = strings.Replace(e, "+", "", -1)
    e = strings.Replace(e, "/", "", -1)
    e = strings.Replace(e, "=", "", -1)
    return e
}

次に oauth_timestamp です。
これはリクエストが作成された時の Unix タイムです。
現在時刻から離れていると Twitter からエラーが来るので注意が必要です。
oauth_nonce と oauth_timestamp はワンタイムパスワードのような役割を持ち、反射攻撃を防ぎます。
タイムスタンプをとるだけなのでコードは以下のように簡単になります。
this.params["oauth_timestamp"] = strconv.Itoa(int(time.Now().Unix()))

次に oauth_signature です。
oauth_signature は他のパラメータと比べると複雑でバグが出やすいです。
oauth_signature は送信するデータと秘密鍵 consumer_secret を使って oauth_signature_method 方式で暗号化した文字列です。
Twitter の場合は HMAC-SHA1 が使われています。
サーバ側でも同じように oauth_signature を計算して、クライアント側から送られてきた oauth_signature と一致するかどうか調べます。
これによって consumer_secret を知っている人(アプリ開発者)だけのアクセスが受け入れられます。

暗号化する前の文字列(base string)は以下のようになっています。

メソッド名&送信先URL&パラメータストリング

メソッド名は GET or POST。必ず大文字です。
送信先URL はリクエストの送信先の URL をパーセントエンコードしたものです。
パラメータストリングは oauth_signature 以外の oauth パラメータをまとめた文字列です。

パラメータストリングの作り方
  1. oauth パラメータをアルファベット順にならべる
  2. パラメータ名と値をパーセントエンコードする
  3. パラメータ名と値を = でくっつける
  4. さらにパラメータ同士を & でくっつける
oauth_signature の作成部分を Go で書くと以下のようになります。
/**
 * oauth_signature を作成する
 * @method
 * @memberof OAuth
 * @param {string} targetUrl リクエスト送信先のURL
 * @returns {string} oauth_signature
 */
func (this *OAuth) createSignature(targetUrl string) string {
    // アルファベット順に並び替える
    keys := make([]string, 0)
    for key := range this.params {
        keys = append(keys, key)
    }
    sort.Strings(keys)
 
    // パラメータストリングの作成
    params := make([]string, len(keys))
    for i := 0; i < len(keys); i++ {
        key := keys[i]
        val := this.params[key]
        params[i] = fmt.Sprintf("%s=%s", url.QueryEscape(key), url.QueryEscape(val))
    }
    paramString := strings.Join(params, "&")
 
    // ベース文字列の作成
    baseString := fmt.Sprintf("POST&%s&%s", url.QueryEscape(targetUrl), url.QueryEscape(paramString))
 
    // consumer secret をエンコード
    signatureKey := fmt.Sprintf("%s&", url.QueryEscape(config["consumer_secret"]))
 
    // HMAC-SHA1 で暗号化
    hash := hmac.New(sha1.New, []byte(signatureKey))
    hash.Write([]byte(baseString))
    signature := hash.Sum(nil)
    return base64.StdEncoding.EncodeToString(signature)
}


アルファベット順に並び替えるのは sort パッケージ
文字列の連結は strings パッケージ
URL エスケープは url パッケージ
HMAC-SHA1 は hmac パッケージと sha1 パッケージが使えます。

 sort package - Go lang

 url package - Go lang

 sha1 package - Go lang

 hmac package - Go lang


以上で 7 つの oauth パラメータが揃いました。
最後に Authorization Header を作ります。

  1. oauth パラメータ名と値をパーセントエンコードする
  2. 「パラメータ名="値"」 という感じでくっつける(ダブルクォート必須)
  3. すべてのパラメータを 「, 」で区切る(カンマの後のスペースはなくてもOK)
  4. 先頭に 「OAuth 」をくっつける

/**
 * ヘッダを作成する
 * @method
 * @memberof OAuth
 * @returns {string} ヘッダ
 */
func (this *OAuth) createHeader() string {
    params := make([]string, 0)
    for key, val := range this.params {
        key = url.QueryEscape(key)
        val = url.QueryEscape(val)
        set := fmt.Sprintf(`%s="%s"`, key, val)
        params = append(params, set)
    }
    header := strings.Join(params, ", ")
    header = fmt.Sprintf("OAuth %s", header)
    return header
}


Authorization Header が作れたら、ほぼできたも同然です。
今日はここまで。

2013年6月16日日曜日

GAE + Go で Sign in with Twitter (2)

昨日に続きまして GAE + Go で Twitter ログインです。

公式ドキュメントはこちら
 Sign in with Twitter
 https://dev.twitter.com/docs/auth/sign-twitter



今回の内容はこちらの Step.1 に該当します
 Implementing Sign in with Twitter
 https://dev.twitter.com/docs/auth/implementing-sign-twitter


Authorization Header

GAE のアプリから Twitter へ HTTP リクエストを送信することで認証を進めます。
Twitter はセキュリティ上、リクエストを送ってきたアプリが、事前に登録されたアプリであることを確認する必要があります。

登録されたアプリかどうかは、アプリ登録時にもらえる consumer key と consumer secret を使って確認します。consumer key はアプリのID、consumer secret はアプリのパスワードのようなものです。

Twitter へ送信されるリクエストの1つを見てみましょう。
POST /oauth/request_token HTTP/1.1
User-Agent: themattharris' HTTP Client
Host: api.twitter.com
Accept: */*
Authorization: 
  OAuth oauth_callback="http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F",
  oauth_consumer_key="cChZNFj6T5R0TigYB9yd1w",
  oauth_nonce="ea9ec8429b68d6b77cd5600adbbb0456",
  oauth_signature="F1Li3tvehgcraF8DMJ7OyxO4w9Y%3D",
  oauth_signature_method="HMAC-SHA1",
  oauth_timestamp="1318467427",
  oauth_version="1.0"


ここで重要になるのが Authorization ヘッダです。Tiwtter へ送るすべてのリクエストには Authorization ヘッダが含まれます。

この中の oauth_signature が登録したアプリ自身であることを証明する署名データです。上の例では oauth_signature="F1Li3tvehgcraF8DMJ7OyxO4w9Y%3D" の部分です。

また、oauth_signature 以外にも色々と送らなければいけません。何を送るかは OAuth 1.0a にて定義されています。


Authorization ヘッダに関する Twitter のドキュメントはこちら
 Authorizing a request - Twitter Developers
 https://dev.twitter.com/docs/auth/authorizing-request



Authorization ヘッダは基本的に 7 つのパラメータから構成されています。
それぞれ oauth_****** という名前が付いています。


1. oauth_callback

Twitter のログインが完了した後にリダイレクトされる URL を指定します。
とりあえずアプリの URL をそのまま使います。
開発が進んだら適切な URL へ変更してください。 

この画面から戻る先のURLです


2. oauth_consumer_key


Twitter にアプリを登録した際に1つだけ割り当てられる ID です。
登録した Twitter アプリケーションの詳細ページから確認できます。
アプリの ID だと思って大丈夫です。

ここの右側に表示されている文字列です

3. oauth_nonce

他のリクエストと識別するためのランダムな文字列です。
oauth_timestamp と合わせて過去に同じリクエストが送られたかどうかを判断するために使われます。
同じリクエストを1度しか受け付けないことで、リプレイ攻撃を防げます。
ランダムな文字列ならなんでも良いですが、今回は Twitter の公式ドキュメントと同じように、「32byte のランダムなデータを base64 エンコードした後、記号を削除した文字列」を使います。

4. oauth_signature

登録されたアプリであることを証明する署名データです。
oauth パラメータや HTTP メソッドなどを含む文字列を、Twitter とアプリ管理者だけが知っている秘密鍵 consumer_secret で暗号化することで、署名とデータの改竄検出が可能になります。
暗号化に使われるメソッドは Twitter の場合、HMAC-SHA1 が使われます。
oauth_signature の作成は他のデータと比べると複雑でバグが出やすいので要注意です。

5. oauth_signature_method

署名データを暗号化するアルゴリズムを指定します。
Twitter は HMAC-SHA1 で固定です。

6. oauth_timestamp

リクエストが作成された時刻を表す UNIX Timestampです。
oauth_nonce と合わせてリプレイ攻撃を防ぎます。
また、現在よりも離れている時間が設定されているとエラーが返されます。
コンピュータの時計が正しく設定されているかを確認することが大事です。
ただし、GAE の場合は時計のずれは気にしなくてよいでしょう。

7.  oauth_version

OAuth のバージョンです。Twitter では "1.0" で固定です。
ちなみに正確には OAuth 1.0 を修正した OAuth 1.0 Revision A(OAuth1.0a) が採用されています。

以上の 7 つの oauth パラメータを Authorization ヘッダに含めて送信します。
リクエストを受け取った Twitter は自分でも HMAC-SHA1 で oauth_signature の値を計算して、リクエストに含まれる oauth_signature と一致するか調べます。
一致したら、登録したアプリであることを認めてリクエストに答えます。


今日はここまで

2013年6月15日土曜日

GAE + Go で Sign in with Twitter (1)

Google App Engine + Go のアプリで "Sign in with Twitter" をやる方法です。
Twitter は OAuth1.0a を採用しています。
長いので数回に分けます。

Twitter の公式ドキュメントはこちら
    Sign in with Twitter - Twitter Developers
    https://dev.twitter.com/docs/auth/sign-twitter

OAuth 1.0 の仕様書はこちら
 OAuth 1.0 Revision A
 http://oauth.net/core/1.0a/


Twitter にアプリを登録する

Twitter でログインするためには事前にWebアプリを登録しなければなりません。

Twitter を開きます。
    https://twitter.com

画面左下の「開発者」リンクをクリックします。




画面右上の Sign in をクリックしてログインします。



画面右上のユーザアイコンから My applications メニューをクリックします。




ログインします。



登録したアプリ一覧が表示されます。
アプリを登録していない場合は何も表示されません。
Create a new application ボタンを押します。



アプリ名、アプリの説明、Web サイトの URL を入力します。
Web サイトの URL には GAE のアプリ URL をそのまま入力します。
Callback URL は後ほど改めて設定します。
利用規約を読み、キャプッチャを入力し、
Create your Twitter application ボタンをクリックします。




アプリの詳細画面が表示されます。
この画面に、通信で使用する暗号鍵など重要な情報が含まれています。



このアプリで Sign in with Twitter を使えるようにしましょう。
アプリの Setting タブを開き、Allow this application to be used to Sign in with Twitter にチェックを入れ、Update this Twitter application's settings ボタンをクリックします。





まずはここまで。続きは明日へ。