tpitale-rack-oauth2-server 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG +202 -0
  2. data/Gemfile +16 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +604 -0
  5. data/Rakefile +90 -0
  6. data/VERSION +1 -0
  7. data/bin/oauth2-server +206 -0
  8. data/lib/rack-oauth2-server.rb +4 -0
  9. data/lib/rack/oauth2/admin/css/screen.css +347 -0
  10. data/lib/rack/oauth2/admin/images/loading.gif +0 -0
  11. data/lib/rack/oauth2/admin/images/oauth-2.png +0 -0
  12. data/lib/rack/oauth2/admin/js/application.coffee +220 -0
  13. data/lib/rack/oauth2/admin/js/jquery.js +166 -0
  14. data/lib/rack/oauth2/admin/js/jquery.tmpl.js +414 -0
  15. data/lib/rack/oauth2/admin/js/protovis-r3.2.js +277 -0
  16. data/lib/rack/oauth2/admin/js/sammy.js +5 -0
  17. data/lib/rack/oauth2/admin/js/sammy.json.js +5 -0
  18. data/lib/rack/oauth2/admin/js/sammy.oauth2.js +142 -0
  19. data/lib/rack/oauth2/admin/js/sammy.storage.js +5 -0
  20. data/lib/rack/oauth2/admin/js/sammy.title.js +5 -0
  21. data/lib/rack/oauth2/admin/js/sammy.tmpl.js +5 -0
  22. data/lib/rack/oauth2/admin/js/underscore.js +722 -0
  23. data/lib/rack/oauth2/admin/views/client.tmpl +58 -0
  24. data/lib/rack/oauth2/admin/views/clients.tmpl +52 -0
  25. data/lib/rack/oauth2/admin/views/edit.tmpl +80 -0
  26. data/lib/rack/oauth2/admin/views/index.html +39 -0
  27. data/lib/rack/oauth2/admin/views/no_access.tmpl +4 -0
  28. data/lib/rack/oauth2/models.rb +27 -0
  29. data/lib/rack/oauth2/models/access_grant.rb +54 -0
  30. data/lib/rack/oauth2/models/access_token.rb +129 -0
  31. data/lib/rack/oauth2/models/auth_request.rb +61 -0
  32. data/lib/rack/oauth2/models/client.rb +93 -0
  33. data/lib/rack/oauth2/rails.rb +105 -0
  34. data/lib/rack/oauth2/server.rb +458 -0
  35. data/lib/rack/oauth2/server/admin.rb +250 -0
  36. data/lib/rack/oauth2/server/errors.rb +104 -0
  37. data/lib/rack/oauth2/server/helper.rb +147 -0
  38. data/lib/rack/oauth2/server/practice.rb +79 -0
  39. data/lib/rack/oauth2/server/railtie.rb +24 -0
  40. data/lib/rack/oauth2/server/utils.rb +30 -0
  41. data/lib/rack/oauth2/sinatra.rb +71 -0
  42. data/rack-oauth2-server.gemspec +24 -0
  43. data/rails/init.rb +11 -0
  44. data/test/admin/api_test.rb +228 -0
  45. data/test/admin/ui_test.rb +38 -0
  46. data/test/oauth/access_grant_test.rb +276 -0
  47. data/test/oauth/access_token_test.rb +311 -0
  48. data/test/oauth/authorization_test.rb +298 -0
  49. data/test/oauth/server_methods_test.rb +292 -0
  50. data/test/rails2/app/controllers/api_controller.rb +40 -0
  51. data/test/rails2/app/controllers/application_controller.rb +2 -0
  52. data/test/rails2/app/controllers/oauth_controller.rb +17 -0
  53. data/test/rails2/config/environment.rb +19 -0
  54. data/test/rails2/config/environments/test.rb +0 -0
  55. data/test/rails2/config/routes.rb +13 -0
  56. data/test/rails3/app/controllers/api_controller.rb +40 -0
  57. data/test/rails3/app/controllers/application_controller.rb +2 -0
  58. data/test/rails3/app/controllers/oauth_controller.rb +17 -0
  59. data/test/rails3/config/application.rb +19 -0
  60. data/test/rails3/config/environment.rb +2 -0
  61. data/test/rails3/config/routes.rb +12 -0
  62. data/test/setup.rb +120 -0
  63. data/test/sinatra/my_app.rb +69 -0
  64. metadata +145 -0
