Rcov C0 Coverage Information - RCov

app/models/user.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
app/models/user.rb 299 199
86.96%
81.91%

Key

Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.

Coverage Details

1 require 'fastercsv'
2 require 'digest' # required for {set,reset}_api_token
3 require 'base64' # required for {set,reset}_api_token
4 # required for repository actions
5 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'repo', 'repository')
6 
7 # We always assume the following fields exists:
8 # => :user_name, :last_name, :first_name
9 # If there are added columns, add the default values to default_values
10 class User < ActiveRecord::Base
11   before_validation :strip_name
12 
13   # Group relationships
14   has_many :memberships
15   has_many :groupings, :through => :memberships
16   has_many :notes, :as => :noteable, :dependent => :destroy
17   has_many :accepted_memberships, :class_name => "Membership", :conditions => {:membership_status => [StudentMembership::STATUSES[:accepted], StudentMembership::STATUSES[:inviter]]}
18 
19   validates_presence_of     :user_name, :last_name, :first_name
20   validates_uniqueness_of   :user_name
21 
22   validates_format_of       :type,          :with => /Student|Admin|Ta/
23   # role constants
24   STUDENT = 'Student'
25   ADMIN = 'Admin'
26   TA = 'Ta'
27 
28   # Authentication constants to be used as return values
29   # see self.authenticated? and main_controller for details
30   AUTHENTICATE_SUCCESS =      0   # valid username/password combination
31   AUTHENTICATE_NO_SUCH_USER = 1   # user does not exist
32   AUTHENTICATE_BAD_PASSWORD = 2   # wrong password
33   AUTHENTICATE_ERROR =        3   # generic/unknown error
34   AUTHENTICATE_BAD_CHAR =     4   # invalid character in username/password
35   AUTHENTICATE_BAD_PLATFORM = 5   # external authentication works for *NIX platforms only
36 
37   # Verifies if user is allowed to enter MarkUs
38   # Returns user object representing the user with the given login.
39   def self.authorize(login)
40     # fetch login in database to see if it is registered.
41     find_by_user_name(login)
42   end
43 
44   # Authenticates login against its password
45   # through a script specified by config VALIDATE_FILE
46   def self.authenticate(login, password)
47     # Do not allow the following characters in usernames/passwords
48     # Right now, this is \n and \0 only, since username and password
49     # are delimited by \n and C programs use \0 to terminate strings
50     not_allowed_regexp = Regexp.new(/[\n\0]+/)
51     if !(not_allowed_regexp.match(login) || not_allowed_regexp.match(password))
52       # Open a pipe and write to stdin of the program specified by config VALIDATE_FILE.
53       # We could read something from the programs stdout, but there is no need
54       # for that at the moment (you would do it by e.g. pipe.readlines)
55 
56       # External validation is supported on *NIX only
57       if RUBY_PLATFORM =~ /(:?mswin|mingw)/ # should match for Windows only
58         return AUTHENTICATE_BAD_PLATFORM
59       end
60 
61       # In general, the external password validation program will return the
62       # following codes (other than 0):
63       #  1 means no such user
64       #  2 means bad password
65       #  3 is used for other error exits
66       pipe = IO.popen( MarkusConfigurator.markus_config_validate_file, "w+" )
67       pipe.puts("#{login}\n#{password}") # write to stdin of markus_config_validate
68       pipe.close
69       m_logger = MarkusLogger.instance
70       case $?.exitstatus
71         when 0
72           m_logger.log("User '#{login}' logged in.", MarkusLogger::INFO)
73           return AUTHENTICATE_SUCCESS
74         when 1
75           m_logger.log("Login failed. Reason: No such user '#{login}'.", MarkusLogger::ERROR)
76           return AUTHENTICATE_NO_SUCH_USER
77         when 2
78           m_logger.log("Wrong username/password: User '#{login}'.", MarkusLogger::ERROR)
79           return AUTHENTICATE_BAD_PASSWORD
80         else
81           m_logger.log("User '#{login}' failed to log in.", MarkusLogger::ERROR)
82           return AUTHENTICATE_ERROR
83       end
84     else
85       m_logger.log("User '#{login}' failed to log in. Username/password contained " +
86                    "illegal characters", MarkusLogger::ERROR)
87       return AUTHENTICATE_BAD_CHAR
88     end
89   end
90 
91 
92   #TODO: make these proper associations. They work fine for now but
93   # they'll be slow in production
94   def active_groupings
95     self.groupings.find(:all, :conditions => ["memberships.membership_status != :u", { :u => StudentMembership::STATUSES[:rejected]}])
96   end
97 
98   # Helper methods -----------------------------------------------------
99 
100   def admin?
101     self.class == Admin
102   end
103 
104   def ta?
105     self.class == Ta
106   end
107 
108   def student?
109     self.class == Student
110   end
111 
112   # Submission helper methods -------------------------------------------------
113 
114   def submission_for(aid)
115     grouping = grouping_for(aid)
116     if grouping.nil?
117       return nil
118     end
119     return grouping.current_submission_used
120   end
121 
122   # Classlist parsing --------------------------------------------------------
123   def self.generate_csv_list(user_list)
124      file_out = FasterCSV.generate do |csv|
125        user_list.each do |user|
126          # csv format is user_name,last_name,first_name
127          # We check for user's section
128          # If the user has a section, we had it to the CSV
129          if !user.student? or user.section.nil?
130            user_array = [user.user_name,user.last_name,user.first_name]
131          else
132            user_array = [user.user_name,user.last_name,user.first_name, user.section.name]
133          end
134          csv << user_array
135        end
136      end
137      return file_out
138   end
139 
140   def self.upload_user_list(user_class, user_list)
141     num_update = 0
142     result = {}
143     result[:invalid_lines] = []  # store lines that were not processed
144     # read each line of the file and update classlist
145     User.transaction do
146       processed_users = []
147       FasterCSV.parse(user_list, :skip_blanks => true, :row_sep => :auto) do |row|
148         # don't know how to fetch line so we concat given array
149         next if FasterCSV.generate_line(row).strip.empty?
150         if processed_users.include?(row[0])
151           result[:invalid_lines] = I18n.t('csv_upload_user_duplicate', {:user_name => row[0]})
152         else
153           if User.add_user(user_class, row).nil?
154             result[:invalid_lines] << row.join(",")
155           else
156             num_update += 1
157             processed_users.push(row[0])
158           end
159         end
160       end # end prase
161     end
162     result[:upload_notice] = "#{num_update} user(s) added/updated."
163     return result
164   end
165 
166   def self.add_user(user_class, row)
167     # convert each line to a hash with FIELDS as corresponding keys
168     # and create or update a user with the hash values
169     #return nil if values.length < UPLOAD_FIELDS.length
170     user_attributes = {}
171     # Loop through the resulting array as key, value pairs
172 
173     user_class::CSV_UPLOAD_ORDER.zip(row) do |key, val|
174       # append them to the hash that is returned by User.get_default_ta/student_attrs
175       # remove the section if the user has one
176       if key == :section_name
177         if !val.nil?
178           # check if the section already exist
179           section = Section.find_or_create_by_name(val)
180           user_attributes["section_id"] = section.id
181         end
182       else
183         user_attributes[key] = val
184       end
185     end
186 
187     # Is there already a Student with this User number?
188     current_user = user_class.find_or_create_by_user_name(user_attributes[:user_name])
189     current_user.attributes = user_attributes
190 
191     if !current_user.save
192       return nil
193     end
194 
195     return current_user
196   end
197 
198   # Convenience method which returns a configuration Hash for the
199   # repository lib
200   def self.repo_config
201     # create config
202     conf = Hash.new
203     conf["IS_REPOSITORY_ADMIN"] = MarkusConfigurator.markus_config_repository_admin?
204     conf["REPOSITORY_PERMISSION_FILE"] = MarkusConfigurator.markus_config_repository_permission_file
205     return conf
206   end
207 
208   # Set API key for user model. The key is a
209   # SHA2 512 bit long digest, which is in turn
210   # MD5 digested and Base64 encoded so that it doesn't
211   # include bad HTTP characters.
212   #
213   # TODO: If we end up
214   # using this heavily we should probably let this token
215   # expire every X days/hours/weeks. When it does, a new
216   # token should be automatically generated.
217   def set_api_key
218     if self.api_key.nil?
219       key = generate_api_key
220       md5 = Digest::MD5.new
221       md5.update(key)
222       # base64 encode md5 hash
223       self.api_key = Base64.encode64(md5.to_s).strip
224       return self.save
225     else
226       return true
227     end
228   end
229 
230   # Resets the api key. Usually triggered, if the
231   # old md5 hash has gotten into the wrong hands.
232   def reset_api_key
233     key = generate_api_key
234     md5 = Digest::MD5.new
235     md5.update(key)
236     # base64 encode md5 hash
237     self.api_key = Base64.encode64(md5.to_s).strip
238     return self.save
239   end
240 
241   private
242   # Create some random, hard to guess SHA2 512 bit long
243   # digest.
244   def generate_api_key
245     digest = Digest::SHA2.new(bitlen=512)
246     # generate a unique token
247     unique_seed = ActiveSupport::SecureRandom.hex(20)
248     return digest.update("#{unique_seed} SECRET! #{Time.now.to_f}").to_s
249   end
250 
251   # strip input string
252   def strip_name
253     if !self.user_name.nil?
254       self.user_name.strip!
255     end
256     if !self.last_name.nil?
257       self.last_name.strip!
258     end
259     if !self.first_name.nil?
260       self.first_name.strip!
261     end
262   end
263 
264   # Adds read and write permissions for each newly created Admin or Ta user
265   def grant_repository_permissions
266     # If we're not the repository admin, bail out
267     return if(self.student? or !MarkusConfigurator.markus_config_repository_admin?)
268 
269     conf = User.repo_config
270     repo = Repository.get_class(MarkusConfigurator.markus_config_repository_type,
271                                 conf)
272     repo_names = Group.all.collect do |group|
273                    File.join(MarkusConfigurator.markus_config_repository_storage,
274                              group.repository_name)
275                  end
276     repo.set_bulk_permissions(repo_names, {self.user_name => Repository::Permission::READ_WRITE})
277   end
278 
279   # Revokes read and write permissions for a deleted admin user
280   def revoke_repository_permissions
281     return if(self.student? or !MarkusConfigurator.markus_config_repository_admin?)
282 
283     conf = User.repo_config
284     repo = Repository.get_class(MarkusConfigurator.markus_config_repository_type, conf)
285     repo_names = Group.all.collect do |group| File.join(MarkusConfigurator.markus_config_repository_storage, group.repository_name) end
286     repo.delete_bulk_permissions(repo_names, [self.user_name])
287   end
288 
289   def maintain_repository_permissions
290     return if(self.student? or !MarkusConfigurator.markus_config_repository_admin?)
291     if self.user_name_changed?
292       conf = User.repo_config
293       repo = Repository.get_class(MarkusConfigurator.markus_config_repository_type, conf)
294       repo_names = Group.all.collect do |group| File.join(MarkusConfigurator.markus_config_repository_storage, group.repository_name) end
295       repo.delete_bulk_permissions(repo_names, [self.user_name_was])
296       repo.set_bulk_permissions(repo_names, {self.user_name => Repository::Permission::READ_WRITE})
297     end
298   end
299 end

Generated on Sun Feb 05 00:08:07 -0500 2012 with rcov 0.9.10