main
1# frozen_string_literal: true
2
3ENV['RAILS_ENV'] ||= 'test'
4require File.expand_path('../config/environment', __dir__)
5require 'rspec/rails'
6require 'vcr'
7require 'ffaker'
8require 'factory_bot_rails'
9
10$server = Capybara::Server.new(Rack::Builder.new do
11 map "/" do
12 run Rails.application
13 end
14end.to_app)
15
16RSpec.configure do |config|
17 config.include FactoryBot::Syntax::Methods
18 config.include(Module.new do
19 def server
20 $server
21 end
22 end)
23
24 config.before :suite do
25 puts "Booting"
26 $server.boot
27 print "." until $server.responsive?
28 FileUtils.rm_rf(Rails.root.join('tmp', '_cassettes'))
29 Net::Hippie.logger = Logger.new('/dev/null')
30 VCR.configure do |x|
31 x.cassette_library_dir = "tmp/_cassettes"
32 x.hook_into :webmock
33 end
34 end
35
36 config.after :suite do
37 erb = ERB.new(IO.read('doc/_includes/curl.erb'))
38 Dir["tmp/_cassettes/**/*.yml"].each do |cassette|
39 @configuration = YAML.safe_load(IO.read(cassette))
40 result = erb.result(binding)
41 IO.write("doc/_includes/#{File.basename(cassette).parameterize.gsub(/-yml/, '')}.html", result)
42 end
43 end
44end
45
46class UserAgent
47 def login_with(scheme: 'https', host:, port:, email:, password:, issuer:)
48 authn_request = Saml::Kit::AuthenticationRequest.build(configuration: Saml::Kit.configuration) do |x|
49 x.issuer = issuer
50 x.embed_signature = false
51 end
52 body = { SAMLRequest: Base64.strict_encode64(authn_request.to_xml) }
53 response = client.post("#{scheme}://#{host}:#{port}/session/new", body: URI.encode_www_form(body))
54 form = Nokogiri::HTML(response.body).css('form').last
55 authenticity_token = form&.css('[name=authenticity_token]')&.first&.attribute('value')&.value
56 body.merge!(
57 'authenticity_token' => authenticity_token,
58 'user[email]' => email,
59 'user[password]' => password
60 )
61 session_cookie = parse_cookie(response['Set-Cookie'])
62 response = client.post("#{scheme}://#{host}:#{port}/session", headers: { 'Cookie' => session_cookie }, body: URI.encode_www_form(body))
63
64 session_cookie = parse_cookie(response['Set-Cookie'])
65 response = client.get(response['Location'], headers: { 'Cookie' => session_cookie })
66 encoded_saml_response = Nokogiri::HTML(response.body).css('#SAMLResponse').attribute('value').value
67 Saml::Kit::Bindings::HttpPost.new(location: '').deserialize(SAMLResponse: encoded_saml_response)
68 end
69
70 private
71
72 def client
73 @client ||= Net::Hippie::Client.new(verify_mode: OpenSSL::SSL::VERIFY_NONE, headers: {})
74 end
75
76 def parse_cookie(value)
77 value.split(';')[0]
78 end
79end
80
81RSpec.describe "documentation" do
82 let(:hippie) { Net::Hippie::Client.new(verify_mode: OpenSSL::SSL::VERIFY_NONE) }
83 let(:client) { create(:client) }
84 let(:user) { create(:user) }
85 let(:user_agent) { UserAgent.new }
86 let(:scheme) { 'http' }
87 let(:host) { server.host }
88 let(:port) { server.port }
89 let(:url_prefix) { "#{scheme}://#{host}:#{port}" }
90
91 specify do
92 VCR.use_cassette("get-well-known-oauth-authorization-server") do
93 response = hippie.get("#{url_prefix}/.well-known/oauth-authorization-server")
94 expect(response.code).to eql('200')
95 end
96 end
97
98 specify do
99 authorization = create(:authorization, client: client)
100 headers = { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(client.to_param, client.password) }
101 body = { grant_type: 'authorization_code', code: authorization.code }
102 VCR.use_cassette("oauth-tokens-authorization-code") do
103 response = hippie.post("#{url_prefix}/oauth/tokens", body: body, headers: headers)
104 expect(response.code).to eql('200')
105 end
106 end
107
108 specify do
109 headers = { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(client.to_param, client.password) }
110 body = { grant_type: 'password', username: user.email, password: user.password }
111 VCR.use_cassette("oauth-tokens-password") do
112 response = hippie.post("#{url_prefix}/oauth/tokens", body: body, headers: headers)
113 expect(response.code).to eql('200')
114 end
115 end
116
117 specify do
118 headers = { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(client.to_param, client.password) }
119 body = { grant_type: 'client_credentials' }
120 VCR.use_cassette("oauth-tokens-client-credentials") do
121 response = hippie.post("#{url_prefix}/oauth/tokens", body: body, headers: headers)
122 expect(response.code).to eql('200')
123 end
124 end
125
126 xspecify do
127 VCR.use_cassette("oauth-tokens-saml-assertion") do
128 saml = user_agent.login_with(scheme: scheme, host: host, port: port, email: user.email, password: user.password, issuer: 'https://saml-kit-airport.herokuapp.com/service_providers/73db6338-5d35-4271-812c-d4c6fbe45cca')
129 headers = { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(client.to_param, client.password) }
130 body = { grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer', assertion: Base64.urlsafe_encode64(saml.assertion.to_xml) }
131 response = hippie.post("#{url_prefix}/oauth/tokens", body: body, headers: headers)
132 expect(response.code).to eql('200')
133 end
134 end
135
136 specify do
137 headers = { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(client.to_param, client.password) }
138 refresh_token = create(:refresh_token, audience: client)
139 body = { grant_type: 'refresh_token', refresh_token: refresh_token.to_jwt }
140 VCR.use_cassette("oauth-tokens-refresh-token") do
141 response = hippie.post("#{url_prefix}/oauth/tokens", body: body, headers: headers)
142 expect(response.code).to eql('200')
143 end
144 end
145
146 specify do
147 body = {
148 redirect_uris: [generate(:uri), generate(:uri)],
149 client_name: FFaker::Name.name,
150 token_endpoint_auth_method: :client_secret_basic,
151 logo_uri: generate(:uri),
152 jwks_uri: generate(:uri),
153 }
154 VCR.use_cassette("oauth-dynamic-client-registration") do
155 response = hippie.post("#{url_prefix}/oauth/clients", body: body)
156 expect(response.code).to eql('201')
157 end
158 end
159
160 specify do
161 code_verifier = SecureRandom.hex(128)
162 authorization = create(:authorization, client: client, challenge: Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)), challenge_method: :sha256)
163 headers = { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(client.to_param, client.password) }
164 body = { grant_type: 'authorization_code', code: authorization.code, code_verifier: code_verifier }
165 VCR.use_cassette("oauth-tokens-pkce") do
166 response = hippie.post("#{url_prefix}/oauth/tokens", body: body, headers: headers)
167 expect(response.code).to eql('200')
168 end
169 end
170
171 specify do
172 token = create(:access_token)
173 headers = { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(client.to_param, client.password) }
174 body = { token: token.to_jwt }
175 VCR.use_cassette("oauth-token-introspection") do
176 response = hippie.post("#{url_prefix}/oauth/tokens/introspect", body: body, headers: headers)
177 expect(response.code).to eql('200')
178 end
179 end
180
181 specify do
182 VCR.use_cassette("scim-service-provider-config") do
183 response = hippie.get("#{url_prefix}/scim/v2/ServiceProviderConfig")
184 expect(response.code).to eql('200')
185 end
186 end
187
188 specify do
189 VCR.use_cassette("scim-schemas") do
190 headers = { 'Content-Type' => Mime[:scim].to_s }
191 response = hippie.get("#{url_prefix}/scim/v2/Schemas", headers: headers)
192 expect(response.code).to eql('200')
193 end
194 end
195
196 specify do
197 VCR.use_cassette("scim-schemas-users") do
198 headers = { 'Content-Type' => Mime[:scim].to_s }
199 response = hippie.get("#{url_prefix}/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User", headers: headers)
200 expect(response.code).to eql('200')
201 end
202 end
203
204 specify do
205 VCR.use_cassette("scim-schemas-groups") do
206 headers = { 'Content-Type' => Mime[:scim].to_s }
207 response = hippie.get("#{url_prefix}/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group", headers: headers)
208 expect(response.code).to eql('200')
209 end
210 end
211
212 specify do
213 VCR.use_cassette("scim-resource-types") do
214 headers = { 'Content-Type' => Mime[:scim].to_s }
215 response = hippie.get("#{url_prefix}/scim/v2/ResourceTypes", headers: headers)
216 expect(response.code).to eql('200')
217 end
218 end
219
220 specify do
221 VCR.use_cassette("scim-resource-type-user") do
222 headers = { 'Content-Type' => Mime[:scim].to_s }
223 response = hippie.get("#{url_prefix}/scim/v2/ResourceTypes/User", headers: headers)
224 expect(response.code).to eql('200')
225 end
226 end
227
228 specify do
229 VCR.use_cassette("scim-resource-type-group") do
230 headers = { 'Content-Type' => Mime[:scim].to_s }
231 response = hippie.get("#{url_prefix}/scim/v2/ResourceTypes/Group", headers: headers)
232 expect(response.code).to eql('200')
233 end
234 end
235end