tpitale-rack-oauth2-server 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +202 -0
- data/Gemfile +16 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +604 -0
- data/Rakefile +90 -0
- data/VERSION +1 -0
- data/bin/oauth2-server +206 -0
- data/lib/rack-oauth2-server.rb +4 -0
- data/lib/rack/oauth2/admin/css/screen.css +347 -0
- data/lib/rack/oauth2/admin/images/loading.gif +0 -0
- data/lib/rack/oauth2/admin/images/oauth-2.png +0 -0
- data/lib/rack/oauth2/admin/js/application.coffee +220 -0
- data/lib/rack/oauth2/admin/js/jquery.js +166 -0
- data/lib/rack/oauth2/admin/js/jquery.tmpl.js +414 -0
- data/lib/rack/oauth2/admin/js/protovis-r3.2.js +277 -0
- data/lib/rack/oauth2/admin/js/sammy.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.json.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.oauth2.js +142 -0
- data/lib/rack/oauth2/admin/js/sammy.storage.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.title.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.tmpl.js +5 -0
- data/lib/rack/oauth2/admin/js/underscore.js +722 -0
- data/lib/rack/oauth2/admin/views/client.tmpl +58 -0
- data/lib/rack/oauth2/admin/views/clients.tmpl +52 -0
- data/lib/rack/oauth2/admin/views/edit.tmpl +80 -0
- data/lib/rack/oauth2/admin/views/index.html +39 -0
- data/lib/rack/oauth2/admin/views/no_access.tmpl +4 -0
- data/lib/rack/oauth2/models.rb +27 -0
- data/lib/rack/oauth2/models/access_grant.rb +54 -0
- data/lib/rack/oauth2/models/access_token.rb +129 -0
- data/lib/rack/oauth2/models/auth_request.rb +61 -0
- data/lib/rack/oauth2/models/client.rb +93 -0
- data/lib/rack/oauth2/rails.rb +105 -0
- data/lib/rack/oauth2/server.rb +458 -0
- data/lib/rack/oauth2/server/admin.rb +250 -0
- data/lib/rack/oauth2/server/errors.rb +104 -0
- data/lib/rack/oauth2/server/helper.rb +147 -0
- data/lib/rack/oauth2/server/practice.rb +79 -0
- data/lib/rack/oauth2/server/railtie.rb +24 -0
- data/lib/rack/oauth2/server/utils.rb +30 -0
- data/lib/rack/oauth2/sinatra.rb +71 -0
- data/rack-oauth2-server.gemspec +24 -0
- data/rails/init.rb +11 -0
- data/test/admin/api_test.rb +228 -0
- data/test/admin/ui_test.rb +38 -0
- data/test/oauth/access_grant_test.rb +276 -0
- data/test/oauth/access_token_test.rb +311 -0
- data/test/oauth/authorization_test.rb +298 -0
- data/test/oauth/server_methods_test.rb +292 -0
- data/test/rails2/app/controllers/api_controller.rb +40 -0
- data/test/rails2/app/controllers/application_controller.rb +2 -0
- data/test/rails2/app/controllers/oauth_controller.rb +17 -0
- data/test/rails2/config/environment.rb +19 -0
- data/test/rails2/config/environments/test.rb +0 -0
- data/test/rails2/config/routes.rb +13 -0
- data/test/rails3/app/controllers/api_controller.rb +40 -0
- data/test/rails3/app/controllers/application_controller.rb +2 -0
- data/test/rails3/app/controllers/oauth_controller.rb +17 -0
- data/test/rails3/config/application.rb +19 -0
- data/test/rails3/config/environment.rb +2 -0
- data/test/rails3/config/routes.rb +12 -0
- data/test/setup.rb +120 -0
- data/test/sinatra/my_app.rb +69 -0
- metadata +145 -0
data/Rakefile
ADDED
@@ -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
|
data/bin/oauth2-server
ADDED
@@ -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,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
|
+
}
|