Class: Clavis::Providers::Apple
- Defined in:
- lib/clavis/providers/apple.rb
Constant Summary collapse
- ISSUER =
"https://appleid.apple.com"- APPLE_AUTH_URL =
"#{ISSUER}/auth/authorize".freeze
- APPLE_TOKEN_URL =
"#{ISSUER}/auth/token".freeze
- APPLE_JWKS_URL =
"#{ISSUER}/auth/keys".freeze
- DEFAULT_CLIENT_SECRET_EXPIRY =
5 minutes in seconds
300
Instance Attribute Summary collapse
-
#authorized_client_ids ⇒ Object
readonly
Returns the value of attribute authorized_client_ids.
-
#client_options ⇒ Object
readonly
Returns the value of attribute client_options.
-
#key_id ⇒ Object
readonly
Returns the value of attribute key_id.
-
#private_key ⇒ Object
readonly
Returns the value of attribute private_key.
-
#private_key_path ⇒ Object
readonly
Returns the value of attribute private_key_path.
-
#team_id ⇒ Object
readonly
Returns the value of attribute team_id.
Attributes inherited from Base
#authorize_endpoint_url, #client_id, #client_secret, #provider_name, #redirect_uri, #scope, #token_endpoint_url, #userinfo_endpoint_url
Instance Method Summary collapse
- #authorization_endpoint ⇒ Object
- #authorize_url(state:, nonce:, scope: nil) ⇒ Object
- #default_scopes ⇒ Object
- #get_user_info(_access_token) ⇒ Object
-
#initialize(config = {}) ⇒ Apple
constructor
A new instance of Apple.
- #openid_provider? ⇒ Boolean
- #process_callback(code, user_data = nil) ⇒ Object
- #refresh_token(_refresh_token) ⇒ Object
- #token_endpoint ⇒ Object
-
#token_exchange(code:, **kwargs) ⇒ Object
Using keyword arguments with support for state verification (for compatibility).
- #userinfo_endpoint ⇒ Object
Methods included from TokenExchangeHandler
#build_token_exchange_params, #handle_connection_error, #handle_error_response, #handle_faraday_error, #handle_parser_error, #handle_standard_error, #make_token_request, #parse_response, #skip_error_for_test?, #test_token_response, #validate_and_clean_code
Constructor Details
#initialize(config = {}) ⇒ Apple
Returns a new instance of Apple.
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 |
# File 'lib/clavis/providers/apple.rb', line 21 def initialize(config = {}) @team_id = config[:team_id] || ENV.fetch("APPLE_TEAM_ID", nil) @key_id = config[:key_id] || ENV.fetch("APPLE_KEY_ID", nil) @private_key = config[:private_key] || ENV.fetch("APPLE_PRIVATE_KEY", nil) @private_key_path = config[:private_key_path] || ENV.fetch("APPLE_PRIVATE_KEY_PATH", nil) @authorized_client_ids = config[:authorized_client_ids] || [] @client_secret_expiry = config[:client_secret_expiry] || DEFAULT_CLIENT_SECRET_EXPIRY @client_options = config[:client_options] || {} # Set up endpoints with potential overrides from client_options endpoints = { authorization_endpoint: @client_options[:authorize_url] || APPLE_AUTH_URL, token_endpoint: @client_options[:token_url] || APPLE_TOKEN_URL, userinfo_endpoint: nil # Apple doesn't have a userinfo endpoint } # Override base URL if site is specified if @client_options[:site] base_uri = URI.parse(@client_options[:site]) auth_uri = URI.parse(endpoints[:authorization_endpoint]) token_uri = URI.parse(endpoints[:token_endpoint]) # Only override the host, keep the paths auth_uri.scheme = base_uri.scheme auth_uri.host = base_uri.host token_uri.scheme = base_uri.scheme token_uri.host = base_uri.host endpoints[:authorization_endpoint] = auth_uri.to_s endpoints[:token_endpoint] = token_uri.to_s end config.merge!(endpoints) super end |
Instance Attribute Details
#authorized_client_ids ⇒ Object (readonly)
Returns the value of attribute authorized_client_ids.
13 14 15 |
# File 'lib/clavis/providers/apple.rb', line 13 def @authorized_client_ids end |
#client_options ⇒ Object (readonly)
Returns the value of attribute client_options.
13 14 15 |
# File 'lib/clavis/providers/apple.rb', line 13 def @client_options end |
#key_id ⇒ Object (readonly)
Returns the value of attribute key_id.
13 14 15 |
# File 'lib/clavis/providers/apple.rb', line 13 def key_id @key_id end |
#private_key ⇒ Object (readonly)
Returns the value of attribute private_key.
13 14 15 |
# File 'lib/clavis/providers/apple.rb', line 13 def private_key @private_key end |
#private_key_path ⇒ Object (readonly)
Returns the value of attribute private_key_path.
13 14 15 |
# File 'lib/clavis/providers/apple.rb', line 13 def private_key_path @private_key_path end |
#team_id ⇒ Object (readonly)
Returns the value of attribute team_id.
13 14 15 |
# File 'lib/clavis/providers/apple.rb', line 13 def team_id @team_id end |
Instance Method Details
#authorization_endpoint ⇒ Object
57 58 59 |
# File 'lib/clavis/providers/apple.rb', line 57 def @authorize_endpoint_url end |
#authorize_url(state:, nonce:, scope: nil) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/clavis/providers/apple.rb', line 131 def (state:, nonce:, scope: nil) # Generate a more secure URL with form_post response mode params = { response_type: "code", client_id: client_id, redirect_uri: Clavis::Security::HttpsEnforcer.enforce_https(redirect_uri), scope: scope || default_scopes, state: state, nonce: nonce, response_mode: "form_post" # Required for getting user information } uri = URI.parse() uri.query = URI.encode_www_form(params) # Enforce HTTPS uri.scheme = "https" if Clavis.configuration.enforce_https && uri.scheme == "http" uri.to_s end |
#default_scopes ⇒ Object
69 70 71 |
# File 'lib/clavis/providers/apple.rb', line 69 def default_scopes "name email" end |
#get_user_info(_access_token) ⇒ Object
126 127 128 129 |
# File 'lib/clavis/providers/apple.rb', line 126 def get_user_info(_access_token) # Apple does not have a userinfo endpoint; user info is in the ID token raise Clavis::UnsupportedOperation, "Apple does not have a userinfo endpoint" end |
#openid_provider? ⇒ Boolean
73 74 75 |
# File 'lib/clavis/providers/apple.rb', line 73 def openid_provider? true end |
#process_callback(code, user_data = nil) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/clavis/providers/apple.rb', line 152 def process_callback(code, user_data = nil) clean_code = code.to_s.gsub(/\A["']|["']\Z/, "") token_data = token_exchange(code: clean_code, user_data: user_data) # Extract user info from id_token and/or user_data user_info = extract_user_info(token_data) # For OpenID Connect, use sub claim as identifier uid = if token_data[:id_token_claims]&.dig(:sub) token_data[:id_token_claims][:sub] else # Generate a hash as fallback data_for_hash = "#{provider_name}:#{token_data[:access_token] || ""}:#{user_info[:email] || ""}" Digest::SHA1.hexdigest(data_for_hash)[0..19] end { provider: provider_name, uid: uid, info: user_info, credentials: { token: token_data[:access_token], refresh_token: token_data[:refresh_token], expires_at: token_data[:expires_at], expires: token_data[:expires_at] && !token_data[:expires_at].nil? }, id_token: token_data[:id_token], id_token_claims: token_data[:id_token_claims] || {} } end |
#refresh_token(_refresh_token) ⇒ Object
77 78 79 80 81 |
# File 'lib/clavis/providers/apple.rb', line 77 def refresh_token(_refresh_token) # Apple doesn't support the standard OAuth refresh token flow # Instead, they use long-lived tokens that don't need refreshing raise Clavis::UnsupportedOperation, "Apple does not support refresh tokens" end |
#token_endpoint ⇒ Object
61 62 63 |
# File 'lib/clavis/providers/apple.rb', line 61 def token_endpoint @token_endpoint_url end |
#token_exchange(code:, **kwargs) ⇒ Object
Using keyword arguments with support for state verification (for compatibility)
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 |
# File 'lib/clavis/providers/apple.rb', line 84 def token_exchange(code:, **kwargs) # Validate inputs raise Clavis::InvalidGrant unless Clavis::Security::InputValidator.valid_code?(code) params = { grant_type: "authorization_code", code: code, redirect_uri: redirect_uri, client_id: client_id, client_secret: generate_client_secret } response = http_client.post(token_endpoint, params) handle_token_error_response(response) if response.status != 200 token_data = parse_token_response(response) # If id_token is present, verify and extract claims if token_data[:id_token] && !token_data[:id_token].empty? begin token_data[:id_token_claims] = verify_and_decode_id_token( token_data[:id_token], kwargs[:nonce] ) rescue StandardError => e Clavis.logger.warn("Failed to verify ID token: #{e.}") end end # Process user data if available if kwargs[:user_data] && !kwargs[:user_data].empty? begin token_data[:user_info] = JSON.parse(kwargs[:user_data]) rescue JSON::ParserError => e Clavis.logger.warn("Failed to parse user data: #{e.}") end end token_data end |
#userinfo_endpoint ⇒ Object
65 66 67 |
# File 'lib/clavis/providers/apple.rb', line 65 def userinfo_endpoint nil # Apple does not have a userinfo endpoint end |