Welcome to mirror list, hosted at ThFree Co, Russian Federation.

weak_passwords.rb « security « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 42b021329338784564a75e6a65dd0d69890de4ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# frozen_string_literal: true
module Security
  module WeakPasswords
    # These words are predictable in GitLab's specific context, and
    # therefore cannot occur anywhere within a password.
    FORBIDDEN_WORDS = Set['gitlab', 'devops'].freeze

    # Substrings shorter than this may appear legitimately in a truly
    # random password.
    MINIMUM_SUBSTRING_SIZE = 4

    class << self
      # Returns true when the password is on a list of weak passwords,
      # or contains predictable substrings derived from user attributes.
      # Case insensitive.
      def weak_for_user?(password, user)
        forbidden_word_appears_in_password?(password) ||
          name_appears_in_password?(password, user) ||
          username_appears_in_password?(password, user) ||
          email_appears_in_password?(password, user) ||
          password_on_weak_list?(password)
      end

      private

      def forbidden_word_appears_in_password?(password)
        contains_predicatable_substring?(password, FORBIDDEN_WORDS)
      end

      def name_appears_in_password?(password, user)
        return false if user.name.blank?

        # Check for the full name
        substrings = [user.name]
        # Also check parts of their name
        substrings += user.name.split(/[^\p{Alnum}]/)

        contains_predicatable_substring?(password, substrings)
      end

      def username_appears_in_password?(password, user)
        return false if user.username.blank?

        # Check for the full username
        substrings = [user.username]
        # Also check sub-strings in the username
        substrings += user.username.split(/[^\p{Alnum}]/)

        contains_predicatable_substring?(password, substrings)
      end

      def email_appears_in_password?(password, user)
        return false if user.email.blank?

        # Check for the full email
        substrings = [user.email]
        # Also check full first part and full domain name
        substrings += user.email.split("@")
        # And any parts of non-word characters (e.g. firstname.lastname+tag@...)
        substrings += user.email.split(/[^\p{Alnum}]/)

        contains_predicatable_substring?(password, substrings)
      end

      def password_on_weak_list?(password)
        # Our weak list stores SHA2 hashes of passwords, not the weak
        # passwords themselves.
        digest = Digest::SHA256.base64digest(password.downcase)
        Settings.gitlab.weak_passwords_digest_set.include?(digest)
      end

      # Case-insensitively checks whether a password includes a dynamic
      # list of substrings. Substrings which are too short are not
      # predictable and may occur randomly, and therefore not checked.
      def contains_predicatable_substring?(password, substrings)
        substrings = substrings.filter_map do |substring|
          substring.downcase if substring.length >= MINIMUM_SUBSTRING_SIZE
        end

        password = password.downcase

        # Returns true when a predictable substring occurs anywhere
        # in the password.
        substrings.any? { |word| password.include?(word) }
      end
    end
  end
end