PageRenderTime 63ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/api/helpers.rb

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