Module: DWH::Adapters::OpenAuthorizable

Included in:
Databricks, Snowflake
Defined in:
lib/dwh/adapters/open_authorizable.rb

Overview

OpenAuthorizable aka OAuth module will add functionality to get and refresh access tokens for databases that supported OAuth.

To use this module include it in your adapter and call the oauth_with class method.

Examples:

Endpoint that needs to use instance to generate

oauth_with authorize: ->(adapter) { "url#{config[:val]}"}, tokenize: "http://blue.com"

Get authorization_url

adapter.authorization_url
Then capture the code and gen tokens

Generate acess tokens

adapter.generate_oauth_tokens(code_from_authorization)
# this will also apply the tokens

Reuse cached tokens

adapter.apply_oauth_tokens(access_token: 'myaccesstoken', refresh_token: 'rtoken', expires_at: Time.now)

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



42
43
44
45
46
47
48
49
# File 'lib/dwh/adapters/open_authorizable.rb', line 42

def self.included(base)
  base.extend(ClassMethods)
  base.include(TokenManageable)
  base.config :oauth_client_id, String, required: false, message: 'OAuth client_id'
  base.config :oauth_client_secret, String, required: false, message: 'OAuth client_secret'
  base.config :oauth_redirect_uri, String, required: false, message: 'OAuth redirect_uri'
  base.config :oauth_scope, String, required: false, message: 'OAuth scope'
end

Instance Method Details

#apply_oauth_tokens(access_token: nil, refresh_token: nil, expires_at: nil) ⇒ Object

You can reuse existing tokens that were saved outside of this app by passing it here. This could be tokens cached from a previous call to @see #generate_oauth_tokens

param access_token [String] the access token

Parameters:

  • refresh_token (String) (defaults to: nil)

    optional refresh token



76
77
78
79
80
# File 'lib/dwh/adapters/open_authorizable.rb', line 76

def apply_oauth_tokens(access_token: nil, refresh_token: nil, expires_at: nil)
  @oauth_access_token = access_token
  @oauth_refresh_token = refresh_token
  @token_expires_at = expires_at
end

#authorization_url(state: SecureRandom.hex(16), scope: nil) ⇒ Object

Generate authorization URL for user to visit



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/dwh/adapters/open_authorizable.rb', line 52

def authorization_url(state: SecureRandom.hex(16), scope: nil)
  raise UnsupportedCapability, "#{adapter_name} does not support authorization-code OAuth flow" unless oauth_supports_authorization_code_flow?

  code_verifier = oauth_pkce_code_verifier_for_session
  params = {
    'response_type' => 'code',
    'client_id' => oauth_client_id,
    'redirect_uri' => oauth_redirect_uri,
    'state' => state,
    'scope' => scope || oauth_scope || oauth_settings[:default_scope]
  }.compact
  params.merge!(oauth_pkce_authorization_params(code_verifier))

  uri = URI(oauth_settings[:authorize])
  uri.query = URI.encode_www_form(params)
  uri.to_s
end

#generate_oauth_tokens(authorization_code) ⇒ Object

Takes the given authorization code and generates new access and refresh tokens. It will also apply them.

Parameters:

  • authorization_code (String)

    this code should come from the redirect that is captured from the #authorization_url

Raises:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/dwh/adapters/open_authorizable.rb', line 86

def generate_oauth_tokens(authorization_code)
  raise UnsupportedCapability, "#{adapter_name} does not support authorization-code OAuth flow" unless oauth_supports_authorization_code_flow?

  code_verifier = oauth_pkce_code_verifier_for_session
  params = {
    grant_type: 'authorization_code',
    code: authorization_code,
    redirect_uri: oauth_redirect_uri
  }
  params.merge!(oauth_pkce_token_params(code_verifier))

  response = oauth_http_client.post(oauth_tokenization_url) do |req|
    req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
    req.headers['Authorization'] = basic_auth_header
    req.body = URI.encode_www_form(params)
  end
  oauth_token_response(response)
end

#mint_access_tokenObject



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/dwh/adapters/open_authorizable.rb', line 119

def mint_access_token
  raise UnsupportedCapability, "#{adapter_name} does not support client-credentials OAuth flow" unless oauth_supports_client_credentials_flow?

  params = oauth_client_credentials_params
  response = oauth_http_client.post(oauth_tokenization_url) do |req|
    req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
    req.headers['Authorization'] = oauth_token_request_auth_header
    req.body = URI.encode_www_form(params)
  end

  oauth_token_response(response)
end

#oauth_access_tokenString

This will return the current access_token or if it expired and refresh_token token is available it will generate a new token.

Returns:

  • (String)

    access token

Raises:



138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/dwh/adapters/open_authorizable.rb', line 138

def oauth_access_token
  load_oauth_tokens_from_store! unless @oauth_access_token || @oauth_refresh_token
  return @oauth_access_token if oauth_token_usable?

  refresh_access_token if oauth_refresh_token_usable?
  return @oauth_access_token if oauth_token_usable?

  mint_access_token if oauth_supports_client_credentials_flow?
  return @oauth_access_token if oauth_token_usable?

  raise AuthenticationError,
        'Access token was never set. Either run the auth flow, mint via client credentials, or set tokens via apply_oauth_tokens.'
end

#oauth_authenticated?Boolean

Check if we have a valid access token

Returns:



153
154
155
# File 'lib/dwh/adapters/open_authorizable.rb', line 153

def oauth_authenticated?
  @oauth_access_token && oauth_token_usable?
end

#oauth_settingsObject



178
179
180
181
182
# File 'lib/dwh/adapters/open_authorizable.rb', line 178

def oauth_settings
  @oauth_settings ||= self.class.oauth_settings.transform_values do
    it.is_a?(Proc) ? it.call(self) : it
  end
end

#oauth_token_infoObject

Get current state of tokens



158
159
160
161
162
163
164
165
166
# File 'lib/dwh/adapters/open_authorizable.rb', line 158

def oauth_token_info
  {
    access_token: @oauth_access_token,
    refresh_token: @oauth_refresh_token,
    expires_at: @token_expires_at,
    expired: !oauth_token_usable?,
    authenticated: oauth_authenticated?
  }
end

#oauth_tokenization_urlObject



184
185
186
# File 'lib/dwh/adapters/open_authorizable.rb', line 184

def oauth_tokenization_url
  oauth_settings[:tokenize]
end

#refresh_access_tokenObject

Refresh access token using refresh token



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/dwh/adapters/open_authorizable.rb', line 106

def refresh_access_token
  raise AuthenticationError, 'No refresh token available' unless @oauth_refresh_token

  params = oauth_refresh_token_params
  response = oauth_http_client.post(oauth_tokenization_url) do |req|
    req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
    req.headers['Authorization'] = oauth_token_request_auth_header
    req.body = URI.encode_www_form(params)
  end

  oauth_token_response(response)
end

#validate_oauth_configObject

Raises:



168
169
170
171
172
173
174
175
176
# File 'lib/dwh/adapters/open_authorizable.rb', line 168

def validate_oauth_config
  raise ConfigError, 'Missing config: oauth_client_id. Required for OAuth.' unless config[:oauth_client_id]
  raise ConfigError, 'Missing config: oauth_client_secret. Required for OAuth.' unless config[:oauth_client_secret]

  raise ConfigError, 'Missing config: oauth_redirect_uri. Required for OAuth.' if oauth_redirect_uri_required? && !config[:oauth_redirect_uri]

  oauth_settings if oauth_supports_authorization_code_flow?
  true
end