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
|
@@ -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
|