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
@@ -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
|