Merge pull request #10 from treasure-data/user_auth

User auth
This commit is contained in:
uu59 2014-05-13 17:10:05 +09:00
commit a1c3fd2371
29 changed files with 764 additions and 25 deletions

5
.gitignore vendored
View File

@ -18,3 +18,8 @@
# built .gem file by `rake build`
/pkg
/Gemfile.production.lock
# generated by simplecov
/coverage
/.DS_Store

View File

@ -8,4 +8,8 @@ group :development, :test do
gem "rake"
gem "pry"
gem "rspec-rails", "~> 2.0"
gem "factory_girl_rails"
gem "database_cleaner", "~> 1.2.0"
gem "capybara", "~> 2.2.1"
gem "simplecov", "~> 0.7.1", require: false
end

View File

@ -2,10 +2,12 @@ PATH
remote: .
specs:
fluentd-ui (0.0.1)
bcrypt (~> 3.1.5)
bundler (~> 1.5)
coffee-rails (~> 4.0.0)
fluentd (= 0.10.46)
haml-rails (~> 0.5.3)
i18n_generators (= 1.2.1)
jbuilder (~> 2.0)
jquery-rails (~> 3.1.0)
rails (= 4.1.1)
@ -43,7 +45,14 @@ GEM
thread_safe (~> 0.1)
tzinfo (~> 1.1)
arel (5.0.1.20140414130214)
bcrypt (3.1.7)
builder (3.2.2)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
coderay (1.1.0)
coffee-rails (4.0.1)
coffee-script (>= 2.2.0)
@ -52,10 +61,18 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.7.0)
cool.io (1.2.3)
cool.io (1.2.4)
database_cleaner (1.2.0)
diff-lcs (1.2.5)
domain_name (0.5.18)
unf (>= 0.0.5, < 1.0.0)
erubis (2.7.0)
execjs (2.0.2)
factory_girl (4.4.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.4.1)
factory_girl (~> 4.4.0)
railties (>= 3.0.0)
fluentd (0.10.46)
cool.io (>= 1.1.1, < 2.0.0, != 1.2.0)
http_parser.rb (>= 0.5.1, < 0.7.0)
@ -71,8 +88,13 @@ GEM
haml (>= 3.1, < 5.0)
railties (>= 4.0.1)
hike (1.2.3)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.6.0)
i18n (0.6.9)
i18n_generators (1.2.1)
mechanize
rails (>= 3.0.0)
jbuilder (2.0.7)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
@ -83,11 +105,26 @@ GEM
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mechanize (2.7.2)
domain_name (~> 0.5, >= 0.5.1)
http-cookie (~> 1.0.0)
mime-types (~> 1.17, >= 1.17.2)
net-http-digest_auth (~> 1.1, >= 1.1.1)
net-http-persistent (~> 2.5, >= 2.5.2)
nokogiri (~> 1.4)
ntlm-http (~> 0.1, >= 0.1.1)
webrobots (>= 0.0.9, < 0.2)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.5.3)
minitest (5.3.3)
msgpack (0.5.8)
multi_json (1.10.0)
net-http-digest_auth (1.4)
net-http-persistent (2.9.4)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
ntlm-http (0.1.1)
polyglot (0.3.4)
pry (0.9.12.6)
coderay (~> 1.0)
@ -131,6 +168,10 @@ GEM
sprockets (~> 2.8, <= 2.11.0)
sprockets-rails (~> 2.0)
sigdump (0.2.2)
simplecov (0.7.1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
slop (3.5.0)
sprockets (2.11.0)
hike (~> 1.2)
@ -153,13 +194,23 @@ GEM
uglifier (2.5.0)
execjs (>= 0.3.0)
json (>= 1.8.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.6)
webrobots (0.1.1)
xpath (2.0.0)
nokogiri (~> 1.3)
yajl-ruby (1.2.0)
PLATFORMS
ruby
DEPENDENCIES
capybara (~> 2.2.1)
database_cleaner (~> 1.2.0)
factory_girl_rails
fluentd-ui!
pry
rake
rspec-rails (~> 2.0)
simplecov (~> 0.7.1)

View File

@ -2,4 +2,15 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :current_user
def current_user
return unless session[:remember_token]
@current_user ||= User.find_by(remember_token: session[:remember_token])
end
def login_required
return true if current_user
redirect_to new_sessions_path
end
end

View File

@ -1,5 +1,6 @@
class Fluentd::DaemonsController < ApplicationController
before_filter :fluentd
before_action :login_required
before_action :fluentd
def show
end

View File

@ -1,4 +1,6 @@
class Fluentd::SettingsController < ApplicationController
before_action :login_required
def show
render text: fluentd.config.to_s, content_type: "text/plain"
end

View File

@ -1,4 +1,6 @@
class FluentdController < ApplicationController
before_action :login_required
def index
@daemons = [Fluentd.new(Rails.root + "tmp" + "fluentd")] # TODO
end

View File

@ -0,0 +1,30 @@
class SessionsController < ApplicationController
def create
user = User.find_by(name: session_params[:name]).try(:authenticate, session_params[:password])
unless user
flash.now[:notice] = I18n.t("error.login_failed")
return render :new
end
sign_in user
redirect_to root_path
end
def destroy
current_user.update_attribute(:remember_token, nil)
session.delete :remember_token
redirect_to new_sessions_path
end
private
def session_params
params.require(:session).permit(:name, :password)
end
def sign_in(user)
token = user.generate_remember_token
session[:remember_token] = token
user.update_attribute(:remember_token, token)
user
end
end

View File

@ -0,0 +1,3 @@
class UsersController < ApplicationController
before_action :login_required
end

13
app/models/user.rb Normal file
View File

@ -0,0 +1,13 @@
class User < ActiveRecord::Base
has_secure_password
validates :name, uniqueness: true, presence: true
validates :remember_token, uniqueness: true, allow_nil: true
def generate_remember_token
begin
token = SecureRandom.base64(32)
end while User.where(remember_token: token).exists?
token
end
end

View File

@ -8,6 +8,7 @@
</head>
<body>
<%= current_user ? link_to(t("terms.sign_out"), sessions_path, method: :delete) : link_to(t("terms.sign_in"), new_sessions_path) %>
<%= yield %>
</body>

View File

@ -0,0 +1,8 @@
= render partial: "shared/error"
= form_for(:session, url: sessions_path) do |f|
= f.label :name
= f.text_field :name
= f.label :password
= f.text_field :password
= submit_tag t("terms.sign_in")

View File

@ -0,0 +1,3 @@
%p.error
= flash[:notice]

View File

@ -28,6 +28,6 @@ module FluentdUi
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
config.i18n.default_locale = 'ja'
end
end

View File

@ -1,23 +1,204 @@
# Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
# <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more, please read the Rails Internationalization guide
# available at http://guides.rubyonrails.org/i18n.html.
en:
hello: "Hello world"
date:
abbr_day_names:
- Sun
- Mon
- Tue
- Wed
- Thu
- Fri
- Sat
abbr_month_names:
-
- Jan
- Feb
- Mar
- Apr
- May
- Jun
- Jul
- Aug
- Sep
- Oct
- Nov
- Dec
day_names:
- Sunday
- Monday
- Tuesday
- Wednesday
- Thursday
- Friday
- Saturday
formats:
default: ! '%Y-%m-%d'
long: ! '%B %d, %Y'
short: ! '%b %d'
month_names:
-
- January
- February
- March
- April
- May
- June
- July
- August
- September
- October
- November
- December
order:
- :year
- :month
- :day
datetime:
distance_in_words:
about_x_hours:
one: about 1 hour
other: about %{count} hours
about_x_months:
one: about 1 month
other: about %{count} months
about_x_years:
one: about 1 year
other: about %{count} years
almost_x_years:
one: almost 1 year
other: almost %{count} years
half_a_minute: half a minute
less_than_x_minutes:
one: less than a minute
other: less than %{count} minutes
less_than_x_seconds:
one: less than 1 second
other: less than %{count} seconds
over_x_years:
one: over 1 year
other: over %{count} years
x_days:
one: 1 day
other: ! '%{count} days'
x_minutes:
one: 1 minute
other: ! '%{count} minutes'
x_months:
one: 1 month
other: ! '%{count} months'
x_seconds:
one: 1 second
other: ! '%{count} seconds'
prompts:
day: Day
hour: Hour
minute: Minute
month: Month
second: Seconds
year: Year
errors:
format: ! '%{attribute} %{message}'
messages:
accepted: must be accepted
blank: can't be blank
present: must be blank
confirmation: ! "doesn't match %{attribute}"
empty: can't be empty
equal_to: must be equal to %{count}
even: must be even
exclusion: is reserved
greater_than: must be greater than %{count}
greater_than_or_equal_to: must be greater than or equal to %{count}
inclusion: is not included in the list
invalid: is invalid
less_than: must be less than %{count}
less_than_or_equal_to: must be less than or equal to %{count}
not_a_number: is not a number
not_an_integer: must be an integer
odd: must be odd
record_invalid: ! 'Validation failed: %{errors}'
restrict_dependent_destroy:
one: "Cannot delete record because a dependent %{record} exists"
many: "Cannot delete record because dependent %{record} exist"
taken: has already been taken
too_long:
one: is too long (maximum is 1 character)
other: is too long (maximum is %{count} characters)
too_short:
one: is too short (minimum is 1 character)
other: is too short (minimum is %{count} characters)
wrong_length:
one: is the wrong length (should be 1 character)
other: is the wrong length (should be %{count} characters)
other_than: "must be other than %{count}"
template:
body: ! 'There were problems with the following fields:'
header:
one: 1 error prohibited this %{model} from being saved
other: ! '%{count} errors prohibited this %{model} from being saved'
helpers:
select:
prompt: Please select
submit:
create: Create %{model}
submit: Save %{model}
update: Update %{model}
number:
currency:
format:
delimiter: ! ','
format: ! '%u%n'
precision: 2
separator: .
significant: false
strip_insignificant_zeros: false
unit: $
format:
delimiter: ! ','
precision: 3
separator: .
significant: false
strip_insignificant_zeros: false
human:
decimal_units:
format: ! '%n %u'
units:
billion: Billion
million: Million
quadrillion: Quadrillion
thousand: Thousand
trillion: Trillion
unit: ''
format:
delimiter: ''
precision: 3
significant: true
strip_insignificant_zeros: true
storage_units:
format: ! '%n %u'
units:
byte:
one: Byte
other: Bytes
gb: GB
kb: KB
mb: MB
tb: TB
percentage:
format:
delimiter: ''
format: "%n%"
precision:
format:
delimiter: ''
support:
array:
last_word_connector: ! ', and '
two_words_connector: ! ' and '
words_connector: ! ', '
time:
am: am
formats:
default: ! '%a, %d %b %Y %H:%M:%S %z'
long: ! '%B %d, %Y %H:%M'
short: ! '%d %b %H:%M'
pm: pm

194
config/locales/ja.yml Normal file
View File

@ -0,0 +1,194 @@
ja:
date:
abbr_day_names:
-
-
-
-
-
-
-
abbr_month_names:
-
- 1月
- 2月
- 3月
- 4月
- 5月
- 6月
- 7月
- 8月
- 9月
- 10月
- 11月
- 12月
day_names:
- 日曜日
- 月曜日
- 火曜日
- 水曜日
- 木曜日
- 金曜日
- 土曜日
formats:
default: ! '%Y/%m/%d'
long: ! '%Y年%m月%d日(%a)'
short: ! '%m/%d'
month_names:
-
- 1月
- 2月
- 3月
- 4月
- 5月
- 6月
- 7月
- 8月
- 9月
- 10月
- 11月
- 12月
order:
- :year
- :month
- :day
datetime:
distance_in_words:
about_x_hours:
one: 約1時間
other: 約%{count}時間
about_x_months:
one: 約1ヶ月
other: 約%{count}ヶ月
about_x_years:
one: 約1年
other: 約%{count}年
almost_x_years:
one: 1年弱
other: ! '%{count}年弱'
half_a_minute: 30秒前後
less_than_x_minutes:
one: 1分以内
other: ! '%{count}分未満'
less_than_x_seconds:
one: 1秒以内
other: ! '%{count}秒未満'
over_x_years:
one: 1年以上
other: ! '%{count}年以上'
x_days:
one: 1日
other: ! '%{count}日'
x_minutes:
one: 1分
other: ! '%{count}分'
x_months:
one: 1ヶ月
other: ! '%{count}ヶ月'
x_seconds:
one: 1秒
other: ! '%{count}秒'
prompts:
day:
hour:
minute:
month:
second:
year:
errors:
format: ! '%{attribute}%{message}'
messages:
accepted: を受諾してください。
blank: を入力してください。
present: は入力しないでください。
confirmation: と%{attribute}の入力が一致しません。
empty: を入力してください。
equal_to: は%{count}にしてください。
even: は偶数にしてください。
exclusion: は予約されています。
greater_than: は%{count}より大きい値にしてください。
greater_than_or_equal_to: は%{count}以上の値にしてください。
inclusion: は一覧にありません。
invalid: は不正な値です。
less_than: は%{count}より小さい値にしてください。
less_than_or_equal_to: は%{count}以下の値にしてください。
not_a_number: は数値で入力してください。
not_an_integer: は整数で入力してください。
odd: は奇数にしてください。
record_invalid: バリデーションに失敗しました。 %{errors}
restrict_dependent_destroy: ! '%{record}が存在しているので削除できません。'
taken: はすでに存在します。
too_long: は%{count}文字以内で入力してください。
too_short: は%{count}文字以上で入力してください。
wrong_length: は%{count}文字で入力してください。
other_than: "は%{count}以外の値にしてください。"
template:
body: 次の項目を確認してください。
header:
one: ! '%{model}にエラーが発生しました。'
other: ! '%{model}に%{count}個のエラーが発生しました。'
helpers:
select:
prompt: 選択してください。
submit:
create: 登録する
submit: 保存する
update: 更新する
number:
currency:
format:
delimiter: ! ','
format: ! '%n%u'
precision: 0
separator: .
significant: false
strip_insignificant_zeros: false
unit:
format:
delimiter: ! ','
precision: 3
separator: .
significant: false
strip_insignificant_zeros: false
human:
decimal_units:
format: ! '%n %u'
units:
billion: 十億
million: 百万
quadrillion: 千兆
thousand:
trillion:
unit: ''
format:
delimiter: ''
precision: 3
significant: true
strip_insignificant_zeros: true
storage_units:
format: ! '%n%u'
units:
byte: バイト
gb: ギガバイト
kb: キロバイト
mb: メガバイト
tb: テラバイト
percentage:
format:
delimiter: ''
format: "%n%"
precision:
format:
delimiter: ''
support:
array:
last_word_connector:
two_words_connector:
words_connector:
time:
am: 午前
formats:
default: ! '%Y/%m/%d %H:%M:%S'
long: ! '%Y年%m月%d日(%a) %H時%M分%S秒 %z'
short: ! '%y/%m/%d %H:%M'
pm: 午後

View File

@ -0,0 +1,17 @@
en:
terms:
sign_in: Sign in
sign_out: Sign out
error:
login_failed: Login failed.
activerecord:
models:
user: user #g
attributes:
user:
name: name #g
password_digest: password_digest #g
remember_token: remember_token #g

View File

@ -0,0 +1,17 @@
ja:
terms:
sign_in: ログイン
sign_out: ログアウト
error:
login_failed: ログインに失敗しました。
activerecord:
models:
user: user #g
attributes:
user:
name: name #g
password_digest: password_digest #g
remember_token: remember_token #g

View File

@ -1,4 +1,7 @@
Rails.application.routes.draw do
root "fluentd#index" # TODO: change to dashboard
resources :fluentd, only: [:index] do
resource :daemon, only: [:show], module: :fluentd do
put "start"
@ -10,6 +13,9 @@ Rails.application.routes.draw do
end
end
resources :users
resource :sessions
resources :misc, only: [] do
end
# The priority is based upon order of creation: first created -> highest priority.

View File

@ -0,0 +1,11 @@
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name, null: false
t.string :password_digest, null: false
t.string :remember_token
t.timestamps
end
end
end

24
db/schema.rb Normal file
View File

@ -0,0 +1,24 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140513053102) do
create_table "users", force: true do |t|
t.string "name", null: false
t.string "password_digest", null: false
t.string "remember_token"
t.datetime "created_at"
t.datetime "updated_at"
end
end

