fixes #2166
This commit is contained in:
17
app/controllers/api_keys_controller.rb
Normal file
17
app/controllers/api_keys_controller.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
class ApiKeysController < ApplicationController
|
||||||
|
before_filter :gold_only
|
||||||
|
|
||||||
|
def new
|
||||||
|
@api_key = ApiKey.new(:user_id => CurrentUser.user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@api_key = ApiKey.generate!(CurrentUser.user)
|
||||||
|
|
||||||
|
if @api_key.errors.empty?
|
||||||
|
redirect_to user_path(CurrentUser.user), :notice => "API key generated"
|
||||||
|
else
|
||||||
|
render :action => "new"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -55,18 +55,18 @@ private
|
|||||||
end
|
end
|
||||||
|
|
||||||
def authenticate_api_key(name, api_key)
|
def authenticate_api_key(name, api_key)
|
||||||
CurrentUser.user = User.authenticate_cookie_hash(name, api_key)
|
|
||||||
CurrentUser.ip_addr = request.remote_ip
|
CurrentUser.ip_addr = request.remote_ip
|
||||||
|
CurrentUser.user = User.authenticate_api_key(name, api_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
def authenticate_legacy_api_key(name, password_hash)
|
def authenticate_legacy_api_key(name, password_hash)
|
||||||
CurrentUser.user = User.authenticate_hash(name, password_hash)
|
|
||||||
CurrentUser.ip_addr = request.remote_ip
|
CurrentUser.ip_addr = request.remote_ip
|
||||||
|
CurrentUser.user = User.authenticate_hash(name, password_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_session_user
|
def load_session_user
|
||||||
CurrentUser.user = User.find_by_id(session[:user_id])
|
|
||||||
CurrentUser.ip_addr = request.remote_ip
|
CurrentUser.ip_addr = request.remote_ip
|
||||||
|
CurrentUser.user = User.find_by_id(session[:user_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_cookie_user
|
def load_cookie_user
|
||||||
|
|||||||
10
app/models/api_key.rb
Normal file
10
app/models/api_key.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class ApiKey < ActiveRecord::Base
|
||||||
|
belongs_to :user
|
||||||
|
validates_uniqueness_of :user_id
|
||||||
|
validates_uniqueness_of :key
|
||||||
|
attr_accessible :user_id, :key
|
||||||
|
|
||||||
|
def self.generate!(user)
|
||||||
|
create(:user_id => user.id, :key => SecureRandom.urlsafe_base64(32))
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -59,6 +59,7 @@ class User < ActiveRecord::Base
|
|||||||
has_many :posts, :foreign_key => "uploader_id"
|
has_many :posts, :foreign_key => "uploader_id"
|
||||||
has_many :bans, lambda {order("bans.id desc")}
|
has_many :bans, lambda {order("bans.id desc")}
|
||||||
has_one :recent_ban, lambda {order("bans.id desc")}, :class_name => "Ban"
|
has_one :recent_ban, lambda {order("bans.id desc")}, :class_name => "Ban"
|
||||||
|
has_one :api_key
|
||||||
has_many :subscriptions, lambda {order("tag_subscriptions.name")}, :class_name => "TagSubscription", :foreign_key => "creator_id"
|
has_many :subscriptions, lambda {order("tag_subscriptions.name")}, :class_name => "TagSubscription", :foreign_key => "creator_id"
|
||||||
has_many :note_versions, :foreign_key => "updater_id"
|
has_many :note_versions, :foreign_key => "updater_id"
|
||||||
has_many :dmails, lambda {order("dmails.id desc")}, :foreign_key => "owner_id"
|
has_many :dmails, lambda {order("dmails.id desc")}, :foreign_key => "owner_id"
|
||||||
@@ -192,6 +193,15 @@ class User < ActiveRecord::Base
|
|||||||
authenticate_hash(name, sha1(pass))
|
authenticate_hash(name, sha1(pass))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate_api_key(name, api_key)
|
||||||
|
key = ApiKey.where(:key => api_key).first
|
||||||
|
return nil if key.nil?
|
||||||
|
user = find_by_name(name)
|
||||||
|
return nil if user.nil?
|
||||||
|
return user if key.user_id == user.id
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def authenticate_hash(name, hash)
|
def authenticate_hash(name, hash)
|
||||||
user = find_by_name(name)
|
user = find_by_name(name)
|
||||||
if user && user.bcrypt_password == hash
|
if user && user.bcrypt_password == hash
|
||||||
@@ -531,9 +541,9 @@ class User < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def api_hourly_limit
|
def api_hourly_limit
|
||||||
if is_platinum?
|
if is_platinum? && api_key.present?
|
||||||
20_000
|
20_000
|
||||||
elsif is_gold?
|
elsif is_gold? && api_key.present?
|
||||||
10_000
|
10_000
|
||||||
else
|
else
|
||||||
3_000
|
3_000
|
||||||
|
|||||||
19
app/views/api_keys/new.html.erb
Normal file
19
app/views/api_keys/new.html.erb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<div id="c-api-keys">
|
||||||
|
<div id="a-new">
|
||||||
|
<h1>New API Key</h1>
|
||||||
|
|
||||||
|
<p>You can generate a new API key to authenticate against <%= Danbooru.config.app_name %>.</p>
|
||||||
|
|
||||||
|
<%= error_messages_for :api_key %>
|
||||||
|
|
||||||
|
<%= simple_form_for(@api_key) do |f| %>
|
||||||
|
<%= submit_tag "Generate" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render "users/secondary_links" %>
|
||||||
|
|
||||||
|
<% content_for(:page_title) do %>
|
||||||
|
New API Key - <%= Danbooru.config.app_name %>
|
||||||
|
<% end %>
|
||||||
@@ -131,7 +131,13 @@
|
|||||||
<% if CurrentUser.user.id == user.id %>
|
<% if CurrentUser.user.id == user.id %>
|
||||||
<tr>
|
<tr>
|
||||||
<th>API Key</th>
|
<th>API Key</th>
|
||||||
<td><%= CurrentUser.user.bcrypt_cookie_password_hash %></td>
|
<td>
|
||||||
|
<% if CurrentUser.user.api_key %>
|
||||||
|
<%= CurrentUser.user.api_key.key %>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to "Generate key", new_api_key_path %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ Rails.application.routes.draw do
|
|||||||
resources :advertisements do
|
resources :advertisements do
|
||||||
resources :hits, :controller => "advertisement_hits", :only => [:create]
|
resources :hits, :controller => "advertisement_hits", :only => [:create]
|
||||||
end
|
end
|
||||||
|
resources :api_keys, :only => [:new, :create]
|
||||||
resources :artists do
|
resources :artists do
|
||||||
member do
|
member do
|
||||||
put :revert
|
put :revert
|
||||||
|
|||||||
13
db/migrate/20140722225753_create_api_keys.rb
Normal file
13
db/migrate/20140722225753_create_api_keys.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class CreateApiKeys < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :api_keys do |t|
|
||||||
|
t.integer :user_id, :null => false
|
||||||
|
t.string :key, :null => false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :api_keys, :user_id, :unique => true
|
||||||
|
add_index :api_keys, :key, :unique => true
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -515,6 +515,38 @@ CREATE SEQUENCE amazon_backups_id_seq
|
|||||||
ALTER SEQUENCE amazon_backups_id_seq OWNED BY amazon_backups.id;
|
ALTER SEQUENCE amazon_backups_id_seq OWNED BY amazon_backups.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: api_keys; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE api_keys (
|
||||||
|
id integer NOT NULL,
|
||||||
|
user_id integer NOT NULL,
|
||||||
|
key character varying(255) NOT NULL,
|
||||||
|
created_at timestamp without time zone,
|
||||||
|
updated_at timestamp without time zone
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: api_keys_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE api_keys_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: api_keys_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE api_keys_id_seq OWNED BY api_keys.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: artist_commentaries; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
-- Name: artist_commentaries; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -3124,6 +3156,13 @@ ALTER TABLE ONLY advertisements ALTER COLUMN id SET DEFAULT nextval('advertiseme
|
|||||||
ALTER TABLE ONLY amazon_backups ALTER COLUMN id SET DEFAULT nextval('amazon_backups_id_seq'::regclass);
|
ALTER TABLE ONLY amazon_backups ALTER COLUMN id SET DEFAULT nextval('amazon_backups_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY api_keys ALTER COLUMN id SET DEFAULT nextval('api_keys_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -4149,6 +4188,14 @@ ALTER TABLE ONLY amazon_backups
|
|||||||
ADD CONSTRAINT amazon_backups_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT amazon_backups_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: api_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY api_keys
|
||||||
|
ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: artist_commentaries_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
-- Name: artist_commentaries_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -4514,6 +4561,20 @@ CREATE INDEX index_advertisement_hits_on_created_at ON advertisement_hits USING
|
|||||||
CREATE INDEX index_advertisements_on_ad_type ON advertisements USING btree (ad_type);
|
CREATE INDEX index_advertisements_on_ad_type ON advertisements USING btree (ad_type);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_api_keys_on_key; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX index_api_keys_on_key ON api_keys USING btree (key);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_api_keys_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX index_api_keys_on_user_id ON api_keys USING btree (user_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_artist_commentaries_on_post_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
-- Name: index_artist_commentaries_on_post_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -6131,13 +6192,6 @@ CREATE INDEX index_forum_posts_on_topic_id ON forum_posts USING btree (topic_id)
|
|||||||
CREATE INDEX index_forum_topic_visits_on_forum_topic_id ON forum_topic_visits USING btree (forum_topic_id);
|
CREATE INDEX index_forum_topic_visits_on_forum_topic_id ON forum_topic_visits USING btree (forum_topic_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_forum_topic_visits_on_last_read_at; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE INDEX index_forum_topic_visits_on_last_read_at ON forum_topic_visits USING btree (last_read_at);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_forum_topic_visits_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
-- Name: index_forum_topic_visits_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -6936,3 +6990,5 @@ INSERT INTO schema_migrations (version) VALUES ('20140613004559');
|
|||||||
|
|
||||||
INSERT INTO schema_migrations (version) VALUES ('20140701224800');
|
INSERT INTO schema_migrations (version) VALUES ('20140701224800');
|
||||||
|
|
||||||
|
INSERT INTO schema_migrations (version) VALUES ('20140722225753');
|
||||||
|
|
||||||
|
|||||||
36
test/functional/api_keys_controller_test.rb
Normal file
36
test/functional/api_keys_controller_test.rb
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ApiKeysControllerTest < ActionController::TestCase
|
||||||
|
context "An api keys controller" do
|
||||||
|
setup do
|
||||||
|
@user = FactoryGirl.create(:gold_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "#new" do
|
||||||
|
should "render" do
|
||||||
|
get :new, {}, {:user_id => @user.id}
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "#create" do
|
||||||
|
should "succeed" do
|
||||||
|
assert_difference("ApiKey.count", 1) do
|
||||||
|
post :create, {}, {:user_id => @user.id}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when an api key already exists" do
|
||||||
|
setup do
|
||||||
|
ApiKey.generate!(@user)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not create another api key" do
|
||||||
|
assert_difference("ApiKey.count", 0) do
|
||||||
|
post :create, {}, {:user_id => @user.id}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -19,15 +19,16 @@ class PostsControllerTest < ActionController::TestCase
|
|||||||
context "passing the api limit" do
|
context "passing the api limit" do
|
||||||
setup do
|
setup do
|
||||||
User.any_instance.stubs(:api_hourly_limit).returns(5)
|
User.any_instance.stubs(:api_hourly_limit).returns(5)
|
||||||
|
ApiKey.generate!(@user)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "work" do
|
should "work" do
|
||||||
CurrentUser.user.api_hourly_limit.times do
|
CurrentUser.user.api_hourly_limit.times do
|
||||||
get :index, {:format => "json", :login => @user.name, :api_key => @user.bcrypt_cookie_password_hash}
|
get :index, {:format => "json", :login => @user.name, :api_key => @user.api_key.key}
|
||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
get :index, {:format => "json", :login => @user.name, :api_key => @user.bcrypt_cookie_password_hash}
|
get :index, {:format => "json", :login => @user.name, :api_key => @user.api_key.key}
|
||||||
assert_response 421
|
assert_response 421
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
23
test/unit/api_key_test.rb
Normal file
23
test/unit/api_key_test.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ApiKeyTest < ActiveSupport::TestCase
|
||||||
|
context "in all cases a user" do
|
||||||
|
setup do
|
||||||
|
@user = FactoryGirl.create(:user, :name => "abcdef")
|
||||||
|
@api_key = ApiKey.generate!(@user)
|
||||||
|
@user.name.mb_chars.downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
should "authenticate via api key" do
|
||||||
|
assert_not_nil(User.authenticate_api_key(@user.name, @api_key.key))
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not authenticate with the wrong api key" do
|
||||||
|
assert_nil(User.authenticate_api_key(@user.name, "xxx"))
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not authenticate with the wrong name" do
|
||||||
|
assert_nil(User.authenticate_api_key("xxx", @api_key.key))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user