PageRenderTime 72ms CodeModel.GetById 24ms app.highlight 42ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/api/helpers.rb

https://gitlab.com/skyruler/gitlab-ce
Ruby | 434 lines | 330 code | 84 blank | 20 comment | 43 complexity | ea40ebd3a4dfedcbb023a3ec6d03c13b MD5 | raw file
  1module API
  2  module Helpers
  3    PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
  4    PRIVATE_TOKEN_PARAM = :private_token
  5    SUDO_HEADER = "HTTP_SUDO"
  6    SUDO_PARAM = :sudo
  7
  8    def parse_boolean(value)
  9      [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value)
 10    end
 11
 12    def find_user_by_private_token
 13      token_string = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
 14      User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
 15    end
 16
 17    def current_user
 18      @current_user ||= (find_user_by_private_token || doorkeeper_guard)
 19
 20      unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
 21        return nil
 22      end
 23
 24      identifier = sudo_identifier()
 25
 26      # If the sudo is the current user do nothing
 27      if identifier && !(@current_user.id == identifier || @current_user.username == identifier)
 28        render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
 29        @current_user = User.by_username_or_id(identifier)
 30        not_found!("No user id or username for: #{identifier}") if @current_user.nil?
 31      end
 32
 33      @current_user
 34    end
 35
 36    def sudo_identifier
 37      identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
 38
 39      # Regex for integers
 40      if !!(identifier =~ /\A[0-9]+\z/)
 41        identifier.to_i
 42      else
 43        identifier
 44      end
 45    end
 46
 47    def user_project
 48      @project ||= find_project(params[:id])
 49      @project || not_found!("Project")
 50    end
 51
 52    def find_project(id)
 53      project = Project.find_with_namespace(id) || Project.find_by(id: id)
 54
 55      if project && can?(current_user, :read_project, project)
 56        project
 57      else
 58        nil
 59      end
 60    end
 61
 62    def project_service
 63      @project_service ||= begin
 64        underscored_service = params[:service_slug].underscore
 65
 66        if Service.available_services_names.include?(underscored_service)
 67          user_project.build_missing_services
 68
 69          service_method = "#{underscored_service}_service"
 70
 71          send_service(service_method)
 72        end
 73      end
 74
 75      @project_service || not_found!("Service")
 76    end
 77
 78    def send_service(service_method)
 79      user_project.send(service_method)
 80    end
 81
 82    def service_attributes
 83      @service_attributes ||= project_service.fields.inject([]) do |arr, hash|
 84        arr << hash[:name].to_sym
 85      end
 86    end
 87
 88    def find_group(id)
 89      begin
 90        group = Group.find(id)
 91      rescue ActiveRecord::RecordNotFound
 92        group = Group.find_by!(path: id)
 93      end
 94
 95      if can?(current_user, :read_group, group)
 96        group
 97      else
 98        not_found!('Group')
 99      end