View File

@ -5,3 +5,5 @@
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
User.create(name: "admin", password: "changeme")

View File

@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
spec.add_dependency "fluentd", "0.10.46"
spec.add_dependency 'rails', '4.1.1'
spec.add_dependency 'i18n_generators', '1.2.1'
spec.add_dependency 'bcrypt', '~> 3.1.5'
spec.add_dependency 'sqlite3'
spec.add_dependency 'sass-rails', '~> 4.0.3'
spec.add_dependency 'uglifier', '>= 1.3.0'

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe SessionsController do
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe UsersController do
end

7
spec/factories/user.rb Normal file
View File

@ -0,0 +1,7 @@
FactoryGirl.define do
factory :user do
sequence(:name) {|n| "user#{n}" }
password "passw0rd"
password_confirmation "passw0rd"
end
end

View File

@ -0,0 +1,60 @@
describe "sessions" do
let(:exists_user) { FactoryGirl.create(:user) }
describe "the sign in process" do
let(:submit_label) { I18n.t("terms.sign_in") }
before do
visit '/sessions/new'
within("form") do
fill_in 'session_name', :with => user.name
fill_in 'session_password', :with => user.password
end
click_button submit_label
end
context "sign in with exists user" do
let(:user) { exists_user }
it "login success, then redirect to root_path" do
current_path.should == root_path
end
end
context "sign in with non-exists user" do
let(:user) { FactoryGirl.build(:user) }
it "current location is not root_path" do
current_path.should_not == root_path
end
it "display form for retry" do
page.body.should have_css('form')
end
end
end
describe "sign out process" do
let(:submit_label) { I18n.t("terms.sign_in") }
before do
visit '/sessions/new'
within("form") do
fill_in 'session_name', :with => exists_user.name
fill_in 'session_password', :with => exists_user.password
end
click_button submit_label
end
before do
visit root_path
click_link I18n.t("terms.sign_out")
end
it "at sign in page after sign out" do
current_path.should == new_sessions_path
end
it "remember_token was destroyed" do
exists_user.reload
exists_user.remember_token.should be_nil
end
end
end

