Class: Google::IdentityToolkit

Inherits:
Object
  • Object
show all
Defined in:
lib/google/identity_toolkit.rb,
lib/google/identity_toolkit/api.rb,
lib/google/identity_toolkit/errors.rb,
lib/google/identity_toolkit/helpers.rb,
lib/google/identity_toolkit/version.rb

Overview

Rack middleware for using the Google Identity Kit for federated login

Defined Under Namespace

Modules: Helpers Classes: Api, ApiError

Constant Summary collapse

VERSION =
"0.0.1"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, &block) ⇒ IdentityToolkit

Creates the Identity toolkit middleware. Requires defining a block to configure the API and define callback methods for loading & authenticating users.

end

Examples:

use Google::IdentityToolkit, do |toolkit|
  toolkit.api_key = "..."
  def fetch_user(email, assertion = nil)
    # Load the user for the given email, optionally registering
    # the user if assertion is provided and '
  end

  def password_valid?(email, password)
    # Validates the user id/password
  end


45
46
47
48
49
50
# File 'lib/google/identity_toolkit.rb', line 45

def initialize(app, &block)
  @path = '/_gitkit'
  @callback_url = nil
  @app = app
  instance_eval(&block) if block_given?
end

Instance Attribute Details

#apiGoogle::IdentityToolkit::Api

Get (and lazy init) the API client

Returns:



84
85
86
# File 'lib/google/identity_toolkit.rb', line 84

def api
  @api
end

#api_keyObject

Returns the value of attribute api_key.



19
20
21
# File 'lib/google/identity_toolkit.rb', line 19

def api_key
  @api_key
end

#appObject

Returns the value of attribute app.



21
22
23
# File 'lib/google/identity_toolkit.rb', line 21

def app
  @app
end

#callback_url(request = nil) ⇒ String

Get the callback URL for use in the javascript helpers.

Parameters:

  • request (Rack::Request) (defaults to: nil)

    current request object

Returns:

  • (String)

    callback URL for verifying assertions



270
271
272
# File 'lib/google/identity_toolkit.rb', line 270

def callback_url
  @callback_url
end

#federated_signup_urlObject

Returns the value of attribute federated_signup_url.



24
25
26
# File 'lib/google/identity_toolkit.rb', line 24

def 
  @federated_signup_url
end

#home_urlObject

Returns the value of attribute home_url.



26
27
28
# File 'lib/google/identity_toolkit.rb', line 26

def home_url
  @home_url
end

#pathObject

Returns the value of attribute path.



20
21
22
# File 'lib/google/identity_toolkit.rb', line 20

def path
  @path
end

#signup_urlObject

Returns the value of attribute signup_url.



25
26
27
# File 'lib/google/identity_toolkit.rb', line 25

def 
  @signup_url
end

Class Method Details

.api_keyString

Static method for fetching the API key from thread-local storage. Only valid for threads intercepted by the rack module. Use by helper methods rendering the login button.

Returns:

  • (String)

    API Key



57
58
59
# File 'lib/google/identity_toolkit.rb', line 57

def self.api_key
  env[:gitkit_api_key]
end

.callback_urlHash

Static method for fetching the callback URL from thread-local storage. Only valid for threads intercepted by the rack module. Use by helper methods rendering the login button.

Returns:

  • (Hash)

    Hash representing the user



75
76
77
# File 'lib/google/identity_toolkit.rb', line 75

def self.callback_url
  env[:gitkit_callback_url]
end

.current_userHash

Static method for fetching the current user from thread-local storage. Only valid for threads intercepted by the rack module. Use by helper methods rendering the login button.

Returns:

  • (Hash)

    Hash representing the user



66
67
68
# File 'lib/google/identity_toolkit.rb', line 66

def self.current_user
  env[:gitkit_user]
end

.envObject



282
283
284
# File 'lib/google/identity_toolkit.rb', line 282

def self.env
  Thread.current[:gitkit_rack_env]
end

Instance Method Details

#build_mobile_html(status, data) ⇒ String

Renders boilerplate HTML for non-poup mobile UI

Parameters:

  • status (String)

    Status of callback – either success, or one of the gitkit supported error messages

  • data (Hash)

    user data from assertion if successful login

Returns:

  • (String)

    HTML



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/google/identity_toolkit.rb', line 246

def build_mobile_html(status, data)
  html = "<script type='text/javascript'>"
  if status == STATUS_SUCCESS
    if data["registered"]
      html << "window.location = '#{home_url}';"
    else
      target = Addressable::URI.parse()
      target.query_values = target.query_values.merge("email" => data["email"])
      html << "window.location = '#{target}';"
    end
  else
    target = Addressable::URI.parse()
    target.query_values = target.query_values.merge("error" => status)
    html << "window.location = '#{}';"
  end
  html << "</script>"
end

#build_notify_html(status, data) ⇒ String

Renders boilerplate HTML for closing popups after login

Parameters:

  • status (String)

    Status of callback – either success, or one of the gitkit supported error messages

  • data (Hash)

    user data from assertion if successful login

Returns:

  • (String)

    HTML



