Class: Masq::Account

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/masq/account.rb

Defined Under Namespace

Classes: ActivationCodeNotFound, AlreadyActivated

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#passwordObject

attr_accessible :login, :email, :password, :password_confirmation, :public_persona_id, :yubikey_mandatory



27
28
29
# File 'app/models/masq/account.rb', line 27

def password
  @password
end

Class Method Details

.authenticate(login, password, basic_auth_used = false) ⇒ Object

Authenticates a user by their login name and password. Returns the user or nil.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'app/models/masq/account.rb', line 55

def authenticate(, password, basic_auth_used = false)
  a = find_by(login: )
  if a.nil? && Masq::Engine.config.masq["create_auth_ondemand"]["enabled"]
    # Need to set some password - but is never used
    pw = if Masq::Engine.config.masq["create_auth_ondemand"]["random_password"]
      SecureRandom.hex(13)
    else
      password
    end
     = Masq::Signup.create_account!(
      login: ,
      password: pw,
      password_confirmation: pw,
      email: "#{}@#{Masq::Engine.config.masq["create_auth_ondemand"]["default_mail_domain"]}",
    )
    a = . if .succeeded?
  end

  if !a.nil? && a.active? && a.enabled
    if a.authenticated?(password) || (Masq::Engine.config.masq["trust_basic_auth"] && basic_auth_used)
      a.last_authenticated_at = Time.now.utc
      a.last_authenticated_by_yubikey = a.authenticated_with_yubikey?
      a.save(validate: false)
      a
    end
  end
end

.encrypt(password, salt) ⇒ Object

Encrypts some data with the salt.



84
85
86
# File 'app/models/masq/account.rb', line 84

def encrypt(password, salt)
  Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end

.extract_yubico_identity_from_otp(yubico_otp) ⇒ Object

Returns the first twelve chars from the Yubico OTP, which are used to identify a Yubikey



99
100
101
# File 'app/models/masq/account.rb', line 99

def extract_yubico_identity_from_otp(yubico_otp)
  yubico_otp[0..11]
end

.find_and_activate!(activation_code) ⇒ Object

Finds the user with the corresponding activation code, activates their account and returns the user.

Raises:

Account::ActivationCodeNotFound

if there is no user with the corresponding activation code

Account::AlreadyActivated

if the user with the corresponding activation code has already activated their account

Raises:

  • (ArgumentError)


44
45
46
47
48
49
50
51
# File 'app/models/masq/account.rb', line 44

def find_and_activate!(activation_code)
  raise ArgumentError if activation_code.nil?
  user = find_by(activation_code: activation_code)
  raise ActivationCodeNotFound unless user
  raise AlreadyActivated.new(user) if user.active?
  user.send(:activate!)
  user
end

.split_password_and_yubico_otp(token) ⇒ Object

Receives a login token which consists of the users password and a Yubico one time password (the otp is always 44 characters long)



90
91
92
93
94
95
# File 'app/models/masq/account.rb', line 90

def split_password_and_yubico_otp(token)
  token.reverse!
  yubico_otp = token.slice!(0..43).reverse
  password = token.reverse
  [password, yubico_otp]
end

.verify_yubico_otp(otp) ⇒ Object

Utilizes the Yubico library to verify a one time password



104
105
106
107
108
# File 'app/models/masq/account.rb', line 104

def verify_yubico_otp(otp)
  Yubikey::OTP::Verify.new(otp).valid?
rescue Yubikey::OTP::InvalidOTPError
  false
end

Instance Method Details

#activate!Object



120
121
122
123
124
125
# File 'app/models/masq/account.rb', line 120

def activate!
  @activated = true
  self.activated_at = Time.now.utc
  self.activation_code = nil
  save
end

#active?Boolean

The existence of an activation code means they have not activated yet

Returns:

  • (Boolean)


116
117
118
# File 'app/models/masq/account.rb', line 116

def active?
  activation_code.nil?
end

#associate_with_yubikey(otp) ⇒ Object



166
167
168
169
170
171
172
173
# File 'app/models/masq/account.rb', line 166

def associate_with_yubikey(otp)
  if self.class.verify_yubico_otp(otp)
    self.yubico_identity = self.class.extract_yubico_identity_from_otp(otp)
    save(validate: false)
  else
    false
  end