56
spec/models/user_spec.rb Normal file
View File

@ -0,0 +1,56 @@
require 'spec_helper'
describe User do
let(:user) { FactoryGirl.build(:user) }
describe "#generate_remember_token" do
subject { user.generate_remember_token }
it { User.find_by(remember_token: subject).should be_nil }
end
describe "#valid?" do
it { user.should be_valid }
describe "name" do
it "nil is invalid" do
user.name = nil
user.should_not be_valid
end
it "taken name is invalid" do
another_user = FactoryGirl.create(:user)
user.name = another_user.name
user.should_not be_valid
end
end
describe "password" do
it "password != password_confirmation is invalid" do
user.password = "a"
user.password_confirmation = "b"
user.should_not be_valid
end
end
describe "remember_token" do
let(:token) { "xxx" }
it "nil is valid" do
user.remember_token = nil
user.should be_valid
end
it "nil and taken is valid " do
FactoryGirl.create(:user, remember_token: nil)
user.remember_token = nil
user.should be_valid
end
it "taken token is invalid" do
FactoryGirl.create(:user, remember_token: token)
user.remember_token = token
user.should_not be_valid
end
end
end
end

View File

@ -1,3 +1,8 @@
if ENV['RAILS_ENV'] == 'test'
require 'simplecov'
SimpleCov.start 'rails'
end
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
@ -39,4 +44,17 @@ RSpec.configure do |config|
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end