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