BigW Consortium Gitlab

helpers.rb 10.2 KB
Newer Older
1
module API
2
  module Helpers
3 4 5 6 7
    PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
    PRIVATE_TOKEN_PARAM = :private_token
    SUDO_HEADER ="HTTP_SUDO"
    SUDO_PARAM = :sudo

8 9 10 11
    def parse_boolean(value)
      [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value)
    end

Nihad Abbasov committed
12
    def current_user
13
      private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
Valery Sizov committed
14
      @current_user ||= (User.find_by(authentication_token: private_token) || doorkeeper_guard)
15 16 17 18 19

      unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
        return nil
      end

20
      identifier = sudo_identifier()
21

22
      # If the sudo is the current user do nothing
23
      if identifier && !(@current_user.id == identifier || @current_user.username == identifier)
24
        render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
25
        @current_user = User.by_username_or_id(identifier)
Nihad Abbasov committed
26
        not_found!("No user id or username for: #{identifier}") if @current_user.nil?
27
      end
28

29 30 31 32
      @current_user
    end

    def sudo_identifier()
33
      identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
34

35
      # Regex for integers
36
      if !!(identifier =~ /^[0-9]+$/)
37 38 39 40
        identifier.to_i
      else
        identifier
      end
Nihad Abbasov committed
41 42
    end

Nihad Abbasov committed
43
    def user_project
44
      @project ||= find_project(params[:id])
45
      @project || not_found!("Project")
46 47
    end

48
    def find_project(id)
49
      project = Project.find_with_namespace(id) || Project.find_by(id: id)
50 51 52

      if project && can?(current_user, :read_project, project)
        project
53
      else
54
        nil
55
      end
Nihad Abbasov committed
56 57
    end

Kirilll Zaitsev committed
58 59 60 61 62 63 64 65
    def project_service
      @project_service ||= begin
        underscored_service = params[:service_slug].underscore

        if Service.available_services_names.include?(underscored_service)
          user_project.build_missing_services

          service_method = "#{underscored_service}_service"
66

Kirilll Zaitsev committed
67 68 69
          send_service(service_method)
        end
      end
70

Kirilll Zaitsev committed
71 72 73 74 75 76 77 78 79 80 81 82 83
      @project_service || not_found!("Service")
    end

    def send_service(service_method)
      user_project.send(service_method)
    end

    def service_attributes
      @service_attributes ||= project_service.fields.inject([]) do |arr, hash|
        arr << hash[:name].to_sym
      end
    end

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
    def find_group(id)
      begin
        group = Group.find(id)
      rescue ActiveRecord::RecordNotFound
        group = Group.find_by!(path: id)
      end

      if can?(current_user, :read_group, group)
        group
      else
        forbidden!("#{current_user.username} lacks sufficient "\
        "access to #{group.name}")
      end
    end

99
    def paginate(relation)
100 101 102
      relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
        add_pagination_headers(data)
      end
Nihad Abbasov committed
103 104
    end

Nihad Abbasov committed
105
    def authenticate!
106
      unauthorized! unless current_user
Nihad Abbasov committed
107
    end
randx committed
108

109
    def authenticate_by_gitlab_shell_token!
110 111 112 113
      input = params['secret_token'].try(:chomp)
      unless Devise.secure_compare(secret_token, input)
        unauthorized!
      end
114 115
    end

116 117 118 119
    def authenticated_as_admin!
      forbidden! unless current_user.is_admin?
    end

120
    def authorize!(action, subject)
randx committed
121
      unless abilities.allowed?(current_user, action, subject)
122
        forbidden!
randx committed
123 124 125
      end
    end

126 127 128 129
    def authorize_push_project
      authorize! :push_code, user_project
    end

130 131 132 133
    def authorize_admin_project
      authorize! :admin_project, user_project
    end

134
    def require_gitlab_workhorse!
135
      unless env['HTTP_GITLAB_WORKHORSE'].present?
136 137 138 139
        forbidden!('Request should be executed via GitLab Workhorse')
      end
    end

140 141 142 143
    def can?(object, action, subject)
      abilities.allowed?(object, action, subject)
    end

144 145 146 147 148 149 150 151 152 153 154
    # Checks the occurrences of required attributes, each attribute must be present in the params hash
    # or a Bad Request error is invoked.
    #
    # Parameters:
    #   keys (required) - A hash consisting of keys that must be present
    def required_attributes!(keys)
      keys.each do |key|
        bad_request!(key) unless params[key].present?
      end
    end

Valery Sizov committed
155
    def attributes_for_keys(keys, custom_params = nil)
156
      params_hash = custom_params || params
Alex Denisov committed
157 158
      attrs = {}
      keys.each do |key|
159 160
        if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false)
          attrs[key] = params_hash[key]
161
        end
Alex Denisov committed
162
      end
163
      ActionController::Parameters.new(attrs).permit!
Alex Denisov committed
164 165
    end

166 167
    # Helper method for validating all labels against its names
    def validate_label_params(params)
168 169
      errors = {}

170 171 172 173 174
      if params[:labels].present?
        params[:labels].split(',').each do |label_name|
          label = user_project.labels.create_with(
            color: Label::DEFAULT_COLOR).find_or_initialize_by(
              title: label_name.strip)
175

176
          if label.invalid?
177
            errors[label.title] = label.errors
178 179 180
          end
        end
      end
181 182

      errors
183 184
    end

