tpitale-rack-oauth2-server 2.2.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.
- 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
@@ -0,0 +1,79 @@
|
|
1
|
+
require "rack/oauth2/server/admin"
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module OAuth2
|
5
|
+
class Server
|
6
|
+
|
7
|
+
class Practice < ::Sinatra::Base
|
8
|
+
register Rack::OAuth2::Sinatra
|
9
|
+
|
10
|
+
get "/" do
|
11
|
+
<<-HTML
|
12
|
+
<html>
|
13
|
+
<head>
|
14
|
+
<title>OAuth 2.0 Practice Server</title>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<h1>Welcome to OAuth 2.0 Practice Server</h1>
|
18
|
+
<p>This practice server is for testing your OAuth 2.0 client library.</p>
|
19
|
+
<dl>
|
20
|
+
<dt>Authorization end-point:</dt>
|
21
|
+
<dd>http://#{request.host}:#{request.port}/oauth/authorize</dd>
|
22
|
+
<dt>Access token end-point:<//dt>
|
23
|
+
<dd>http://#{request.host}:#{request.port}/oauth/access_token</dd>
|
24
|
+
<dt>Resource requiring authentication:</dt>
|
25
|
+
<dd>http://#{request.host}:#{request.port}/secret</dd>
|
26
|
+
<dt>Resource requiring authorization and scope "sudo":</dt>
|
27
|
+
<dd>http://#{request.host}:#{request.port}/make</dd>
|
28
|
+
</dl>
|
29
|
+
<p>The scope can be "nobody", "sudo", "oauth-admin" or combination of the three.</p>
|
30
|
+
<p>You can manage client applications and tokens from the <a href="/oauth/admin">OAuth console</a>.</p>
|
31
|
+
</body>
|
32
|
+
</html>
|
33
|
+
HTML
|
34
|
+
end
|
35
|
+
|
36
|
+
# -- Simple authorization --
|
37
|
+
|
38
|
+
get "/oauth/authorize" do
|
39
|
+
<<-HTML
|
40
|
+
<html>
|
41
|
+
<head>
|
42
|
+
<title>OAuth 2.0 Practice Server</title>
|
43
|
+
</head>
|
44
|
+
<body>
|
45
|
+
<h1><a href="#{oauth.client.link}">#{oauth.client.display_name}</a> wants to access your account with the scope #{oauth.scope.join(", ")}</h1>
|
46
|
+
<form action="/oauth/grant" method="post" style="display:inline-block">
|
47
|
+
<button>Grant</button>
|
48
|
+
<input type="hidden" name="authorization" value="#{oauth.authorization}">
|
49
|
+
</form>
|
50
|
+
<form action="/oauth/deny" method="post" style="display:inline-block">
|
51
|
+
<button>Deny</button>
|
52
|
+
<input type="hidden" name="authorization" value="#{oauth.authorization}">
|
53
|
+
</form>
|
54
|
+
</body>
|
55
|
+
</html>
|
56
|
+
HTML
|
57
|
+
end
|
58
|
+
post "/oauth/grant" do
|
59
|
+
oauth.grant! "Superman"
|
60
|
+
end
|
61
|
+
post "/oauth/deny" do
|
62
|
+
oauth.deny!
|
63
|
+
end
|
64
|
+
|
65
|
+
# -- Protected resources --
|
66
|
+
|
67
|
+
oauth_required "/secret"
|
68
|
+
get "/private" do
|
69
|
+
"You're awesome!"
|
70
|
+
end
|
71
|
+
|
72
|
+
oauth_required "/make", :scope=>"sudo"
|
73
|
+
get "/write" do
|
74
|
+
"Sandwhich"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "rack/oauth2/server"
|
2
|
+
require "rack/oauth2/rails"
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module OAuth2
|
7
|
+
class Server
|
8
|
+
# Rails 3.x integration.
|
9
|
+
class Railtie < ::Rails::Railtie # :nodoc:
|
10
|
+
config.oauth = Server::Options.new
|
11
|
+
|
12
|
+
initializer "rack-oauth2-server" do |app|
|
13
|
+
app.middleware.use ::Rack::OAuth2::Server, app.config.oauth
|
14
|
+
config.oauth.logger ||= ::Rails.logger
|
15
|
+
class ::ActionController::Base
|
16
|
+
helper ::Rack::OAuth2::Rails::Helpers
|
17
|
+
include ::Rack::OAuth2::Rails::Helpers
|
18
|
+
extend ::Rack::OAuth2::Rails::Filters
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class Server
|
4
|
+
|
5
|
+
module Utils
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Parses the redirect URL, normalizes it and returns a URI object.
|
9
|
+
#
|
10
|
+
# Raises InvalidRequestError if not an absolute HTTP/S URL.
|
11
|
+
def parse_redirect_uri(redirect_uri)
|
12
|
+
raise InvalidRequestError, "Missing redirect URL" unless redirect_uri
|
13
|
+
uri = URI.parse(redirect_uri).normalize rescue nil
|
14
|
+
raise InvalidRequestError, "Redirect URL looks fishy to me" unless uri
|
15
|
+
raise InvalidRequestError, "Redirect URL must be absolute URL" unless uri.absolute? && uri.host
|
16
|
+
raise InvalidRequestError, "Redirect URL must point to HTTP/S location" unless uri.scheme == "http" || uri.scheme == "https"
|
17
|
+
uri
|
18
|
+
end
|
19
|
+
|
20
|
+
# Given scope as either array or string, return array of same names,
|
21
|
+
# unique and sorted.
|
22
|
+
def normalize_scope(scope)
|
23
|
+
(Array === scope ? scope.join(" ") : scope || "").split(/\s+/).compact.uniq.sort
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "rack/oauth2/server"
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module OAuth2
|
5
|
+
|
6
|
+
# Sinatra support.
|
7
|
+
#
|
8
|
+
# Adds oauth instance method that returns Rack::OAuth2::Helper, see there for
|
9
|
+
# more details.
|
10
|
+
#
|
11
|
+
# Adds oauth_required class method. Use this filter with paths that require
|
12
|
+
# authentication, and with paths that require client to have a specific
|
13
|
+
# access scope.
|
14
|
+
#
|
15
|
+
# Adds oauth setting you can use to configure the module (e.g. setting
|
16
|
+
# available scope, see example).
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# require "rack/oauth2/sinatra"
|
20
|
+
# class MyApp < Sinatra::Base
|
21
|
+
# register Rack::OAuth2::Sinatra
|
22
|
+
# oauth[:scope] = %w{read write}
|
23
|
+
#
|
24
|
+
# oauth_required "/api"
|
25
|
+
# oauth_required "/api/edit", :scope=>"write"
|
26
|
+
#
|
27
|
+
# before { @user = User.find(oauth.identity) if oauth.authenticated? }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @see Helpers
|
31
|
+
module Sinatra
|
32
|
+
|
33
|
+
# Adds before filter to require authentication on all the listed paths.
|
34
|
+
# Use the :scope option if client must also have access to that scope.
|
35
|
+
#
|
36
|
+
# @param [String, ...] path One or more paths that require authentication
|
37
|
+
# @param [optional, Hash] options Currently only :scope is supported.
|
38
|
+
def oauth_required(*args)
|
39
|
+
options = args.pop if Hash === args.last
|
40
|
+
scope = options[:scope] if options
|
41
|
+
args.each do |path|
|
42
|
+
before path do
|
43
|
+
if oauth.authenticated?
|
44
|
+
if scope && !oauth.scope.include?(scope)
|
45
|
+
halt oauth.no_scope! scope
|
46
|
+
end
|
47
|
+
else
|
48
|
+
halt oauth.no_access!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Helpers
|
55
|
+
# Returns the OAuth helper.
|
56
|
+
#
|
57
|
+
# @return [Server::Helper]
|
58
|
+
def oauth
|
59
|
+
@oauth ||= Server::Helper.new(request, response)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.registered(base)
|
64
|
+
base.helpers Helpers
|
65
|
+
base.set :oauth, Server::Options.new
|
66
|
+
base.use Server, base.settings.oauth
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + "/lib"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "tpitale-rack-oauth2-server"
|
5
|
+
spec.version = IO.read("VERSION")
|
6
|
+
spec.author = "Assaf Arkin"
|
7
|
+
spec.email = "assaf@labnotes.org"
|
8
|
+
spec.homepage = "http://github.com/flowtown/#{spec.name}"
|
9
|
+
spec.summary = "OAuth 2.0 Authorization Server as a Rack module for ActiveRecord"
|
10
|
+
spec.description = "Because you don't allow strangers into your app, and OAuth 2.0 is the new awesome."
|
11
|
+
spec.post_install_message = "To get started, run the command oauth2-server"
|
12
|
+
|
13
|
+
spec.files = Dir["{bin,lib,rails,test}/**/*", "CHANGELOG", "VERSION", "MIT-LICENSE", "README.rdoc", "Rakefile", "Gemfile", "*.gemspec"]
|
14
|
+
spec.executable = "oauth2-server"
|
15
|
+
|
16
|
+
spec.extra_rdoc_files = "README.rdoc", "CHANGELOG"
|
17
|
+
spec.rdoc_options = "--title", "tpitale-rack-oauth2-server #{spec.version}", "--main", "README.rdoc",
|
18
|
+
"--webcvs", "http://github.com/tpitale/rack-oauth2-server"
|
19
|
+
spec.license = "MIT"
|
20
|
+
|
21
|
+
spec.required_ruby_version = '>= 1.8.7'
|
22
|
+
spec.add_dependency "rack", "~>1.1"
|
23
|
+
spec.add_dependency "rails", ">= 2.3.11"
|
24
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Rails 2.x initialization.
|
2
|
+
require "rack/oauth2/rails"
|
3
|
+
|
4
|
+
config.extend ::Rack::OAuth2::Rails::Configuration
|
5
|
+
config.oauth.logger ||= Rails.logger
|
6
|
+
config.middleware.use ::Rack::OAuth2::Server, config.oauth
|
7
|
+
class ActionController::Base
|
8
|
+
helper ::Rack::OAuth2::Rails::Helpers
|
9
|
+
include ::Rack::OAuth2::Rails::Helpers
|
10
|
+
extend ::Rack::OAuth2::Rails::Filters
|
11
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require "test/setup"
|
2
|
+
|
3
|
+
class AdminApiTest < Test::Unit::TestCase
|
4
|
+
module Helpers
|
5
|
+
def should_fail_authentication
|
6
|
+
should "respond with status 401 (Unauthorized)" do
|
7
|
+
assert_equal 401, last_response.status
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def should_forbid_access
|
12
|
+
should "respond with status 403 (Forbidden)" do
|
13
|
+
assert_equal 403, last_response.status
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
extend Helpers
|
18
|
+
|
19
|
+
|
20
|
+
def without_scope
|
21
|
+
token = Server.token_for("Superman", client.id, "nobody")
|
22
|
+
header "Authorization", "OAuth #{token}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_scope
|
26
|
+
token = Server.token_for("Superman", client.id, "oauth-admin")
|
27
|
+
header "Authorization", "OAuth #{token}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def json
|
31
|
+
JSON.parse(last_response.body)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
context "force SSL" do
|
36
|
+
setup do
|
37
|
+
Server::Admin.force_ssl = true
|
38
|
+
with_scope
|
39
|
+
end
|
40
|
+
|
41
|
+
context "HTTP request" do
|
42
|
+
setup { get "/oauth/admin/api/clients" }
|
43
|
+
|
44
|
+
should "redirect to HTTPS" do
|
45
|
+
assert_equal 302, last_response.status
|
46
|
+
assert_equal "https://example.org/oauth/admin/api/clients", last_response.location
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "HTTPS request" do
|
51
|
+
setup { get "https://example.org/oauth/admin/api/clients" }
|
52
|
+
|
53
|
+
should "serve request" do
|
54
|
+
assert_equal 200, last_response.status
|
55
|
+
assert Array === json["list"]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
teardown { Server::Admin.force_ssl = false }
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# -- /oauth/admin/api/clients
|
64
|
+
|
65
|
+
context "all clients" do
|
66
|
+
context "without authentication" do
|
67
|
+
setup { get "/oauth/admin/api/clients" }
|
68
|
+
should_fail_authentication
|
69
|
+
end
|
70
|
+
|
71
|
+
context "without scope" do
|
72
|
+
setup { without_scope ; get "/oauth/admin/api/clients" }
|
73
|
+
should_forbid_access
|
74
|
+
end
|
75
|
+
|
76
|
+
context "proper request" do
|
77
|
+
setup { with_scope ; get "/oauth/admin/api/clients" }
|
78
|
+
should "return OK" do
|
79
|
+
assert_equal 200, last_response.status
|
80
|
+
end
|
81
|
+
should "return JSON document" do
|
82
|
+
assert_equal "application/json", last_response.content_type.split(";").first
|
83
|
+
end
|
84
|
+
should "return list of clients" do
|
85
|
+
assert Array === json["list"]
|
86
|
+
end
|
87
|
+
should "return known scope" do
|
88
|
+
assert_equal %w{read write}, json["scope"]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "client list" do
|
93
|
+
setup do
|
94
|
+
with_scope
|
95
|
+
get "/oauth/admin/api/clients"
|
96
|
+
@first = json["list"].first
|
97
|
+
end
|
98
|
+
|
99
|
+
should "provide client identifier" do
|
100
|
+
assert_equal client.id.to_s, @first["id"]
|
101
|
+
end
|
102
|
+
should "provide client secret" do
|
103
|
+
assert_equal client.secret, @first["secret"]
|
104
|
+
end
|
105
|
+
should "provide redirect URI" do
|
106
|
+
assert_equal client.redirect_uri, @first["redirectUri"]
|
107
|
+
end
|
108
|
+
should "provide display name" do
|
109
|
+
assert_equal client.display_name, @first["displayName"]
|
110
|
+
end
|
111
|
+
should "provide site URL" do
|
112
|
+
assert_equal client.link, @first["link"]
|
113
|
+
end
|
114
|
+
should "provide image URL" do
|
115
|
+
assert_equal client.image_url, @first["imageUrl"]
|
116
|
+
end
|
117
|
+
should "provide created timestamp" do
|
118
|
+
assert_equal client.created_at.to_i, @first["created"]
|
119
|
+
end
|
120
|
+
should "provide link to client resource"do
|
121
|
+
assert_equal ["/oauth/admin/api/client", client.id].join("/"), @first["url"]
|
122
|
+
end
|
123
|
+
should "provide link to revoke resource"do
|
124
|
+
assert_equal ["/oauth/admin/api/client", client.id, "revoke"].join("/"), @first["revoke"]
|
125
|
+
end
|
126
|
+
should "provide scope for client" do
|
127
|
+
assert_equal %w{oauth-admin read write}, @first["scope"]
|
128
|
+
end
|
129
|
+
should "tell if not revoked" do
|
130
|
+
assert @first["revoked"].nil?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "revoked client" do
|
135
|
+
setup do
|
136
|
+
client.revoke!
|
137
|
+
with_scope
|
138
|
+
get "/oauth/admin/api/clients"
|
139
|
+
@first = json["list"].first
|
140
|
+
end
|
141
|
+
|
142
|
+
should "provide revoked timestamp" do
|
143
|
+
assert_equal client.revoked.to_i, @first["revoked"]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "tokens" do
|
148
|
+
setup do
|
149
|
+
tokens = []
|
150
|
+
1.upto(10).map do |days|
|
151
|
+
Timecop.travel -days*86400 do
|
152
|
+
tokens << Server.token_for("Superman#{days}", client.id)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
# Revoke one token today (within past 7 days), one 10 days ago (beyond)
|
156
|
+
Timecop.travel -7 * 86400 do
|
157
|
+
Server.get_access_token(tokens[0]).revoke!
|
158
|
+
end
|
159
|
+
Server.get_access_token(tokens[1]).revoke!
|
160
|
+
with_scope ; get "/oauth/admin/api/clients"
|
161
|
+
end
|
162
|
+
|
163
|
+
should "return total number of tokens" do
|
164
|
+
assert_equal 11, json["tokens"]["total"]
|
165
|
+
end
|
166
|
+
should "return number of tokens created past week" do
|
167
|
+
assert_equal 7, json["tokens"]["week"]
|
168
|
+
end
|
169
|
+
should "return number of revoked token past week" do
|
170
|
+
assert_equal 1, json["tokens"]["revoked"]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
# -- /oauth/admin/api/client/:id
|
177
|
+
|
178
|
+
context "single client" do
|
179
|
+
context "without authentication" do
|
180
|
+
setup { get "/oauth/admin/api/client/#{client.id}" }
|
181
|
+
should_fail_authentication
|
182
|
+
end
|
183
|
+
|
184
|
+
context "without scope" do
|
185
|
+
setup { without_scope ; get "/oauth/admin/api/client/#{client.id}" }
|
186
|
+
should_forbid_access
|
187
|
+
end
|
188
|
+
|
189
|
+
context "with scope" do
|
190
|
+
setup { with_scope ; get "/oauth/admin/api/client/#{client.id}" }
|
191
|
+
|
192
|
+
should "return OK" do
|
193
|
+
assert_equal 200, last_response.status
|
194
|
+
end
|
195
|
+
should "return JSON document" do
|
196
|
+
assert_equal "application/json", last_response.content_type.split(";").first
|
197
|
+
end
|
198
|
+
should "provide client identifier" do
|
199
|
+
assert_equal client.id.to_s, json["id"]
|
200
|
+
end
|
201
|
+
should "provide client secret" do
|
202
|
+
assert_equal client.secret, json["secret"]
|
203
|
+
end
|
204
|
+
should "provide redirect URI" do
|
205
|
+
assert_equal client.redirect_uri, json["redirectUri"]
|
206
|
+
end
|
207
|
+
should "provide display name" do
|
208
|
+
assert_equal client.display_name, json["displayName"]
|
209
|
+
end
|
210
|
+
should "provide site URL" do
|
211
|
+
assert_equal client.link, json["link"]
|
212
|
+
end
|
213
|
+
should "provide image URL" do
|
214
|
+
assert_equal client.image_url, json["imageUrl"]
|
215
|
+
end
|
216
|
+
should "provide created timestamp" do
|
217
|
+
assert_equal client.created_at.to_i, json["created"]
|
218
|
+
end
|
219
|
+
should "provide link to client resource"do
|
220
|
+
assert_equal ["/oauth/admin/api/client", client.id].join("/"), json["url"]
|
221
|
+
end
|
222
|
+
should "provide link to revoke resource"do
|
223
|
+
assert_equal ["/oauth/admin/api/client", client.id, "revoke"].join("/"), json["revoke"]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|