twimock 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +125 -0
- data/Rakefile +6 -0
- data/db/.gitkeep +0 -0
- data/lib/twimock/access_token.rb +31 -0
- data/lib/twimock/api/account/verify_credentials.rb +40 -0
- data/lib/twimock/api/application.rb +29 -0
- data/lib/twimock/api/intent/sessions.rb +60 -0
- data/lib/twimock/api/oauth/access_token.rb +65 -0
- data/lib/twimock/api/oauth/authenticate.rb +51 -0
- data/lib/twimock/api/oauth/request_token.rb +49 -0
- data/lib/twimock/api/oauth.rb +83 -0
- data/lib/twimock/api.rb +35 -0
- data/lib/twimock/application.rb +21 -0
- data/lib/twimock/auth_hash.rb +8 -0
- data/lib/twimock/config.rb +90 -0
- data/lib/twimock/database/table.rb +359 -0
- data/lib/twimock/database.rb +133 -0
- data/lib/twimock/errors.rb +13 -0
- data/lib/twimock/omniauth/strategies/twitter.rb +28 -0
- data/lib/twimock/omniauth_twitter.rb +36 -0
- data/lib/twimock/request_token.rb +23 -0
- data/lib/twimock/user.rb +58 -0
- data/lib/twimock/version.rb +3 -0
- data/lib/twimock.rb +39 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/api_spec_helper.rb +30 -0
- data/spec/support/omniauth_twitter_helper.rb +26 -0
- data/spec/support/tables_helper.rb +54 -0
- data/spec/support/test_application_helper.rb +9 -0
- data/spec/twimock/access_token_spec.rb +128 -0
- data/spec/twimock/api/account/verify_credentials_spec.rb +125 -0
- data/spec/twimock/api/application_spec.rb +27 -0
- data/spec/twimock/api/intent/sessions_spec.rb +184 -0
- data/spec/twimock/api/oauth/access_token_spec.rb +185 -0
- data/spec/twimock/api/oauth/authenticate_spec.rb +96 -0
- data/spec/twimock/api/oauth/request_token_spec.rb +123 -0
- data/spec/twimock/api_spec.rb +81 -0
- data/spec/twimock/application_spec.rb +120 -0
- data/spec/twimock/auth_hash_spec.rb +7 -0
- data/spec/twimock/config_spec.rb +192 -0
- data/spec/twimock/database/table_spec.rb +769 -0
- data/spec/twimock/database_spec.rb +261 -0
- data/spec/twimock/omniauth_twitter_spec.rb +129 -0
- data/spec/twimock/request_token_spec.rb +140 -0
- data/spec/twimock/user_spec.rb +271 -0
- data/spec/twimock_spec.rb +76 -0
- data/twimock.gemspec +38 -0
- data/view/authenticate.html.erb +23 -0
- metadata +343 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 98d87b06d405b5e1b62b1e556b0364db89fd791f
|
4
|
+
data.tar.gz: 51a1cc2ac6c7bf7bc616330947265a52f3736462
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1bada1bcbe144100f5d8358303b6e9214cface5cd33052e5f5f4f0f8154a06ac45b24b606b658dc09f2bc38748ef4f808b186d5b6c8242e37a11e7b562568d29
|
7
|
+
data.tar.gz: 46af02913e36302f7eb310e1eda72711fa7857bedd0dc7cce99f72e5dc9e3a2c0038ac101d96975272ea588f562b17910645393a5da4871d14533eb813904fd6
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
tmp/
|
19
|
+
db/*.sqlite3
|
20
|
+
vendor/bundle/
|
21
|
+
vendor/bundler/
|
22
|
+
log/*.log
|
23
|
+
.ruby-*
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 ogawatti
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
[](http://badge.fury.io/rb/twimock)
|
2
|
+
[](https://travis-ci.org/ogawatti/twimock)
|
3
|
+
[](https://coveralls.io/r/ogawatti/twimock?branch=master)
|
4
|
+
[<img src="https://gemnasium.com/ogawatti/twimock.png" />](https://gemnasium.com/ogawatti/twimock)
|
5
|
+
[](https://codeclimate.com/github/ogawatti/twimock)
|
6
|
+
|
7
|
+
# Twimock
|
8
|
+
|
9
|
+
This gem is used to mock the communication part of the twitter api.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'twimock'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install twimock
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### For Rails App Settings (config/initializers/twimock.rb)
|
28
|
+
|
29
|
+
# Create Twimock Application & User
|
30
|
+
application = Twimock::Application.create!
|
31
|
+
user = Twimock::User.create!
|
32
|
+
|
33
|
+
# Associate App and User
|
34
|
+
user.generate_access_token(app.id)
|
35
|
+
|
36
|
+
# Twimock Setting
|
37
|
+
Twimock::Config.host = 'example.com'
|
38
|
+
Twimock::Config.port = 3000
|
39
|
+
Twimock::Config.callback_url = '/users/auth/twitter/callback'
|
40
|
+
|
41
|
+
# Enable Twimock
|
42
|
+
Twimock::API.on
|
43
|
+
Twimock::OmniAuthTwitter.on
|
44
|
+
|
45
|
+
# Add Rack Middleware for twimock
|
46
|
+
[ Twimock::API::OAuth::Authenticate, Twimock::API::Intent::Sessions ].each do |middleware|
|
47
|
+
Rails.application.config.middleware.use middleware
|
48
|
+
end
|
49
|
+
|
50
|
+
### Create Apps and Users at once by yaml file
|
51
|
+
|
52
|
+
require 'twimock'
|
53
|
+
|
54
|
+
filename = File.expand_path('../test_users.yml', __FILE__)
|
55
|
+
Twimock::Config.load_users(filename)
|
56
|
+
|
57
|
+
Twimock::Application.find_by_id(1).api_key #=> avb0vlu767yhu37hti5qq9hcc
|
58
|
+
Twimock::User.find_by_id(1).name #=> testuser01
|
59
|
+
|
60
|
+
yaml file see below.
|
61
|
+
|
62
|
+
---
|
63
|
+
- :id: 1
|
64
|
+
:api_key: avb0vlu767yhu37hti5qq9hcc
|
65
|
+
:api_secret: e85vl7fc4susiyjjp0pncz0hf2xtf3vm29gj7hhp2ktv28wunl
|
66
|
+
:users:
|
67
|
+
- :id: 1
|
68
|
+
:name: testuser01
|
69
|
+
:password: r3xkhy64w
|
70
|
+
:access_token: 6697725737-9ntcnith1wq7zgphnisxu6bqybl019bms05t8l9
|
71
|
+
:access_token_secret: 22ogzdkn5kqtlihr3u5vwplrlh8noie61pr6ndeangrpt
|
72
|
+
:application_id: 1
|
73
|
+
- :id: 2
|
74
|
+
:name: testuser02
|
75
|
+
:password: 5ush05lp0
|
76
|
+
:access_token: 6891305263-xpvu78zd1p76s3cp6jwrudgb0g0sffxe9hp7mdj
|
77
|
+
:access_token_secret: or1xkqs96tim8n7vhc77yxo2i6ed9a6bmhru0zozjao80
|
78
|
+
:application_id: 1
|
79
|
+
- :id: 2
|
80
|
+
:api_key: w6cb9sj17fyf5g1rr4fl5ignp
|
81
|
+
:api_secret: 2vrdpujwvl3421qatn8qah9ishpia9khq7mprnkfx49mldo0k6
|
82
|
+
:users:
|
83
|
+
- :id: 3
|
84
|
+
:name: testuser03
|
85
|
+
:password: ylgi4lth
|
86
|
+
:access_token: 6932630251-1sshumnflh0abshkgaf2scxa6l02cr8tyi2kt00
|
87
|
+
:access_token_secret: txncllipm1wl0g21wvtc750lqz2dleu6e0lqg62vt7eam
|
88
|
+
:application_id: 2
|
89
|
+
|
90
|
+
### User Model
|
91
|
+
|
92
|
+
require 'twimock'
|
93
|
+
|
94
|
+
# Create
|
95
|
+
user = Twimock::User.new
|
96
|
+
user.name = "twimock_test_user"
|
97
|
+
user.save!
|
98
|
+
|
99
|
+
user = Twimock::User.new(name: "hoge", password: "fuga")
|
100
|
+
user.name #=> "hoge"
|
101
|
+
user.save!
|
102
|
+
|
103
|
+
user = Twimock::User.create!(name: "hogehoge", password: "fugafuga")
|
104
|
+
user.name #=> "hogehoge"
|
105
|
+
user.password #=> "fugafuga"
|
106
|
+
|
107
|
+
# Find
|
108
|
+
Twimock::User.find_by_id(1)
|
109
|
+
Twimock::User.find_by_name("testuser01")
|
110
|
+
Twimock::User.where(name: "testuser02")
|
111
|
+
Twimock::User.all
|
112
|
+
Twimock::User.first
|
113
|
+
Twimock::User.last
|
114
|
+
|
115
|
+
# Delete
|
116
|
+
user = User.last
|
117
|
+
user.destroy
|
118
|
+
|
119
|
+
## Contributing
|
120
|
+
|
121
|
+
1. Fork it
|
122
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
123
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
124
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
125
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/db/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faker'
|
2
|
+
require 'twimock/database/table'
|
3
|
+
|
4
|
+
module Twimock
|
5
|
+
class AccessToken < Database::Table
|
6
|
+
TABLE_NAME = :access_tokens
|
7
|
+
COLUMN_NAMES = [:id, :string, :secret, :application_id, :user_id, :created_at]
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
opts = Hashie::Mash.new(options)
|
11
|
+
id = opts.id.to_i
|
12
|
+
@id = id if id > 0
|
13
|
+
app_id = opts.application_id.to_i
|
14
|
+
@application_id = app_id if app_id > 0
|
15
|
+
user_id = opts.user_id.to_i
|
16
|
+
@user_id = user_id if user_id > 0
|
17
|
+
|
18
|
+
@string = generate_string(opts.string)
|
19
|
+
@secret = opts.secret || Faker::Lorem.characters(45)
|
20
|
+
@created_at = opts.created_at
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def generate_string(string=nil)
|
26
|
+
return string if string
|
27
|
+
return "#{@user_id}-#{Faker::Lorem.characters(39)}" if @user_id
|
28
|
+
return Faker::Lorem.characters(50)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'twimock/api/oauth'
|
2
|
+
require 'twimock/user'
|
3
|
+
|
4
|
+
module Twimock
|
5
|
+
module API
|
6
|
+
# OAuth 1.1, OAuth Echo で利用するAPI
|
7
|
+
# ユーザ情報を取得する
|
8
|
+
module Account
|
9
|
+
class VerifyCredentials < OAuth
|
10
|
+
METHOD = "GET"
|
11
|
+
PATH = "/1.1/account/verify_credentials.json"
|
12
|
+
AUTHORIZATION_REGEXP = /OAuth oauth_consumer_key=\"(.*)\", oauth_nonce=\"(.*)\", oauth_signature=\"(.*)\", oauth_signature_method=\"(.*)\", oauth_timestamp=\"(.*)\", oauth_token=\"(.*)\", oauth_version=\"(.*)\".*/
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
return super unless called?(env)
|
16
|
+
|
17
|
+
begin
|
18
|
+
authorization_header = env["authorization"] || env["HTTP_AUTHORIZATION"]
|
19
|
+
oauth = parse_authorization_header(authorization_header)
|
20
|
+
|
21
|
+
raise Twimock::Errors::InvalidConsumerKey.new if !validate_consumer_key(oauth.consumer_key)
|
22
|
+
application = Twimock::Application.find_by_api_key(oauth.consumer_key)
|
23
|
+
raise Twimock::Errors::InvalidAccessToken.new if !validate_access_token(oauth.token, application.id)
|
24
|
+
access_token = Twimock::AccessToken.find_by_string(oauth.token)
|
25
|
+
user = Twimock::User.find_by_id(access_token.user_id)
|
26
|
+
rescue Twimock::Errors::InvalidAccessToken, Twimock::Errors::InvalidConsumerKey => @error
|
27
|
+
return unauthorized
|
28
|
+
rescue => @error
|
29
|
+
return internal_server_error
|
30
|
+
end
|
31
|
+
|
32
|
+
status = '200 OK'
|
33
|
+
body = user.info.to_json
|
34
|
+
header = { "Content-Length" => body.bytesize.to_s }
|
35
|
+
[ status, header, [ body ] ]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'excon'
|
2
|
+
|
3
|
+
module Twimock
|
4
|
+
module API
|
5
|
+
# Rack Application
|
6
|
+
# Net::HTTP は ShamRack で偽装されるため, Excon (Socket) で通信する
|
7
|
+
class Application
|
8
|
+
def call(env)
|
9
|
+
request(env)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def request(env)
|
15
|
+
rackreq = Rack::Request.new(env)
|
16
|
+
connection = Excon.new(rackreq.url)
|
17
|
+
|
18
|
+
options = {}
|
19
|
+
options[:method] = rackreq.request_method
|
20
|
+
options[:path] = rackreq.path
|
21
|
+
options[:headers] = rackreq.env.select{|k,v| k !~ /^rack\./}
|
22
|
+
options[:body] = rackreq.body.read
|
23
|
+
|
24
|
+
res = connection.request(options)
|
25
|
+
[ res.status, res.headers, [ res.body ] ]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'erb'
|
3
|
+
require 'json'
|
4
|
+
require 'addressable/uri'
|
5
|
+
require 'twimock/errors'
|
6
|
+
|
7
|
+
module Twimock
|
8
|
+
module API
|
9
|
+
# POST https://twitter.com/intent/sessions
|
10
|
+
# body: { 'session[username_or_email]' => "xxx", 'session[password]' => "xxx", oauth_token: "xxx" }
|
11
|
+
module Intent
|
12
|
+
class Sessions < OAuth
|
13
|
+
METHOD = "POST"
|
14
|
+
PATH = "/intent/sessions"
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
return super unless called?(env)
|
18
|
+
begin
|
19
|
+
request = Rack::Request.new(env)
|
20
|
+
body = query_string_to_hash(request.body.read)
|
21
|
+
@oauth_token = body.oauth_token
|
22
|
+
@username_or_email = body["session[username_or_email]"]
|
23
|
+
@password = body["session[password]"]
|
24
|
+
|
25
|
+
if !validate_request_token(@oauth_token)
|
26
|
+
raise Twimock::Errors::InvalidRequestToken.new
|
27
|
+
elsif !(user = Twimock::User.find_by_tiwtter_id_or_email(@username_or_email))
|
28
|
+
raise Twimock::Errors::InvalidUsernameOrEmail.new
|
29
|
+
elsif @password.blank? || @password != user.password
|
30
|
+
raise Twimock::Errors::InvalidPassword.new
|
31
|
+
end
|
32
|
+
request_token = Twimock::RequestToken.find_by_string(@oauth_token)
|
33
|
+
request_token.user_id = user.id
|
34
|
+
request_token.save!
|
35
|
+
|
36
|
+
uri = Addressable::URI.new
|
37
|
+
uri.query_values = { oauth_token: request_token.string,
|
38
|
+
oauth_verifier: request_token.verifier }
|
39
|
+
callback_url = Twimock::Config.callback_url + "?" + uri.query
|
40
|
+
|
41
|
+
status = 302
|
42
|
+
body = ""
|
43
|
+
header = { "Content-Length" => body.bytesize.to_s,
|
44
|
+
"Location" => callback_url }
|
45
|
+
[ status, header, [ body ] ]
|
46
|
+
rescue Twimock::Errors::InvalidUsernameOrEmail, Twimock::Errors::InvalidPassword => @error
|
47
|
+
response = unauthorized
|
48
|
+
response[0] = 302
|
49
|
+
response[1].merge!( {"Location" => "/oauth/authenticate?oauth_token=#{@oauth_token}" })
|
50
|
+
response
|
51
|
+
rescue Twimock::Errors::InvalidRequestToken => @error
|
52
|
+
return unauthorized
|
53
|
+
rescue => @error
|
54
|
+
internal_server_error
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'twimock/api/oauth'
|
2
|
+
require 'twimock/user'
|
3
|
+
|
4
|
+
module Twimock
|
5
|
+
module API
|
6
|
+
class OAuth
|
7
|
+
# OAuth 1.1 で利用するAPI
|
8
|
+
# Access Token を取得する
|
9
|
+
class AccessToken < OAuth
|
10
|
+
METHOD = "POST"
|
11
|
+
PATH = "/oauth/access_token"
|
12
|
+
AUTHORIZATION_REGEXP = /OAuth oauth_body_hash=\"(.*)\", oauth_consumer_key=\"(.*)\", oauth_nonce=\"(.*)\", oauth_signature=\"(.*)\", oauth_signature_method=\"(.*)\", oauth_timestamp=\"(.*)\", oauth_token=\"(.*)\", oauth_verifier=\"(.*)\", oauth_version=\"(.*)\"/
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
return super unless called?(env)
|
16
|
+
begin
|
17
|
+
authorization_header = env["authorization"] || env["HTTP_AUTHORIZATION"]
|
18
|
+
oauth = parse_authorization_header(authorization_header)
|
19
|
+
consumer_key = oauth.consumer_key
|
20
|
+
request_token = oauth.token
|
21
|
+
|
22
|
+
raise Twimock::Errors::InvalidConsumerKey.new if !validate_consumer_key(consumer_key)
|
23
|
+
application = Twimock::Application.find_by_api_key(consumer_key)
|
24
|
+
if !validate_request_token(request_token, application.id)
|
25
|
+
raise Twimock::Errors::InvalidRequestToken.new
|
26
|
+
end
|
27
|
+
request_token = Twimock::RequestToken.find_by_string(request_token)
|
28
|
+
user = Twimock::User.find_by_id(request_token.user_id)
|
29
|
+
access_tokens = Twimock::AccessToken.where(user_id: user.id)
|
30
|
+
unless access_token = access_tokens.find{|at| at.application_id == application.id }
|
31
|
+
access_token = user.generate_access_token(application.id)
|
32
|
+
end
|
33
|
+
rescue Twimock::Errors::InvalidConsumerKey, Twimock::Errors::InvalidRequestToken => @error
|
34
|
+
return unauthorized
|
35
|
+
rescue => @error
|
36
|
+
return internal_server_error
|
37
|
+
end
|
38
|
+
|
39
|
+
status = "200 OK"
|
40
|
+
params = {
|
41
|
+
oauth_token: access_token.string,
|
42
|
+
oauth_token_secret: access_token.secret,
|
43
|
+
user_id: user.id,
|
44
|
+
screen_name: user.twitter_id
|
45
|
+
}
|
46
|
+
body = params.inject([]){|a, (k, v)| a << "#{k}=#{v}"}.join('&')
|
47
|
+
header = { "Content-Length" => body.bytesize.to_s }
|
48
|
+
|
49
|
+
[ status, header, [ body ] ]
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_request_token(request_token, application_id)
|
55
|
+
return false unless super(request_token)
|
56
|
+
|
57
|
+
request_token = Twimock::RequestToken.find_by_string(request_token)
|
58
|
+
return false unless request_token.application_id == application_id
|
59
|
+
return false unless User.find_by_id(request_token.user_id)
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Twimock
|
5
|
+
module API
|
6
|
+
# OAuthでブラウザ認証するAPI
|
7
|
+
# GET http://api.twimock.com/authenticate?oauth_token=xxx
|
8
|
+
class OAuth
|
9
|
+
class Authenticate < OAuth
|
10
|
+
METHOD = "GET"
|
11
|
+
PATH = "/oauth/authenticate"
|
12
|
+
VIEW_DIRECTORY = File.expand_path("../../../../../view", __FILE__)
|
13
|
+
VIEW_FILE_NAME = "authenticate.html.erb"
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
return super unless called?(env)
|
17
|
+
begin
|
18
|
+
request = Rack::Request.new(env)
|
19
|
+
@oauth_token = request.params["oauth_token"]
|
20
|
+
|
21
|
+
if !validate_request_token(@oauth_token)
|
22
|
+
raise Twimock::Errors::InvalidRequestToken.new
|
23
|
+
end
|
24
|
+
|
25
|
+
status = 200
|
26
|
+
body = Twimock::API::OAuth::Authenticate.view(@oauth_token)
|
27
|
+
header = { "Content-Length" => body.bytesize.to_s }
|
28
|
+
[ status, header, [ body ] ]
|
29
|
+
rescue Twimock::Errors::InvalidRequestToken => @error
|
30
|
+
unauthorized
|
31
|
+
rescue => @error
|
32
|
+
internal_server_error
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.view(oauth_token)
|
37
|
+
@action_url = Twimock::API::Intent::Sessions::PATH
|
38
|
+
@oauth_token = oauth_token
|
39
|
+
erb = ERB.new(File.read(filepath))
|
40
|
+
erb.result(binding)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def self.filepath
|
46
|
+
File.join(VIEW_DIRECTORY, VIEW_FILE_NAME)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'twimock/api/oauth'
|
2
|
+
require 'twimock/application'
|
3
|
+
|
4
|
+
module Twimock
|
5
|
+
module API
|
6
|
+
# Twitter OAuth で利用するAPI
|
7
|
+
# Request Token を発行する
|
8
|
+
class OAuth
|
9
|
+
class RequestToken < OAuth
|
10
|
+
METHOD = "POST"
|
11
|
+
PATH = "/oauth/request_token"
|
12
|
+
AUTHORIZATION_REGEXP = /OAuth oauth_callback=\"(.*)\", oauth_consumer_key=\"(.*)\", oauth_nonce=\"(.*)\", oauth_signature=\"(.*)\", oauth_signature_method=\"(.*)\", oauth_timestamp=\"(.*)\", oauth_version=\"(.*)\".*/
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
return super unless called?(env)
|
16
|
+
begin
|
17
|
+
authorization_header = env["authorization"] || env["HTTP_AUTHORIZATION"]
|
18
|
+
oauth = parse_authorization_header(authorization_header)
|
19
|
+
consumer_key = oauth.consumer_key
|
20
|
+
|
21
|
+
raise Twimock::Errors::InvalidConsumerKey.new if !validate_consumer_key(consumer_key)
|
22
|
+
application = Twimock::Application.find_by_api_key(consumer_key)
|
23
|
+
rescue Twimock::Errors::InvalidConsumerKey => @error
|
24
|
+
return unauthorized
|
25
|
+
rescue => @error
|
26
|
+
return internal_server_error
|
27
|
+
end
|
28
|
+
|
29
|
+
request_token = create_request_token(application.id)
|
30
|
+
status = "200 OK"
|
31
|
+
params = { oauth_token: request_token.string,
|
32
|
+
oauth_token_secret: request_token.secret,
|
33
|
+
oauth_callback_confirmed: true }
|
34
|
+
body = params.inject([]){|a, (k, v)| a << "#{k}=#{v}"}.join('&')
|
35
|
+
header = { "Content-Length" => body.bytesize.to_s }
|
36
|
+
[ status, header, [ body ] ]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def create_request_token(application_id)
|
42
|
+
request_token = Twimock::RequestToken.new(application_id: application_id)
|
43
|
+
request_token.save!
|
44
|
+
request_token
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'twimock/api/oauth/access_token'
|
2
|
+
require 'twimock/api/oauth/request_token'
|
3
|
+
require 'twimock/api/oauth/authenticate'
|
4
|
+
require 'twimock/api/intent/sessions'
|
5
|
+
require 'twimock/api/account/verify_credentials'
|
6
|
+
require 'twimock/errors'
|
7
|
+
|
8
|
+
module Twimock
|
9
|
+
module API
|
10
|
+
class OAuth
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate_consumer_key(consumer_key)
|
22
|
+
return false if consumer_key.blank?
|
23
|
+
return false unless application = Twimock::Application.find_by_api_key(consumer_key)
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_request_token(request_token)
|
28
|
+
return false if request_token.blank?
|
29
|
+
return false unless request_token = Twimock::RequestToken.find_by_string(request_token)
|
30
|
+
return false unless request_token.application_id
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate_access_token(access_token_string, application_id)
|
35
|
+
return false if access_token_string.blank?
|
36
|
+
return false unless access_token = Twimock::AccessToken.find_by_string(access_token_string)
|
37
|
+
return false unless access_token.application_id
|
38
|
+
return false unless access_token.application_id == application_id
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
def called?(env)
|
43
|
+
request = Rack::Request.new(env)
|
44
|
+
request.request_method == self.class::METHOD && request.path == self.class::PATH
|
45
|
+
end
|
46
|
+
|
47
|
+
def unauthorized
|
48
|
+
generate_error_response(401)
|
49
|
+
end
|
50
|
+
|
51
|
+
def internal_server_error
|
52
|
+
generate_error_response(500)
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_authorization_header(authorization_header)
|
56
|
+
authorization = case authorization_header
|
57
|
+
when Array then authorization_header.first
|
58
|
+
when String then authorization_header
|
59
|
+
else ""
|
60
|
+
end
|
61
|
+
|
62
|
+
oauth = Hashie::Mash.new
|
63
|
+
authorization.scan(/oauth_(\w+)=\"([\w%-.]+)\"/) do |key, value|
|
64
|
+
oauth[key] = value
|
65
|
+
end
|
66
|
+
oauth
|
67
|
+
end
|
68
|
+
|
69
|
+
def query_string_to_hash(query_string)
|
70
|
+
ary = URI.decode(query_string).split("&").inject([]){|a, s| a << s.split("=")}
|
71
|
+
Hashie::Mash.new(Hash[ary])
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_error_response(status)
|
75
|
+
error_code = @error.class.to_s.split("::").last
|
76
|
+
body = { error: { code: error_code } }.to_json
|
77
|
+
header = { "Content-Type" => "application/json; charset=utf-8",
|
78
|
+
"Content-Length" => body.bytesize.to_s }
|
79
|
+
[ status, header, [ body ] ]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/twimock/api.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'twimock/api/application'
|
2
|
+
require 'twimock/api/oauth'
|
3
|
+
require 'sham_rack'
|
4
|
+
|
5
|
+
module Twimock
|
6
|
+
module API
|
7
|
+
extend self
|
8
|
+
|
9
|
+
HOSTNAME = "api.twitter.com"
|
10
|
+
PORT = 443
|
11
|
+
MIDDLEWARES = [ OAuth::AccessToken, OAuth::RequestToken, Account::VerifyCredentials ]
|
12
|
+
|
13
|
+
def on
|
14
|
+
ShamRack.at(HOSTNAME, PORT){|env| app.call(env) } unless on?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def off
|
19
|
+
ShamRack.unmount_all
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def on?
|
24
|
+
!ShamRack.application_for(HOSTNAME, PORT).nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Rack Application
|
28
|
+
def app
|
29
|
+
app = Twimock::API::Application.new
|
30
|
+
MIDDLEWARES.inject(app) do |app, klass|
|
31
|
+
app = klass.new(app)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'faker'
|
2
|
+
require 'twimock/database/table'
|
3
|
+
require 'twimock/access_token'
|
4
|
+
require 'twimock/request_token'
|
5
|
+
|
6
|
+
module Twimock
|
7
|
+
class Application < Database::Table
|
8
|
+
TABLE_NAME = :applications
|
9
|
+
COLUMN_NAMES = [:id, :api_key, :api_secret, :created_at]
|
10
|
+
CHILDREN = [ Twimock::AccessToken, Twimock::RequestToken ]
|
11
|
+
|
12
|
+
# WANT : DBに登録済みの値と重複しないようにする(id, api_secret)
|
13
|
+
def initialize(options={})
|
14
|
+
opts = Hashie::Mash.new(options)
|
15
|
+
@id = ( opts.id.to_i > 0 ) ? opts.id.to_i : Faker::Number.number(10).to_i
|
16
|
+
@api_key = opts.api_key || Faker::Lorem.characters(25)
|
17
|
+
@api_secret = opts.api_secret || Faker::Lorem.characters(50)
|
18
|
+
@created_at = opts.created_at
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|