Class: MangoApps::OAuth
- Inherits:
-
Object
- Object
- MangoApps::OAuth
- Defined in:
- lib/mangoapps/oauth.rb
Defined Under Namespace
Classes: Discovery
Instance Method Summary collapse
-
#authorization_url(state:, code_challenge: nil, code_challenge_method: "S256", extra_params: {}) ⇒ Object
Build an auth URL for browser-based login (PKCE optional).
-
#client ⇒ Object
OAuth2::Client using discovered endpoints.
-
#discover! ⇒ Object
Discover OIDC endpoints from well-known config.
- #discovery ⇒ Object
-
#get_token(authorization_code:, code_verifier: nil) ⇒ Object
Exchange code for tokens (PKCE verifier optional).
-
#get_userinfo(access_token) ⇒ Object
Get user info from OAuth userinfo endpoint.
-
#initialize(config) ⇒ OAuth
constructor
A new instance of OAuth.
- #load_token ⇒ Object
- #refresh!(token) ⇒ Object
Constructor Details
#initialize(config) ⇒ OAuth
Returns a new instance of OAuth.
13 14 15 |
# File 'lib/mangoapps/oauth.rb', line 13 def initialize(config) @config = config end |
Instance Method Details
#authorization_url(state:, code_challenge: nil, code_challenge_method: "S256", extra_params: {}) ⇒ Object
Build an auth URL for browser-based login (PKCE optional)
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/mangoapps/oauth.rb', line 70 def (state:, code_challenge: nil, code_challenge_method: "S256", extra_params: {}) params = { redirect_uri: @config.redirect_uri, scope: @config.scope, state: state, response_type: "code", }.merge(extra_params) if code_challenge params[:code_challenge] = code_challenge params[:code_challenge_method] = code_challenge_method end client.auth_code.(params) end |
#client ⇒ Object
OAuth2::Client using discovered endpoints
59 60 61 62 63 64 65 66 67 |
# File 'lib/mangoapps/oauth.rb', line 59 def client @client ||= ::OAuth2::Client.new( @config.client_id, @config.client_secret, site: @config.base_url, authorize_url: discovery., token_url: discovery.token_endpoint ) end |
#discover! ⇒ Object
Discover OIDC endpoints from well-known config
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 |
# File 'lib/mangoapps/oauth.rb', line 18 def discover! # Ensure we always use HTTPS for discovery base_url = @config.base_url base_url = base_url.gsub(/^http:/, 'https:') unless base_url.start_with?('https:') url = URI.parse("#{base_url}/.well-known/openid-configuration") begin # Use proper HTTPS handling for discovery http = Net::HTTP.new(url.host, url.port) http.use_ssl = (url.scheme == 'https') http.verify_mode = OpenSSL::SSL::VERIFY_PEER if http.use_ssl? request = Net::HTTP::Get.new(url) res = http.request(request) raise MangoApps::DiscoveryError, "OIDC discovery failed: #{res.code}" unless res.is_a?(Net::HTTPSuccess) data = JSON.parse(res.body) validate_discovery_data(data) @discovery = Discovery.new( data["issuer"], data["authorization_endpoint"], data["token_endpoint"], data["userinfo_endpoint"], data["end_session_endpoint"], data["jwks_uri"] ) rescue JSON::ParserError => e raise MangoApps::DiscoveryError, "Invalid JSON response from discovery endpoint: #{e.}" rescue Net::ReadTimeout, Net::OpenTimeout, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error => e raise MangoApps::DiscoveryError, "Failed to connect to discovery endpoint: #{e.}" rescue OpenSSL::SSL::SSLError => e raise MangoApps::DiscoveryError, "SSL connection failed for discovery endpoint: #{e.}" end end |
#discovery ⇒ Object
54 55 56 |
# File 'lib/mangoapps/oauth.rb', line 54 def discovery @discovery || discover! end |
#get_token(authorization_code:, code_verifier: nil) ⇒ Object
Exchange code for tokens (PKCE verifier optional)
87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/mangoapps/oauth.rb', line 87 def get_token(authorization_code:, code_verifier: nil) token = client.auth_code.get_token( , redirect_uri: @config.redirect_uri, headers: { "Content-Type" => "application/x-www-form-urlencoded" }, code_verifier: code_verifier ) persist(token) token rescue OAuth2::Error => e raise MangoApps::TokenExchangeError, "Token exchange failed: #{e.}" end |
#get_userinfo(access_token) ⇒ Object
Get user info from OAuth userinfo endpoint
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/mangoapps/oauth.rb', line 117 def get_userinfo(access_token) raise MangoApps::TokenExpiredError, "No access token provided" unless access_token userinfo_url = discovery.userinfo_endpoint # Ensure userinfo endpoint uses HTTPS userinfo_url = userinfo_url.gsub(/^http:/, 'https:') unless userinfo_url.start_with?('https:') url = URI.parse(userinfo_url) begin # Ensure we use the correct port for HTTPS port = url.port || (url.scheme == 'https' ? 443 : 80) http = Net::HTTP.new(url.host, port) http.use_ssl = (url.scheme == 'https') http.verify_mode = OpenSSL::SSL::VERIFY_PEER if http.use_ssl? request = Net::HTTP::Get.new(url) request['Authorization'] = "Bearer #{access_token}" request['Accept'] = 'application/json' response = http.request(request) # Handle redirects (301, 302) if response.is_a?(Net::HTTPRedirection) redirect_url = response['location'] if redirect_url # Follow the redirect with HTTPS redirect_uri = URI.parse(redirect_url) redirect_uri.scheme = 'https' if redirect_uri.scheme == 'http' http = Net::HTTP.new(redirect_uri.host, redirect_uri.port) http.use_ssl = (redirect_uri.scheme == 'https') http.verify_mode = OpenSSL::SSL::VERIFY_PEER if http.use_ssl? request = Net::HTTP::Get.new(redirect_uri) request['Authorization'] = "Bearer #{access_token}" request['Accept'] = 'application/json' response = http.request(request) end end unless response.is_a?(Net::HTTPSuccess) raise MangoApps::APIError, "Userinfo request failed: #{response.code} #{response.}" end JSON.parse(response.body) rescue JSON::ParserError => e raise MangoApps::APIError, "Invalid JSON response from userinfo endpoint: #{e.}" rescue Net::ReadTimeout, Net::OpenTimeout, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error => e raise MangoApps::APIError, "Failed to connect to userinfo endpoint: #{e.}" rescue OpenSSL::SSL::SSLError => e raise MangoApps::APIError, "SSL connection failed for userinfo endpoint: #{e.}" end end |
#load_token ⇒ Object
112 113 114 |
# File 'lib/mangoapps/oauth.rb', line 112 def load_token @config.token_store&.load end |
#refresh!(token) ⇒ Object
100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/mangoapps/oauth.rb', line 100 def refresh!(token) raise MangoApps::TokenExpiredError, "No refresh_token available" unless token&.refresh_token begin new_token = token.refresh! persist(new_token) new_token rescue OAuth2::Error => e raise MangoApps::TokenExpiredError, "Token refresh failed: #{e.}" end end |