end

#authenticated?(password) ⇒ Boolean

Returns:

  • (Boolean)


142
143
144
145
146
147
148
149
150
151
# File 'app/models/masq/account.rb', line 142

def authenticated?(password)
  if password.nil?
    false
  elsif password.length < 50 && !(yubico_identity? && yubikey_mandatory?)
    encrypt(password) == crypted_password
  elsif Masq::Engine.config.masq["can_use_yubikey"]
    password, yubico_otp = self.class.split_password_and_yubico_otp(password)
    @authenticated_with_yubikey = yubikey_authenticated?(yubico_otp) if encrypt(password) == crypted_password
  end
end

#authenticated_with_yubikey?Boolean

Returns:

  • (Boolean)


162
163
164
# File 'app/models/masq/account.rb', line 162

def authenticated_with_yubikey?
  @authenticated_with_yubikey ||= false
end

#disable!Object



221
222
223
# File 'app/models/masq/account.rb', line 221

def disable!
  update_attribute(:enabled, false)
end

#encrypt(password) ⇒ Object

Encrypts the password with the user salt



138
139
140
# File 'app/models/masq/account.rb', line 138

def encrypt(password)
  self.class.encrypt(password, salt)
end

#forget_meObject



194
195
196
197
198
# File 'app/models/masq/account.rb', line 194

def forget_me
  self.remember_token_expires_at = nil
  self.remember_token = nil
  save(validate: false)
end

#forgot_password!Object



200
201
202
203
204
# File 'app/models/masq/account.rb', line 200

def forgot_password!
  @forgotten_password = true
  make_password_reset_code
  save
end

#has_otp_device?Boolean

Does the user have the possibility to authenticate with a one time password?

Returns:

  • (Boolean)


133
134
135
# File 'app/models/masq/account.rb', line 133

def has_otp_device?
  !yubico_identity.nil?
end

#pending?Boolean

True if the user has just been activated

Returns:

  • (Boolean)


128
129
130
# File 'app/models/masq/account.rb', line 128

def pending?
  @activated ||= false
end

#recently_forgot_password?Boolean

Returns:

  • (Boolean)


213
214
215
# File 'app/models/masq/account.rb', line 213

def recently_forgot_password?
  @forgotten_password ||= false
end

#recently_reset_password?Boolean

Returns:

  • (Boolean)


217
218
219
# File 'app/models/masq/account.rb', line 217

def recently_reset_password?
  @reset_password ||= false
end

#remember_meObject

These create and unset the fields required for remembering users between browser closes



180
181
182
# File 'app/models/masq/account.rb', line 180

def remember_me
  remember_me_for(2.weeks)
end

#remember_me_for(time) ⇒ Object



184
185
186
# File 'app/models/masq/account.rb', line 184

def remember_me_for(time)
  remember_me_until(time.from_now.utc)
end

#remember_me_until(time) ⇒ Object



188
189
190
191
192
# File 'app/models/masq/account.rb', line 188

def remember_me_until(time)
  self.remember_token_expires_at = time
  self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
  save(validate: false)
end

#remember_token?Boolean

Returns:

  • (Boolean)


175
176
177
# File 'app/models/masq/account.rb', line 175

def remember_token?
  remember_token_expires_at && Time.now.utc < remember_token_expires_at
end

#reset_passwordObject

First update the password_reset_code before setting the reset_password flag to avoid duplicate email notifications.



208
209
210
211
# File 'app/models/masq/account.rb', line 208

def reset_password
  update_attribute(:password_reset_code, nil)
  @reset_password = true
end

#to_paramObject



111
112
113
# File 'app/models/masq/account.rb', line 111

def to_param
  
end

#yubikey_authenticated?(otp) ⇒ Boolean

Is the Yubico OTP valid and belongs to this account?

Returns:

  • (Boolean)


154
155
156
157
158
159
160
# File 'app/models/masq/account.rb', line 154

def yubikey_authenticated?(otp)
  if yubico_identity? && self.class.verify_yubico_otp(otp)
    (self.class.extract_yubico_identity_from_otp(otp) == yubico_identity)
  else
    false
  end
end