185 186 187 188
    def validate_access_level?(level)
      Gitlab::Access.options_with_owner.values.include? level.to_i
    end

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
    def issuable_order_by
      if params["order_by"] == 'updated_at'
        'updated_at'
      else
        'created_at'
      end
    end

    def issuable_sort
      if params["sort"] == 'asc'
        :asc
      else
        :desc
      end
    end

205 206 207 208
    def filter_by_iid(items, iid)
      items.where(iid: iid)
    end

209 210
    # error helpers

211 212 213 214
    def forbidden!(reason = nil)
      message = ['403 Forbidden']
      message << " - #{reason}" if reason
      render_api_error!(message.join(' '), 403)
215 216
    end

217 218 219 220 221 222
    def bad_request!(attribute)
      message = ["400 (Bad request)"]
      message << "\"" + attribute.to_s + "\" not given"
      render_api_error!(message.join(' '), 400)
    end

223 224 225 226
    def not_found!(resource = nil)
      message = ["404"]
      message << resource if resource
      message << "Not Found"
227
      render_api_error!(message.join(' '), 404)
228 229 230
    end

    def unauthorized!
231
      render_api_error!('401 Unauthorized', 401)
232 233 234
    end

    def not_allowed!
235 236 237 238 239 240 241
      render_api_error!('405 Method Not Allowed', 405)
    end

    def conflict!(message = nil)
      render_api_error!(message || '409 Conflict', 409)
    end

242 243 244 245
    def file_to_large!
      render_api_error!('413 Request Entity Too Large', 413)
    end

246
    def render_validation_error!(model)
247
      if model.errors.any?
248 249
        render_api_error!(model.errors.messages || '400 Bad Request', 400)
      end
250 251 252
    end

    def render_api_error!(message, status)
253
      error!({ 'message' => message }, status)
254 255
    end

Valery Sizov committed
256 257 258 259 260 261 262 263 264 265 266 267
    # Projects helpers

    def filter_projects(projects)
      # If the archived parameter is passed, limit results accordingly
      if params[:archived].present?
        projects = projects.where(archived: parse_boolean(params[:archived]))
      end

      if params[:search].present?
        projects = projects.search(params[:search])
      end

268 269
      if params[:visibility].present?
        projects = projects.search_by_visibility(params[:visibility])
270 271
      end

272
      projects.reorder(project_order_by => project_sort)
Valery Sizov committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
    end

    def project_order_by
      order_fields = %w(id name path created_at updated_at last_activity_at)

      if order_fields.include?(params['order_by'])
        params['order_by']
      else
        'created_at'
      end
    end

    def project_sort
      if params["sort"] == 'asc'
        :asc
      else
        :desc
      end
    end

293 294
    # file helpers

295
    def uploaded_file(field, uploads_path)
296 297 298 299
      if params[field]
        bad_request!("#{field} is not a file") unless params[field].respond_to?(:filename)
        return params[field]
      end
300

301 302
      return nil unless params["#{field}.path"] && params["#{field}.name"]

303
      # sanitize file paths
304 305
      # this requires all paths to exist
      required_attributes! %W(#{field}.path)
306
      uploads_path = File.realpath(uploads_path)
307
      file_path = File.realpath(params["#{field}.path"])
308 309 310 311
      bad_request!('Bad file path') unless file_path.start_with?(uploads_path)

      UploadedFile.new(
        file_path,
312 313
        params["#{field}.name"],
        params["#{field}.type"] || 'application/octet-stream',
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
      )
    end

    def present_file!(path, filename, content_type = 'application/octet-stream')
      filename ||= File.basename(path)
      header['Content-Disposition'] = "attachment; filename=#{filename}"
      header['Content-Transfer-Encoding'] = 'binary'
      content_type content_type

      # Support download acceleration
      case headers['X-Sendfile-Type']
      when 'X-Sendfile'
        header['X-Sendfile'] = path
        body
      else
        file FileStreamer.new(path)
      end
    end

333
    private
randx committed
334

335 336 337 338 339 340 341 342 343 344 345
    def add_pagination_headers(paginated_data)
      header 'X-Total',       paginated_data.total_count.to_s
      header 'X-Total-Pages', paginated_data.total_pages.to_s
      header 'X-Per-Page',    paginated_data.limit_value.to_s
      header 'X-Page',        paginated_data.current_page.to_s
      header 'X-Next-Page',   paginated_data.next_page.to_s
      header 'X-Prev-Page',   paginated_data.prev_page.to_s
      header 'Link',          pagination_links(paginated_data)
    end

    def pagination_links(paginated_data)
346
      request_url = request.url.split('?').first
347 348
      request_params = params.clone
      request_params[:per_page] = paginated_data.limit_value
349 350

      links = []
351 352 353 354 355 356 357 358 359 360 361 362

      request_params[:page] = paginated_data.current_page - 1
      links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?

      request_params[:page] = paginated_data.current_page + 1
      links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?

      request_params[:page] = 1
      links << %(<#{request_url}?#{request_params.to_query}>; rel="first")

      request_params[:page] = paginated_data.total_pages
      links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
363

364
      links.join(', ')
365 366
    end

randx committed
367 368
    def abilities
      @abilities ||= begin
369 370 371 372
                       abilities = Six.new
                       abilities << Ability
                       abilities
                     end
randx committed
373
    end
374 375

    def secret_token
376
      File.read(Gitlab.config.gitlab_shell.secret_file).chomp
377
    end
378 379 380 381 382

    def handle_member_errors(errors)
      error!(errors[:access_level], 422) if errors[:access_level].any?
      not_found!(errors)
    end
Nihad Abbasov committed
383 384
  end
end