tentd-admin 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|