Class: DoorMat::AccessToken

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
AttrSymmetricStore
Defined in:
app/models/door_mat/access_token.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from AttrSymmetricStore

included

Instance Attribute Details

#is_publicObject

Returns the value of attribute is_public.



14
15
16
# File 'app/models/door_mat/access_token.rb', line 14

def is_public
  @is_public
end

#remember_meObject

Returns the value of attribute remember_me.



14
15
16
# File 'app/models/door_mat/access_token.rb', line 14

def remember_me
  @remember_me
end

#tokenObject

Returns the value of attribute token.



14
15
16
# File 'app/models/door_mat/access_token.rb', line 14

def token
  @token
end

Class Method Details

.clean_up(cookies) ⇒ Object



310
311
312
# File 'app/models/door_mat/access_token.rb', line 310

def self.clean_up(cookies)
  cookies.delete(:token)
end

.clear_current_access_tokenObject



35
36
37
38
39
40
41
# File 'app/models/door_mat/access_token.rb', line 35

def self.clear_current_access_token
  access_token = current_access_token
  RequestStore.store[:current_access_token] = nil

  access_token.destroy if access_token.persisted?
  nil
end

.create_from_params(token_for, identifier, confirm_identifier, name, is_public, remember_me, request) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
# File 'app/models/door_mat/access_token.rb', line 59

def self.create_from_params(token_for, identifier, confirm_identifier, name, is_public, remember_me, request)
  clear_current_access_token
  is_public = '1' == is_public.to_s
  remember_me = '1' == remember_me.to_s

  access_token = new_with_token_for(token_for, request)
  return access_token unless access_token.errors.blank?

  access_token.identifier = identifier
  access_token.name = name || 'access token'

  if access_token.identifier.blank?
    access_token.errors[:identifier] << I18n.t("door_mat.password_less_session.blank_identifier")
    return access_token
  end

  if access_token.session_parameters[:challenge].include? :email
    if DoorMat::Regex.simple_email.match(access_token.identifier).blank?
      access_token.errors[:identifier] << I18n.t("door_mat.password_less_session.expect_email_identifier")
      return access_token
    end
  end

  unless identifier == confirm_identifier
    access_token.errors[:identifier] << I18n.t("door_mat.password_less_session.identifier_error")
    return access_token
  end

  unless [:single_use, :multiple_use].include? access_token.session_parameters[:status]
    DoorMat.configuration.logger.error "ERROR: #{request.remote_ip} Status must be either :single_use or :multiple_use check your configuration - found #{access_token.session_parameters[:status]}"
    access_token.errors[:base] << I18n.t("door_mat.password_less_session.create_failed")
    return access_token
  end
  access_token.status = access_token.session_parameters[:status]

  unless access_token.load_sub_session
    access_token.errors[:base] << I18n.t("door_mat.password_less_session.actor_missing")
    return access_token
  end

  access_token.generate_new_token

  if is_public
    access_token.public_computer!
  else
    access_token.private_computer!
  end

  # User asked to be remembered
  if DoorMat.configuration.allow_remember_me_feature && remember_me
    access_token.remember_me! unless (
    DoorMat.configuration.remember_me_require_private_computer_confirmation &&
        access_token.public_computer?
    )
  end

  access_token
end

.current_access_tokenObject



31
32
33
# File 'app/models/door_mat/access_token.rb', line 31

def self.current_access_token
  RequestStore.store[:current_access_token] ||= self.new
end

.destroy_if_linked_to(cookies) ⇒ Object



290
291
292
293
294
295
296
297
298
299
# File 'app/models/door_mat/access_token.rb', line 290

def self.destroy_if_linked_to(cookies)
  token = cookies.encrypted[:token] || ''
  clean_up(cookies)

  access_token = load_token(token, nil, false)
  return false if access_token.blank?

  access_token.destroy! unless access_token.multiple_use?
  true
end

Returns:

  • (Boolean)


205
206
207
# File 'app/models/door_mat/access_token.rb', line 205

def self.is_cookie_present?(cookies)
  !cookies.encrypted[:token].blank?
end

.load_token(token, request, verbose = true) ⇒ Object



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/models/door_mat/access_token.rb', line 209

