Issues (1490)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Models/Code/Wiki.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This file is part of Fabrica.
5
 *
6
 * (c) Alexandre Salomé <[email protected]>
7
 * (c) Julien DIDIER <[email protected]>
8
 *
9
 * This source file is subject to the GPL license that is bundled
10
 * with this source code in the file LICENSE.
11
 */
12
13
namespace Fabrica\Models\Code;
14
15
use Pedreiro\Models\Base;
16
17 View Code Duplication
class Wiki extends Base
0 ignored issues
show
This class seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
18
{
19
    public static $TYPE_ID = 2;
20
21
    protected $table = 'wikis';   
22
23
    protected $organizationPerspective = true;
24
25
    /**
26
     * The attributes that are mass assignable.
27
     *
28
     * @var array
29
     */
30
    protected $fillable = [
31
        'author_id',
32
        'committer_id',
33
        'token',
34
        'company_token',
35
        'is_active'
36
    ];
37
    /*# coding: utf-8
38
    # frozen_string_literal: true
39
40
    class Commit
41
    extend ActiveModel::Naming
42
    extend Gitlab::Cache::RequestCache
43
44
    include ActiveModel::Conversion
45
    include Noteable
46
    include Participable
47
    include Mentionable
48
    include Referable
49
    include StaticModel
50
    include Presentable
51
    include ::Gitlab::Utils::StrongMemoize
52
53
    attr_mentionable :safe_message, pipeline: :single_line
54
55
    participant :author
56
    participant :committer
57
    participant :notes_with_associations
58
59
    attr_accessor :project, :author
60
    attr_accessor :redacted_description_html
61
    attr_accessor :redacted_title_html
62
    attr_accessor :redacted_full_title_html
63
    attr_reader :gpg_commit
64
65
    DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
66
67
    # Commits above this size will not be rendered in HTML
68
    DIFF_HARD_LIMIT_FILES = 1000
69
    DIFF_HARD_LIMIT_LINES = 50000
70
71
    MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
72
    COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
73
    # Used by GFM to match and present link extensions on node texts and hrefs.
74
    LINK_EXTENSION_PATTERN = /(patch)/.freeze
75
76
    def banzai_render_context(field)
77
    pipeline = field == :description ? :commit_description : :single_line
78
    context = { pipeline: pipeline, project: self.project }
79
    context[:author] = self.author if self.author
80
81
    context
82
    end
83
84
    class << self
85
    def decorate(commits, project)
86
      commits.map do |commit|
87
        if commit.is_a?(Commit)
88
          commit
89
        else
90
          self.new(commit, project)
91
        end
92
      end
93
    end
94
95
    # Calculate number of lines to render for diffs
96
    def diff_line_count(diffs)
97
      diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) }
98
    end
99
100
    def order_by(collection:, order_by:, sort:)
101
      return collection unless %w[email name commits].include?(order_by)
102
      return collection unless %w[asc desc].include?(sort)
103
104
      collection.sort do |a, b|
105
        operands = [a, b].tap { |o| o.reverse! if sort == 'desc' }
106
107
        attr1, attr2 = operands.first.public_send(order_by), operands.second.public_send(order_by) # rubocop:disable PublicSend
108
109
        # use case insensitive comparison for string values
110
        order_by.in?(%w[email name]) ? attr1.casecmp(attr2) : attr1 <=> attr2
111
      end
112
    end
113
114
    # Truncate sha to 8 characters
115
    def truncate_sha(sha)
116
      sha[0..MIN_SHA_LENGTH]
117
    end
118
119
    def max_diff_options
120
      {
121
        max_files: DIFF_HARD_LIMIT_FILES,
122
        max_lines: DIFF_HARD_LIMIT_LINES
123
      }
124
    end
125
126
    def from_hash(hash, project)
127
      raw_commit = Gitlab::Git::Commit.new(project.repository.raw, hash)
128
      new(raw_commit, project)
129
    end
130
131
    def valid_hash?(key)
132
      !!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
133
    end
134
135
    def lazy(project, oid)
136
      BatchLoader.for({ project: project, oid: oid }).batch do |items, loader|
137
        items_by_project = items.group_by { |i| i[:project] }
138
139
        items_by_project.each do |project, commit_ids|
140
          oids = commit_ids.map { |i| i[:oid] }
141
142
          project.repository.commits_by(oids: oids).each do |commit|
143
            loader.call({ project: commit.project, oid: commit.id }, commit) if commit
144
          end
145
        end
146
      end
147
    end
148
149
    def parent_class
150
      ::Project
151
    end
152
    end
153
154
    attr_accessor :raw
155
156
    def initialize(raw_commit, project)
157
    raise "Nil as raw commit passed" unless raw_commit
158
159
    @raw = raw_commit
160
    @project = project
161
    @statuses = {}
162
    @gpg_commit = Gitlab::Gpg::Commit.new(self) if project
163
    end
164
165
    def id
166
    raw.id
167
    end
168
169
    def project_id
170
    project.id
171
    end
172
173
    def ==(other)
174
    other.is_a?(self.class) && raw == other.raw
175
    end
176
177
    def self.reference_prefix
178
    '@'
179
    end
180
181
    # Pattern used to extract commit references from text
182
    #
183
    # This pattern supports cross-project references.
184
    def self.reference_pattern
185
    @reference_pattern ||= %r{
186
      (?:#{Project.reference_pattern}#{reference_prefix})?
187
      (?<commit>#{COMMIT_SHA_PATTERN})
188
    }x
189
    end
190
191
    def self.link_reference_pattern
192
    @link_reference_pattern ||=
193
      super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})?(\.(?<extension>#{LINK_EXTENSION_PATTERN}))?/)
194
    end
195
196
    def to_reference(from = nil, full: false)
197
    commit_reference(from, id, full: full)
198
    end
199
200
    def reference_link_text(from = nil, full: false)
201
    commit_reference(from, short_id, full: full)
202
    end
203
204
    def diff_line_count
205
    @diff_line_count ||= Commit.diff_line_count(raw_diffs)
206
    @diff_line_count
207
    end
208
209
    # Returns the commits title.
210
    #
211
    # Usually, the commit title is the first line of the commit message.
212
    # In case this first line is longer than 100 characters, it is cut off
213
    # after 80 characters + `...`
214
    def title
215
    return full_title if full_title.length < 100
216
217
    # Use three dots instead of the ellipsis Unicode character because
218
    # some clients show the raw Unicode value in the merge commit.
219
    full_title.truncate(81, separator: ' ', omission: '...')
220
    end
221
222
    # Returns the full commits title
223
    def full_title
224
    @full_title ||=
225
      if safe_message.blank?
226
        no_commit_message
227
      else
228
        safe_message.split(/[\r\n]/, 2).first
229
      end
230
    end
231
232
    # Returns full commit message if title is truncated (greater than 99 characters)
233
    # otherwise returns commit message without first line
234
    def description
235
    return safe_message if full_title.length >= 100
236
    return no_commit_message if safe_message.blank?
237
238
    safe_message.split("\n", 2)[1].try(:chomp)
239
    end
240
241
    def description?
242
    description.present?
243
    end
244
245
    def hook_attrs(with_changed_files: false)
246
    data = {
247
      id: id,
248
      message: safe_message,
249
      timestamp: committed_date.xmlschema,
250
      url: Gitlab::UrlBuilder.build(self),
251
      author: {
252
        name: author_name,
253
        email: author_email
254
      }
255
    }
256
257
    if with_changed_files
258
      data.merge!(repo_changes)
259
    end
260
261
    data
262
    end
263
264
    # Discover issues should be closed when this commit is pushed to a project's
265
    # default branch.
266
    def closes_issues(current_user = self.committer)
267
    Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
268
    end
269
270
    def lazy_author
271
    BatchLoader.for(author_email.downcase).batch do |emails, loader|
272
      users = User.by_any_email(emails).includes(:emails)
273
274
      emails.each do |email|
275
        user = users.find { |u| u.any_email?(email) }
276
277
        loader.call(email, user)
278
      end
279
    end
280
    end
281
282
    def author
283
    # We use __sync so that we get the actual objects back (including an actual
284
    # nil), instead of a wrapper, as returning a wrapped nil breaks a lot of
285
    # code.
286
    lazy_author.__sync
287
    end
288
    request_cache(:author) { author_email.downcase }
289
290
    def committer
291
    @committer ||= User.find_by_any_email(committer_email)
292
    end
293
294
    def parents
295
    @parents ||= parent_ids.map { |oid| Commit.lazy(project, oid) }
296
    end
297
298
    def parent
299
    strong_memoize(:parent) do
300
      project.commit_by(oid: self.parent_id) if self.parent_id
301
    end
302
    end
303
304
    def notes
305
    project.notes.for_commit_id(self.id)
306
    end
307
308
    def discussion_notes
309
    notes.non_diff_notes
310
    end
311
312
    def notes_with_associations
313
    notes.includes(:author, :award_emoji)
314
    end
315
316
    def merge_requests
317
    @merge_requests ||= project.merge_requests.by_commit_sha(sha)
318
    end
319
320
    def method_missing(method, *args, &block)
321
    @raw.__send__(method, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
322
    end
323
324
    def respond_to_missing?(method, include_private = false)
325
    @raw.respond_to?(method, include_private) || super
326
    end
327
328
    def short_id
329
    @raw.short_id(MIN_SHA_LENGTH)
330
    end
331
332
    def diff_refs
333
    Gitlab::Diff::DiffRefs.new(
334
      base_sha: self.parent_id || Gitlab::Git::BLANK_SHA,
335
      head_sha: self.sha
336
    )
337
    end
338
339
    def pipelines
340
    project.ci_pipelines.where(sha: sha)
341
    end
342
343
    def last_pipeline
344
    strong_memoize(:last_pipeline) do
345
      pipelines.last
346
    end
347
    end
348
349
    def status(ref = nil)
350
    return @statuses[ref] if @statuses.key?(ref)
351
352
    @statuses[ref] = status_for_project(ref, project)
353
    end
354
355
    def status_for_project(ref, pipeline_project)
356
    pipeline_project.ci_pipelines.latest_status_per_commit(id, ref)[id]
357
    end
358
359
    def set_status_for_ref(ref, status)
360
    @statuses[ref] = status
361
    end
362
363
    def signature
364
    return @signature if defined?(@signature)
365
366
    @signature = gpg_commit.signature
367
    end
368
369
    delegate :has_signature?, to: :gpg_commit
370
371
    def revert_branch_name
372
    "revert-#{short_id}"
373
    end
374
375
    def cherry_pick_branch_name
376
    project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
377
    end
378
379
    def cherry_pick_description(user)
380
    message_body = ["(cherry picked from commit #{sha})"]
381
382
    if merged_merge_request?(user)
383
      commits_in_merge_request = merged_merge_request(user).commits
384
385
      if commits_in_merge_request.present?
386
        message_body << ""
387
388
        commits_in_merge_request.reverse.each do |commit_in_merge|
389
          message_body << "#{commit_in_merge.short_id} #{commit_in_merge.title}"
390
        end
391
      end
392
    end
393
394
    message_body.join("\n")
395
    end
396
397
    def cherry_pick_message(user)
398
    %Q{#{message}\n\n#{cherry_pick_description(user)}}
399
    end
400
401
    def revert_description(user)
402
    if merged_merge_request?(user)
403
      "This reverts merge request #{merged_merge_request(user).to_reference}"
404
    else
405
      "This reverts commit #{sha}"
406
    end
407
    end
408
409
    def revert_message(user)
410
    %Q{Revert "#{title.strip}"\n\n#{revert_description(user)}}
411
    end
412
413
    def reverts_commit?(commit, user)
414
    description? && description.include?(commit.revert_description(user))
415
    end
416
417
    def merge_commit?
418
    parent_ids.size > 1
419
    end
420
421
    def merged_merge_request(current_user)
422
    # Memoize with per-user access check
423
    @merged_merge_request_hash ||= Hash.new do |hash, user|
424
      hash[user] = merged_merge_request_no_cache(user)
425
    end
426
427
    @merged_merge_request_hash[current_user]
428
    end
429
430
    def has_been_reverted?(current_user, notes_association = nil)
431
    ext = all_references(current_user)
432
    notes_association ||= notes_with_associations
433
434
    notes_association.system.each do |note|
435
      note.all_references(current_user, extractor: ext)
436
    end
437
438
    ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) }
439
    end
440
441
    def change_type_title(user)
442
    merged_merge_request?(user) ? 'merge request' : 'commit'
443
    end
444
445
    # Get the URI type of the given path
446
    #
447
    # Used to build URLs to files in the repository in GFM.
448
    #
449
    # path - String path to check
450
    #
451
    # Examples:
452
    #
453
    #   uri_type('doc/README.md') # => :blob
454
    #   uri_type('doc/logo.png')  # => :raw
455
    #   uri_type('doc/api')       # => :tree
456
    #   uri_type('not/found')     # => nil
457
    #
458
    # Returns a symbol
459
    def uri_type(path)
460
    entry = @raw.tree_entry(path)
461
    return unless entry
462
463
    if entry[:type] == :blob
464
      blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project)
465
      blob.image? || blob.video? ? :raw : :blob
466
    else
467
      entry[:type]
468
    end
469
    end
470
471
    def raw_diffs(*args)
472
    raw.diffs(*args)
473
    end
474
475
    def raw_deltas
476
    @deltas ||= raw.deltas
477
    end
478
479
    def diffs(diff_options = {})
480
    Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options)
481
    end
482
483
    def persisted?
484
    true
485
    end
486
487
    def to_ability_name
488
    model_name.singular
489
    end
490
491
    def touch
492
    # no-op but needs to be defined since #persisted? is defined
493
    end
494
495
    def touch_later
496
    # No-op.
497
    # This method is called by ActiveRecord.
498
    # We don't want to do anything for `Commit` model, so this is empty.
499
    end
500
501
    WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze
502
503
    def work_in_progress?
504
    !!(title =~ WIP_REGEX)
505
    end
506
507
    def merged_merge_request?(user)
508
    !!merged_merge_request(user)
509
    end
510
511
    def cache_key
512
    "commit:#{sha}"
513
    end
514
515
    private
516
517
    def commit_reference(from, referable_commit_id, full: false)
518
    reference = project.to_reference(from, full: full)
519
520
    if reference.present?
521
      "#{reference}#{self.class.reference_prefix}#{referable_commit_id}"
522
    else
523
      referable_commit_id
524
    end
525
    end
526
527
    def repo_changes
528
    changes = { added: [], modified: [], removed: [] }
529
530
    raw_deltas.each do |diff|
531
      if diff.deleted_file
532
        changes[:removed] << diff.old_path
533
      elsif diff.renamed_file || diff.new_file
534
        changes[:added] << diff.new_path
535
      else
536
        changes[:modified] << diff.new_path
537
      end
538
    end
539
540
    changes
541
    end
542
543
    def merged_merge_request_no_cache(user)
544
    MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
545
    end
546
    end
547
    */
548
}
549