Guest User

Untitled

a guest
Feb 20th, 2018
73
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.69 KB | None | 0 0
  1. require 'digest/sha2'
  2. require 'model_security'
  3.  
  4. # A generic user login facility. Provides a user login, password
  5. # management, and administrative facilities. Logs users in via HTTP Basic
  6. # authentication, a login form, or a security token. Maintains the login
  7. # state using Session.
  8. #
  9. # I started out with the Salted Hash login generator, and essentially rewrote
  10. # the whole thing, learning a lot from the previous versions. This is not a
  11. # criticism of the previous work, my goals were different. So, it's fair to
  12. # say that this is derived from the work of Joe Hosteny and Tobias Leutke.
  13. #
  14. class User < ActiveRecord::Base
  15. # This causes the security features to be added to the model.
  16. include ModelSecurity
  17.  
  18. has_many :roles, :through => :granted_roles
  19. has_many :granted_roles, :dependent => :destroy
  20.  
  21. private
  22. attr_accessible :login, :name, :email, :password, :password_confirmation, \
  23. :old_password
  24.  
  25. # Hash a given password with the salt. This method localizes the encryption
  26. # function so that it can be easily changed.
  27. def encypher(s)
  28. Digest::SHA512.hexdigest(salt + s)
  29. end
  30.  
  31. # Create a new user record.
  32. #
  33. # This is either used to create an ephemeral prototype object to initialize
  34. # a form, or an object resulting from a form submission that will become a
  35. # persistent record.
  36. #
  37. def initialize(attributes = nil)
  38. super
  39.  
  40. if password
  41. @password_is_new = true
  42. end
  43. end
  44.  
  45. # Returns true if it is intended that the password be replaced when this
  46. # record is saved.
  47. def password_new?
  48. @password_is_new
  49. end
  50.  
  51. Char64 = (('a'..'z').collect + ('A'..'Z').collect + ('0'..'9').collect + ['.','/']).freeze
  52.  
  53. # Create a security token for use in logging in a user who has forgotten
  54. # his password or has just created his login.
  55. def token_string(n)
  56. s = ""
  57. n.times { s << Char64[(Time.now.tv_usec * rand) % 64] }
  58. s
  59. end
  60.  
  61. # Validates that initialize() sets @password_is_new to true, so that
  62. # password validation works correctly. This would fail only in the case
  63. # of a programming error.
  64. def validate_on_create
  65. password_new?
  66. end
  67.  
  68. # Validates that if we're changing the password or email, the old password
  69. # has been given and matches the record. This is a defense against
  70. # cookie-capture attacks.
  71. def validate_on_update
  72. if not (id.nil? or User.admin?) and (password_new? or @email_is_new)
  73. if encypher(old_password) != cypher
  74. errors.add(:old_password, "The old password doesn't match.")
  75. return false
  76. end
  77. end
  78. true
  79. end
  80.  
  81. before_save :prepare_save
  82.  
  83. validates_presence_of :login
  84. validates_uniqueness_of :login
  85. validates_uniqueness_of :email
  86. validates_format_of :email, \
  87. :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/
  88. validates_presence_of :password, :if => :password_new?
  89. validates_presence_of :password_confirmation, :if => :password_new?
  90. validates_length_of :login, :within => 3..80
  91. validates_length_of :password, :within => 5..128, :if => :password_new?
  92. validates_confirmation_of :password, :if => :password_new?
  93.  
  94. # Here are the new model security specifications.
  95.  
  96. # These just control display.
  97. let_display :all, :if => :never?
  98. let_display :admin, :activated, :if => :admin?
  99. let_display :login, :name, :email
  100.  
  101. # These control both reading and writing.
  102.  
  103. # Let the administrator access all data. This implements a Unix-like
  104. # super-user. Note that the coarse-grained override of the super-user
  105. # is not a _necessary_ pattern for the ModelSecurity module, you can
  106. # implement controls as fine-grained as you like.
  107. let_access :all, :if => :admin?
  108.  
  109. # These control reading of model attributes.
  110.  
  111. # The controller before_filter require_admin tests if the current user
  112. # is the administrator, thus we have to make the admin attribute readable
  113. # by all. We would not have to make it readable were admin? only being
  114. # used as a security test, as security tests can access all attributes
  115. # with impunity. Login and name are public information.
  116. #
  117. let_read :admin, :login, :name
  118.  
  119. # Allow the very first user to be promoted to administrator.
  120. # Once there's an admin, that user has "let_access :all" and can
  121. # promote others to administrator.
  122. let_write :admin, :if => :initial_self_promotion?
  123.  
  124. # If this is a new (never saved) record, or if this record corresponds to
  125. # the currently-logged-in user, allow reading of the email address.
  126. # let_read :email, :if => :new_or_me_or_logging_in?
  127.  
  128. # These attributes are concerned with login security, and can only be read
  129. # while a user is logging in. We create a pseudo-user for the process of= 1
  130. # logging in and a security test :logging_in? that tests for that user.
  131. let_read :activated, :cypher, :email, :salt, :token, :token_expiry, \
  132. :if => :new_or_me_or_logging_in?
  133.  
  134. # These control writing of model attributes.
  135.  
  136. # Only in the case of a new (never saved) record can these fields be written.
  137. #
  138. let_write :login, :name, :if => :new_record?
  139.  
  140. # Only allow this information to be updated by the user who owns the record,
  141. # unless this record is new (has never been saved).
  142. let_write :cypher, :email, :salt, :if => :new_or_me?
  143.  
  144.  
  145. # The security token can only be changed if we're the special "login" user.
  146. let_write :activated, :token, :token_expiry, :if => :logging_in?
  147.  
  148. public
  149. attr_accessor :password, :password_confirmation, :old_password
  150.  
  151. # NOTE: :password, :password_confirmation, and :old_password
  152. # are not attributes of the record, they are instance variables of the
  153. # class and aren't written to disk under those names. But I declare them
  154. # here because otherwise ModelSecurityHelper (which doesn't know that)
  155. # isn't going to allow me to enter them into a form field.
  156. #
  157. # I like how fine-grained I can get.
  158. let_write :password, :if => :new_or_me_or_logging_in?
  159. let_write :password_confirmation, :if => :new_or_me?
  160. let_write :old_password, :if => :me?
  161.  
  162. # Return the user for the current request. It is guaranteed that this is
  163. # set for each request in the before_filter for the application.
  164. #
  165. # This function uses the Ruby Thread class to do thread-local storage,
  166. # which will be overkill if the Rails server implementation isn't also
  167. # using Ruby threads, but works everywhere.
  168. #
  169. # User.current(), User.current=(), and UserSupport#user_setup encapsulate
  170. # session storage of user information. Only these three functions should
  171. # know whether we store the entire User object in the session or only
  172. # User#id.
  173. #
  174. def User.current
  175. # This does not refer to the session because the application has set
  176. # this from the session in user_setup.
  177. Thread.current[:user]
  178. end
  179.  
  180. # Set the user for the current request. It is guaranteed that this is
  181. # set for each request in the before_filter for the application.
  182. #
  183. # This function uses the Ruby Thread class to do thread-local storage,
  184. # which will be overkill if the Rails server implementation isn't also
  185. # using Ruby threads, but works everywhere.
  186. #
  187. # User.current(), User.current=(), and UserSupport#user_setup encapsulate
  188. # session storage of user information. Only these three functions should
  189. # know whether we store the entire User object in the session or only
  190. # User#id.
  191. #
  192. def User.current=(u)
  193. Thread.current[:user] = u
  194.  
  195. session = Thread.current[:session]
  196.  
  197. if session.nil?
  198. message = "Programming error: Please add \"before_filter :user_setup\" to your application controller. See the ModelSecurity documentation."
  199.  
  200. raise RuntimeError.new(message)
  201. end
  202.  
  203. # Don't cause a session store unnecessarily
  204. if session[:user] != u
  205. session[:user] = u
  206. end
  207. end
  208.  
  209. # Change the user's password. Confirm the old password while doing so.
  210. def change_password(attributes)
  211. @password_is_new = true
  212. self.password = attributes['password']
  213. self.password_confirmation = attributes['password_confirmation']
  214. self.old_password = attributes['old_password']
  215. end
  216.  
  217. # Change the user's email address.
  218. # FIX: send confirmation email.
  219. def change_email(attributes)
  220. @email_is_new = true
  221. self.email = attributes['email']
  222. self.old_password = attributes['old_password']
  223. end
  224.  
  225. # Return true if this record corresponds to the currently-logged-in user.
  226. # This is used as a security test.
  227. def me?
  228. u = User.current
  229. u and u.id == id
  230. end
  231.  
  232. # Return true if the currently-logged-in user is the administrator.
  233. # Class method. This is used as a pseudo-security test by let_display.
  234. def User.admin?
  235. u = User.current
  236. return ((u != nil ) and (u.admin.to_i == 1))
  237. end
  238.  
  239. # Return true if the currently-logged-in user is the administrator.
  240. # Instance method. This is used as a security test.
  241. def admin?
  242. return User.admin?
  243. end
  244.  
  245. # Return true if the user's ID is 1 and the user is attempting to promote
  246. # himself to administrator. This is used to bootstrap the first administrator
  247. # and for no other purpose.
  248. def initial_self_promotion?
  249. return ((not admin?) and (self.class.count == 1))
  250. end
  251.  
  252. # Return true if the user is currently logging in. This security test allows
  253. # us to designate model fields to be visible *only* while a user is logging
  254. # in.
  255. def logging_in?
  256. # FIX: create a real login user.
  257. return User.current.nil?
  258. end
  259.  
  260. def User.login_user
  261. # FIX: create a real login user.
  262. nil
  263. end
  264.  
  265. # Return true if the user record is new (never been saved) or if it
  266. # corresponds to the currently-logged-in user. This security test is
  267. # a common pattern applied to a number of user record attributes.
  268. def new_or_me?
  269. new_record? or User.current == self
  270. end
  271.  
  272. # Return true if the user record is new (never been saved) or if it
  273. # corresponds to the currently-logged-in user, or if the current user
  274. # is the special "login" user. This security test is a common pattern
  275. # applied to a number of user record attributes.
  276. def new_or_me_or_logging_in?
  277. new_record? or User.current == self or logging_in?
  278. end
  279.  
  280. # Create a new security token, or if the current one is not yet expired,
  281. # return the current one. Should only be called with nobody logged in, it
  282. # will log out the current user if one is logged in.
  283. # Instance method.
  284. def new_token
  285. User.current = User.login_user
  286. if token == '' or token_expiry < Time.now
  287. self.token = token_string(10)
  288. self.token_expiry = 7.days.from_now
  289. result = save
  290. end
  291. User.current = nil
  292. return token
  293. end
  294.  
  295. # Create a new security token, or if the current one is not yet expired,
  296. # return the current one. Should only be called with nobody logged in, it
  297. # will log out the current user if one is logged in.
  298. # Class method.
  299. def User.new_token(email)
  300. u = User.find_first(['email = ?', email])
  301. u.new_token
  302. end
  303.  
  304. # Encrypt the password before saving. Then wipe out the provided plaintext
  305. # password, so that it won't trigger unnecessary security tests and
  306. # validations the next time this record is saved. Wiping out the plaintext
  307. # is more secure, anyway.
  308. def prepare_save
  309. # The salt is used to add a random factor to the plaintext. This might
  310. # make some cryptographic attacks more difficult.
  311. if password_new?
  312. self.salt = token_string(40)
  313. self.cypher = encypher(password)
  314.  
  315. self.password = nil
  316. self.password_confirmation = nil
  317. @password_is_new = nil
  318. end
  319. true
  320. end
  321.  
  322. # Log off the current user.
  323. def User.sign_off
  324. User.current = nil
  325. end
  326.  
  327. # Log on the user for this record, given a password. Instance method.
  328. def sign_on(pass)
  329. User.current = User.login_user
  330. begin
  331. if (activated == 1) and (pass != nil) and (encypher(pass) == cypher)
  332. return (User.current = self)
  333. end
  334. rescue
  335. end
  336. User.current = nil
  337. end
  338.  
  339. # Log on the user for this record, given a user name and password.
  340. # Class method.
  341. def User.sign_on(handle, pwd)
  342. user = find_first(['login = ?', handle])
  343. if user
  344. user.sign_on(pwd)
  345. else
  346. nil
  347. end
  348. end
  349.  
  350. # Sign on the user using a security token. Instance method.
  351. def sign_on_by_token(t)
  352. User.current = User.login_user
  353. if t == token and (token_expiry >= Time.now)
  354. self.token = ""
  355. self.token_expiry = Time.now
  356. self.activated = 1
  357. save!
  358. User.current = self
  359. return self;
  360. end
  361. return nil
  362. end
  363.  
  364. # Sign on the user using an ID (record index) and security token.
  365. # Class method.
  366. def User.sign_on_by_token(id, token)
  367. u = User.find(id)
  368. return u.sign_on_by_token(token)
  369. end
  370. end
Add Comment
Please, Sign In to add comment