PageRenderTime 118ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/api/helpers.rb

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