100    end
101
102    def find_project_label(id)
103      label = user_project.labels.find_by_id(id) || user_project.labels.find_by_title(id)
104      label || not_found!('Label')
105    end
106
107    def find_project_issue(id)
108      issue = user_project.issues.find(id)
109      not_found! unless can?(current_user, :read_issue, issue)
110      issue
111    end
112
113    def paginate(relation)
114      relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
115        add_pagination_headers(data)
116      end
117    end
118
119    def authenticate!
120      unauthorized! unless current_user
121    end
122
123    def authenticate_by_gitlab_shell_token!
124      input = params['secret_token'].try(:chomp)
125      unless Devise.secure_compare(secret_token, input)
126        unauthorized!
127      end
128    end
129
130    def authenticated_as_admin!
131      forbidden! unless current_user.is_admin?
132    end
133
134    def authorize!(action, subject)
135      forbidden! unless abilities.allowed?(current_user, action, subject)
136    end
137
138    def authorize_push_project
139      authorize! :push_code, user_project
140    end
141
142    def authorize_admin_project
143      authorize! :admin_project, user_project
144    end
145
146    def require_gitlab_workhorse!
147      unless env['HTTP_GITLAB_WORKHORSE'].present?
148        forbidden!('Request should be executed via GitLab Workhorse')
149      end
150    end
151
152    def can?(object, action, subject)
153      abilities.allowed?(object, action, subject)
154    end
155
156    # Checks the occurrences of required attributes, each attribute must be present in the params hash
157    # or a Bad Request error is invoked.
158    #
159    # Parameters:
160    #   keys (required) - A hash consisting of keys that must be present
161    def required_attributes!(keys)
162      keys.each do |key|
163        bad_request!(key) unless params[key].present?
164      end
165    end
166
167    def attributes_for_keys(keys, custom_params = nil)
168      params_hash = custom_params || params
169      attrs = {}
170      keys.each do |key|
171        if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false)
172          attrs[key] = params_hash[key]
173        end
174      end
175      ActionController::Parameters.new(attrs).permit!
176    end
177
178    # Helper method for validating all labels against its names
179    def validate_label_params(params)
180      errors = {}
181
182      if params[:labels].present?
183        params[:labels].split(',').each do |label_name|
184          label = user_project.labels.create_with(
185            color: Label::DEFAULT_COLOR).find_or_initialize_by(
186              title: label_name.strip)
187
188          if label.invalid?
189            errors[label.title] = label.errors
190          end
191        end
192      end
193
194      errors
195    end
196
197    def validate_access_level?(level)
198      Gitlab::Access.options_with_owner.values.include? level.to_i
199    end
200
201    # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
202    # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
203    #
204    # Parameters:
205    #   keys (required) - An array consisting of elements that must be parseable as dates from the params hash
206    def datetime_attributes!(*keys)
207      keys.each do |key|
208        begin
209          params[key] = Time.xmlschema(params[key]) if params[key].present?
210        rescue ArgumentError
211          message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"
212          render_api_error!(message, 400)
213        end
214      end
215    end
216
217    def issuable_order_by
218      if params["order_by"] == 'updated_at'
219        'updated_at'
220      else
221        'created_at'
222      end
223    end
224
225    def issuable_sort
226      if params["sort"] == 'asc'
227        :asc
228      else
229        :desc
230      end
231    end
232
233    def filter_by_iid(items, iid)
234      items.where(iid: iid)
235    end
236
237    # error helpers
238
239    def forbidden!(reason = nil)
240      message = ['403 Forbidden']
241      message << " - #{reason}" if reason
242      render_api_error!(message.join(' '), 403)
243    end
244
245    def bad_request!(attribute)
246      message = ["400 (Bad request)"]
247      message << "\"" + attribute.to_s + "\" not given"
248      render_api_error!(message.join(' '), 400)
249    end
250
251    def not_found!(resource = nil)
252      message = ["404"]
253      message << resource if resource
254      message << "Not Found"
255      render_api_error!(message.join(' '), 404)
256    end
257
258    def unauthorized!
259      render_api_error!('401 Unauthorized', 401)
260    end
261
262    def not_allowed!
263      render_api_error!('405 Method Not Allowed', 405)
264    end
265
266    def conflict!(message = nil)
267      render_api_error!(message || '409 Conflict', 409)
268    end
269
270    def file_to_large!
271      render_api_error!('413 Request Entity Too Large', 413)
272    end
273
274    def not_modified!
275      render_api_error!('304 Not Modified', 304)
276    end
277
278    def render_validation_error!(model)
279      if model.errors.any?
280        render_api_error!(model.errors.messages || '400 Bad Request', 400)
281      end
282    end
283
284    def render_api_error!(message, status)
285      error!({ 'message' => message }, status)
286    end
287
288    # Projects helpers
289
290    def filter_projects(projects)
291      # If the archived parameter is passed, limit results accordingly
292      if params[:archived].present?
293        projects = projects.where(archived: parse_boolean(params[:archived]))
294      end
295
296      if params[:search].present?
297        projects = projects.search(params[:search])
298      end
299
300      if params[:visibility].present?
301        projects = projects.search_by_visibility(params[:visibility])
302      end
303
304      projects.reorder(project_order_by => project_sort)
305    end
306
307    def project_order_by
308      order_fields = %w(id name path created_at updated_at last_activity_at)
309
310      if order_fields.include?(params['order_by'])
311        params['order_by']
312      else
313        'created_at'
314      end
315    end
316
317    def project_sort
318      if params["sort"] == 'asc'
319        :asc
320      else
321        :desc
322      end
323    end
324
325    # file helpers
326
327    def uploaded_file(field, uploads_path)
328      if params[field]
329        bad_request!("#{field} is not a file") unless params[field].respond_to?(:filename)
330        return params[field]
331      end
332
333      return nil unless params["#{field}.path"] && params["#{field}.name"]
334
335      # sanitize file paths
336      # this requires all paths to exist
337      required_attributes! %W(#{field}.path)
338      uploads_path = File.realpath(uploads_path)
339      file_path = File.realpath(params["#{field}.path"])
340      bad_request!('Bad file path') unless file_path.start_with?(uploads_path)
341
342      UploadedFile.new(
343        file_path,
344        params["#{field}.name"],
345        params["#{field}.type"] || 'application/octet-stream',
346      )
347    end
348
349    def present_file!(path, filename, content_type = 'application/octet-stream')
350      filename ||= File.basename(path)
351      header['Content-Disposition'] = "attachment; filename=#{filename}"
352      header['Content-Transfer-Encoding'] = 'binary'
353      content_type content_type
354
355      # Support download acceleration
356      case headers['X-Sendfile-Type']
357      when 'X-Sendfile'
358        header['X-Sendfile'] = path
359        body
360      else
361        file FileStreamer.new(path)
362      end
363    end
364
365    private
366
367    def add_pagination_headers(paginated_data)
368      header 'X-Total',       paginated_data.total_count.to_s
369      header 'X-Total-Pages', paginated_data.total_pages.to_s
370      header 'X-Per-Page',    paginated_data.limit_value.to_s
371      header 'X-Page',        paginated_data.current_page.to_s
372      header 'X-Next-Page',   paginated_data.next_page.to_s
373      header 'X-Prev-Page',   paginated_data.prev_page.to_s
374      header 'Link',          pagination_links(paginated_data)
375    end
376
377    def pagination_links(paginated_data)
378      request_url = request.url.split('?').first
379      request_params = params.clone
380      request_params[:per_page] = paginated_data.limit_value
381
382      links = []
383
384      request_params[:page] = paginated_data.current_page - 1
385      links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?
386
387      request_params[:page] = paginated_data.current_page + 1
388      links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?
389
390      request_params[:page] = 1
391      links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
392
393      request_params[:page] = paginated_data.total_pages
394      links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
395
396      links.join(', ')
397    end
398
399    def abilities
400      @abilities ||= begin
401                       abilities = Six.new
402                       abilities << Ability
403                       abilities
404                     end
405    end
406
407    def secret_token
408      File.read(Gitlab.config.gitlab_shell.secret_file).chomp
409    end
410
411    def handle_member_errors(errors)
412      error!(errors[:access_level], 422) if errors[:access_level].any?
413      not_found!(errors)
414    end
415
416    def send_git_blob(repository, blob)
417      env['api.format'] = :txt
418      content_type 'text/plain'
419      header(*Gitlab::Workhorse.send_git_blob(repository, blob))
420    end
421
422    def send_git_archive(repository, ref:, format:)
423      header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
424    end
425
426    def issue_entity(project)
427      if project.has_external_issue_tracker?
428        Entities::ExternalIssue
429      else
430        Entities::Issue
431      end
432    end
433  end
434end