223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/google/identity_toolkit.rb', line 223

def build_notify_html(status, data)
  html = <<-EOF
    <script type='text/javascript' src='https://ajax.googleapis.com/jsapi'></script>
    <script type='text/javascript'>google.load("identitytoolkit", "1.0", {packages: ["notify"]});</script>
    <script type='text/javascript'>
  EOF
  case status
  when STATUS_SUCCESS
    html << "window.google.identitytoolkit.notifyFederatedSuccess(#{data.to_json});"
  else
    html << "window.google.identitytoolkit.notifyFederatedError('#{status}', #{data.to_json});"
  end
  html << "</script>"
end

#call(env) ⇒ Array

Handles web requests for identity toolkit callbacks

Parameters:

  • env (Hash)

    Rack request context

Returns:

  • (Array)

    HTTP response



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/google/identity_toolkit.rb', line 95

def call(env)
  request = Rack::Request.new(env)
  env[:gitkit_api_key] = @api_key
  env[:gitkit_callback_url] = callback_url(request)
  env[:gitkit_user] = request.session[:git_user]
  Thread.current[:gitkit_rack_env] = env
  begin
    return @app.call(env) unless request.path == @path
    case request.params['rp_target']
      when "callback"
        body = callback(request)
        [200, {CONTENT_TYPE => CONTENT_TYPE_HTML}, [body]]
      when "login"
        data = (request)
        [200, {CONTENT_TYPE => CONTENT_TYPE_JSON}, [data.to_json]]
      when "userStatus"
        data = status(request)
        [200, {CONTENT_TYPE => CONTENT_TYPE_JSON}, [data.to_json]]
    end
  ensure
    Thread.current[:gitkit_rack_env] = nil
  end
end

#callback(request) ⇒ String

Handles IDP responses during login

Parameters:

  • request (Rack::Request)

    current request

Returns:

  • (String)

    HTML boilerplate to close popup & notify parent window



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
# File 'lib/google/identity_toolkit.rb', line 126

def callback(request)
  render_method = !!request.params['mobile'] ? :build_mobile_html : :build_notify_html
  begin
    assertion = api.verify_assertion(request.url, request.body)
    input_email = request.params['rp_input_email']
    email = assertion['verifiedEmail']
    return send(render_method, STATUS_INVALID_EMAIL, {}) if email.nil?
    return send(render_method, STATUS_ACCOUNT_MISMATCH, {
        "inputEmail" => input_email,
        "validatedEmail" => email
    }) unless input_email.nil? or input_email == email

    user = fetch_user(email, assertion)
    if user.nil?
      # Save the assertion for use in signup page 
      request.session[:gitkit_assertion] = assertion unless session.nil?
      send(render_method, STATUS_SUCCESS, {
          "email" => email,
          "registered" => false,
          "displayName" => assertion["displayName"],
          "photoUrl" => assertion["photoUrl"]
      })
    else
      # User exits, login
      upgrade_user(email)
      (user)
      send(render_method, STATUS_SUCCESS, {
          "email" => email,
          "registered" => true,
          "displayName" => assertion["displayName"],
          "photoUrl" => assertion["photoUrl"]
      })
    end
  rescue Exception => e
    send(render_method, 'invalidAssertion', {})
  end
end

#envObject



286
287
288
# File 'lib/google/identity_toolkit.rb', line 286

def env
  Thread.current[:gitkit_rack_env]
end

#handle_login(user) ⇒ Object

Log the user after either a successful assertion or password validation

Parameters:

  • User (Hash)

    (from find_user)

  • request (Rack::Request)

    Current request



208
209
210
211
212
213
# File 'lib/google/identity_toolkit.rb', line 208

def (user)
  unless session.nil?
    request.session[:git_user] = user
    (user)
  end
end

#login(request) ⇒ Hash

Handles authentication requests for non-federated users

Parameters:

  • request (Rack::Request)

    current request

Returns:

  • (Hash)

    Result of login. Currently just sets the value of :status in the hash



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/google/identity_toolkit.rb', line 171

def (request)
  user = fetch_user(request.params["email"])
  if user.nil?
    {:status => "emailNotExist"}
  elsif user[:federated]
    {:status => "federated"}
  else
    if password_valid?(request.params["email"], request.params["password"])
      user = fetch_user(request.params["email"])
      (user)
      {:status => "OK"}
    else
      {:status => "passwordError"}
    end
  end
end

#requestObject



290
291
292
# File 'lib/google/identity_toolkit.rb', line 290

def request
  Rack::Request.new(env)      
end

#sessionObject



294
295
296
# File 'lib/google/identity_toolkit.rb', line 294

def session
  request.session
end

#status(request) ⇒ Hash

Checks the status of a user

Parameters:

  • request (Rack::Request)

    current request

Returns:

  • (Hash)

    Status of user



195
196
197
198
199
200
# File 'lib/google/identity_toolkit.rb', line 195

def status(request)
  user = fetch_user(request.params["email"])
  #referrer = request.params["referrer"]
  { "registered" => !!user,
    "legacy" => !(user && user[:federated]) }
end