tentd 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module TentD
|
5
|
+
class API
|
6
|
+
class AuthenticationVerification < Middleware
|
7
|
+
def action(env)
|
8
|
+
if env.hmac? && !(env.hmac.verified = verify_signature(env))
|
9
|
+
env = [403, {}, ['Invalid MAC Signature']]
|
10
|
+
end
|
11
|
+
env
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# constant-time comparison algorithm to prevent timing attacks
|
17
|
+
def secure_compare(a, b)
|
18
|
+
return false unless a.bytesize == b.bytesize
|
19
|
+
|
20
|
+
l = a.unpack "C#{a.bytesize}"
|
21
|
+
|
22
|
+
res = 0
|
23
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
24
|
+
res == 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify_signature(env)
|
28
|
+
secure_compare(env.hmac.mac, build_request_signature(env))
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_request_signature(env)
|
32
|
+
time = env.hmac.ts.to_i
|
33
|
+
nonce = env.hmac.nonce
|
34
|
+
request_string = build_request_string(time, nonce, env)
|
35
|
+
signature = Base64.encode64(OpenSSL::HMAC.digest(openssl_digest(env.hmac.algorithm).new, env.hmac.secret, request_string)).sub("\n", '')
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_request_string(time, nonce, env)
|
39
|
+
body = env['rack.input'].read
|
40
|
+
env['rack.input'].rewind
|
41
|
+
request_uri = env.SCRIPT_NAME + (env.QUERY_STRING != '' ? "?#{env.QUERY_STRING}" : '')
|
42
|
+
[time.to_s, nonce, env.REQUEST_METHOD.to_s.upcase, request_uri, env.HTTP_HOST.split(':').first, (env.HTTP_X_FORWARDED_PORT || env.SERVER_PORT), body, nil].join("\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
def openssl_digest(mac_algorithm)
|
46
|
+
@openssl_digest ||= OpenSSL::Digest.const_get(mac_algorithm.to_s.gsub(/hmac|-/, '').upcase)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module TentD
|
2
|
+
class API
|
3
|
+
module Authorizable
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class Unauthorized < Error
|
8
|
+
end
|
9
|
+
|
10
|
+
def authorize_env!(env, scope)
|
11
|
+
unless authorize_env?(env, scope)
|
12
|
+
raise Unauthorized
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def authorize_env?(env, scope)
|
17
|
+
env.authorized_scopes.to_a.include?(scope)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module TentD
|
2
|
+
class API
|
3
|
+
class Authorization < Middleware
|
4
|
+
def action(env)
|
5
|
+
env.authorized_scopes = []
|
6
|
+
if env.current_auth.kind_of?(Model::AppAuthorization)
|
7
|
+
env.authorized_scopes = env.current_auth.scopes.map(&:to_sym)
|
8
|
+
env.authorized_scopes.delete(:read_secrets) unless env.params && env.params.secrets.to_s == 'true'
|
9
|
+
end
|
10
|
+
env
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
module TentD
|
4
|
+
class API
|
5
|
+
class CoreProfileData < Hashie::Mash
|
6
|
+
def expected_version
|
7
|
+
v = TentVersion.from_uri(Model::ProfileInfo::TENT_PROFILE_TYPE_URI)
|
8
|
+
v.parts = v.parts[0..-2] << 'x'
|
9
|
+
v
|
10
|
+
end
|
11
|
+
|
12
|
+
def versions
|
13
|
+
keys.select { |key|
|
14
|
+
key =~ %r|^#{Model::ProfileInfo::TENT_PROFILE_TYPE_URI.sub(/\/v.*?$/, '')}|
|
15
|
+
}.map { |key| TentVersion.from_uri(key) }.sort
|
16
|
+
end
|
17
|
+
|
18
|
+
def version
|
19
|
+
versions.find do |v|
|
20
|
+
v == expected_version
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def version_key
|
25
|
+
Model::ProfileInfo::TENT_PROFILE_TYPE_URI.sub(%r|/v.*?$|, '/v' << version.to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
def entity?(entity)
|
29
|
+
self[version_key][:entity] == entity
|
30
|
+
end
|
31
|
+
|
32
|
+
def servers
|
33
|
+
self[version_key][:servers]
|
34
|
+
end
|
35
|
+
|
36
|
+
def entity
|
37
|
+
self[version_key][:entity]
|
38
|
+
end
|
39
|
+
|
40
|
+
def licenses
|
41
|
+
self[version_key][:licenses]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
module TentD
|
2
|
+
class API
|
3
|
+
class Followers
|
4
|
+
include Router
|
5
|
+
|
6
|
+
class GetActualId < Middleware
|
7
|
+
def action(env)
|
8
|
+
[:follower_id, :before_id, :since_id].each do |id_key|
|
9
|
+
if env.params[id_key] && (f = Model::Follower.first(:public_id => env.params[id_key], :fields => [:id]))
|
10
|
+
env.params[id_key] = f.id
|
11
|
+
else
|
12
|
+
env.params[id_key] = nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
env
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class AuthorizeReadOne < Middleware
|
20
|
+
def action(env)
|
21
|
+
if env.params.follower_id && env.current_auth && env.current_auth.kind_of?(Model::Follower) &&
|
22
|
+
env.current_auth.id == env.params.follower_id
|
23
|
+
env.authorized_scopes << :self
|
24
|
+
end
|
25
|
+
env.full_read_authorized = authorize_env?(env, :read_followers)
|
26
|
+
env
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class AuthorizeReadMany < Middleware
|
31
|
+
def action(env)
|
32
|
+
env.full_read_authorized = authorize_env?(env, :read_followers)
|
33
|
+
env
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class AuthorizeWriteOne < Middleware
|
38
|
+
def action(env)
|
39
|
+
unless env.params.follower_id && env.current_auth && env.current_auth.kind_of?(Model::Follower) &&
|
40
|
+
env.current_auth.id == env.params.follower_id
|
41
|
+
authorize_env!(env, :write_followers)
|
42
|
+
end
|
43
|
+
env.authorized_scopes << :self
|
44
|
+
env
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Discover < Middleware
|
49
|
+
def action(env)
|
50
|
+
return env if env.authorized_scopes.include?(:write_followers)
|
51
|
+
return [422, {}, ['Invalid notification path']] unless env.params.data.notification_path.kind_of?(String) &&
|
52
|
+
!env.params.data.notification_path.match(%r{\Ahttps?://})
|
53
|
+
return [406, {}, ['Can not follow self']] if Model::User.current.profile_entity == env.params.data.entity
|
54
|
+
client = ::TentClient.new(nil, :faraday_adapter => TentD.faraday_adapter)
|
55
|
+
profile, profile_url = client.discover(env.params[:data]['entity']).get_profile
|
56
|
+
return [404, {}, ['Not Found']] unless profile
|
57
|
+
|
58
|
+
profile = CoreProfileData.new(profile)
|
59
|
+
return [409, {}, ['Entity Mismatch']] unless profile.entity?(env.params.data.entity)
|
60
|
+
env['profile'] = profile
|
61
|
+
env
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Confirm < Middleware
|
66
|
+
def action(env)
|
67
|
+
return env if env.authorized_scopes.include?(:write_followers)
|
68
|
+
client = TentClient.new(env.profile.servers, :faraday_adapter => TentD.faraday_adapter)
|
69
|
+
if client.follower.challenge(env.params.data.notification_path)
|
70
|
+
env
|
71
|
+
else
|
72
|
+
[403, {}, ['Unauthorized Follower']]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Create < Middleware
|
78
|
+
def action(env)
|
79
|
+
return env if env.authorized_scopes.include?(:write_followers)
|
80
|
+
if follower = Model::Follower.create_follower(env.params[:data].merge('profile' => env['profile']))
|
81
|
+
env.authorized_scopes << :read_secrets
|
82
|
+
env.authorized_scopes << :self
|
83
|
+
env.notify_action = 'create'
|
84
|
+
env.notify_instance = follower
|
85
|
+
env.response = follower
|
86
|
+
end
|
87
|
+
env
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Import < Middleware
|
92
|
+
def action(env)
|
93
|
+
return env unless env.authorized_scopes.include?(:write_followers)
|
94
|
+
if env.authorized_scopes.include?(:write_secrets)
|
95
|
+
if follower = Model::Follower.create_follower(env.params.data, env.authorized_scopes)
|
96
|
+
env.response = ''
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise Unauthorized
|
100
|
+
end
|
101
|
+
env
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class GetOne < Middleware
|
106
|
+
def action(env)
|
107
|
+
if env.full_read_authorized || authorize_env?(env, :self)
|
108
|
+
follower = Model::Follower.find(env.params.follower_id)
|
109
|
+
else
|
110
|
+
follower = Model::Follower.find_with_permissions(env.params.follower_id, env.current_auth)
|
111
|
+
end
|
112
|
+
|
113
|
+
if env.full_read_authorized || authorize_env?(env, :self) || (follower && follower.public?)
|
114
|
+
env.response = follower
|
115
|
+
else
|
116
|
+
raise Unauthorized
|
117
|
+
end
|
118
|
+
|
119
|
+
env
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class GetCount < Middleware
|
124
|
+
def action(env)
|
125
|
+
env.params.return_count = true
|
126
|
+
env
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class GetMany < Middleware
|
131
|
+
def action(env)
|
132
|
+
if env.full_read_authorized
|
133
|
+
followers = Model::Follower.fetch_all(env.params)
|
134
|
+
else
|
135
|
+
followers = Model::Follower.fetch_with_permissions(env.params, env.current_auth)
|
136
|
+
end
|
137
|
+
env.response = followers if followers
|
138
|
+
env
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Update < Middleware
|
143
|
+
def action(env)
|
144
|
+
env.response = Model::Follower.update_follower(env.params[:follower_id], env.params[:data], env.authorized_scopes)
|
145
|
+
env
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Destroy < Middleware
|
150
|
+
def action(env)
|
151
|
+
if (follower = Model::Follower.first(:id => env.params[:follower_id])) && follower.destroy
|
152
|
+
env.notify_action = 'delete'
|
153
|
+
env.notify_instance = follower
|
154
|
+
env.response = ''
|
155
|
+
end
|
156
|
+
env
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Notify < Middleware
|
161
|
+
def action(env)
|
162
|
+
return env unless follower = env.notify_instance
|
163
|
+
post = Model::Post.create(
|
164
|
+
:type => 'https://tent.io/types/post/follower/v0.1.0',
|
165
|
+
:entity => env['tent.entity'],
|
166
|
+
:content => {
|
167
|
+
:id => follower.public_id,
|
168
|
+
:entity => follower.entity,
|
169
|
+
:action => env.notify_action
|
170
|
+
}
|
171
|
+
)
|
172
|
+
Notifications.trigger(:type => post.type.uri, :post_id => post.id)
|
173
|
+
env
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
post '/followers' do |b|
|
178
|
+
b.use Discover
|
179
|
+
b.use Confirm
|
180
|
+
b.use Create
|
181
|
+
b.use Import
|
182
|
+
b.use Notify
|
183
|
+
end
|
184
|
+
|
185
|
+
get '/followers/count' do |b|
|
186
|
+
b.use AuthorizeReadMany
|
187
|
+
b.use GetActualId
|
188
|
+
b.use GetCount
|
189
|
+
b.use GetMany
|
190
|
+
end
|
191
|
+
|
192
|
+
get '/followers/:follower_id' do |b|
|
193
|
+
b.use GetActualId
|
194
|
+
b.use AuthorizeReadOne
|
195
|
+
b.use GetOne
|
196
|
+
end
|
197
|
+
|
198
|
+
get '/followers' do |b|
|
199
|
+
b.use AuthorizeReadMany
|
200
|
+
b.use GetActualId
|
201
|
+
b.use GetMany
|
202
|
+
end
|
203
|
+
|
204
|
+
put '/followers/:follower_id' do |b|
|
205
|
+
b.use GetActualId
|
206
|
+
b.use AuthorizeWriteOne
|
207
|
+
b.use Update
|
208
|
+
end
|
209
|
+
|
210
|
+
delete '/followers/:follower_id' do |b|
|
211
|
+
b.use GetActualId
|
212
|
+
b.use AuthorizeWriteOne
|
213
|
+
b.use Destroy
|
214
|
+
b.use Notify
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
module TentD
|
2
|
+
class API
|
3
|
+
class Followings
|
4
|
+
include Router
|
5
|
+
|
6
|
+
class GetActualId < Middleware
|
7
|
+
def action(env)
|
8
|
+
id_mapping = [:following_id, :since_id, :before_id].select { |key| env.params.has_key?(key) }.inject({}) { |memo, key|
|
9
|
+
memo[env.params[key]] = key
|
10
|
+
env.params[key] = nil
|
11
|
+
memo
|
12
|
+
}
|
13
|
+
followings = Model::Following.all(:public_id => id_mapping.keys, :fields => [:id, :public_id])
|
14
|
+
followings.each do |following|
|
15
|
+
key = id_mapping[following.public_id]
|
16
|
+
env.params[key] = following.id
|
17
|
+
end
|
18
|
+
env
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class AuthorizeWrite < Middleware
|
23
|
+
def action(env)
|
24
|
+
authorize_env!(env, :write_followings)
|
25
|
+
env
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class GetOne < Middleware
|
30
|
+
def action(env)
|
31
|
+
if authorize_env?(env, :read_followings)
|
32
|
+
if following = Model::Following.first(:id => env.params.following_id, :confirmed => true)
|
33
|
+
env.following = following
|
34
|
+
env.response = following
|
35
|
+
end
|
36
|
+
else
|
37
|
+
following = Model::Following.find_with_permissions(env.params.following_id, env.current_auth) { |p,q,b| q << 'AND followings.confirmed = true' }
|
38
|
+
if following
|
39
|
+
env.response = following
|
40
|
+
else
|
41
|
+
raise Unauthorized
|
42
|
+
end
|
43
|
+
end
|
44
|
+
env
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class GetCount < Middleware
|
49
|
+
def action(env)
|
50
|
+
env.params.return_count = true
|
51
|
+
env
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class GetMany < Middleware
|
56
|
+
def action(env)
|
57
|
+
if authorize_env?(env, :read_followings)
|
58
|
+
env.response = Model::Following.fetch_all(env.params) { |p,q,b| q << 'followings.confirmed = true' }
|
59
|
+
else
|
60
|
+
env.response = Model::Following.fetch_with_permissions(env.params, env.current_auth) { |p,q,b| q << 'AND followings.confirmed = true' }
|
61
|
+
end
|
62
|
+
env
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Discover < Middleware
|
67
|
+
def action(env)
|
68
|
+
client = ::TentClient.new(nil, :faraday_adapter => TentD.faraday_adapter)
|
69
|
+
profile, profile_url = client.discover(env.params.data.entity).get_profile
|
70
|
+
return [404, {}, ['Not Found']] unless profile
|
71
|
+
|
72
|
+
profile = CoreProfileData.new(profile)
|
73
|
+
return [409, {}, ['Entity Mismatch']] unless profile.entity?(env.params.data.entity)
|
74
|
+
env.profile = profile
|
75
|
+
env.server_url = profile_url.sub(%r{/profile$}, '')
|
76
|
+
env
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Follow < Middleware
|
81
|
+
def action(env)
|
82
|
+
env.following = Model::Following.create(:entity => env.params.data.entity,
|
83
|
+
:groups => env.params.data.groups.to_a.map { |g| g['id'] },
|
84
|
+
:confirmed => false)
|
85
|
+
client = ::TentClient.new(env.server_url, :faraday_adapter => TentD.faraday_adapter)
|
86
|
+
res = client.follower.create(
|
87
|
+
:entity => env['tent.entity'],
|
88
|
+
:licenses => Model::ProfileInfo.tent_info.content['licenses'],
|
89
|
+
:notification_path => "notifications/#{env.following.public_id}"
|
90
|
+
)
|
91
|
+
case res.status
|
92
|
+
when 200...300
|
93
|
+
env.follow_data = res.body
|
94
|
+
else
|
95
|
+
return [res.status, res.headers, res.body]
|
96
|
+
end
|
97
|
+
env
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Create < Middleware
|
102
|
+
def action(env)
|
103
|
+
if env.following.confirm_from_params(env.follow_data.merge(env.params.data).merge(:profile => env.profile))
|
104
|
+
env.response = env.following
|
105
|
+
env.notify_action = 'create'
|
106
|
+
env.notify_instance = env.following
|
107
|
+
end
|
108
|
+
env
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Update < Middleware
|
113
|
+
def action(env)
|
114
|
+
if following = Model::Following.first(:id => env.params.following_id)
|
115
|
+
following.update_from_params(env.params.data, env.authorized_scopes)
|
116
|
+
env.response = following
|
117
|
+
end
|
118
|
+
env
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Destroy < Middleware
|
123
|
+
def action(env)
|
124
|
+
if (following = Model::Following.first(:id => env.params.following_id))
|
125
|
+
client = ::TentClient.new(following.core_profile.servers, following.auth_details.merge(:faraday_adapter => TentD.faraday_adapter))
|
126
|
+
res = client.follower.delete(following.remote_id)
|
127
|
+
following.destroy
|
128
|
+
if (200...300).to_a.include?(res.status)
|
129
|
+
env.response = ''
|
130
|
+
env.notify_action = 'delete'
|
131
|
+
env.notify_instance = following
|
132
|
+
end
|
133
|
+
end
|
134
|
+
env
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class ProxyRequest < Middleware
|
139
|
+
def action(env)
|
140
|
+
following = env.following
|
141
|
+
client = TentClient.new(following.core_profile.servers.first,
|
142
|
+
following.auth_details.merge(:skip_serialization => true,
|
143
|
+
:faraday_adapter => TentD.faraday_adapter))
|
144
|
+
env.params.delete(:following_id)
|
145
|
+
path = env.params.delete(:proxy_path)
|
146
|
+
res = client.http.get(path, env.params, whitelisted_headers(env))
|
147
|
+
[res.status, res.headers, [res.body]]
|
148
|
+
end
|
149
|
+
|
150
|
+
def whitelisted_headers(env)
|
151
|
+
%w(Accept If-Modified-Since).inject({}) do |h,k|
|
152
|
+
h[k] = env['HTTP_' + k.gsub('-', '_').upcase]; h
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class RewriteProxyCaptureParams < Middleware
|
158
|
+
def action(env)
|
159
|
+
matches = env.params.delete(:captures)
|
160
|
+
env.params.following_id = matches.first
|
161
|
+
env.params.proxy_path = '/' + matches.last
|
162
|
+
env
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Notify < Middleware
|
167
|
+
def action(env)
|
168
|
+
return env unless following = env.notify_instance
|
169
|
+
post = Model::Post.create(
|
170
|
+
:type => 'https://tent.io/types/post/following/v0.1.0',
|
171
|
+
:entity => env['tent.entity'],
|
172
|
+
:content => {
|
173
|
+
:id => following.public_id,
|
174
|
+
:entity => following.entity,
|
175
|
+
:action => env.notify_action
|
176
|
+
}
|
177
|
+
)
|
178
|
+
Notifications.trigger(:type => post.type.uri, :post_id => post.id)
|
179
|
+
env
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class RedirectToFollowUI < Middleware
|
184
|
+
def action(env)
|
185
|
+
if follow_url = Model::AppAuthorization.follow_url(env.params.entity)
|
186
|
+
return [302, { "Location" => follow_url }, []]
|
187
|
+
end
|
188
|
+
env
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
get '/follow' do |b|
|
193
|
+
b.use RedirectToFollowUI
|
194
|
+
end
|
195
|
+
|
196
|
+
get '/followings/count' do |b|
|
197
|
+
b.use GetActualId
|
198
|
+
b.use GetCount
|
199
|
+
b.use GetMany
|
200
|
+
end
|
201
|
+
|
202
|
+
get '/followings/:following_id' do |b|
|
203
|
+
b.use GetActualId
|
204
|
+
b.use GetOne
|
205
|
+
end
|
206
|
+
|
207
|
+
get '/followings' do |b|
|
208
|
+
b.use GetActualId
|
209
|
+
b.use GetMany
|
210
|
+
end
|
211
|
+
|
212
|
+
get %r{/followings/(\w+)/(.+)} do |b|
|
213
|
+
b.use RewriteProxyCaptureParams
|
214
|
+
b.use GetActualId
|
215
|
+
b.use GetOne
|
216
|
+
b.use ProxyRequest
|
217
|
+
end
|
218
|
+
|
219
|
+
post '/followings' do |b|
|
220
|
+
b.use AuthorizeWrite
|
221
|
+
b.use Discover
|
222
|
+
b.use Follow
|
223
|
+
b.use Create
|
224
|
+
b.use Notify
|
225
|
+
end
|
226
|
+
|
227
|
+
put '/followings/:following_id' do |b|
|
228
|
+
b.use AuthorizeWrite
|
229
|
+
b.use GetActualId
|
230
|
+
b.use Update
|
231
|
+
end
|
232
|
+
|
233
|
+
delete '/followings/:following_id' do |b|
|
234
|
+
b.use AuthorizeWrite
|
235
|
+
b.use GetActualId
|
236
|
+
b.use Destroy
|
237
|
+
b.use Notify
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|