Class: Oauth3

Inherits:
Object
  • Object
show all
Defined in:
lib/oauth3.rb

Instance Method Summary collapse

Constructor Details

#initialize(registrar, options = {}) ⇒ Oauth3

Returns a new instance of Oauth3.



17
18
19
20
21
22
23
24
25
# File 'lib/oauth3.rb', line 17

def initialize(registrar, options={})
  # make sure all options for the OAuth module and faraday
  # pass all the way down
  @options = options
  @states = {}
  @providers = {}
  @clients = {}
  @registrar = registrar
end

Instance Method Details

#authorization_code_callback(params) ⇒ Object



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
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
# File 'lib/oauth3.rb', line 104

def authorization_code_callback(params)
  # TODO needs error handling function to make this DRY

  server_state = params[:state] or params[:server_state]
  if not server_state
    if params[:error]
      return params
    else
      return { error: "E_NO_STATE", error_description: "provider_did_not_return_state" }
    end
  end

  meta_state = @@oauth3.get_state(server_state)
  if not meta_state
    return { error: "E_INVALID_SERVER_STATE", error_description: "server_state_missing_or_expired" }
  end

  # TODO provider_uri should not be necessary because we have state
  # (but I don't think oauth3.html implements that completely yet)
  # meta_state = { created_at, provider_uri, params }
  browser_params = meta_state[:params].merge({ provider_uri: meta_state[:provider_uri] })
  if not browser_params
    browser_params = { error: "E_SANITY_FAIL", error_description: "sanity fail: server_state missing browser_params" }
    params.delete(:state)
    return params.merge(browser_params)
  end

  browser_state = browser_params[:state] or browser_params[:browser_state]
  if not browser_state
    browser_params[:error] = "E_INVALID_BROWSER_STATE"
    browser_params[:error_description] = "server_state missing or expired"
    return browser_params
  end

  # TODO decide on one or the other. Favor explicit to avoid confusion?
  result_params = { browser_state: browser_state, state: browser_state }
  if params[:error]
    params.delete(:state)
    return params.merge(result_params)
  end

  code = params[:code]

  if not code
    result_params[:error] = "E_INVALID_BROWSER_STATE"
    result_params[:error_description] = "state_missing_or_expired"
    params.delete(:state)
    return params.merge(result_params)
  end

  provider_uri = browser_params[:provider_uri]
  if token = @@oauth3.get_token(provider_uri, code).token
    result_params[:access_token] = token
  end

  # Note in the future the server will send back
  #   granted_scopes=foo,bar,baz,etc
  #   expires_at=2015-04-01T12:30:00.000Z
  #   app_scoped_id=<<default-account-app-scoped-id>>
  params.delete(:state)
  params.merge(result_params)
end

#authorize_url(provider_uri, params) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/oauth3.rb', line 84

def authorize_url(provider_uri, params)
  provider_uri = @@oauth3.normalize_provider_uri(provider_uri)
  authorization_code_callback_uri = @options[:authorization_code_callback_uri] || @options[:redirect_uri]
  rnd = random_string()

  @states[rnd] = {
    created_at: Time.now,
    provider_uri: provider_uri,
    params: params
  }

  get_oauth2_client(provider_uri).auth_code.authorize_url({
    # Note that no parameters can be passed tothis redirect
    # It is required to be verbatim for Facebook and probably many other providers
    redirect_uri: authorization_code_callback_uri,
    scope: params[:scope],
    state: rnd
  })
end

#get_directive(provider_uri) ⇒ Object



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
# File 'lib/oauth3.rb', line 31

def get_directive(provider_uri)
  if @providers[provider_uri] # and @directive.timestamp < 1.day.old
    return @providers[provider_uri][:directive]
  end

  registration = @registrar.get(provider_uri)
  dynamic = true

  if registration and registration['directives']
    directives = registration['directives']
    dynamic = false
  else
    # TODO if there's no prefix (https://), add it first
    # TODO if the directive is stale, refresh it
    http = HTTPClient.new()
    response = http.get_content("#{provider_uri}/oauth3.json")
    directives = JSON.parse(response)
  end

  @providers[provider_uri] = {
    provider_uri: provider_uri,
    directive: directives,
    timestamp: Time.now,
    dynamic: dynamic
  }
  @providers[provider_uri][:directive]
end

#get_oauth2_client(provider_uri) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/oauth3.rb', line 59

def get_oauth2_client(provider_uri)
  # TODO refresh the client when refreshing the directive
  if @clients[provider_uri]
    return @clients[provider_uri]
  end

  client_options = @options.dup
  client_options[:site] = ""
  client_options[:authorize_url] = get_directive(provider_uri)['authorization_dialog']['url']
  client_options[:token_url] = get_directive(provider_uri)['access_token']['url']
  token_method = (get_directive(provider_uri)['access_token']['method'] || 'POST').downcase.to_sym
  client_options[:token_method] = token_method

  @clients[provider_uri] = OAuth2::Client.new(
    @registrar.get(provider_uri)['id'],
    @registrar.get(provider_uri)['secret'],
    client_options
  )
end

#get_profile(provider_uri, token) ⇒ Object



182
183
184
185
# File 'lib/oauth3.rb', line 182

def get_profile(provider_uri, token)
  url = get_directive(provider_uri)['profile']['url']
  OAuth2::AccessToken.new(get_oauth2_client(provider_uri), token).get(url)
end

#get_resource(provider_uri, token, path) ⇒ Object



187
188
189
190
# File 'lib/oauth3.rb', line 187

def get_resource(provider_uri, token, path)
  url = get_directive(provider_uri)['api_base_url']
  OAuth2::AccessToken.new(get_oauth2_client(provider_uri), token).get("#{url}/#{path}")
end

#get_state(state) ⇒ Object



167
168
169
# File 'lib/oauth3.rb', line 167

def get_state(state)
  @states.delete(state)
end

#get_token(provider_uri, code) ⇒ Object



176
177
178
179
180
# File 'lib/oauth3.rb', line 176

def get_token(provider_uri, code)
  get_oauth2_client(provider_uri).auth_code.get_token(code, {
    redirect_uri: @options[:authorization_code_callback_uri]
  })
end

#normalize_provider_uri(uri) ⇒ Object



27
28
29
# File 'lib/oauth3.rb', line 27

def normalize_provider_uri(uri)
  'https://' + uri.gsub(/https?:\/\//, '')
end

#random_stringObject



80
81
82
# File 'lib/oauth3.rb', line 80

def random_string
  (0...50).map { ('a'..'z').to_a[rand(26)] }.join
end

#validate_state(provider_uri, state) ⇒ Object



171
172
173
174
# File 'lib/oauth3.rb', line 171

def validate_state(provider_uri, state)
  # TODO delete stale states
  @states[state]
end