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.
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'