File: e2e.rb

package info (click to toggle)
ruby-oauth2 2.0.17-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,608 kB
  • sloc: ruby: 5,441; javascript: 529; makefile: 4; sh: 4
file content (138 lines) | stat: -rw-r--r-- 4,689 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# frozen_string_literal: true

# End-to-end example using oauth2 gem against a local mock-oauth2-server.
# Prerequisites:
#   1) Start the mock server (HTTP on 8080):
#        docker compose -f docker-compose-ssl.yml up -d --wait
#   2) Run this script:
#        ruby examples/e2e.rb
#   3) Stop the server when you're done:
#        docker compose -f docker-compose-ssl.yml down
# Notes:
#   - The mock server uses a self-signed certificate. SSL verification is disabled in this example.
#   - Tested down to Ruby 2.4 (avoid newer syntax).

require "oauth2"
require "json"
require "net/http"
require "uri"

module E2E
  class ClientCredentialsDemo
    attr_reader :client_id, :client_secret, :issuer_base, :realm

    # issuer_base: e.g., https://localhost:8080
    # realm: mock-oauth2-server issuer id ("default" by default)
    def initialize(client_id, client_secret, issuer_base, realm)
      @client_id = client_id
      @client_secret = client_secret
      @issuer_base = issuer_base
      @realm = realm
    end

    def run
      wait_for_server_ready
      well_known = discover
      token = fetch_token(well_known)
      puts "Access token (truncated): #{token.token[0, 20]}..."
      call_userinfo(well_known, token)
      puts "E2E complete"
    end

  private

    def discovery_url
      File.join(@issuer_base, @realm, "/.well-known/openid-configuration")
    end

    def wait_for_server_ready(timeout = nil)
      timeout = (timeout || ENV["E2E_WAIT_TIMEOUT"] || 90).to_i
      uri = URI(discovery_url)
      deadline = Time.now + timeout
      announced = false
      loop do
        begin
          http = Net::HTTP.new(uri.host, uri.port)
          http.use_ssl = uri.scheme == "https"
          http.verify_mode = OpenSSL::SSL::VERIFY_NONE
          req = Net::HTTP::Get.new(uri.request_uri)
          res = http.request(req)
          return if res.code.to_i == 200
        rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET, SocketError, EOFError, OpenSSL::SSL::SSLError
          # ignore and retry until timeout
        end
        unless announced
          puts "Waiting for mock OAuth2 server at #{uri} ..."
          announced = true
        end
        break if Time.now >= deadline
        sleep(0.5)
      end
      raise "Server not reachable at #{uri} within #{timeout}s. Ensure it's running: docker compose -f docker-compose-ssl.yml up -d --wait. You can increase the wait by setting E2E_WAIT_TIMEOUT (seconds)."
    end

    def discover
      uri = URI(discovery_url)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = uri.scheme == "https"
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      req = Net::HTTP::Get.new(uri.request_uri)
      res = http.request(req)
      unless res.code.to_i == 200
        raise "Discovery failed: #{res.code} #{res.message} - #{res.body}"
      end
      data = JSON.parse(res.body)
      # Expect token_endpoint and possibly userinfo_endpoint
      data
    end

    def fetch_token(well_known)
      client = OAuth2::Client.new(
        @client_id,
        @client_secret,
        site: @issuer_base,
        token_url: URI.parse(well_known["token_endpoint"]).request_uri,
        ssl: {verify: false},
        auth_scheme: :request_body, # send client creds in request body (compatible default for mock servers)
      )
      # Use client_credentials grant
      client.client_credentials.get_token
    end

    def call_userinfo(well_known, token)
      userinfo = well_known["userinfo_endpoint"]
      unless userinfo
        puts "No userinfo_endpoint advertised by server; skipping userinfo call."
        return
      end
      uri = URI(userinfo)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = uri.scheme == "https"
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      req = Net::HTTP::Get.new(uri.request_uri)
      req["Authorization"] = "Bearer #{token.token}"
      res = http.request(req)
      puts "userinfo status: #{res.code} #{res.message}"
      if res.code.to_i == 200
        begin
          body = JSON.parse(res.body)
        rescue StandardError
          body = res.body
        end
        puts "userinfo body: #{body.inspect}"
      else
        puts "userinfo error body: #{res.body}"
      end
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  # These must match the mock server configuration (see config-ssl.json)
  client_id = ENV["E2E_CLIENT_ID"] || "demo-client"
  client_secret = ENV["E2E_CLIENT_SECRET"] || "demo-secret"
  issuer_base = ENV["E2E_ISSUER_BASE"] || "http://localhost:8080"
  realm = ENV["E2E_REALM"] || "default"

  E2E::ClientCredentialsDemo.new(client_id, client_secret, issuer_base, realm).run
end