Skip to content

Commit 2408d64

Browse files
will-gantctlong
authored andcommitted
Test new /v3/service_instances/:guid/permissions
Also update the CATS service broker to retrieve the value of the HTTP_X_API_INFO_LOCATION header from calls from the Cloud Controller, store it in memory, and then serve it on a new endpoint. This is a slightly more realistic way to test the SSO lifecycle, as the old helper function used by these tests hardcoded the API info endpoint as `/info`. That worked because, like `/v2/info`, its response object also had the authorization_endpoint at the top level.
1 parent 145a745 commit 2408d64

5 files changed

Lines changed: 80 additions & 11 deletions

File tree

assets/service_broker/service_broker.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'pp'
77
require 'logger'
88
require 'rainbow/ext/string'
9+
require 'uri'
910

1011
require 'bundler'
1112
Bundler.require :default, ENV['RACK_ENV'].to_sym
@@ -132,6 +133,7 @@ def behavior_for_type(type, plan_id)
132133

133134
class ServiceBroker < Sinatra::Base
134135
set :logging, true
136+
set :cf_api_info_location, ''
135137

136138
configure :production, :development, :test do
137139
$datasource = DataSource.new
@@ -160,6 +162,11 @@ def log_response(status, body)
160162
body
161163
end
162164

165+
def get_info_endpoint(request)
166+
api_info_location_header = request.env['HTTP_X_API_INFO_LOCATION']
167+
settings.cf_api_info_location = api_info_location_header
168+
end
169+
163170
def respond_with_behavior(behavior, accepts_incomplete=false)
164171
sleep behavior['sleep_seconds']
165172

@@ -187,8 +194,23 @@ def respond_from_config(behavior)
187194
end
188195
end
189196

197+
def cf_respond_with_api_info_location(cf_api_info_location)
198+
if cf_api_info_location.empty?
199+
status 503
200+
log_response(status, JSON.pretty_generate({
201+
error: true,
202+
message: "CF API info URL not known - either the cloud controller has not called the broker API yet, or it has failed to include a X-Api-Info-Location header that was a valid URL",
203+
path: request.url,
204+
type: '503'
205+
}))
206+
else
207+
log_response(status, cf_api_info_location)
208+
end
209+
end
210+
190211
before do
191212
log(request)
213+
get_info_endpoint(request) if request.path.start_with?("/v2/")
192214
end
193215

194216
# fetch catalog
@@ -319,6 +341,10 @@ def respond_from_config(behavior)
319341
log_response(status, JSON.pretty_generate($datasource.without_instances_or_bindings))
320342
end
321343

344+
get '/cf_api_info_url' do
345+
cf_respond_with_api_info_location(settings.cf_api_info_location)
346+
end
347+
322348
error do
323349
status 500
324350
e = env['sinatra.error']

assets/service_broker/spec/service_broker_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'spec_helper'
22
require 'json'
3+
require 'timeout'
34

45
describe ServiceBroker do
56
before do
@@ -200,6 +201,33 @@
200201
end
201202
end
202203

204+
describe 'cf api info location' do
205+
api_not_known_error = JSON.pretty_generate({
206+
"error" => true,
207+
"message" => "CF API info URL not known - either the cloud controller has not called the broker API yet, or it has failed to include a X-Api-Info-Location header that was a valid URL",
208+
"path" => "http://example.org/cf_api_info_url",
209+
"type" => '503'
210+
})
211+
212+
context "no request to a /v2 endpoint has been made yet" do
213+
it 'responds with internal server error' do
214+
get '/cf_api_info_url'
215+
expect(last_response.status).to eq(503)
216+
expect(last_response.body).to eq(api_not_known_error)
217+
end
218+
end
219+
220+
context "a /v2 request from the cloud controller had the X-Api-Info-Location header set to a valid url" do
221+
it 'receives a url in response' do
222+
info_endpoint = 'system-domain.com/v2/info'
223+
get '/v2/catalog', nil, { 'HTTP_X_API_INFO_LOCATION' => info_endpoint }
224+
get '/cf_api_info_url'
225+
expect(last_response.body).to eq(info_endpoint)
226+
expect(last_response.status).to eq(200)
227+
end
228+
end
229+
end
230+
203231
describe 'configuration management' do
204232
before do
205233
post '/config/reset'

helpers/services/broker.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"io/ioutil"
7+
"net/http"
78
"strings"
89

910
. "github.com/onsi/gomega"
@@ -123,6 +124,18 @@ func (b ServiceBroker) PushWithBuildpackAndManifest(config cats_config.CatsConfi
123124
).Wait(Config.BrokerStartTimeoutDuration())).To(Exit(0))
124125
}
125126

127+
func (b ServiceBroker) GetApiInfoUrl() string {
128+
brokerURL := helpers.AppUri(b.Name, "/cf_api_info_url", Config)
129+
resp, err := http.Get(brokerURL)
130+
Expect(err == nil)
131+
Expect(resp.StatusCode == 200)
132+
133+
respData, err := ioutil.ReadAll(resp.Body)
134+
Expect(err == nil)
135+
resp.Body.Close()
136+
return string(respData)
137+
}
138+
126139
func (b ServiceBroker) Configure() {
127140
Expect(helpers.Curl(Config, helpers.AppUri(b.Name, "/config", Config), "-d", b.ToJSON()).Wait()).To(Exit(0))
128141
}

helpers/services/sso.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,19 @@ func ParseJsonResponse(response []byte) (resultMap map[string]interface{}) {
3636
return
3737
}
3838