def self.load_token(token, request, verbose=true)
  token = token.to_s.strip
  if DoorMat::Regex.session_guid.match(token).blank?
    DoorMat.configuration.logger.warn "WARN: #{request.remote_ip} Attempted to use token with invalid format #{token}" if verbose
    return nil
  end

  access_token = self.find_by_hashed_token DoorMat::Crypto::FastHash.sha256(token)
  if access_token.blank?
    DoorMat.configuration.logger.warn "WARN: #{request.remote_ip} Attempted to use inexistent token #{token}" if verbose
    return nil
  end

  return nil unless access_token.load_sub_session

  # Reload the token using find now that the sub_session is loaded
  # to allow the encrypted field to be decrypted
  # the request hits the cache so there is no additional round trip to the DB
  access_token = self.find(access_token.id)
  access_token.token = token
  access_token
end

.new_with_token_for(token_for, request) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
# File 'app/models/door_mat/access_token.rb', line 47

def self.new_with_token_for(token_for, request)
  access_token = self.new
  token_for_symbol = token_for.to_s.strip.to_sym
  if token_for_is_valid(token_for_symbol)
    access_token.token_for = self.token_fors[token_for_symbol]
  else
    DoorMat.configuration.logger.warn "WARN: #{request.remote_ip} Attempted to use inexistent token_for #{token_for}"
    access_token.errors[:base] << I18n.t("door_mat.password_less_session.create_failed")
  end
  access_token
end

.swap_token!(cookies, valid_current_session_tokens, new_session_token, force_new_token_generation = false) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/door_mat/access_token.rb', line 166

def self.swap_token!(cookies, valid_current_session_tokens, new_session_token, force_new_token_generation = false)
  access_token = current_access_token

  # Our current access token is in order
  return unless access_token.valid?

  valid_transitions = access_token.session_parameters.fetch(:transitions, [])
  # The current access token is for one of the valid_current_session_tokens
  return unless Array(valid_current_session_tokens).include? access_token.token_for.to_sym
  # The transition is valid
  return unless valid_transitions.include? new_session_token

  blank_new_session_access_token = self.new
  blank_new_session_access_token.token_for = self.token_fors[new_session_token]
  return unless blank_new_session_access_token.load_actor_for_session
  issue_new_token = force_new_token_generation || access_token.multiple_use? || (access_token.actor_id != blank_new_session_access_token.actor_id)

  if issue_new_token
    RequestStore.store[:current_access_token] = blank_new_session_access_token

    if access_token.used?
      access_token.destroy!
    end

    blank_new_session_access_token.renew_token(cookies)
    blank_new_session_access_token.name = access_token.name
    blank_new_session_access_token.identifier = access_token.identifier
    blank_new_session_access_token.data = access_token.data
    blank_new_session_access_token.reference_id = access_token.reference_id
    blank_new_session_access_token.used!
  else
    # For a single user ticket, just update the token_for value
    if access_token.used?
      access_token.token_for = self.token_fors[new_session_token]
      access_token.save!
    end
  end
end

.token_for_is_valid(token_for_symbol) ⇒ Object



43
44
45
# File 'app/models/door_mat/access_token.rb', line 43

def self.token_for_is_valid(token_for_symbol)
  DoorMat.configuration.password_less_sessions.has_key? token_for_symbol
end


284
285
286
287
288
# File 'app/models/door_mat/access_token.rb', line 284

def self.validate_from_cookie(cookies, request)
  token = cookies.encrypted[:token]
  RequestStore.store[:current_access_token] = validate_token(token, cookies, request)
  clean_up(cookies) if RequestStore.store[:current_access_token].nil?
end

.validate_token(token, cookies, request) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'app/models/door_mat/access_token.rb', line 238

def self.validate_token(token, cookies, request)
  access_token = load_token(token, request)

  access_token = case
    when access_token.blank?
      nil
    # For an unused token, check if the link has expired against access_token.session_parameters[:expiration_delay].ago
    when access_token.single_use?, access_token.multiple_use?
      if access_token.created_at < access_token.session_parameters[:expiration_delay].ago
        DoorMat.configuration.logger.info "INFO: #{request.remote_ip} Attempted to use expired token #{token}"
        access_token.destroy!
        nil
      else
        access_token
      end
    # otherwise, for an ongoing session, use public / private / remember_me expiration delay
    when access_token.public_computer?
      if access_token.updated_at < DoorMat.configuration.public_computer_access_session_timeout.minutes.ago
        access_token.destroy!
        nil
      else
        access_token
      end
    when access_token.private_computer?
      if access_token.updated_at < DoorMat.configuration.private_computer_access_session_timeout.minutes.ago
        access_token.destroy!
        nil
      else
        access_token
      end
    when access_token.remember_me?
      if access_token.created_at < DoorMat.configuration.remember_me_max_day_count.days.ago
        access_token.destroy!
        nil
      else
        if access_token.updated_at < DoorMat.configuration.private_computer_access_session_timeout.minutes.ago
          access_token.renew_token(cookies)
          access_token.save
        end
        access_token
      end
  end

  access_token
