tentd-admin 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 +2 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +201 -0
- data/LICENSE.txt +22 -0
- data/Procfile +1 -0
- data/README.md +104 -0
- data/Rakefile +30 -0
- data/assets/images/chosen-sprite.png +0 -0
- data/assets/images/glyphicons-halflings-white.png +0 -0
- data/assets/images/glyphicons-halflings.png +0 -0
- data/assets/javascripts/application.js.coffee +24 -0
- data/assets/javascripts/bootstrap.js +2027 -0
- data/assets/javascripts/chosen.jquery.js +1129 -0
- data/assets/javascripts/jquery.js +9301 -0
- data/assets/stylesheets/application.css.sass +34 -0
- data/assets/stylesheets/bootstrap-responsive.css +1040 -0
- data/assets/stylesheets/bootstrap.css +5624 -0
- data/assets/stylesheets/chosen.css.erb +397 -0
- data/config.ru +36 -0
- data/config/asset_sync.rb +12 -0
- data/lib/tentd-admin.rb +7 -0
- data/lib/tentd-admin/app.rb +242 -0
- data/lib/tentd-admin/set_entity.rb +10 -0
- data/lib/tentd-admin/setup_tent.rb +54 -0
- data/lib/tentd-admin/sprockets/environment.rb +23 -0
- data/lib/tentd-admin/sprockets/helpers.rb +5 -0
- data/lib/tentd-admin/version.rb +5 -0
- data/lib/tentd-admin/views/_profile_type_fields.slim +20 -0
- data/lib/tentd-admin/views/_profile_type_section.slim +4 -0
- data/lib/tentd-admin/views/apps.slim +39 -0
- data/lib/tentd-admin/views/auth_confirm.slim +34 -0
- data/lib/tentd-admin/views/dashboard.slim +53 -0
- data/lib/tentd-admin/views/followers.slim +12 -0
- data/lib/tentd-admin/views/followings.slim +22 -0
- data/lib/tentd-admin/views/layout.slim +15 -0
- data/lib/tentd-admin/views/profile.slim +11 -0
- data/lib/tentd-admin/views/setup.slim +20 -0
- data/tentd-admin.gemspec +32 -0
- metadata +252 -0
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'sprockets'
|
4
|
+
require 'hashie'
|
5
|
+
require 'tentd'
|
6
|
+
require 'tent-client'
|
7
|
+
require 'rack/csrf'
|
8
|
+
require 'slim'
|
9
|
+
require 'coffee_script'
|
10
|
+
require 'sass'
|
11
|
+
require 'oj'
|
12
|
+
|
13
|
+
module TentD
|
14
|
+
class Admin < Sinatra::Base
|
15
|
+
require 'tentd-admin/sprockets/environment'
|
16
|
+
|
17
|
+
configure do
|
18
|
+
set :asset_manifest, Oj.load(File.read(ENV['ADMIN_ASSET_MANIFEST'])) if ENV['ADMIN_ASSET_MANIFEST']
|
19
|
+
set :cdn_url, ENV['ADMIN_CDN_URL']
|
20
|
+
|
21
|
+
set :method_override, true
|
22
|
+
end
|
23
|
+
|
24
|
+
use Rack::Csrf
|
25
|
+
|
26
|
+
include SprocketsEnvironment
|
27
|
+
|
28
|
+
helpers do
|
29
|
+
def path_prefix
|
30
|
+
env['SCRIPT_NAME']
|
31
|
+
end
|
32
|
+
|
33
|
+
def asset_path(path)
|
34
|
+
path = asset_manifest_path(path) || assets.find_asset(path).digest_path
|
35
|
+
if settings.cdn_url?
|
36
|
+
"#{settings.cdn_url}/assets/#{path}"
|
37
|
+
else
|
38
|
+
full_path("/assets/#{path}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def asset_manifest_path(asset)
|
43
|
+
if settings.respond_to?(:asset_manifest?) && settings.asset_manifest?
|
44
|
+
settings.asset_manifest['files'].detect { |k,v| v['logical_path'] == asset }[0]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def full_path(path)
|
49
|
+
"#{path_prefix}/#{path}".gsub(%r{//}, '/')
|
50
|
+
end
|
51
|
+
|
52
|
+
def csrf_tag
|
53
|
+
Rack::Csrf.tag(env)
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_override(method)
|
57
|
+
"<input type='hidden' name='_method' value='#{method}' />"
|
58
|
+
end
|
59
|
+
|
60
|
+
def scope_name(scope)
|
61
|
+
{
|
62
|
+
:read_posts => "Read Posts",
|
63
|
+
:write_posts => "Write Posts",
|
64
|
+
:import_posts => "Import Posts",
|
65
|
+
:read_profile => "Read Profile",
|
66
|
+
:write_profile => "Write Profile",
|
67
|
+
:read_followers => "Read Followers",
|
68
|
+
:write_followers => "Write Followers",
|
69
|
+
:read_followings => "Read Followings",
|
70
|
+
:write_followings => "Write Followings",
|
71
|
+
:read_groups => "Read Groups",
|
72
|
+
:write_groups => "Write Groups",
|
73
|
+
:read_permissions => "Read Permissions",
|
74
|
+
:write_permissions => "Write Permissions",
|
75
|
+
:read_apps => "Read Apps",
|
76
|
+
:write_apps => "Write Apps",
|
77
|
+
:follow_ui => "Follow UI",
|
78
|
+
:read_secrets => "Read Secrets",
|
79
|
+
:write_secrets => "Write Secrets"
|
80
|
+
}[scope.to_sym]
|
81
|
+
end
|
82
|
+
|
83
|
+
def nav_active_class(path)
|
84
|
+
env['PATH_INFO'] == path ? 'active' : ''
|
85
|
+
end
|
86
|
+
|
87
|
+
def server_url
|
88
|
+
env['rack.url_scheme'] + "://" + env['HTTP_HOST']
|
89
|
+
end
|
90
|
+
|
91
|
+
def tent_client
|
92
|
+
env['tent.client']
|
93
|
+
end
|
94
|
+
|
95
|
+
def current_user
|
96
|
+
return unless defined?(TentD)
|
97
|
+
current = TentD::Model::User.current
|
98
|
+
current if session[:current_user_id] == current.id
|
99
|
+
end
|
100
|
+
|
101
|
+
def authenticate!
|
102
|
+
halt 403 unless current_user
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if ENV['RACK_ENV'] != 'production' || ENV['SERVE_ASSETS']
|
107
|
+
get '/assets/*' do
|
108
|
+
new_env = env.clone
|
109
|
+
new_env["PATH_INFO"].gsub!("/assets", "")
|
110
|
+
assets.call(new_env)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
get '/' do
|
115
|
+
authenticate!
|
116
|
+
@profile = tent_client.profile.get.body
|
117
|
+
@profile['https://tent.io/types/info/basic/v0.1.0'] ||= {
|
118
|
+
'public' => true,
|
119
|
+
'name' => '',
|
120
|
+
'avatar_url' => '',
|
121
|
+
'birthdate' => '',
|
122
|
+
'location' => '',
|
123
|
+
'gender' => '',
|
124
|
+
'bio' => ''
|
125
|
+
}
|
126
|
+
|
127
|
+
@apps = tent_client.app.list.body
|
128
|
+
@apps.kind_of?(Array) ? @apps.map! { |a| Hashie::Mash.new(a) } : @apps = []
|
129
|
+
@apps = @apps.sort_by { |a| -a.authorizations.size }
|
130
|
+
|
131
|
+
slim :dashboard
|
132
|
+
end
|
133
|
+
|
134
|
+
get '/signout' do
|
135
|
+
session.clear
|
136
|
+
redirect full_path('/')
|
137
|
+
end
|
138
|
+
|
139
|
+
put '/profile' do
|
140
|
+
authenticate!
|
141
|
+
params.each_pair do |key, val|
|
142
|
+
next unless key =~ %r{tent.io/types/info}
|
143
|
+
tent_client.profile.update(key, val)
|
144
|
+
end
|
145
|
+
|
146
|
+
redirect full_path('/')
|
147
|
+
end
|
148
|
+
|
149
|
+
delete '/apps/:app_id' do
|
150
|
+
authenticate!
|
151
|
+
tent_client.app.delete(params[:app_id])
|
152
|
+
redirect full_path('/')
|
153
|
+
end
|
154
|
+
|
155
|
+
delete '/apps/:app_id/authorizations/:app_auth_id' do
|
156
|
+
authenticate!
|
157
|
+
tent_client.app.authorization.delete(params[:app_id], params[:app_auth_id])
|
158
|
+
redirect full_path('/')
|
159
|
+
end
|
160
|
+
|
161
|
+
get '/oauth/confirm' do
|
162
|
+
authenticate!
|
163
|
+
@app_params = %w{ client_id redirect_uri state scope tent_profile_info_types tent_post_types tent_notification_url }.inject({}) { |memo, k|
|
164
|
+
memo[k] = params[k] if params.has_key?(k)
|
165
|
+
memo
|
166
|
+
}
|
167
|
+
session[:current_app_params] = @app_params
|
168
|
+
@app_params = Hashie::Mash.new(@app_params)
|
169
|
+
|
170
|
+
@app = session[:current_app] = tent_client.app.get(@app_params.client_id).body
|
171
|
+
@app = @app.kind_of?(Hash) ? Hashie::Mash.new(@app) : @app
|
172
|
+
|
173
|
+
@app ||= "Invalid client_id"
|
174
|
+
|
175
|
+
redirect_uri = URI(@app_params.redirect_uri.to_s)
|
176
|
+
redirect_uri.query ||= ""
|
177
|
+
if @app.kind_of?(String)
|
178
|
+
redirect_uri.query += "error=#{URI.encode(@app)}"
|
179
|
+
redirect redirect_uri.to_s
|
180
|
+
return
|
181
|
+
end
|
182
|
+
|
183
|
+
unless @app.redirect_uris.to_a.include?(@app_params.redirect_uri)
|
184
|
+
redirect_uri.query += 'error=invalid_redirect_uri'
|
185
|
+
redirect redirect_uri.to_s
|
186
|
+
return
|
187
|
+
end
|
188
|
+
|
189
|
+
# TODO: allow updating tent_profile_info_types, tent_post_types, scopes
|
190
|
+
# (user must confirm adding anything)
|
191
|
+
|
192
|
+
if @app.authorizations.any?
|
193
|
+
authorization = @app.authorizations.first
|
194
|
+
|
195
|
+
unless authorization.notification_url == @app_params.tent_notification_url
|
196
|
+
tent_client.app.authorization.update(@app.id, authorization.id, :notification_url => @app_params.notification_url)
|
197
|
+
end
|
198
|
+
|
199
|
+
redirect_uri.query +="&code=#{authorization.token_code}"
|
200
|
+
redirect_uri.query += "&state=#{@app_params.state}" if @app_params.has_key?(:state)
|
201
|
+
redirect redirect_uri.to_s
|
202
|
+
return
|
203
|
+
end
|
204
|
+
|
205
|
+
slim :auth_confirm
|
206
|
+
end
|
207
|
+
|
208
|
+
post '/oauth/confirm' do
|
209
|
+
authenticate!
|
210
|
+
@app = Hashie::Mash.new(session.delete(:current_app))
|
211
|
+
@app_params = Hashie::Mash.new(session.delete(:current_app_params))
|
212
|
+
|
213
|
+
redirect_uri = URI(@app_params.redirect_uri.to_s)
|
214
|
+
redirect_uri.query ||= ""
|
215
|
+
|
216
|
+
if params[:commit] == 'Abort'
|
217
|
+
redirect_uri.query += "error=user_abort"
|
218
|
+
redirect redirect_uri.to_s
|
219
|
+
return
|
220
|
+
end
|
221
|
+
|
222
|
+
data = {
|
223
|
+
:scopes => (@app.scopes || {}).inject([]) { |memo, (k,v)|
|
224
|
+
params[k] == 'on' ? memo << k : nil
|
225
|
+
memo
|
226
|
+
},
|
227
|
+
:profile_info_types => @app_params.tent_profile_info_types.to_s.split(',').select { |type|
|
228
|
+
params[type] == 'on'
|
229
|
+
},
|
230
|
+
:post_types => @app_params.tent_post_types.to_s.split(',').select { |type|
|
231
|
+
params[type] == 'on'
|
232
|
+
},
|
233
|
+
:notification_url => @app_params.tent_notification_url
|
234
|
+
}
|
235
|
+
authorization = Hashie::Mash.new(tent_client.app.authorization.create(@app.id, data).body)
|
236
|
+
|
237
|
+
redirect_uri.query +="&code=#{authorization.token_code}"
|
238
|
+
redirect_uri.query += "&state=#{@app_params.state}" if @app_params.has_key?(:state)
|
239
|
+
redirect redirect_uri.to_s
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class SetupTent
|
2
|
+
def initialize(app)
|
3
|
+
@app = app
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(env)
|
7
|
+
TentD::Model::User.current = user = TentD::Model::User.first_or_create
|
8
|
+
env['rack.session']['current_user_id'] = user.id
|
9
|
+
set_client(env)
|
10
|
+
create_core_profile(env)
|
11
|
+
@app.call(env)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def set_client(env)
|
17
|
+
app = TentD::Model::App.first(:url => 'http://tent-admin') || create_app(env)
|
18
|
+
auth = app.authorizations.first
|
19
|
+
env['tent.app'] = app
|
20
|
+
env['tent.app_auth'] = auth
|
21
|
+
env['tent.client'] = TentClient.new(server_url(env), auth.auth_details)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_app(env)
|
25
|
+
app = TentD::Model::App.create(
|
26
|
+
:name => "Tent Admin",
|
27
|
+
:description => "Tent Admin App",
|
28
|
+
:url => 'http://tent-admin'
|
29
|
+
)
|
30
|
+
app.authorizations.create(
|
31
|
+
:scopes => %w(read_posts write_posts import_posts read_profile write_profile read_followers write_followers read_followings write_followings read_groups write_groups read_permissions write_permissions read_apps write_apps follow_ui read_secrets write_secrets),
|
32
|
+
:profile_info_types => ['all'],
|
33
|
+
:post_types => ['all'],
|
34
|
+
:follow_url => "#{server_url(env)}/admin/followings"
|
35
|
+
)
|
36
|
+
app
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_core_profile(env)
|
40
|
+
TentD::Model::ProfileInfo.first_or_create({ :type_base => 'https://tent.io/types/info/core' },
|
41
|
+
:content => {
|
42
|
+
:entity => server_url(env),
|
43
|
+
:licenses => [],
|
44
|
+
:servers => [server_url(env)]
|
45
|
+
},
|
46
|
+
:type_version => '0.1.0',
|
47
|
+
:public => true
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def server_url(env)
|
52
|
+
(env['HTTP_X_FORWARDED_PROTO'] || env['rack.url_scheme']) + '://' + env['HTTP_HOST']
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'tentd-admin/sprockets/helpers'
|
2
|
+
|
3
|
+
module TentD
|
4
|
+
class Admin
|
5
|
+
module SprocketsEnvironment
|
6
|
+
def assets
|
7
|
+
return @assets if defined?(@assets)
|
8
|
+
@assets = Sprockets::Environment.new do |env|
|
9
|
+
env.logger = Logger.new(STDOUT)
|
10
|
+
env.context_class.class_eval do
|
11
|
+
include SprocketsHelpers
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
paths = %w{ javascripts stylesheets images }
|
16
|
+
paths.each do |path|
|
17
|
+
@assets.append_path((File.join(File.expand_path('../../../../', __FILE__), "assets/#{path}")))
|
18
|
+
end
|
19
|
+
@assets
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
- input_name = "#{type}[#{name}]"
|
2
|
+
- if value.kind_of? Array
|
3
|
+
- input_name << '[]'
|
4
|
+
label.control-label for=input_name = name
|
5
|
+
.controls
|
6
|
+
select.input.input-xxlarge name=input_name multiple=true
|
7
|
+
- value.each do |option|
|
8
|
+
option value=option selected=true = option
|
9
|
+
- elsif value.kind_of?(Hash)
|
10
|
+
- value.each_pair do |name, value|
|
11
|
+
.control-group.span8
|
12
|
+
legend = name
|
13
|
+
== slim :_profile_type_fields, :locals => { :type => type, :name => name, :value => value }
|
14
|
+
- elsif value.kind_of?(TrueClass) || value.kind_of?(FalseClass)
|
15
|
+
label.checkbox
|
16
|
+
input type='checkbox' name=input_name value='true' checked=value = name
|
17
|
+
- else
|
18
|
+
label.control-label for=input_name = name
|
19
|
+
.controls
|
20
|
+
input.input.input-xxlarge type='text' name=input_name value=value
|
@@ -0,0 +1,39 @@
|
|
1
|
+
h2.page-header Authorized Apps
|
2
|
+
|
3
|
+
ul.unstyled
|
4
|
+
- @apps.each do |app|
|
5
|
+
li.app
|
6
|
+
.row
|
7
|
+
.span4
|
8
|
+
h3
|
9
|
+
a href=app.url = app.name
|
10
|
+
.span6
|
11
|
+
ul.unstyled
|
12
|
+
- app.authorizations.each do |auth|
|
13
|
+
li.authorization
|
14
|
+
.row
|
15
|
+
.span4
|
16
|
+
h4.page-header Scopes
|
17
|
+
= auth.scopes.map { |s| scope_name(s) }.join(', ')
|
18
|
+
|
19
|
+
h4.page-header Post Types
|
20
|
+
ul
|
21
|
+
- auth.post_types.each do |post_type|
|
22
|
+
li = post_type
|
23
|
+
|
24
|
+
h4.page-header Profile Info Types
|
25
|
+
ul
|
26
|
+
- auth.profile_info_types.each do |info_type|
|
27
|
+
li = info_type
|
28
|
+
|
29
|
+
.span2
|
30
|
+
form.remove-app action=full_path("/apps/#{app.id}/authorizations/#{auth.id}") method="POST"
|
31
|
+
== method_override("DELETE")
|
32
|
+
== csrf_tag
|
33
|
+
input.btn.btn-danger type='submit' value="Revoke Authorization" confirm="Revoke #{app.name} Authorization?"
|
34
|
+
.span2
|
35
|
+
form.remove-app action=full_path("/apps/#{app.id}") method="POST"
|
36
|
+
== method_override("DELETE")
|
37
|
+
== csrf_tag
|
38
|
+
input.btn.btn-danger type='submit' value="Remove App" confirm="Delete #{app.name}?"
|
39
|
+
hr/
|
@@ -0,0 +1,34 @@
|
|
1
|
+
h2 Authorize #{ @app.name }
|
2
|
+
|
3
|
+
form.form-horizontal action=full_path("oauth/confirm") method="POST"
|
4
|
+
== csrf_tag
|
5
|
+
|
6
|
+
.row
|
7
|
+
.span6
|
8
|
+
legend Scopes
|
9
|
+
- (@app.scopes || {}).each_pair do |scope, description|
|
10
|
+
.control-group
|
11
|
+
.controls
|
12
|
+
label.control-label
|
13
|
+
input.pull-left name=scope type='checkbox' checked=true = ' ' + scope_name(scope)
|
14
|
+
blockquote = description
|
15
|
+
|
16
|
+
.span6
|
17
|
+
legend Post Types
|
18
|
+
- @app_params.tent_post_types.to_s.split(',').each do |type|
|
19
|
+
.control-group
|
20
|
+
.controls
|
21
|
+
label.control-label
|
22
|
+
input.pull-left name=type type='checkbox' checked=true = ' ' + type
|
23
|
+
|
24
|
+
legend Profile Info Types
|
25
|
+
- @app_params.tent_profile_info_types.to_s.split(',').each do |type|
|
26
|
+
.control-group
|
27
|
+
.controls
|
28
|
+
label.control-label
|
29
|
+
input.pull-left name=type type='checkbox' checked=true = ' ' + type
|
30
|
+
|
31
|
+
.span12
|
32
|
+
.form-actions
|
33
|
+
input.btn.btn-success type='submit' name='commit' value='Grant Access'
|
34
|
+
input.btn.btn-danger.pull-right type='submit' name='commit' value='Abort'
|