39-
func SetOauthEndpoints(apiEndpoint string, oAuthConfig *OAuthConfig, config cats_config.CatsConfig) {
39+
func SetOauthEndpoints(apiInfoEndpoint string, oAuthConfig *OAuthConfig, config cats_config.CatsConfig) {
4040
args := []string{}
4141
if config.GetSkipSSLValidation() {
4242
args = append(args, "--insecure")
4343
}
44-
args = append(args, fmt.Sprintf("%v/info", apiEndpoint))
44+
args = append(args, apiInfoEndpoint)
4545
curl := helpers.Curl(Config, args...).Wait()
4646
Expect(curl).To(Exit(0))
4747
apiResponse := curl.Out.Contents()
4848
jsonResult := ParseJsonResponse(apiResponse)
4949

5050
oAuthConfig.TokenEndpoint = fmt.Sprintf("%v", jsonResult[`token_endpoint`])
5151
oAuthConfig.AuthorizationEndpoint = fmt.Sprintf("%v", jsonResult[`authorization_endpoint`])
52-
return
5352
}
5453

5554
func AuthenticateUser(authorizationEndpoint string, username string, password string) (cookie string) {
@@ -148,10 +147,9 @@ func GetAccessToken(authCode string, config OAuthConfig) (accessToken string) {
148147
return
149148
}
150149

151-
func QueryServiceInstancePermissionEndpoint(apiEndpoint string, accessToken string, serviceInstanceGuid string) (canManage string, httpCode string) {
152-
canManage = `not populated`
150+
func QueryServiceInstancePermissionEndpoint(apiEndpoint string, accessToken string, serviceInstanceGuid string) (canRead string, canManage string, httpCode string) {
153151
authHeader := fmt.Sprintf("Authorization: bearer %v", accessToken)
154-
permissionsUri := fmt.Sprintf("%v/v2/service_instances/%v/permissions", apiEndpoint, serviceInstanceGuid)
152+
permissionsUri := fmt.Sprintf("%v/v3/service_instances/%v/permissions", apiEndpoint, serviceInstanceGuid)
155153

156154
curl := helpers.Curl(Config, permissionsUri, `-H`, authHeader, `-w`, `:TestReponseCode:%{http_code}`, `--insecure`, `-v`).Wait()
157155
Expect(curl).To(Exit(0))
@@ -164,6 +162,7 @@ func QueryServiceInstancePermissionEndpoint(apiEndpoint string, accessToken stri
164162
if httpCode == `200` {
165163
jsonResult := ParseJsonResponse([]byte(resultText))
166164
canManage = fmt.Sprintf("%v", jsonResult[`manage`])
165+
canRead = fmt.Sprintf("%v", jsonResult[`read`])
167166
}
168167

169168
return

services/sso_lifecycle.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var _ = ServicesDescribe("SSO Lifecycle", func() {
2525
if !Config.GetIncludeSSO() {
2626
Skip(skip_messages.SkipSSOMessage)
2727
}
28+
apiEndpoint = Config.Protocol() + Config.GetApiEndpoint()
2829
broker = NewServiceBroker(
2930
random_name.CATSRandomName("BRKR"),
3031
assets.NewAssets().ServiceBroker,
@@ -40,10 +41,10 @@ var _ = ServicesDescribe("SSO Lifecycle", func() {
4041
oauthConfig.RedirectUri = redirectUri
4142
oauthConfig.RequestedScopes = `openid,cloud_controller_service_permissions.read`
4243

43-
apiEndpoint = Config.Protocol() + Config.GetApiEndpoint()
44-
SetOauthEndpoints(apiEndpoint, &oauthConfig, Config)
45-
4644
broker.Create()
45+
46+
apiInfoEndpoint := Config.Protocol() + broker.GetApiInfoUrl() // the CF API must have already called the broker for the first time with its HTTP_X_API_INFO_LOCATION header
47+
SetOauthEndpoints(apiInfoEndpoint, &oauthConfig, Config)
4748
})
4849

4950
AfterEach(func() {
@@ -75,10 +76,11 @@ var _ = ServicesDescribe("SSO Lifecycle", func() {
7576
accessToken := GetAccessToken(authCode, oauthConfig)
7677

7778
// use the access token to perform an operation on the user's behalf
78-
canManage, httpCode := QueryServiceInstancePermissionEndpoint(apiEndpoint, accessToken, serviceInstanceGuid)
79+
canRead, canManage, httpCode := QueryServiceInstancePermissionEndpoint(apiEndpoint, accessToken, serviceInstanceGuid)
7980

8081
Expect(httpCode).To(Equal(`200`), `The provided access token was not valid.`)
8182
Expect(canManage).To(Equal(`true`))
83+
Expect(canRead).To(Equal(`true`))
8284
})
8385
})
8486

@@ -111,10 +113,11 @@ var _ = ServicesDescribe("SSO Lifecycle", func() {
111113
accessToken := GetAccessToken(authCode, oauthConfig)
112114

113115
// use the access token to perform an operation on the user's behalf
114-
canManage, httpCode := QueryServiceInstancePermissionEndpoint(apiEndpoint, accessToken, serviceInstanceGuid)
116+
canRead, canManage, httpCode := QueryServiceInstancePermissionEndpoint(apiEndpoint, accessToken, serviceInstanceGuid)
115117

116118
Expect(httpCode).To(Equal(`200`), `The provided access token was not valid.`)
117119
Expect(canManage).To(Equal(`true`))
120+
Expect(canRead).To(Equal(`true`))
118121
})
119122
})
120123

0 commit comments

Comments
 (0)