HerokuへFuelPHP環境を構築する手順をメモ
FuelPHP Advent Calendar 2013の23日目です。昨日は、@egmcさんの「FuelPHPがOpAuth対応になったのでfacebookログインをしてみる」でした。
以前まではサービスをリリースするときにはレンタルサーバを借りてサービスをデプロイすることが一般的でしたが、最近はPaaSと呼ばれるアプリケーションの動作環境をプラットフォーム上で一式提供されている形態を使う事が増えてきています。有名どころではAmazonのAWSやMicrosoftのWindows Azureなどがあります。
PaaSサービスのそれぞれの違いはスペック・料金・機能などがあり、レンタルサーバと比較した場合のPaaSのメリットはスケールアップ、アウトが簡単に行えることです。
サービスのユーザ数が大幅にふえてサーバに負荷がかかるようになった場合や、予想してた以上にユーザ数が延びなかった場合などに役立ちます。
個人的にはHerokuはRailsのアプリをテスト的に公開したくなったときなどに使ったりしています。もともとHeroku自体はrubyの環境用としてスタートしていて現在はjava,node.js,ruby,phthonなどをサポートしています。
現在Herokuではphpは非サポートとなっていますがphpも動作します。
Herokuコマンド参考ページ:http://d.hatena.ne.jp/xyk/20101102
————————————————————————————————–
試みたのは、下記サイトに記載されているbuildpackを使用してサーバはnginxを使用するつもりでしたが、
nginxのconfigファイルの設定がFuelPHPのプロジェクトに合わせるとなぜかうまくページが表示されず原因不明だったので今回は見送ることにします。すみません。
http://tkyk.name/blog/2012/11/28/php-on-heroku/
————————————————————————————————–
1.Herokuアカウントを取得
公式サイトより「login」押下して「signup」よりアカウントを取得します
公式サイト:https://www.heroku.com/
2.heroku toolbelt(ターミナルからherokuを操作するツール)をインストール
下記サイトよりtoolbeltを環境に合わせてインストールします
3.SSH公開鍵の設定
(1)ターミナルを立ち上げてloginコマンドを実行
$ heroku login Enter your Heroku credentials. Email: [1で作成したアカウントを入力] Password (typing will be hidden): [1で作成したアカウントのパスワードを入力] Authentication successful.
「Authentication successful.」でアカウント認証成功 ※「Authentication failed.」は失敗
(2)公開鍵をジェネレート
Could not find an existing public key. Would you like to generate one? [Yn] [Yを指定してエンター] Generating new SSH public key. Uploading SSH public key /Users/(PCユーザー名)/.ssh/id_rsa.pub Authentication successful.
「Authentication successful.」で成功
初期ログイン時に公開鍵を生成しなかった場合は手動で「ssh-keygen」で作成しherokuに公開鍵を設定する必要があります
ssh-keygenについては前記事参照http://to-developer.com/blog/?p=563
Herokuに公開鍵を設定する
$ heroku keys:add
※Herokuのgui画面から設定も可
4.fuelphpプロジェクトを生成
(1)アプリケーションを作成
$ oil create [アプリ名を入力]
(2)index.php作成
Herokuはrootディレクトリにindex.phpがないと動作しないため、今のところ空のindex.phpファイルを生成しときます
$ touch index.php
(3).htaccessを作成(fuelphpのディレクトリ構成に合わせてリダイレクト処理を入れる)
fuelphpディレクトリ構成のpublic/以下にindex.phpのアクセスをリダイレクト
参考サイト:http://blog.livedoor.jp/erscape/archives/6937126.html
$ vim .htaccess
.htaccessの設定情報
RewriteEngine on RewriteBase / RewriteRule ^(.+)-info\.php$ $1-info.php [L] RewriteCond %{SCRIPT_FILENAME} !^/app/www/public/ RewriteRule ^(.*)$ public/$1 [L]
(4)不要ファイル削除(サブモジュールなどはaddできないので削除)
$ rm -rf .git .gitmodules $ rm *.md $ rm -rf docs // 下記も不要なため削除 $ rm -fr fuel/core/ $ rm -fr fuel/packages/auth/ $ rm -fr fuel/packages/email/ $ rm -fr fuel/packages/oil/ $ rm -fr fuel/packages/orm/ $ rm -fr fuel/packages/parser/
※サブモジュールをaddする方法(git submodule addコマンド)
例)opauthサブモジュールの場合
$ git submodule add git://github.com/andreoav/fuel-opauth.git fuel/packages/opauth
5.ローカルリポジトリにコミット
$ cd [アプリ名を入力] $ git init $ git commit -am "initial commit"
6.buildpackをherokuへインストール
$ heroku create --buildpack https://github.com/winglian/heroku-buildpack-php [アプリ名を入力]
※アプリ名はここで入れなくてもデフォルトの名前が付けられる。gui画面などから確認・変更が可能
buildpack:https://github.com/winglian/heroku-buildpack-php
7.herokuのリポジトリへ反映
$ git push heroku master
※アプリが複数存在する場合、Herokuのリモートリポジトリが違いpushできない場合があるので都度確認が必要
※1 リモートリポジトリherokuの設定
$ git remote add heroku [リモートリポジトリ]
※2 リモートリポジトリ確認
$ git remote show
※3 リモートリポジトリ削除
$ git remote rm [リモートリポジトリ]
8.動作確認
$ heroku open
9.mysqlアドオンを入れる
無料版のアドオンを入れる場合も公式サイトからログインを行いクレジットカードの登録が必要です。
ただ無料版の場合は料金が引かれるなど初期費用なども基本ないようです。
mysqlのアドオンはhttps://addons.heroku.com/からsearchボックスに「mysql」と検索すると2013/12時点で4つのアドオンが見つかりました。
Adminium Full fledged admin interface without touching your app code heroku addons:add adminium
Amazon RDS Hook your app up to Amazon’s RDS heroku addons:add amazon_rds
ClearDB MySQL Database The high speed, 100% uptime database for your MySQL powered applications. heroku addons:add cleardb
Xeround Cloud Database αlpha Scalable, highly available, zero-management cloud database for MySQL heroku addons:add xeround
今回は「ClearDB」とする
参考サイト:http://www.ownway.info/Ruby/index.php?heroku%2Fhow%2Fmanagement%2Fdatabase%2Fcleardb
(1)公式サイトよりクレジットカード登録を行う
(2)アドオンをインストール
$ heroku addons:add cleardb:ignite Adding cleardb:ignite on tranquil-cliffs-2547... done, v6 (free) Use `heroku addons:docs cleardb:ignite` to view documentation.
インストール完了
(3)接続情報を確認
$ heroku config === tranquil-cliffs-2547 Config Vars BUILDPACK_URL: https://github.com/winglian/heroku-buildpack-php CLEARDB_DATABASE_URL: mysql://[ユーザ名]:[パスワード]@[ホスト名]/[DB名]?reconnect=true
※CLEARDB_DATABASE_URLに接続文字列が表示
CLEARDB_DATABASE_URL: mysql://[ユーザ名]:[パスワード]@[ホスト名]/[DB名]?reconnect=true
(4)シェル接続方法は?
mysql –host=[ホスト名] –user=[ユーザ名] –password=[パスワード] [DB名]
10.fuelphpプロジェクトからmysqlへ環境変数で接続
$cleardb = parse_url(getenv('CLEARDB_DATABASE_URL')); $conn = new PDO( sprintf("mysql:dbname=%s;host=%s", substr($cleardb['path'], 1), $cleardb['host']), $cleardb['user'], $cleardb['pass'] );
ここまででphp + mysql + apacheのFuelPHPが動作する環境の構築完了です
まとめ
Herokuにやや癖があり使いにくいと思ってしまった事もありましたが、慣れるとここまで行うのに15分程でできるとおもいます。一度環境を作ってしまえばあとの更新作業はすぐにできますね。
他のPaaSはあまり使った事がありませんが、動作確認程度の使用でしたらHerokuでいいと思います。
またHerokuから公式にphpがサポートされれば、もっとアドオン等増えてくるのではないでしょうか。
明日はクリスマスイブですね。いいことありますように!
FuelPHPメモというよりGitHub寄りなメモ
GitHubは空ディレクトリが存在するとアップできないのが仕様のよう。
FuelPHPのプロジェクトは空ディレクトリが多々あり、Gitは空ディレクトリは隠しファイル[.gitkeep]ファイルを配置することを推奨?している
gitのクライアントツールなどでは自動的に空ディレクトリに[.gitkeep]ファイルを作成してくれるが。
クライアントツールを使わない場合にまとめて入れるコマンドをメモ
プロジェクト上位フォルダへ移動して以下のシェルコマンドでまとめて空[.gitkeep]ファイルを作成
for d in $(find ./fuel/ ./public/ ./doc/ -type d -empty); do touch "$d/.gitkeep"; done
docフォルダはGitHubに上げるときは削除した方がいいと思うので不要かな。
シェルはmacで標準のbashで確認してるけど、zshとか他のはどうなんだろう。多分変わらないと思うけど
opauthライブラリを使用してTwitter認証を実装する手順をメモ
既にTwitterのストラテジが組み込まれているため、設定をするだけで実装可能
Twitter認証は別途tmhOAuthというライブラリが使用されている。どうやら、Twitterのoauth認証を実装する場合に使われるライブラリのよう。
プロジェクトフォルダ(プロジェクト名)は「basedemo」としてローカル環境で試す
導入手順 ※導入はおよそ5分の作業
前提:
前の投稿記事の[FuelPHP]opauthライブラリでFacebook認証を速攻実装するを既に実装していること
手順1.Twitter開発者ページの設定
アプリ登録を行い、「Consumer key」「Consumer secret」を取得する。
[Callback URL]には、Controller_AuthクラスのAction_Callbackメソッドとなるので以下のようにする。
http://[サイトパス]/auth/callback/
※ローカル環境の実行の場合は127.0.0.1とする。localhostにすると登録できない
今回は「http://127.0.0.1/basedemo/auth/callback/」を設定する
手順2.opauth.php設定ファイルの設定
前回のFacebook認証の設定にTwitter認証設定を追加する
<!--?php 'path' =--> '/basedemo/auth/login/', 'security_salt' => 'testtesttest', // デフォルトで設定されている値と違うものにする ※同じ場合動作しない 'callback_url' => '/basedemo/auth/callback/', 'Strategy' => array( 'Facebook' => array( 'app_id' => 'APP_ID', 'app_secret' => 'APP_SECRET' ), 'Twitter' => array( 'key' => 'KEY', // 手順1で取得した「Consumer key」の値を設定 'secret' => 'SECRET' // 手順1で取得した「Consumer secret」の値を設定 ), ),
手順3.エラー修正
手順2までで完了になるが、この段階で実行すると以下のエラーが出た
エラーとしては
ErrorException [ Error ]: Class ‘Opauth\tmhOAuth’ not found
となり、tmhOAuthがありませんって言ってる。
見えていないようなので、tmhOAuth/tmhOAuth.phpにopauthのnamespaceに組み込むことにする。
namespace Opauth; class tmhOAuth { const VERSION = 0.621; /** * Creates a new tmhOAuth object * * @param string $config, the configuration to use for this request */ function __construct($config) { $this->params = array(); $this->headers = array(); $this->auto_fixed_time = false; 〜〜 省略 〜〜
動作確認
「http://localhost/basedemo/auth/login/twitter/」を実行してtwitterのログインページに遷移すればOK。あとはログインして完了すると「OK: Auth response is validated.」が表示されれば成功。
画面上はtwitterからのレスポンス情報が表示されているはず。
最後に・・・
facebookを先に実装しているのでより簡単に行えた。あとはこちらもDBに情報格納やらプロジェクトにあったコールバック処理、遷移に変えればよさそう。
その他ストラテジの追加は下記公式サイトよりストラテジファイルをダウンロードして配置して、config設定を行い多少修正すれば、おそらくすぐに実装が可能。
公式サイト:http://opauth.org/
facebookのアクセストークンの有効期限についてメモ
facebookにはログイン認証が承認されるとアクセストークンが取得でき、2012年まではオフラインアクセストークンは一度取得すると無期限で使用可能だったが、facebookの仕様が改変され現在は廃止されている。
オフラインアクセストークンは一度ユーザから許可を取り取得すると、ユーザがアプリを起動していない場合にもfacebook情報を取得することが可能。現在は無期限ではなく、デフォルトでは期限は2時間程とのこと。
2時間を無期限にすることはできないが、apiが用意されており、以下クエリをfacebookに投げると2ヶ月に延長することが可能。ただしユーザがパスワード変更した場合やアプリを削除すればアクセストークンも使用不可になる。
https://graph.facebook.com/oauth/access_token? client_id=APP_ID& client_secret=APP_SECRET& grant_type=fb_exchange_token& fb_exchange_token=EXISTING_ACCESS_TOKEN
opauthライブラリのfacebook認証では、アクセストークンの期限は2ヶ月となる。
アプリの仕様でログインしてない場合にも情報を取得する必要がある場合には、アクセストークンをDBなどに格納しておくと良い。
opauthライブラリを使用してFacebook認証を実装する手順をメモ
[FuelPHP]opauthライブラリで速攻SNS認証(Twitter,Facebook,Google etc)で投稿しているopauthライブラリの実装方法について。
opauthはPHPのフレームワーク毎にパッケージ化されており、FuelPHPも既にパッケージ化されている。
https://github.com/andreoav/fuel-opauth
※バグ改修、コードリファクリングで何度か更新されているっぽい
一度大元となるFuelPHPのopauthパッケージをプロジェクトに導入すると、あとは各ストラテジのファイルを入れ込むだけで簡単に各種SNS認証が導入できる。
2013/1現在のFuelPHPのopauthパッケージには、既にFacebook,Twitterの二つのストラテジは入れ込まれている。
今回はとりあえずFacebookの認証を動くところまで試す。取り込み手順はREADMEのHow to use の4手順。
プロジェクトフォルダ(プロジェクト名)は「basedemo」としてローカル環境で試す
導入手順 ※導入はおよそ10分程
手順1.Facebook管理者ページの設定
Facebook開発者ページからアプリ登録を行い、「App ID」「App Secret」を取得する。
https://developers.facebook.com/apps
[Facebookでログインするウェブサイト]の[Webサイト]には手順5で作成するController_AuthクラスのAction_Loginメソッドにストラテジ文字列を引数としたアドレスがログインページとなるので以下のようにする。
http://[サイトパス]/auth/login/facebook/
※ローカル環境の実行の場合はlocalhostで問題なし
今回は「http://localhost/basedemo/auth/login/facebook/」を設定する
この[Webサイト]パスを間違えると下記のようなエラーが出るので注意
—————————————————————————————————————————————
API Error Code: 191
API Error Description: The specified URL is not owned by the application
Error Message: Invalid redirect_uri: 指定されたURLは、アプリケーションの設定で許可されていません。
—————————————————————————————————————————————
手順2.プロジェクトにopauthライブラリをgitからclonesで取り込む ※How to use の1
cd basedemo/fuel/packages/ git clone git://github.com/andreoav/fuel-opauth.git opauth
※zipファイルでダウンロードしてフォルダをパッケージ配下に配置しても同様
手順3.opauth.php設定ファイルの作成 ※How to useの2
opauth/config/opauth.phpをbasedemo/fuel/app/config/にコピーする ※新規作成でも良い
<?php 'path' => '/basedemo/auth/login/', 'security_salt' => 'testtesttest', // デフォルトで設定されている値と違うものにする ※同じ場合動作しない 'callback_url' => '/basedemo/auth/callback/', 'Strategy' => array( 'Facebook' => array( 'app_id' => 'APP_ID', // 手順1で取得した「App ID」の値を設定 'app_secret' => 'APP_SECRET' // 手順1で取得した「App Secret」の値を設定 ), ),
security_saltはデフォルトと違う値かどうかをコード上チェックしているため、別の適当な値を設定すること。
今後ストラテジを追加する場合にはFacebookと同様に追加して記載する。
Twitterの場合:
'Twitter' => array( 'key' => 'KEY', 'secret' => 'SECRET' ),
pathとcallback_urlについてはサイト直下に作成している場合は「How to useの2」に記載されている通りpath[/auth/login/]、callback_url[/auth/callback/]でいいが、今回はlocalhost直下ではなくlocalhost/basedemoなので[/basedemo]を付け忘れないこと。今回ここで4時間ぐらい無駄にハマった・・・
それぞれController_AuthクラスのAction_Authメソッド、Action_Callbackメソッドを呼び出している。
手順4.config.phpの設定 ※How to useの3
opauthをパッケージとして読み込む設定をする
<!--?php 'always_load' =--> array( 'packages' => array( 'opauth', ), ),
手順5.コントローラファイルの作成 ※How to useの4
auth.phpを[/fuel/app/classes/controller]へ新規作成する
あとは「How to useの2」のコードをそのままコピペ
<!--?php class Controller_Auth extends Controller { private $_config = null; public function before() { if(!isset($this--->_config)) { $this->_config = Config::load('opauth', 'opauth'); } } /** * eg. http://www.exemple.org/auth/login/facebook/ will call the facebook opauth strategy. * Check if $provider is a supported strategy. */ public function action_login($_provider = null) { if(array_key_exists(Inflector::humanize($_provider), Arr::get($this->_config, 'Strategy'))) { $_oauth = new Opauth($this->_config, true); } else { return Response::forge('Strategy not supported'); } } // Print the user credentials after the authentication. Use this information as you need. (Log in, registrer, ...) public function action_callback() { $_opauth = new Opauth($this->_config, false); switch($_opauth->env['callback_transport']) { case 'session': session_start(); $response = $_SESSION['opauth']; unset($_SESSION['opauth']); break; } if (array_key_exists('error', $response)) { echo '<strong style="color: red;">Authentication error: </strong> Opauth returns error auth response.'." \n"; } else { if (empty($response['auth']) || empty($response['timestamp']) || empty($response['signature']) || empty($response['auth']['provider']) || empty($response['auth']['uid'])) { echo '<strong style="color: red;">Invalid auth response: </strong>Missing key auth response components.'." \n"; } elseif (!$_opauth->validate(sha1(print_r($response['auth'], true)), $response['timestamp'], $response['signature'], $reason)) { echo '<strong style="color: red;">Invalid auth response: </strong>'.$reason.". \n"; } else { echo '<strong style="color: green;">OK: </strong>Auth response is validated.'." \n"; /** * It's all good. Go ahead with your application-specific authentication logic */ } } return Response::forge(var_dump($response)); } }
動作確認
「http://localhost/basedemo/auth/login/facebook/」を実行してfacebookのログインページに遷移すればOK。あとはログインして完了すると「OK: Auth response is validated.」が表示されれば成功。
画面上はfacebookからのレスポンス情報が表示されているはず。
最後に・・・
単に配置しただけで認証のみは簡単に行えた。あとはDBに情報格納やらプロジェクトにあったコールバック処理、遷移に変えればよさそう。
その他ストラテジの追加は下記公式サイトよりストラテジファイルをダウンロードして配置して、config設定を行い多少修正すれば、おそらくすぐに実装が可能。
公式サイト:http://opauth.org/
あれ、ログアウト処理ないね。。セッション値を削除すればいいかな。
GitHubリポジトリにアップしたときにエラーとなった時の対応法メモ
FuelPHPプロジェクトをGitHubにリポジトリ作ってアップしようとしたらエラーが出たので調べてみた
エラー:fatal: Not a git repository: fuel/core/../../.git/modules/fuel/core
リポジトリが存在しないエラー。全く意味が分からない・・・
まずfuel/core/../../.git/modules/fuel/coreディレクトリを探してみるが.git/modules/の段階でディレクトリが存在しない。普通はあるのかな?新規でcloneしたらいいのかな。不明。
とりあえず呼んでるファイルはどこかを探してみる・・・
grep -r ".git/modules" *
実行結果:
fuel/core/.git:gitdir: ../../.git/modules/fuel/core
fuel/packages/auth/.git:gitdir: ../../../.git/modules/fuel/packages/auth
fuel/packages/email/.git:gitdir: ../../../.git/modules/fuel/packages/email
fuel/packages/oil/.git:gitdir: ../../../.git/modules/fuel/packages/oil
fuel/packages/orm/.git:gitdir: ../../../.git/modules/fuel/packages/orm
fuel/packages/parser/.git:gitdir: ../../../.git/modules/fuel/packages/parser
6ファイル。fuel/packagesを少し弄った気もするのでそれが原因かな?
状況が全くよくわからないからもういいや、めんどくさいから全部ファイル削除・・・
rm -r $(grep -rl "modules/fuel" *)
これで一応エラーを回避してプッシュまで成功。
git使ってる人ならおそらくすぐにわかることなのかもしれないけど、スキル不足で原因、解決策共によくわからない。原因ファイルを消してるだけなので、支障ありそうだけど、とりあえずアップできたからいいや。
gitは後々勉強しよう
サンプルデモアプリ「BaseDemoプロジェクト」を公開
FuelPHPをでサービスを作るとして必要となるであろう機能のベースを勉強しながら作成している。途中過程をGitHubへアップ。前回の投稿記事「[FuelPHP]独自オリジナル認証ドライバをsimpleauthを参考に実装する」反映済み
機能としてはログイン(オリジナルドライバ)、サインアップ、アカウント管理機能を実装
※SNS認証についてはアップ段階では未実装。今から直ちに実装予定。
GitHubリポジトリ:
https://github.com/y-matsumoto/BaseDemo.git
※README参照
機能詳細
1.認証ログイン
下記、投稿記事コードを実装
[FuelPHP]独自オリジナル認証ドライバをsimpleauthを参考に実装する1
[FuelPHP]独自オリジナル認証ドライバをsimpleauthを参考に実装する2
[FuelPHP]完成?:独自オリジナル認証ドライバをsimpleauthを参考に実装する3
2.サインアップ
画面:
一般ユーザ権限で作成可能。管理ユーザは画面上作成不可 ※コンソールから作成
権限:
Admin/Userのみ ※権限により処理(画面など)をswitchしてる
管理画面[controller/admin]・一般ユーザ画面[controller/user]を継承
3.アカウント管理(プレビュー)
新規・編集
※スキャフォールドをベースに作成してるため、あまり使えるViewではないのかもない
導入手順
1.clone作成
$ git clone git://github.com/y-matsumoto/BaseDemo.git
2.db関連
プロジェクト直下database.sqlを実行
3.ファイルバーミッション設定 ※やらなくてもいい
$ oil refine install //or [oil r install]
4.apache設定
basedemo/public/をhtdocsへ置く // or シンボリックリンク
5.account作成(admin権限) ※signupで作成する場合は一般ユーザ権限のみ
•[oil console]コマンドでコンソール上からcreatメソッドをコール
$ oil console // start
>>> Auth::create_user(‘admin’, ‘password’, ‘test@test.co.jp’, 100); // create user execute
1 // complete
>>> exit; // end
※導入手順は未確認のため、なにかあればご連絡ください。
よければ・・・
その他、ソースコードの指摘やフォルダ構成の指摘、全体アドバイスなど
もらえると参考になるのでコメント期待!
身近にスキル上、レビューできる人がいないのでコードが煩雑かも・・・
さてSNS認証つけよっと。
前回の続き
ドライバの実装が終わったので、呼び出し側のコード
<?php class Controller_Base extends Controller_Template { public $template = 'common/template'; public function before() { parent::before(); $this->current_user = Auth::check() ? Model_User::find_by_username(Auth::get_screen_name()) : null; View::set_global('current_user', $this->current_user); } }
これはoil generate admin コマンドで自動生成されるベースファイルをそのまま流用
テンプレートパスは’common/template’にしている
ログインを管理するsign.phpを作成
<?php class Controller_Sign extends Controller_Base { public function before() { parent::before(); } public function action_login() { if(Auth::check()) Response::redirect($this->get_groups_redirect_path()); $val = Validation::forge(); if (Input::method() == 'POST') { $val->add('email', 'Email or Username') ->add_rule('required'); $val->add('password', 'Password') ->add_rule('required'); if ($val->run()) { if (Auth::instance()->login(Input::post('email'), Input::post('password'))) { $current_user = Model_User::find_by_username(Auth::get_screen_name()); Session::set_flash('success', e('Welcome, '.$current_user->username)); Response::redirect($this->get_groups_redirect_path()); } // login failure Session::set_flash('error', e('login error. please try agein')); } } $this->template->title = 'Login'; $this->template->content = View::forge('admin/login')->set('val', $val, false); } public function action_logout() { if(!Auth::logout()) { Session::set_flash('info', e('logout success')); Response::redirect('sign/login'); } } public function get_groups_redirect_path() { $groups = Auth::instance()->get_groups(); $group = $groups[0][1]; $group == Util_Const::GROUP_ADMINISTRATOR_KEY and $redirect_path = 'admin/index'; $group == Util_Const::GROUP_USER_KEY and $redirect_path = 'user/index'; return empty($redirect_path) ? '':$redirect_path; } }
ログイン認証を通った場合のリダイレクト先についてはこちらの都合上、groupの権限でswitchしている。
groupのkey値をconstに指定。
<?php class Util_Const { /* ======= db ======= */ const DB_DLT_OFF = 0; const DB_DLT_ON = 1; /* ======= group ======= */ const GROUP_USER_KEY = 1; const GROUP_ADMINISTRATOR_KEY = 100; }
ここまでで呼び出し完了のはず。今回はログインのみのためtb_usersテーブルにアカウントを作成しないと動かない。アカウント作成用のメソッドをauth/login/baseauth.phpに作成。
public function create_user($username, $password, $email, $group = 1,$auth_type = 1,$dlt_flg = 0) { $password = trim($password); $email = filter_var(trim($email), FILTER_VALIDATE_EMAIL); if (empty($username) or empty($password) or empty($email)) throw new Exception('Username, password or email address is not given, or email address is invalid'); $user = new Model_User(); $user->username = $username; $user->password = $this->hash_password((string) $password); $user->email = $email; $user->group = $group; $user->auth_type = $auth_type; $user->last_login = ''; $user->salt = ''; $user->dlt_flg = $dlt_flg; try{ $user->save(); }catch(Exception $e){ throw new Exception('create user registry error'); } return 1; }
それでは呼び出してadminアカウントを作成。ここではsignup画面は省略してoil consoleから作成。
$ oil console >>> Auth::create_user('admin', 'password', 'test@test.co.jp', 100); 1 >>> exit
正直戻り値が成功で「1」っていうのは意味が分からないが、simpleauthに便乗している。trueにしたいところ。phpだからしかたないのかな。これでログインできれば成功かな。
今回はviewなどのコードをアップしていないので、分かりにくいところがあるので、一応githubにsignup画面込みでアップする予定。
前回の続き
抽象メソッド全て実装。simpleauthパクってる所も多い。
本来はDB操作はモデルでメソッド作成するべきだろう。
あとUtil_Const::DB_DLT_OFFをバインドパラメータに直でbind(‘dlt_flg’ , Util_Const::DB_DLT_OFF)とパラメータ参照エラーになる。fuelphpとかではなくDB上の話。
dlt_flgでデフォルトの物理削除ではなく論理削除に回避しているので、取得時も条件式にいれている。
<?php class Auth_Login_BaseAuth extends Auth\Auth_Login_Driver { protected $config = array('drivers' => array('group' => array('BaseAuth'))); protected $user; protected function perform_check() { $current_user = Session::get('current_user'); if (!is_null($current_user) && is_array($current_user)) { if (isset($current_user['id']) && isset($current_user['salt'])) { $dlt_flg = Util_Const::DB_DLT_OFF; $users = DB::query( 'SELECT * FROM tb_users WHERE id = :id AND salt = :salt AND dlt_flg = :dlt_flg' ) ->bind('id', $current_user['id']) ->bind('salt' , $current_user['salt']) ->bind('dlt_flg' , $dlt_flg) ->as_object('Model_User') ->execute() ->as_array(); if (!is_null($users) && count($users) === 1) { $this->user = reset($users); return true; } } } return false; } public function validate_user($username_or_email = '', $password = '') { if(empty($username_or_email) || empty($password)) return false; $username_or_email = trim($username_or_email); $password = trim($password); $password = $this->hash_password($password); $dlt_flg = Util_Const::DB_DLT_OFF; $users = DB::query( 'SELECT * FROM tb_users WHERE (username = :username_or_email or email = :username_or_email) AND password = :password AND dlt_flg = :dlt_flg' ) ->bind('username_or_email', $username_or_email) ->bind('password' , $password) ->bind('dlt_flg' , $dlt_flg) ->as_object('Model_User') ->execute() ->as_array(); if (!is_null($users) && count($users) === 1){ $this->user = reset($users); $this->user->last_login = Date::forge()->get_timestamp(); $this->user->salt = $this->create_salt(); $this->user->save(); Session::set('current_user', array('id' => $this->user->id,'salt' => $this->user->salt)); return true; } return false; } public function login($username_or_email = '', $password = '') { return $this->validate_user($username_or_email, $password); } public function logout() { Session::delete('current_user'); return true; } public function get_user_id() { if (!empty($this->user) && isset($this->user['id'])) { return array($this->id, (int)$this->user['id']); } return null; } public function get_groups() { if (!empty($this->user) && isset($this->user['group'])) { return array(array('BaseAuth', $this->user['group'])); } return null; } public function get_email() { if (!empty($this->user) && isset($this->user['email'])) { return $this->user['email']; } return null; } public function get_screen_name() { if (!empty($this->user) && isset($this->user['username'])) { return $this->user['username']; } return null; } public function has_access($condition, $driver = null, $entity = null) { if (is_null($entity) && !empty($this->user)) { $groups = $this->get_groups(); $entity = reset($groups); } return parent::has_access($condition, $driver, $entity); } public function create_salt() { if (empty($this->user)) throw new Exception(); return sha1(Config::get('baseauth.login_hash_salt').$this->user->username.$this->user->last_login); } }
<?php class Auth_Group_BaseAuth extends Auth\Auth_Group_Driver { protected $config = array('drivers' => array('acl' => array('BaseAuth'))); public function get_name($group = null) { // noop } public function member($group, $user = null) { $auth = empty($user) ? Auth::instance() : Auth::instance($user[0]); $groups = $auth->get_groups(); return in_array(array($this->id, $group), $groups); } }
<?php class Auth_Acl_BaseAuth extends Auth\Auth_Acl_Driver { public function has_access($condition, Array $entity) { if (count($entity) > 0) { $group = Auth::group($entity[0]); if (!is_null($group) || !empty($group)) return $group->member($condition); } return false; } }
ここまでで実装完了。コード中にutilで定義している定数を使っているのでこちらのソースも公開
<?php class Util_Const { /* ======= db ======= */ const DB_DLT_OFF = 0; const DB_DLT_ON = 1; }
パスワードの暗号化はsimpleauthと同様driversのメソッドを呼び出し。
今回はここまで
ここまででドライバは一応実装済み。
simpleauthドライバよりもシンプルで特に勝るところもなく仕上がったかな。
あとは呼び出しのみ。
認証ドライバの作成方法についてメモ
FuelPHPで認証用に用意されているAuthパッケージがある。Authパッケージを既に実装しているsimpleauthドライバを使用すると簡単に認証機能を実装できる。しかし、simpleauthの仕様にあわせてテーブルカラムを用意しなければならなそう?なので、中で何をやっているか解析しつつ独自にドライバを作成。
※simpleauth仕様:
http://press.nekoget.com/fuelphp_doc_1.2/packages/auth/simpleauth/intro.html#/database
■参考
•公式サイト
http://press.nekoget.com/fuelphp_doc_1.2/packages/auth/drivers.html
※情報が少なく全然役に立たない
•公式ドキュメント
http://press.nekoget.com/fuelphp_doc_1.2/packages/auth/simpleauth/login.html
http://press.nekoget.com/fuelphp_doc_1.2/packages/auth/simpleauth/groups.html
http://press.nekoget.com/fuelphp_doc_1.2/packages/auth/simpleauth/acl.html
※英語だがAPIマニュアルとして多少役立つ
•simpleauth[packages/auth/classes/auth/配下]の実装
•親クラスdriver.php[packages/auth/classes/auth/配下]の実装
※取り込み元のコードなのでほとんどこれらを参考
実装要件
今回はドライバ名は「baseauth」にします
そしてuserテーブル構成は以下のようにします
CREATE TABLE `tb_users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) NOT NULL, `group` int(11) NOT NULL DEFAULT '1', `email` varchar(255) NOT NULL, `last_login` int(11) NOT NULL, `auth_type` int(11) NOT NULL DEFAULT '1', `salt` varchar(255) NOT NULL, `dlt_flg` tinyint(1) NOT NULL, `created_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username_email` (`username`,`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
auth_typeはログイン画面のbasic認証か各SNS(Twitter,Facebook etc)認証かのタイプを格納するつもり(とりあえず)。ここではbasic認証のみのため、常に1で良い。dlt_flgは削除されたときに物理削除をさけるため、論理削除とするため。groupは今回は管理者adminと一般ユーザのuserのみとする。
ドライバの準備
まずは下記3点を準備する。
•Login (Auth_Login_Driver).
•Group (Auth_Group_Driver).
•Acl (Auth_Acl_Driver).
公式サイト:http://press.nekoget.com/fuelphp_doc_1.2/packages/auth/drivers.html
つまり
fuel/app/classes/authフォルダへ以下3つのフォルダを用意し、それぞれにドライバ名をつけたphpファイル「baseauth.php」を用意する。
fuel/app/classes/auth/login
fuel/app/classes/auth/group
fuel/app/classes/auth/acl
親クラスdriver.php内で抽象メソッドとして宣言しているものをとりあえずbaseauth.phpへすべて実装しておきます。
abstract protected function perform_check(); abstract public function validate_user(); abstract public function login(); abstract public function logout(); abstract public function get_user_id(); abstract public function get_groups(); abstract public function get_email(); abstract public function get_screen_name();
abstract public function member($group, $user = null); abstract public function get_name($group);
abstract public function has_access($condition, Array $entity);
これら各メソッドの処理を実装したらドライバができるはずです。それぞれメソッドでなにをやるかはsimpleauthの実装を見ながらゆっくり考えていこうと思います。
configファイル準備
simpleauthのconfigファイルもそのまま流用。
「fuel/packages/auth/config」フォルダにあるsimpleauth.phpを「fuel/app/config」フォルダにbaseauth.phpとしてコピー。
設定情報を下記のように変更。ついでに不要なものは削除。
login_hash_saltはログイン認証後にSessionの認証をするために使用する文字列。ある程度の文字列にするとセキュリティ的に有効。simpleauthの仕様と同様に固定指定とする。
return array( 'login_hash_salt' => 'ZegakljelktPm16AA$Y/dlgadkjjelijmaaZe', 'groups' => array( 1 => array('name' => 'Users', 'roles' => array('user')), 100 => array('name' => 'Administrators', 'roles' => array('admin')), ), 'auth_type' => array( 1 => array('type' => 'Basic'), 2 => array('type' => 'Twitter'), 3 => array('type' => 'Facebook'), ), );
authファイル準備
「fuel/packages/auth/config」フォルダにあるauth.phpを「fuel/app/config」フォルダにコピー。
そしてBaseAuthをドライバとして登録。
saltは親クラスdrivers.phpでパスワードに付与して暗号化する文字列。ある程度複雑な文字列の方がセキュリティ上よい。ただ固定していのため、ブルートフォースに負ける可能性が高い。別メモ参照
return array( 'driver' => 'BaseAuth', 'verify_multiple_logins' => false, 'salt' => 'b70Ga(Z0s4zu&1!cR#DoXrAgaa7&6aTsM$C/)ZmkZ00bkaz', );
今回はここまで
migrateエラーの対応策をメモ
maigrateをしようとして「Already on the latest migration for app:default.」
エラーとなった
事象発生手順
1.oil generate migrationコマンドで生成されたマイグレーションファイルを実行。
oil refine migrate // 又はoil r migrate
2.作成されたテーブルを削除
drop table [table名]
3.再びマイグレーションファイルを実行
oil refine migrate // 又はoil r migrate
結果:
「Already on the latest migration for app:default.」発生
既にマイグレーションは最新の状態ですといった内容
解決策
1.マイグレーションの実行で作成されたmigrationテーブルの対象のレコードを削除する
2.下記コマンドを実行
oil refine migrate:current // 又はoil r migrate:current
公式サイト:http://press.nekoget.com/fuelphp_doc/general/migrations.html
DB::queryの戻り値をModel型で返す方法をメモ
データベースへクエリを投げる方法が多々あるが、副問い合わせなどを使用した複雑なSQLなどになるとSQLを直接書いた方が可読性を考えてもよい
※直接SQLを実行する場合にはエスケープ処理をは自動的に行われないため、手動でいれることが必要になる
select文(稀にupdate文も)に限って言うとSQL直接記述のほうが今後のカスタマイズなど考えてもトータル間違いない。この場合crudすべてをSQL直接記述にしたほうが統一していいが、便利なメソッドもあるので、適時使い分けるのがいい。
データベースへクエリを実行する方法
データベース処理を行う方法
1.DBクラスのDB::queryメソッドで直接SQLを記述してクエリを実行する
2.DBクラスのクエリビルダーメソッドを使用してクエリを実行する
3.orm/Modelクラスの静的メソッドを使用してクエリを実行する
「1.DBクラスのDB::queryメソッドで直接SQLを記述してクエリを実行する」を使う場合と「3.orm/Modelクラスの静的メソッドを使用してクエリを実行する」を使う場合だと戻り値の型が違う。戻り値に関しては「3.orm/Modelクラスの静的メソッドを使用してクエリを実行する」の場合にはorm/Modelが返り、modelに関わるメソッドを備えているためなにかと便利。
そのため、DB::queryをmodel型に変換する方法をメモ
※参考:http://docs.fuelphp.com/classes/database/usage.html
Orm/Modelを継承したModel_Userを用意
protected static $_properties = array( 'id', 'username', 'password', 'group', 'email', 'last_login', 'created_at', 'updated_at' );
「3.orm/Modelクラスの静的メソッドを使用してクエリを実行する」でusername,passwordに一致するレコードを取得
$users = Model_User::find( 'all', array('username' => $username,'password' => $password) );
array 2 => object(Model_User)[32] protected '_is_new' => boolean false protected '_frozen' => boolean false protected '_data' => array 'id' => string '2' (length=1) 'username' => string 'admin' (length=5) 'password' => string '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' (length=40) 'group' => string '1' (length=1) 'email' => string 'test@hotmail.com' (length=16) 'last_login' => string '1' (length=1) 'created_at' => string '111111111' (length=9) 'updated_at' => string '1356073749' (length=10) protected '_original' => array 'id' => string '2' (length=1) 'username' => string 'admin' (length=5) 'password' => string '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' (length=40) 'group' => string '1' (length=1) 'email' => string 'test@hotmail.com' (length=16) 'last_login' => string '1' (length=1) 'created_at' => string '111111111' (length=9) 'updated_at' => string '1356073749' (length=10) protected '_data_relations' => array empty protected '_original_relations' => array empty protected '_reset_relations' => array empty protected '_view' => null protected '_iterable' => array empty
「1.DBクラスのDB::queryメソッドで直接SQLを記述してクエリを実行する」でusername,passwordに一致するレコードを取得
「3.orm/Modelクラスの静的メソッドを使用してクエリを実行する」と同様に戻り値の型をMode_Userにするためにはas_objectメソッドでオブジェクト名を指定する必要がある。さらにas_arrayメソッドで配列へ変換することも同時に必要。これで実行すると同様の結果になる。
※bindメソッドでエスケープ処理を行っている
$users = DB::query( 'SELECT * FROM tb_users WHERE username = :username AND password = :password' ) ->bind('username', $username) ->bind('password' , $password) ->as_object('Model_User') ->execute() ->as_array();
array 0 => object(Model_User)[33] protected '_is_new' => boolean false protected '_frozen' => boolean false protected '_data' => array 'id' => string '2' (length=1) 'username' => string 'admin' (length=5) 'password' => string '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' (length=40) 'group' => string '1' (length=1) 'email' => string 'test@hotmail.com' (length=16) 'last_login' => string '1' (length=1) 'created_at' => string '111111111' (length=9) 'updated_at' => string '1356073881' (length=10) protected '_original' => array 'id' => string '2' (length=1) 'username' => string 'admin' (length=5) 'password' => string '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' (length=40) 'group' => string '1' (length=1) 'email' => string 'test@hotmail.com' (length=16) 'last_login' => string '1' (length=1) 'created_at' => string '111111111' (length=9) 'updated_at' => string '1356073881' (length=10) protected '_data_relations' => array empty protected '_original_relations' => array empty protected '_reset_relations' => array empty protected '_view' => null protected '_iterable' => array empty
戻り値の型がModel_Userになっている
created_atとupdated_atのUNIXタイムスタンプとdatetimeについてメモ
FuelPHPは規約に従うとテーブルカラムのcreated_at と updated_at はデフォルトUNIX タイム スタンプで記録 します。datetimeで保存することも可能。UNIXタイムスタンプとdatetimeを変換することが結構ありそうなのでメモ。2038年問題があがっているのでdatetimeがいいかな。
created_at とupdated_atをdatetimeで保存する方法
mysql_timestamp のデフォルト値「false」を「true」にすればOK
create_at
protected static $_observers = array( 'Orm\Observer_CreatedAt' => array( 'events' => array('before_insert'), 'mysql_timestamp' => true, 'property' => 'created', ), );
update_at
protected static $_observers = array( 'Orm\Observer_UpdatedAt' => array( 'events' => array('before_save'), 'mysql_timestamp' => true, 'property' => 'updated', ), );
まとめて設定
protected static $_observers = array( 'Orm\Observer_CreatedAt' => array('events'=>array('before_insert'), 'mysql_timestamp' => true,), 'Orm\Observer_UpdatedAt' => array('events'=>array('before_save'), 'mysql_timestamp' => true,), );
公式サイト:http://fuelphp.com/docs/packages/orm/observers/included.html
UNIXタイムスタンプとdatetimeを変換
datetimeをUNIXタイムスタンプに変換
$date = "2012-12-12 12:12:12"; echo strtotime($date);
UNIXタイムスタンプをdatetimeに変換
$date = time(); echo date("Y-m-d H:i:s",$date);
simpleauthドライバのsaltの有効性と可変への移行について考えた事をメモ
config/auth.php内で指定できる、saltがパスワードのプレフィックスのsaltとして使用されている。このsaltの固定指定はセキュリティとしては問題とあがっている。saltが固定ということは、解析する場合にも一つ解析されるとすべてのパスワードが解析できてしまうことになる。ブルートフォース攻撃といわれるもので、総当たり解析ツールを使用することでsaltの長さにもよるが、簡単に解析できる。
※saltがある程度長いと解析に時間がかかる。固定でも可変でもある程度の複雑さがあり文字数が必要そう。
return array( 'driver' => 'BaseAuth', 'verify_multiple_logins' => false, 'salt' => 'test', // 固定指定。ある程度複雑な指定が有効 );
そう考えると、可変指定に変更した場合にも調べることができそうなので、ならばsimpleauthの仕様通り固定のままでいくこともセキュリティ的にみてもありなのではないか思う。ならパスワードを平文でもいいとはさすがに思わない。
simplauthではAuthパッケージ内のメソッドを使用してBase64エンコードになっている
base64_encode($this->hasher()->pbkdf2($password, Config::get('auth.salt'), 10000, 32));
※折角テーブルにタイムスタンプやユーザ名などユニークな値をもっているため、付加したらもうすこしセキュリティとしていいのではないか
単純に可変にした場合にもセキュリティ面ではあまり有効ではないが可変にする方法を考える
saltをユーザ毎に持たせることが必要になり、さらにこの場合のsaltはテーブル内に格納する訳にはいかないため、サーバ上のリソース(テキストファイルなど)にユーザキーと共に格納することが必要。
前回はopauthについて投稿しましたが、他にも使い勝手がよさそうなのがありました。
HybridAuthというものでこちらもopauth同様のことができるそうです。
ただHybridAuthにはAPIの処理の実装もされているため、今後SNSのAPIを使用する予定がある場合には、こちらの方がよさそうです。
ただFuelPHPにはまだライブラリとして提供はされていないので、公式サイトから必要なファイルをFuelPHPの規約にあわせて取り込むことが必要そう。
coffee-break
Don't write code that useless.
1日5杯はコーヒー、カフェオレ飲みます。狭心症のため安静にします☆松本 雄貴
Kotlinでサービスリリース目指す!
iOSでチャットアプリ作成中。自然言語解析LSIを習得中
Mac / Android・iOS / Rails / Oracle
2017年 Lpic L2取得
2012年 Android技術者資格取得
2010年 OracleMasterGold10g取得
2008年 CCNAQiitaもたまに投稿
https://qiita.com/y-matsumoto東京近郊で常駐開発探してる方はこちらよりご連絡ください
SES企業でパートナー会社を探している企業様はこちらよりご連絡ください
スプリットカメラ iOS / Android
音声認識で聞いた日付から曜日当てアプリ Android
ソーシャルタイマー Android
カテゴリー
- ActiveRecord (2)
- Android (52)
- AndroidStudio (10)
- Ansible (1)
- AWS (1)
- Bash (18)
- Blog (7)
- BootStrap (1)
- CentOS (16)
- Chef (1)
- css (2)
- Eclipse (5)
- error (1)
- Facebook (2)
- Firebase (1)
- FuelPHP (16)
- Git (22)
- GitHub (3)
- Gradle (2)
- GraphAPI (1)
- Grunt (1)
- heroku (2)
- illustrator (1)
- iOS (17)
- Java (4)
- Jenkins (1)
- jQuery (3)
- Kotlin (2)
- Mac (22)
- nginx (1)
- Node.js (3)
- peco (1)
- php (5)
- Python (1)
- Rails (16)
- Ruby (11)
- shell (1)
- SNS (1)
- Swift (2)
- tmux (2)
- Vagrant (6)
- Vim (6)
- windows (2)
- WordPress (3)
- zsh (4)
- フリーランス (1)
- ライブラリ (1)
- 勉強会 (2)
- 宣伝 (1)
- 未分類 (2)
最近の投稿
- [MAC]HighSierraでgitプッシュエラー「Unable to negotiate with xxx.xxx.xxx.xxx port xx: no matching cipher found. Their offer: aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,arcfour,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se fatal: Could not read from remote repository.」
- [iOS]iOS11からFacebook,Twitter連携(シェアなど)廃止の対応方法
- [iOS]速報2017AppleSpecialEventのiOS11、iPhone8など発表内容について
- [iOS][Firebase]The default Firebase app has not yet been configured. Add `[FIRApp configure];
- [iOS]2017年9月リリースのiOS11で開発者が対応するべきこと
- 今人気の現金化サービスCASH(キャッシュ)を使ったレビュー
- [Pandoc][Mac]pandocでmarkdownからwordファイル作成
- [Android]映画サマーウォーズの聞いた日付(誕生日)から曜日当てをアプリ音声認識で簡単に実現
- [Android]起動しているActivityを取得するadb shell コマンド
- [Android][Kotlin]kotlin学習で参考になるサイト一覧
2023年6月 月 火 水 木 金 土 日 « 5月 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 アーカイブ
- 2018年5月
- 2017年9月
- 2017年8月
- 2017年7月
- 2017年6月
- 2017年5月
- 2017年2月
- 2017年1月
- 2016年12月
- 2016年7月
- 2016年6月
- 2016年1月
- 2015年12月
- 2015年11月
- 2015年10月
- 2015年9月
- 2015年8月
- 2015年7月
- 2015年6月
- 2015年5月
- 2015年4月
- 2015年3月
- 2015年2月
- 2015年1月
- 2014年12月
- 2014年11月
- 2014年6月
- 2014年5月
- 2014年4月
- 2014年3月
- 2014年2月
- 2014年1月
- 2013年12月
- 2013年11月
- 2013年9月
- 2013年8月
- 2013年7月
- 2013年6月
- 2013年5月
- 2013年4月
- 2013年3月
- 2013年2月
- 2013年1月
- 2012年12月
- 2012年10月
- 2012年5月
- 2010年6月
エントリ