tentd 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.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +9 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +49 -0
- data/Rakefile +8 -0
- data/bin/tent-server +3 -0
- data/lib/tentd.rb +31 -0
- data/lib/tentd/api.rb +58 -0
- data/lib/tentd/api/apps.rb +196 -0
- data/lib/tentd/api/authentication_finalize.rb +12 -0
- data/lib/tentd/api/authentication_lookup.rb +27 -0
- data/lib/tentd/api/authentication_verification.rb +50 -0
- data/lib/tentd/api/authorizable.rb +21 -0
- data/lib/tentd/api/authorization.rb +14 -0
- data/lib/tentd/api/core_profile_data.rb +45 -0
- data/lib/tentd/api/followers.rb +218 -0
- data/lib/tentd/api/followings.rb +241 -0
- data/lib/tentd/api/groups.rb +161 -0
- data/lib/tentd/api/middleware.rb +32 -0
- data/lib/tentd/api/posts.rb +373 -0
- data/lib/tentd/api/profile.rb +78 -0
- data/lib/tentd/api/router.rb +123 -0
- data/lib/tentd/api/router/caching_headers.rb +49 -0
- data/lib/tentd/api/router/extract_params.rb +88 -0
- data/lib/tentd/api/router/serialize_response.rb +38 -0
- data/lib/tentd/api/user_lookup.rb +10 -0
- data/lib/tentd/core_ext/hash/slice.rb +29 -0
- data/lib/tentd/datamapper/array_property.rb +23 -0
- data/lib/tentd/datamapper/query.rb +19 -0
- data/lib/tentd/json_patch.rb +181 -0
- data/lib/tentd/model.rb +30 -0
- data/lib/tentd/model/app.rb +68 -0
- data/lib/tentd/model/app_authorization.rb +113 -0
- data/lib/tentd/model/follower.rb +105 -0
- data/lib/tentd/model/following.rb +100 -0
- data/lib/tentd/model/group.rb +24 -0
- data/lib/tentd/model/mention.rb +19 -0
- data/lib/tentd/model/notification_subscription.rb +56 -0
- data/lib/tentd/model/permissible.rb +227 -0
- data/lib/tentd/model/permission.rb +28 -0
- data/lib/tentd/model/post.rb +178 -0
- data/lib/tentd/model/post_attachment.rb +27 -0
- data/lib/tentd/model/post_version.rb +64 -0
- data/lib/tentd/model/profile_info.rb +80 -0
- data/lib/tentd/model/random_public_id.rb +46 -0
- data/lib/tentd/model/serializable.rb +58 -0
- data/lib/tentd/model/type_properties.rb +36 -0
- data/lib/tentd/model/user.rb +39 -0
- data/lib/tentd/model/user_scoped.rb +14 -0
- data/lib/tentd/notifications.rb +13 -0
- data/lib/tentd/notifications/girl_friday.rb +30 -0
- data/lib/tentd/notifications/sidekiq.rb +50 -0
- data/lib/tentd/tent_type.rb +20 -0
- data/lib/tentd/tent_version.rb +41 -0
- data/lib/tentd/version.rb +3 -0
- data/spec/fabricators/app_authorizations_fabricator.rb +5 -0
- data/spec/fabricators/apps_fabricator.rb +11 -0
- data/spec/fabricators/followers_fabricator.rb +14 -0
- data/spec/fabricators/followings_fabricator.rb +17 -0
- data/spec/fabricators/groups_fabricator.rb +3 -0
- data/spec/fabricators/mentions_fabricator.rb +3 -0
- data/spec/fabricators/notification_subscriptions_fabricator.rb +4 -0
- data/spec/fabricators/permissions_fabricator.rb +1 -0
- data/spec/fabricators/post_attachments_fabricator.rb +8 -0
- data/spec/fabricators/post_versions_fabricator.rb +12 -0
- data/spec/fabricators/posts_fabricator.rb +12 -0
- data/spec/fabricators/profile_infos_fabricator.rb +30 -0
- data/spec/integration/api/apps_spec.rb +466 -0
- data/spec/integration/api/followers_spec.rb +535 -0
- data/spec/integration/api/followings_spec.rb +688 -0
- data/spec/integration/api/groups_spec.rb +207 -0
- data/spec/integration/api/posts_spec.rb +874 -0
- data/spec/integration/api/profile_spec.rb +285 -0
- data/spec/integration/api/router_spec.rb +102 -0
- data/spec/integration/model/app_authorization_spec.rb +59 -0
- data/spec/integration/model/app_spec.rb +63 -0
- data/spec/integration/model/follower_spec.rb +344 -0
- data/spec/integration/model/following_spec.rb +97 -0
- data/spec/integration/model/group_spec.rb +39 -0
- data/spec/integration/model/notification_subscription_spec.rb +145 -0
- data/spec/integration/model/post_spec.rb +658 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/expect_server.rb +3 -0
- data/spec/support/json_request.rb +54 -0
- data/spec/support/with_constant.rb +23 -0
- data/spec/support/with_warnings.rb +6 -0
- data/spec/unit/api/authentication_finalize_spec.rb +45 -0
- data/spec/unit/api/authentication_lookup_spec.rb +65 -0
- data/spec/unit/api/authentication_verification_spec.rb +50 -0
- data/spec/unit/api/authorizable_spec.rb +50 -0
- data/spec/unit/api/authorization_spec.rb +44 -0
- data/spec/unit/api/caching_headers_spec.rb +121 -0
- data/spec/unit/core_profile_data_spec.rb +64 -0
- data/spec/unit/json_patch_spec.rb +407 -0
- data/spec/unit/tent_type_spec.rb +28 -0
- data/spec/unit/tent_version_spec.rb +68 -0
- data/tentd.gemspec +36 -0
- metadata +435 -0
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'mocha_standalone'
|
|
6
|
+
require 'rack/test'
|
|
7
|
+
require 'tentd'
|
|
8
|
+
require 'fabrication'
|
|
9
|
+
require 'tentd/core_ext/hash/slice'
|
|
10
|
+
require 'girl_friday'
|
|
11
|
+
require 'tentd/notifications/girl_friday'
|
|
12
|
+
|
|
13
|
+
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |f| require f }
|
|
14
|
+
|
|
15
|
+
ENV['RACK_ENV'] ||= 'test'
|
|
16
|
+
|
|
17
|
+
require 'data_mapper'
|
|
18
|
+
|
|
19
|
+
RSpec.configure do |config|
|
|
20
|
+
config.include Rack::Test::Methods
|
|
21
|
+
config.include JsonRequest
|
|
22
|
+
config.mock_with :mocha
|
|
23
|
+
|
|
24
|
+
config.around do |suite|
|
|
25
|
+
with_constants "TentD::Notifications::NOTIFY_ENTITY_QUEUE" => [], "TentD::Notifications::TRIGGER_QUEUE" => [] do
|
|
26
|
+
suite.run
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
config.before(:suite) do
|
|
31
|
+
GirlFriday::WorkQueue.immediate!
|
|
32
|
+
# DataMapper::Logger.new(STDOUT, :debug)
|
|
33
|
+
DataMapper.setup(:default, ENV['TEST_DATABASE_URL'] || 'postgres://localhost/tent_server_test')
|
|
34
|
+
DataMapper.auto_migrate!
|
|
35
|
+
TentD::Model::User.current = TentD::Model::User.first_or_create
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module JsonRequest
|
|
4
|
+
def json_patch(path, data = {}, rack_env = {})
|
|
5
|
+
patch path, data.to_json, { 'CONTENT_TYPE' => TentD::API::MEDIA_TYPE }.merge(rack_env)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def json_put(path, data = {}, rack_env= {})
|
|
9
|
+
put path, data.to_json, { 'CONTENT_TYPE' => TentD::API::MEDIA_TYPE }.merge(rack_env)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def json_post(path, data = {}, rack_env = {})
|
|
13
|
+
post path, data.to_json, { 'CONTENT_TYPE' => TentD::API::MEDIA_TYPE }.merge(rack_env)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def json_get(path, data = {}, rack_env = {})
|
|
17
|
+
get path, data, { 'HTTP_ACCEPT' => TentD::API::MEDIA_TYPE }.merge(rack_env)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def multipart_post(path, json, parts, rack_env = {})
|
|
21
|
+
body = build_json_part(json) + build_parts(parts) + "--#{Rack::Multipart::MULTIPART_BOUNDARY}--\r"
|
|
22
|
+
post path, body, { 'CONTENT_TYPE' => "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}",
|
|
23
|
+
'HTTP_ACCEPT' => TentD::API::MEDIA_TYPE }.merge(rack_env)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def multipart_put(path, json, parts, rack_env = {})
|
|
27
|
+
body = build_json_part(json) + build_parts(parts) + "--#{Rack::Multipart::MULTIPART_BOUNDARY}--\r"
|
|
28
|
+
put path, body, { 'CONTENT_TYPE' => "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}",
|
|
29
|
+
'HTTP_ACCEPT' => TentD::API::MEDIA_TYPE }.merge(rack_env)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def build_json_part(json)
|
|
35
|
+
build_part('post', :filename => 'post.json', :content_type => TentD::API::MEDIA_TYPE, :content => json.to_json)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build_parts(parts)
|
|
39
|
+
parts.map do |k,v|
|
|
40
|
+
v.kind_of?(Array) ? v.each_with_index.map { |part,i| build_part("#{k}[#{i}]", part) } : build_part(k, v)
|
|
41
|
+
end.join
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_part(name, data)
|
|
45
|
+
<<-EOF
|
|
46
|
+
--#{Rack::Multipart::MULTIPART_BOUNDARY}\r
|
|
47
|
+
Content-Disposition: form-data; name="#{name}"; filename="#{Rack::Utils.escape(data[:filename])}"\r
|
|
48
|
+
Content-Type: #{data[:content_type]}\r
|
|
49
|
+
Content-Length: #{data[:content].size}\r
|
|
50
|
+
\r
|
|
51
|
+
#{data[:content]}\r
|
|
52
|
+
EOF
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
def split_constant_string(const_string)
|
|
2
|
+
parent_constant = const_string.to_s.split("::")[0..-2].inject(Object) { |o, c| o.const_get(c) }
|
|
3
|
+
child_constant = const_string.to_s.split("::").last
|
|
4
|
+
[parent_constant, child_constant]
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def with_constants(constants, &block)
|
|
8
|
+
saved_constants = {}
|
|
9
|
+
constants.each_pair do |constant, val|
|
|
10
|
+
parent, child = split_constant_string(constant)
|
|
11
|
+
saved_constants[ constant ] = parent.const_get( child )
|
|
12
|
+
with_warnings(nil) { parent.const_set( child, val ) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
begin
|
|
16
|
+
block.call
|
|
17
|
+
ensure
|
|
18
|
+
constants.each_pair do |constant, val|
|
|
19
|
+
parent, child = split_constant_string(constant)
|
|
20
|
+
with_warnings(nil) { parent.const_set( child, saved_constants[ constant ] ) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe TentD::API::AuthenticationFinalize do
|
|
4
|
+
def app
|
|
5
|
+
TentD::API.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "should set current_auth" do
|
|
9
|
+
instance = stub(:mac_timestamp_delta => 1346171050)
|
|
10
|
+
env = Hashie::Mash.new({
|
|
11
|
+
"potential_auth" => instance,
|
|
12
|
+
'hmac' => { 'ts' => "1336363200", 'verified' => true }
|
|
13
|
+
})
|
|
14
|
+
described_class.new(app).call(env)
|
|
15
|
+
expect(env["current_auth"]).to eq(instance)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "should set mac_timestamp_delta on current_auth" do
|
|
19
|
+
now = Time.now; Time.stubs(:now).returns(now)
|
|
20
|
+
delta = now.to_i - 1336363200
|
|
21
|
+
instance = stub(:mac_timestamp_delta => nil)
|
|
22
|
+
env = Hashie::Mash.new({
|
|
23
|
+
"potential_auth" => instance,
|
|
24
|
+
'hmac' => { 'ts' => "1336363200", 'verified' => true }
|
|
25
|
+
})
|
|
26
|
+
instance.expects(:update).with(:mac_timestamp_delta => delta).returns(true)
|
|
27
|
+
described_class.new(app).call(env)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "should not set mac_timestamp_delta on current_auth if already set" do
|
|
31
|
+
instance = stub(:mac_timestamp_delta => 1346171050)
|
|
32
|
+
env = Hashie::Mash.new({
|
|
33
|
+
"potential_auth" => instance,
|
|
34
|
+
'hmac' => { 'ts' => "1336363200", 'verified' => true }
|
|
35
|
+
})
|
|
36
|
+
instance.expects(:update).never
|
|
37
|
+
described_class.new(app).call(env)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'should do nothing unless env.hmac.verified present' do
|
|
41
|
+
env = Hashie::Mash.new({ "potential_auth" => stub(:mac_timestamp_delta => 1346171050) })
|
|
42
|
+
described_class.new(app).call(env)
|
|
43
|
+
expect(env.current_auth).to be_nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe TentD::API::AuthenticationLookup do
|
|
4
|
+
def app
|
|
5
|
+
TentD::API.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
let(:env) { Hashie::Mash.new({}) }
|
|
9
|
+
let(:auth_header) { 'MAC id="%s:h480djs93hd8", ts="1336363200", nonce="dj83hs9s", mac="hqpo01mLJLSYDbxmfRgNMEw38Wg="' }
|
|
10
|
+
|
|
11
|
+
it 'should parse hmac authorization header' do
|
|
12
|
+
TentD::Model::Follower.all.destroy!
|
|
13
|
+
follow = Fabricate(:follower, :mac_key_id => "s:h480djs93hd8")
|
|
14
|
+
env['HTTP_AUTHORIZATION'] = auth_header % 's'
|
|
15
|
+
described_class.new(app).call(env)
|
|
16
|
+
expect(env['hmac']).to eq({
|
|
17
|
+
"algorithm" => "hmac-sha-256",
|
|
18
|
+
"id" => "s:h480djs93hd8",
|
|
19
|
+
"ts" => "1336363200",
|
|
20
|
+
"nonce" => "dj83hs9s",
|
|
21
|
+
"secret" => follow.mac_key,
|
|
22
|
+
"mac" => "hqpo01mLJLSYDbxmfRgNMEw38Wg="
|
|
23
|
+
})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'should lookup server authentication model' do
|
|
27
|
+
TentD::Model::Follower.all(:mac_key_id => "s:h480djs93hd8").destroy!
|
|
28
|
+
follow = Fabricate(:follower, :mac_key_id => "s:h480djs93hd8")
|
|
29
|
+
expect(follow.saved?).to be_true
|
|
30
|
+
env['HTTP_AUTHORIZATION'] = auth_header % 's'
|
|
31
|
+
described_class.new(app).call(env)
|
|
32
|
+
expect(env.potential_auth).to eq(follow.reload)
|
|
33
|
+
expect(env.hmac.secret).to eq(follow.mac_key)
|
|
34
|
+
expect(env.hmac.algorithm).to eq(follow.mac_algorithm)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'should lookup app authentication model' do
|
|
38
|
+
TentD::Model::App.all.destroy
|
|
39
|
+
authed_app = Fabricate(:app, :mac_key_id => "a:h480djs93hd8")
|
|
40
|
+
expect(authed_app.saved?).to be_true
|
|
41
|
+
env['HTTP_AUTHORIZATION'] = auth_header % 'a'
|
|
42
|
+
described_class.new(app).call(env)
|
|
43
|
+
expect(env.potential_auth).to eq(authed_app)
|
|
44
|
+
expect(env.hmac.secret).to eq(authed_app.mac_key)
|
|
45
|
+
expect(env.hmac.algorithm).to eq(authed_app.mac_algorithm)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'should lookup user authentication model' do
|
|
49
|
+
TentD::Model::AppAuthorization.all.destroy!
|
|
50
|
+
authed_user = TentD::Model::AppAuthorization.create(:mac_key_id => "u:h480djs93hd8",
|
|
51
|
+
:app => Fabricate(:app))
|
|
52
|
+
expect(authed_user.saved?).to be_true
|
|
53
|
+
env['HTTP_AUTHORIZATION'] = auth_header % 'u'
|
|
54
|
+
described_class.new(app).call(env)
|
|
55
|
+
expect(env.potential_auth).to eq(authed_user)
|
|
56
|
+
expect(env.hmac.secret).to eq(authed_user.mac_key)
|
|
57
|
+
expect(env.hmac.algorithm).to eq(authed_user.mac_algorithm)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'should do nothing unless HTTP_AUTHORIZATION header' do
|
|
61
|
+
env = {}
|
|
62
|
+
described_class.new(app).call(env)
|
|
63
|
+
expect(env['hmac']).to be_nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe TentD::API::AuthenticationVerification do
|
|
4
|
+
def app
|
|
5
|
+
TentD::API.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it 'should verify mac signature' do
|
|
9
|
+
env = Hashie::Mash.new({
|
|
10
|
+
'hmac' => {
|
|
11
|
+
"id" => "s:h480djs93hd8", "ts" => "1336363200", "nonce" => "dj83hs9s", "mac" => "hqpo01mLJLSYDbxmfRgNMEw38Wg=",
|
|
12
|
+
"secret" => '489dks293j39',
|
|
13
|
+
'algorithm' => 'hmac-sha-1',
|
|
14
|
+
},
|
|
15
|
+
'rack.input' => StringIO.new("asdf\nasdf"),
|
|
16
|
+
'REQUEST_METHOD' => 'POST',
|
|
17
|
+
'SCRIPT_NAME' => "/resource/1",
|
|
18
|
+
'QUERY_STRING' => "b=1&a=2",
|
|
19
|
+
'HTTP_HOST' => "example.com",
|
|
20
|
+
'SERVER_PORT' => "80"
|
|
21
|
+
})
|
|
22
|
+
described_class.new(app).call(env)
|
|
23
|
+
expect(env.hmac.verified).to be_true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'should respond 403 Unauthorized if signature fails verification' do
|
|
27
|
+
env = Hashie::Mash.new({
|
|
28
|
+
'hmac' => {
|
|
29
|
+
"id" => "s:h480djs93hd8", "ts" => "1336363200", "nonce" => "dj83hs9s", "mac" => "hqpo01mLJLSYDbxmfRgNMEw38Wg=",
|
|
30
|
+
"secret" => 'WRONG-KEY',
|
|
31
|
+
'algorithm' => 'hmac-sha-1',
|
|
32
|
+
},
|
|
33
|
+
'rack.input' => StringIO.new("asdf\nasdf"),
|
|
34
|
+
'REQUEST_METHOD' => 'POST',
|
|
35
|
+
'SCRIPT_NAME' => "/resource/1",
|
|
36
|
+
'QUERY_STRING' => "b=1&a=2",
|
|
37
|
+
'HTTP_HOST' => "example.com",
|
|
38
|
+
'SERVER_PORT' => "80"
|
|
39
|
+
})
|
|
40
|
+
env = described_class.new(app).call(env)
|
|
41
|
+
expect(env).to be_an(Array)
|
|
42
|
+
expect(env.first).to eq(403)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'should not do anything if no signature' do
|
|
46
|
+
env = Hashie::Mash.new({})
|
|
47
|
+
res = described_class.new(app).call(env)
|
|
48
|
+
expect(env.hmac).to be_nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'hashie'
|
|
3
|
+
|
|
4
|
+
describe TentD::API::Authorizable do
|
|
5
|
+
class TestMiddleware2
|
|
6
|
+
include TentD::API::Authorizable
|
|
7
|
+
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
authorize_env!(env, :read_posts)
|
|
14
|
+
@app.call(env)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class OtherTestMiddleware < TentD::API::Middleware
|
|
19
|
+
def action(env)
|
|
20
|
+
authorize_env!(env, :read_posts)
|
|
21
|
+
env
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def app
|
|
26
|
+
TentD::API.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
let(:env) { Hashie::Mash.new }
|
|
30
|
+
let(:middleware) { TestMiddleware2.new(app) }
|
|
31
|
+
|
|
32
|
+
describe '#authorize_env!(env, scope)' do
|
|
33
|
+
it 'should raise Unauthorized unless env.authorized_scopes includes scope' do
|
|
34
|
+
expect( lambda { middleware.call(env) } ).to raise_error(described_class::Unauthorized)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'should do nothing if env.authorized_scopes includes scope' do
|
|
38
|
+
env.authorized_scopes = [:read_posts]
|
|
39
|
+
expect( lambda { middleware.call(env) } ).to_not raise_error
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context 'when TentD::API::Middleware' do
|
|
43
|
+
it 'should respond 403 unless env.authorized_scopes includes scope' do
|
|
44
|
+
response = OtherTestMiddleware.new(app).call(env)
|
|
45
|
+
expect(response).to be_an(Array)
|
|
46
|
+
expect(response.first).to be(403)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'hashie'
|
|
3
|
+
|
|
4
|
+
describe TentD::API::Authorization do
|
|
5
|
+
def app
|
|
6
|
+
TentD::API.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
let(:env) { Hashie::Mash.new }
|
|
10
|
+
|
|
11
|
+
not_authorized = proc do
|
|
12
|
+
it 'should not authorize scopes' do
|
|
13
|
+
described_class.new(app).call(env)
|
|
14
|
+
expect(env.authorized_scopes).to eq([])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context 'without current_auth', ¬_authorized
|
|
19
|
+
|
|
20
|
+
context 'with current_auth' do
|
|
21
|
+
context 'when Follower' do
|
|
22
|
+
before do
|
|
23
|
+
env.current_auth = Fabricate(:follower)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context '', ¬_authorized
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'when AppAuthorization' do
|
|
30
|
+
before do
|
|
31
|
+
env.current_auth = Fabricate(
|
|
32
|
+
:app_authorization,
|
|
33
|
+
:scopes => ['read_posts', 'write_posts'],
|
|
34
|
+
:app => Fabricate(:app)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'should lookup authorized scopes' do
|
|
39
|
+
described_class.new(app).call(env)
|
|
40
|
+
expect(env.authorized_scopes).to eq([:read_posts, :write_posts])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe TentD::API::Router::CachingHeaders do
|
|
4
|
+
let(:app) { lambda { |env| [200, {}, ''] } }
|
|
5
|
+
let(:middleware) { TentD::API::Router::CachingHeaders.new(app) }
|
|
6
|
+
let(:env) { Hashie::Mash.new('REQUEST_METHOD' => 'GET') }
|
|
7
|
+
|
|
8
|
+
shared_examples 'conditional get' do
|
|
9
|
+
it 'should respond with a 304 when cached' do
|
|
10
|
+
status, headers, body = middleware.call env.merge('response' => response,
|
|
11
|
+
'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate)
|
|
12
|
+
expect(status).to eq(304)
|
|
13
|
+
expect(body).to be_nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should not 304 when not cached' do
|
|
17
|
+
status, headers, body = middleware.call env.merge('response' => response,
|
|
18
|
+
'HTTP_IF_MODIFIED_SINCE' => (Time.now - 60).httpdate)
|
|
19
|
+
expect(status).to eq(200)
|
|
20
|
+
expect(headers['Last-Modified']).to_not be_nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
shared_examples 'public response' do
|
|
25
|
+
it 'should set Cache-Control to public' do
|
|
26
|
+
expect(middleware.call(env.merge('response' => response))[1]['Cache-Control']).to eq('public')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
shared_examples 'private response' do
|
|
31
|
+
it 'should set Cache-Control to private' do
|
|
32
|
+
expect(middleware.call(env.merge('response' => response))[1]['Cache-Control']).to eq('private')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'object instance with #updated_at' do
|
|
37
|
+
let(:response) { stub(:updated_at => Time.now-1) }
|
|
38
|
+
it_behaves_like 'conditional get'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'hash with "updated_at"' do
|
|
42
|
+
let(:response) { { 'updated_at' => Time.now-1 } }
|
|
43
|
+
it_behaves_like 'conditional get'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'object array with #updated_at' do
|
|
47
|
+
let(:response) { [stub(:updated_at => Time.now-1), stub(:updated_at => Time.now-90)] }
|
|
48
|
+
it_behaves_like 'conditional get'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'hash array with "updated_at"' do
|
|
52
|
+
let(:response) { [{ "updated_at" => Time.now-1 }, { "updated_at" => Time.now-90 }] }
|
|
53
|
+
it_behaves_like 'conditional get'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "object that doesn't respond to #updated_at" do
|
|
57
|
+
it "should return without changes" do
|
|
58
|
+
expect(middleware.call(env.merge('response' => stub)).first).to eq(200)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "object that is #public" do
|
|
63
|
+
let(:response) { stub(:public => true) }
|
|
64
|
+
it_behaves_like 'public response'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "object that is not #public" do
|
|
68
|
+
let(:response) { stub(:public => false) }
|
|
69
|
+
it_behaves_like 'private response'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "array of #public objects" do
|
|
73
|
+
let(:response) { [stub(:public => true)] * 2 }
|
|
74
|
+
it_behaves_like 'public response'
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context "array of public and private objects" do
|
|
78
|
+
let(:response) { [stub(:public => true), stub(:public => false)] }
|
|
79
|
+
it_behaves_like 'private response'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context 'hash that is "public"' do
|
|
83
|
+
let(:response) { { "public" => true } }
|
|
84
|
+
it_behaves_like 'public response'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context 'hash that is not "public"' do
|
|
88
|
+
let(:response) { { "public" => false } }
|
|
89
|
+
it_behaves_like 'private response'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
context 'array of hashes that are "public"' do
|
|
93
|
+
let(:response) { [{ "public" => true }]*2 }
|
|
94
|
+
it_behaves_like 'public response'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context 'array of hashes that are not "public"' do
|
|
98
|
+
let(:response) { [{ "public" => false }]*2 }
|
|
99
|
+
it_behaves_like 'private response'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
context 'hash that has public set on permissions' do
|
|
103
|
+
let(:response) { { "permissions" => { "public" => true } } }
|
|
104
|
+
it_behaves_like 'public response'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context 'hash that has public set to false on permissions' do
|
|
108
|
+
let(:response) { { "permissions" => { "public" => false } } }
|
|
109
|
+
it_behaves_like 'private response'
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
context 'array of hashes that have public set on permissions' do
|
|
113
|
+
let(:response) { [{ "permissions" => { "public" => true } }]*2 }
|
|
114
|
+
it_behaves_like 'public response'
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context 'array of hashes that have public set to false on permissions' do
|
|
118
|
+
let(:response) { [{ "permissions" => { "public" => false } }]*2 }
|
|
119
|
+
it_behaves_like 'private response'
|
|
120
|
+
end
|
|
121
|
+
end
|