end

Instance Method Details

#default_failure_urlObject



135
136
137
# File 'app/models/door_mat/access_token.rb', line 135

def default_failure_url
  session_parameters.fetch(:default_failure_url, generic_redirect_url)
end

#default_parametersObject



131
132
133
# File 'app/models/door_mat/access_token.rb', line 131

def default_parameters
  @default_parameters ||= DoorMat.configuration.password_less_sessions[:password_less_defaults]
end

#default_success_urlObject



139
140
141
# File 'app/models/door_mat/access_token.rb', line 139

def default_success_url
  session_parameters.fetch(:default_success_url, generic_redirect_url)
end

#form_submit_path(controller) ⇒ Object



118
119
120
# File 'app/models/door_mat/access_token.rb', line 118

def form_submit_path(controller)
  session_parameters.fetch(:form_submit_path).inject(controller) { |lhs, rhs| lhs.send(rhs) }
end

#generate_new_tokenObject



122
123
124
125
# File 'app/models/door_mat/access_token.rb', line 122

def generate_new_token
  @token = SecureRandom.uuid
  self.hashed_token = DoorMat::Crypto::FastHash.sha256(@token)
end

#generic_redirect_urlObject



143
144
145
# File 'app/models/door_mat/access_token.rb', line 143

def generic_redirect_url
  default_parameters.fetch(:generic_redirect_url, [:main_app, :root_url])
end

#initObject



18
19
20
21
# File 'app/models/door_mat/access_token.rb', line 18

def init
  self.is_public = true if self.is_public.nil?
  self.remember_me = false if self.remember_me.nil?
end

#initialization_performed?Boolean

Returns:

  • (Boolean)


25
26
27
28
29
# File 'app/models/door_mat/access_token.rb', line 25

def initialization_performed?
  if self.actor.blank? || self.hashed_token.blank?
    errors.add(:base, "Access token invalid")
  end
end

#load_actor_for_sessionObject



147
148
149
150
151
152
153
154
# File 'app/models/door_mat/access_token.rb', line 147

def load_actor_for_session
  self.actor ||= DoorMat::Actor.authenticate_with(self.session_parameters[:actor][:email], self.session_parameters[:actor][:password])
  if self.actor.nil?
    DoorMat.configuration.logger.error "ERROR: Could not authenticate actor #{self.session_parameters[:actor][:email]} is it in your database?"
    return false
  end
  true
end

#load_sub_sessionObject



156
157
158
159
160
161
162
163
164
# File 'app/models/door_mat/access_token.rb', line 156

def load_sub_session
  return false unless load_actor_for_session

  unless DoorMat::Session.current_session.session_for_actor_loaded? self.actor
    sub_session = DoorMat::Session.new_sub_session_for_actor(self.actor, self.session_parameters[:actor][:password])
    DoorMat::Session.current_session.append_sub_session(sub_session)
  end
  true
end

#renew_token(cookies) ⇒ Object



232
233
234
235
236
# File 'app/models/door_mat/access_token.rb', line 232

def renew_token(cookies)
  generate_new_token

  set_up(cookies)
end

#session_parametersObject



127
128
129
# File 'app/models/door_mat/access_token.rb', line 127

def session_parameters
  @session_params ||= DoorMat.configuration.password_less_sessions[self.token_for.to_sym]
end

#set_up(cookies) ⇒ Object



301
302
303
304
305
306
307
308
# File 'app/models/door_mat/access_token.rb', line 301

def set_up(cookies)
  options = {
      secure: DoorMat.configuration.transmit_cookies_only_over_https,
      httponly: true
  }
  options.merge!({ expires: DoorMat.configuration.remember_me_max_day_count.days.since(self.created_at) }) unless self.public_computer?
  cookies.encrypted[:token] = options.merge({value: self.token})
end