@@ -0,0 +1,90 @@
1
+ require "rake/testtask"
2
+
3
+ spec = Gem::Specification.load(Dir["*.gemspec"].first)
4
+
5
+ desc "Install dependencies"
6
+ task :setup do
7
+ puts "Installing gems for testing with Sinatra ..."
8
+ sh "bundle install"
9
+ puts "Installing gems for testing with Rails 2.3 ..."
10
+ sh "env BUNDLE_GEMFILE=Rails2 bundle install"
11
+ puts "Installing gems for testing with Rails 3.x ..."
12
+ sh "env BUNDLE_GEMFILE=Rails3 bundle install"
13
+ end
14
+
15
+ desc "Run this in development mode when updating the CoffeeScript file"
16
+ task :coffee do
17
+ sh "coffee -w -o lib/rack/oauth2/admin/js/ lib/rack/oauth2/admin/js/application.coffee"
18
+ end
19
+
20
+ task :compile do
21
+ sh "coffee -c -l -o lib/rack/oauth2/admin/js/ lib/rack/oauth2/admin/js/application.coffee"
22
+ end
23
+
24
+ desc "Build the Gem"
25
+ task :build=>:compile do
26
+ sh "gem build #{spec.name}.gemspec"
27
+ end
28
+
29
+ desc "Install #{spec.name} locally"
30
+ task :install=>:build do
31
+ sudo = "sudo" unless File.writable?( Gem::ConfigMap[:bindir])
32
+ sh "#{sudo} gem install #{spec.name}-#{spec.version}.gem"
33
+ end
34
+
35
+ desc "Push new release to gemcutter and git tag"
36
+ task :push=>["test:all", "build"] do
37
+ sh "git push"
38
+ puts "Tagging version #{spec.version} .."
39
+ sh "git tag v#{spec.version}"
40
+ sh "git push --tag"
41
+ puts "Building and pushing gem .."
42
+ sh "gem push #{spec.name}-#{spec.version}.gem"
43
+ end
44
+
45
+ desc "Run all tests"
46
+ Rake::TestTask.new do |task|
47
+ task.test_files = FileList['test/**/*_test.rb']
48
+ if Rake.application.options.trace
49
+ #task.warning = true
50
+ task.verbose = true
51
+ elsif Rake.application.options.silent
52
+ task.ruby_opts << "-W0"
53
+ else
54
+ task.verbose = true
55
+ end
56
+ task.ruby_opts << "-I."
57
+ end
58
+
59
+ namespace :test do
60
+ task :all=>["test:sinatra", "test:rails2", "test:rails3"]
61
+ desc "Run all tests against Sinatra"
62
+ task :sinatra do
63
+ sh "rake test FRAMEWORK=sinatra"
64
+ end
65
+ desc "Run all tests against Rails"
66
+ task :rails do
67
+ sh "rake test FRAMEWORK=rails"
68
+ end
69
+ desc "Run all tests against Rails 2.3.x"
70
+ task :rails2 do
71
+ sh "env BUNDLE_GEMFILE=Rails2 rake test FRAMEWORK=rails"
72
+ end
73
+ desc "Run all tests against Rails 3.x"
74
+ task :rails3 do
75
+ sh "env BUNDLE_GEMFILE=Rails3 bundle exec rake test FRAMEWORK=rails"
76
+ end
77
+ end
78
+ task :default=>"test:all"
79
+
80
+ begin
81
+ require "yard"
82
+ YARD::Rake::YardocTask.new do |doc|
83
+ doc.files = FileList["lib/**/*.rb"]
84
+ end
85
+ rescue LoadError
86
+ end
87
+
88
+ task :clean do
89
+ rm_rf %w{doc .yardoc *.gem}
90
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.2.1
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ require "rack/oauth2/server"
4
+ require "uri"
5
+
6
+ # this is a weird way to shorten the namespace
7
+ include Rack::OAuth2
8
+
9
+
10
+ # if (i = ARGV.index("--db")) && ARGV[i+1]
11
+ # url = ARGV[i + 1]
12
+ # uri = URI.parse(url)
13
+ # uri = URI.parse("mongo://#{url}") if uri.opaque
14
+ # db = Mongo::Connection.new(uri.host, uri.port)[uri.path.sub(/^\//, "")]
15
+ # db.authenticate uri.user, uri.password if uri.user
16
+ # Server.database = db
17
+ # ARGV[i,2] = []
18
+ # end
19
+ if (i = ARGV.index("--port") || ARGV.index("-p")) && ARGV[i+1]
20
+ port = ARGV[i + 1].to_i
21
+ ARGV[i,2] = []
22
+ end
23
+
24
+
25
+ case ARGV[0]
26
+ when "list"
27
+
28
+ # fail "No database. Use the --db option to tell us which database to use" unless Server.database
29
+ Server::Client.all.each do |client|
30
+ next if client.revoked
31
+ print "%-30s\t%s\n" % [client.display_name, client.link]
32
+ print " ID %s\tSecret %s\n" % [client.id, client.secret]
33
+ print "\n"
34
+ end
35
+
36
+ when "register"
37
+
38
+ # fail "No database. Use the --db option to tell us which database to use" unless Server.database
39
+ begin
40
+ print "Application name:\t"
41
+ display_name = $stdin.gets
42
+ print "Application URL:\t"
43
+ link = $stdin.gets
44
+ print "Redirect URI:\t\t"
45
+ redirect_uri = $stdin.gets
46
+ print "Scope (space separated names):\t\t"
47
+ scope = $stdin.gets
48
+ client = Server::Client.create(:display_name=>display_name, :link=>link, :redirect_uri=>redirect_uri, :scope=>scope)
49
+ rescue
50
+ puts "\nFailed to register client: #{$!}"
51
+ exit -1
52
+ end
53
+ puts "Registered #{client.display_name}"
54
+ puts "ID\t#{client.id}"
55
+ puts "Secret\t#{client.secret}"
56
+
57
+ when "setup"
58
+
59
+ # fail "No database. Use the --db option to tell us which database to use" unless Server.database
60
+ puts "Where would you mount the Web console? This is a URL that must end with /admin,"
61
+ puts "for example, http://example.com/oauth/admin"
62
+ print ": "
63
+ uri = URI.parse($stdin.gets)
64
+ begin
65
+ uri.normalize!
66
+ fail "No an HTTP/S URL" unless uri.absolute? && %{http https}.include?(uri.scheme)
67
+ fail "Path must end with /admin" unless uri.path[/\/admin$/]
68
+ client = Server::Client.create(:display_name=>"OAuth Console", :link=>uri.to_s, :image_url=>"#{uri.to_s}/images/oauth-2.png",
69
+ :redirect_uri=>uri.to_s, :scope=>"oauth-admin")
70
+ rescue
71
+ puts "\nFailed to register client: #{$!}"
72
+ exit -1
73
+ end
74
+ print <<-TEXT
75
+
76
+ Next Steps
77
+ ==========
78
+
79
+ Make sure you ONLY authorize administrators to use the oauth-admin scope.
80
+ For example:
81
+
82
+ before_filter do
83
+ # Only admins allowed to authorize the scope oauth-admin
84
+ head oauth.deny! if oauth.scope.include?("oauth-admin") && !current_user.admin?
85
+ end
86
+
87
+ Rails 2.x, add the following to config/environment.rb:
88
+
89
+ config.after_initialize do
90
+ config.middleware.use Rack::OAuth2::Server::Admin.mount "#{uri.path}"
91
+ Rack::OAuth2::Server::Admin.set :client_id, "#{client.id}"
92
+ Rack::OAuth2::Server::Admin.set :client_secret, "#{client.secret}"
93
+ end
94
+
95
+ Rails 3.x, add the following to config/application.rb:
96
+
97
+ config.after_initialize do
98
+ Rack::OAuth2::Server::Admin.set :client_id, "#{client.id}"
99
+ Rack::OAuth2::Server::Admin.set :client_secret, "#{client.secret}"
100
+ end
101
+
102
+ And add the follownig to config/routes.rb:
103
+
104
+ mount Rack::OAuth2::Server::Admin=>"/oauth/admin"
105
+
106
+ Sinatra, Padrino and other Rack applications, mount the console:
107
+
108
+ Rack::Builder.new do
109
+ map("#{uri.path}") { run Rack::OAuth2::Server::Admin }
110
+ map("/") { run MyApp }
111
+ end
112
+ Rack::OAuth2::Server::Admin.set :client_id, "#{client.id}"
113
+ Rack::OAuth2::Server::Admin.set :client_secret, "#{client.secret}"
114
+
115
+ The console will authorize access by redirecting to
116
+ https://#{uri.host}/oauth/authorize
117
+
118
+ If this is not your OAuth 2.0 authorization endpoint, you can change it by
119
+ setting the :authorize option.
120
+ TEXT
121
+
122
+ when "practice"
123
+
124
+ require "logger"
125
+ begin
126
+ require "thin"
127
+ rescue LoadError
128
+ puts "Needs the Thin Web server. Please gem install thin and run again"
129
+ exit -1
130
+ end
131
+ require "rack/oauth2/server/practice"
132
+
133
+ # fail "No database. Use the --db option to tell us which database to use" unless Server.database
134
+ port ||= 8080
135
+ admin_url = "http://localhost:#{port}/oauth/admin"
136
+ unless client = Server::Client.lookup(admin_url)
137
+ client = Server::Client.create(:display_name=>"Practice OAuth Console", :image_url=>"#{admin_url}/images/oauth-2.png",
138
+ :link=>admin_url, :redirect_uri=>admin_url, :scope=>"oauth-admin")
139
+ end
140
+ Server::Admin.configure do |config|
141
+ logger = Logger.new(STDOUT)
142
+ logger.level = Logger::DEBUG
143
+ config.set :client_id, client.id
144
+ config.set :client_secret, client.secret
145
+ config.set :scope, "nobody sudo"
146
+ config.set :logger, logger
147
+ config.set :logging, true
148
+ config.set :dump_errors, true
149
+ config.oauth.logger = logger
150
+ end
151
+
152
+ Server::Practice.configure do |config|
153
+ logger = Logger.new(STDOUT)
154
+ logger.level = Logger::DEBUG
155
+ config.set :logger, logger
156
+ config.set :logging, true
157
+ config.set :dump_errors, true
158
+ config.oauth.logger = logger
159
+ end
160
+
161
+ print "\nFiring up the practice server.\nFor instructions, go to http://localhost:#{port}/\n\n\n"
162
+ Thin::Server.new "127.0.0.1", port do
163
+ map("/") { run Server::Practice.new }
164
+ map("/oauth/admin") { run Server::Admin.new }
165
+ end.start
166
+
167
+ # when "migrate"
168
+ #
169
+ # # fail "No database. Use the --db option to tell us which database to use" unless Server.database
170
+ # puts "Set all clients to this scope (can change later by calling Client.register):"
171
+ # print ": "
172
+ # scope = $stdin.gets.strip.split
173
+ # puts "Updating Client scope to #{scope.join(", ")}"
174
+ # Server::Client.collection.find({ :scope=>{ :$exists=>false } }, :fields=>[]).each do |client|
175
+ # update = { :scope=>scope,
176
+ # :tokens_granted=>Server::AccessToken.count(:client_id=>client["_id"]),
177
+ # :tokens_revoked=>Server::AccessToken.count(:client_id=>client["_id"], :revoked=>true) }
178
+ # Server::Client.collection.update({ :_id=>client["_id"] }, { :$set=>update })
179
+ # end
180
+ # [Server::AccessToken, Server::AccessGrant, Server::AuthRequest].each do |mod|
181
+ # puts "Updating #{mod.name} scope from string to array"
182
+ # mod.collection.find({ :scope=>{ :$type=>2 } }, :fields=>[]).each do |token|
183
+ # scope = token["scope"].split
184
+ # mod.collection.update({ :_id=>token["_id"] }, { :$set=>{ :scope=>scope } })
185
+ # end
186
+ # end
187
+ else
188
+
189
+ print <<-TEXT
190
+ Usage: oauth2-server [options] COMMAND [args]
191
+ Version #{Server::VERSION}
192
+
193
+ Commands:
194
+ list Lists all active clients
195
+ migrate Run this when migrating from 1.x to 2.x
196
+ practice Runs a dummy OAuth 2.0 server, use this to test your OAuth 2.0 client
197
+ register Register a new client application
198
+ setup Create new admin account and help you setup the OAuth Web console
199
+
200
+ Options:
201
+ --db database Database name or connection URL
202
+ --port number Port to run admin server, detault is 8080
203
+ TEXT
204
+ exit -1
205
+
206
+ end
@@ -0,0 +1,4 @@
1
+ require "rack/oauth2/server"
2
+ require "rack/oauth2/sinatra" if defined?(Sinatra)
3
+ require "rack/oauth2/rails" if defined?(Rails)
4
+ require "rack/oauth2/server/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,347 @@
1
+ html {
2
+ background: #eee;
3
+ }
4
+ body {
5
+ margin: 0;
6
+ padding: 0;
7
+ width: 100%;
8
+ font: 12pt "Helvetica", "Lucida Sans", "Verdana";
9
+ }
10
+
11
+ a { text-decoration: none; color: #00c; }
12
+ a:hover, a:focus { text-decoration: underline; color: #00c; }
13
+ h1, h2 {
14
+ text-shadow: rgba(255,255,255,.2) 0 1px 1px;
15
+ color: rgb(76, 86, 108);
16
+ }
17
+ h1 { font-size: 18pt; margin: 0.6em 0 }
18
+ h2 { font-size: 16pt; margin: 0.3em 0 }
19
+
20
+ label {
21
+ display: block;
22
+ color: #000;
23
+ font-weight: 600;
24
+ font-size: 0.9em;
25
+ margin: 0.9em 0;
26
+ }
27
+ label input, label textarea, label select {
28
+ display: block;
29
+ }
30
+ label.check {
31
+ font-weight: normal;
32
+ }
33
+ label.check input {
34
+ display: inline;
35
+ }
36
+ label input, label textarea {
37
+ font-size: 12pt;
38
+ line-height: 1.3em;
39
+ }
40
+ label .hint {
41
+ font-weight: normal;
42
+ color: #666;
43
+ margin: 0;
44
+ }
45
+ button {
46
+ font-size: 11pt;
47
+ text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
48
+ border: 1px solid #dddddd;
49
+ background: #f6f6f6 50% 50% repeat-x;
50
+ font-weight: bold;
51
+ color: #0073ea;
52
+ outline: none;
53
+ line-height: 1.3em;
54
+ vertical-align: bottom;
55
+ padding: 2px 8px;
56
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(226,226,226,0.0)), to(rgba(226,226,226,1.0)));
57
+ -webkit-border-radius: 4px; -moz-border-radius: 4px;
58
+ -moz-box-shadow: 0 0 4px rgba(0,0,0,0.0);
59
+ -webkit-box-shadow: 0 0 4px rgba(0,0,0,0.0);
60
+ }
61
+ button:hover, button:focus {
62
+ text-shadow: 0 -1px 1px rgba(255,255,255,0.25);
63
+ border: 1px solid #0073ea;
64
+ background: #0073ea 50% 50% repeat-x;
65
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(0, 115, 234, 0.5)), to(rgba(0,115,234, 1.0)));
66
+ color: #fff;
67
+ text-decoration: none;
68
+ cursor: pointer;
69
+ -moz-box-shadow: 0 2px 4px rgba(0,0,0,0.5);
70
+ -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5);
71
+ }
72
+ button:active { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(0, 115, 234, 1.0)), to(rgba(0,115,234, 0.5))); position: relative; top: 1px }
73
+ .error {
74
+ color: #f44;
75
+ padding-left: 1em;
76
+ }
77
+
78
+
79
+ /* Message dropping down from the top */
80
+ #notice {
81
+ position: absolute;
82
+ top: 0;
83
+ left: 0;
84
+ right: 0;
85
+ line-height: 1.6em;
86
+ background: #FFFE36;
87
+ color: #000;
88
+ border-bottom: 1px solid #ddd;
89
+ text-align: center;
90
+ z-index: 99;
91
+ }
92
+
93
+ #header {
94
+ margin: 0;
95
+ padding: 1em 2em 3em 2em;
96
+ border-bottom: 2px solid #CCC;
97
+ background: #6595A4;
98
+ color: #fff;
99
+ }
100
+ #header .title {
101
+ font-size: 18pt;
102
+ font-weight: bold;
103
+ display: block;
104
+ line-height: 32px;
105
+ float: left;
106
+ }
107
+ #header .title a {
108
+ color: #fff;
109
+ text-shadow: white 0px 0px 1px;
110
+ text-decoration: none;
111
+ }
112
+ #header .title img {
113
+ width: 32px;
114
+ height: 32px;
115
+ vertical-align: bottom;
116
+ }
117
+ #header .signout, #header .signin {
118
+ color: #C8E9F3;
119
+ float: right;
120
+ font-size: 11pt;
121
+ line-height: 32px;
122
+ display: none;
123
+ }
124
+
125
+ #main {
126
+ margin: 0;
127
+ padding: 2em 2em 4em 2em;
128
+ background: #fff;
129
+ border: 1px solid #fff;
130
+ min-width: 960px;
131
+ }
132
+ #footer {
133
+ background: #eee;
134
+ color: #666;
135
+ border-top: 1px solid #ccc;
136
+ font-size: 90%;
137
+ padding: 0 2em 2em 2em;
138
+ }
139
+
140
+ table {
141
+ width: 100%;
142
+ table-layout: auto;
143
+ empty-cells: show;
144
+ border-collapse: separate;
145
+ border-spacing: 0px;
146
+ margin-top: 2em;
147
+ }
148
+ table th {
149
+ text-align: left;
150
+ border-bottom: 1px solid #ccc;
151
+ margin-right: 48px;
152
+ }
153
+ table td {
154
+ text-align: left;
155
+ vertical-align: top;
156
+ border-bottom: 1px solid #ddf;
157
+ line-height: 32px;
158
+ margin: 0;
159
+ padding: 0;
160
+ }
161
+ table tr:hover td {
162
+ background: #ddf;
163
+ }
164
+ table td.created, table td.revoke, table td.accessed {
165
+ width: 6em;
166
+ }
167
+ table tr.revoked td, table tr.revoked a {
168
+ color: #888;
169
+ }
170
+ table button {
171
+ margin-top: -2px;
172
+ font-size: 10pt;
173
+ }
174
+
175
+ table.clients td.name {
176
+ padding-left: 32px;
177
+ }
178
+ table.clients td.name img {
179
+ width: 24px;
180
+ height: 24px;
181
+ border: none;
182
+ margin: 4px 4px -4px -32px;
183
+ }
184
+ table.clients td.secrets {
185
+ width: 28em;
186
+ }
187
+ table.clients td.secrets dl {
188
+ display: none;
189
+ width: 40em;
190
+ margin: 0 -12em 0.6em 0;
191
+ line-height: 1.3em;
192
+ }
193
+ table.clients td.secrets dt {
194
+ width: 4em;
195
+ float: left;
196
+ color: #888;
197
+ margin-bottom: 0.3em;
198
+ }
199
+ table.clients td.secrets dd:after {
200
+ content: ".";
201
+ display: block;
202
+ clear: both;
203
+ visibility: hidden;
204
+ line-height: 0;
205
+ height: 0;
206
+ }
207
+
208
+ table.tokens td.token {
209
+ width: 36em;
210
+ }
211
+ table.tokens td.scope {
212
+ float: none;
213
+ }
214
+
215
+ .pagination {
216
+ width: 100%;
217
+ margin-top: 2em;
218
+ }
219
+ .pagination a[rel=next] {
220
+ float: right;
221
+ }
222
+ .pagination a[rel=previous] {
223
+ float: left;
224
+ }
225
+
226
+ .metrics {
227
+ height: 100px;
228
+ }
229
+ .metrics #fig {
230
+ float: left;
231
+ width: 500px;
232
+ height: 60px;
233
+ }
234
+ .metrics:after {
235
+ content: ".";
236
+ display: block;
237
+ clear: both;
238
+ visibility: hidden;
239
+ line-height: 0;
240
+ height: 0;
241
+ }
242
+ .badges {
243
+ list-style: none;
244
+ margin: 0;
245
+ padding: 0;
246
+ text-align: right;
247
+ width: 100%;
248
+ }
249
+ .badges li {
250
+ display: inline-block;
251
+ margin-left: 8px;
252
+ min-width: 8em;
253
+ }
254
+ .badges big {
255
+ font-size: 22pt;
256
+ display: block;
257
+ text-align: center;
258
+ }
259
+ .badges small {
260
+ font-size: 11pt;
261
+ display: block;
262
+ text-align: center;
263
+ }
264
+
265
+ .client .details {
266
+ margin: 0 0 2em 0;
267
+ }
268
+ .client .details .name {
269
+ margin: 0;
270
+ font-size: 16pt;
271
+ font-weight: bold;
272
+ float: left;
273
+ }
274
+ .client .details img {
275
+ border: none;
276
+ width: 24px;
277
+ height: 24px;
278
+ vertical-align: bottom;
279
+ }
280
+ .client .details .actions {
281
+ float: left;
282
+ line-height: 20pt;
283
+ margin-left: 1em;
284
+ }
285
+ .client .details .actions a {
286
+ margin: 0 0 0 0.3em;
287
+ }
288
+ .client .details .meta {
289
+ clear: both;
290
+ display: block;
291
+ color: #888;
292
+ font-size: 10pt;
293
+ }
294
+ .client .details .notes {
295
+ font-size: 11pt;
296
+ margin: 0.2em 0;
297
+ }
298
+
299
+ .client.new, .client.edit {
300
+ margin: 0;
301
+ padding: 1em;
302
+ border: 1px solid #eee;
303
+ }
304
+ .client .fields>#image {
305
+ float: left;
306
+ margin: 0.5em 12px 0 0;
307
+ width: 48px;
308
+ height: 48px;
309
+ }
310
+ .client .fields>* {
311
+ margin-left: 60px;
312
+ }
313
+ .client .fields {
314
+ float: left;
315
+ }
316
+ .client .scope {
317
+ float: right;
318
+ }
319
+ .client .scope .uncommon {
320
+ color: red;
321
+ }
322
+ .client hr {
323
+ clear: both;
324
+ border: none;
325
+ margin: 1em;
326
+ }
327
+
328
+ .no-access {
329
+ margin: 0;
330
+ padding: 0;
331
+ }
332
+ .no-access h1 {
333
+ color: red;
334
+ }
335
+
336
+ #throbber {
337
+ display: none;
338
+ position: absolute;
339
+ top: 6em;
340
+ right: 2em;
341
+ width: 48px;
342
+ height: 48px;
343
+ content: "";
344
+ }
345
+ .loading {
346
+ background: url("../images/loading.gif") no-repeat 50% 50%;
347
+ }