tentd-admin 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +15 -0
  3. data/Gemfile.lock +201 -0
  4. data/LICENSE.txt +22 -0
  5. data/Procfile +1 -0
  6. data/README.md +104 -0
  7. data/Rakefile +30 -0
  8. data/assets/images/chosen-sprite.png +0 -0
  9. data/assets/images/glyphicons-halflings-white.png +0 -0
  10. data/assets/images/glyphicons-halflings.png +0 -0
  11. data/assets/javascripts/application.js.coffee +24 -0
  12. data/assets/javascripts/bootstrap.js +2027 -0
  13. data/assets/javascripts/chosen.jquery.js +1129 -0
  14. data/assets/javascripts/jquery.js +9301 -0
  15. data/assets/stylesheets/application.css.sass +34 -0
  16. data/assets/stylesheets/bootstrap-responsive.css +1040 -0
  17. data/assets/stylesheets/bootstrap.css +5624 -0
  18. data/assets/stylesheets/chosen.css.erb +397 -0
  19. data/config.ru +36 -0
  20. data/config/asset_sync.rb +12 -0
  21. data/lib/tentd-admin.rb +7 -0
  22. data/lib/tentd-admin/app.rb +242 -0
  23. data/lib/tentd-admin/set_entity.rb +10 -0
  24. data/lib/tentd-admin/setup_tent.rb +54 -0
  25. data/lib/tentd-admin/sprockets/environment.rb +23 -0
  26. data/lib/tentd-admin/sprockets/helpers.rb +5 -0
  27. data/lib/tentd-admin/version.rb +5 -0
  28. data/lib/tentd-admin/views/_profile_type_fields.slim +20 -0
  29. data/lib/tentd-admin/views/_profile_type_section.slim +4 -0
  30. data/lib/tentd-admin/views/apps.slim +39 -0
  31. data/lib/tentd-admin/views/auth_confirm.slim +34 -0
  32. data/lib/tentd-admin/views/dashboard.slim +53 -0
  33. data/lib/tentd-admin/views/followers.slim +12 -0
  34. data/lib/tentd-admin/views/followings.slim +22 -0
  35. data/lib/tentd-admin/views/layout.slim +15 -0
  36. data/lib/tentd-admin/views/profile.slim +11 -0
  37. data/lib/tentd-admin/views/setup.slim +20 -0
  38. data/tentd-admin.gemspec +32 -0
  39. 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,10 @@
1
+ class SetEntity
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def call(env)
7
+ env['tent.entity'] = (env['HTTP_X_FORWARDED_PROTO'] || env['rack.url_scheme']) + '://' + env['HTTP_HOST']
8
+ @app.call(env)
9
+ end
10
+ 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,5 @@
1
+ module SprocketsHelpers
2
+ def asset_path(source, options = {})
3
+ "./#{environment.find_asset(source).digest_path}"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Tentd
2
+ class Admin
3
+ VERSION = "0.0.1"
4
+ end
5
+ 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,4 @@
1
+ .control-group
2
+ legend = type
3
+ - data.each_pair do |name, value|
4
+ == slim :_profile_type_fields, :locals => { :type => type, :name => 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'