hibp-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Helpers
5
+ # Hibp::Helpers::AttributeAssignment
6
+ #
7
+ # Used to assign attributes in models
8
+ #
9
+ module AttributeAssignment
10
+ private
11
+
12
+ def assign_attributes(new_attributes)
13
+ unless new_attributes.is_a?(Hash)
14
+ raise ArgumentError, 'Attributes must be a Hash'
15
+ end
16
+
17
+ return if new_attributes.nil? || new_attributes.empty?
18
+
19
+ attributes = stringify_keys(new_attributes)
20
+ attributes.each { |k, v| _assign_attribute(k, v) }
21
+ end
22
+
23
+ def stringify_keys(hash)
24
+ transform_keys(hash, &:to_s)
25
+ end
26
+
27
+ def transform_keys(hash)
28
+ hash.each_with_object({}) do |(key, value), result|
29
+ result[yield(key)] = value
30
+ end
31
+ end
32
+
33
+ def _assign_attribute(attr_name, attr_value)
34
+ return unless respond_to?("#{attr_name}=")
35
+
36
+ public_send("#{attr_name}=", attr_value)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Helpers
5
+ # Hibp::Helpers::JsonConversion
6
+ #
7
+ # Used to convert raw API response data to the entity models
8
+ #
9
+ module JsonConversion
10
+ protected
11
+
12
+ # Convert raw data to the entity model
13
+ #
14
+ # @param data [Array<Hash>, Hash] - Raw data from response
15
+ #
16
+ def convert(data, &block)
17
+ data.is_a?(Array) ? convert_to_list(data, &block) : convert_to_entity(data, &block)
18
+ end
19
+
20
+ private
21
+
22
+ def convert_to_list(data, &block)
23
+ data.map { |d| convert_to_entity(d, &block) }
24
+ end
25
+
26
+ def convert_to_entity(data)
27
+ attributes = data.each_with_object({}) do |(key, value), hash|
28
+ hash[transform_key(key)] = value
29
+ end
30
+
31
+ yield(attributes)
32
+ end
33
+
34
+ def transform_key(key)
35
+ underscore(key.to_s).to_sym
36
+ end
37
+
38
+ def underscore(camel_cased_word)
39
+ camel_cased_word.gsub(/::/, '/')
40
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
41
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
42
+ .tr('-', '_')
43
+ .downcase
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Models
5
+ # Hibp::Models::Breach
6
+ #
7
+ # Used to construct a "breach" model
8
+ #
9
+ # A "breach" is an instance of a system having been compromised by an
10
+ # attacker and the data disclosed.
11
+ #
12
+ # For example, Adobe was a breach, Gawker was a breach etc.
13
+ #
14
+ # A "breach" is an incident where data is inadvertently exposed in a vulnerable system,
15
+ # usually due to insufficient access controls or security weaknesses in the software.
16
+ #
17
+ # @see https://haveibeenpwned.com/FAQs
18
+ #
19
+ class Breach
20
+ include Helpers::AttributeAssignment
21
+
22
+ attr_accessor :name, :title, :domain, :description, :logo_path,
23
+ :data_classes, :pwn_count,
24
+ :breach_date, :added_date, :modified_date,
25
+ :is_verified, :is_fabricated, :is_sensitive, :is_retired, :is_spam_list
26
+
27
+ # @param attributes [Hash] - Attributes in a hash
28
+ #
29
+ # @option attributes [String] :name -
30
+ # A name representing the breach which is unique across all other breaches.
31
+ # This value never changes and may be used to name dependent assets
32
+ # (such as images) but should not be shown directly to end users
33
+ # (see the "title" attribute instead).
34
+ #
35
+ # @option attributes [String] :title -
36
+ # A descriptive title for the breach suitable for displaying to end users.
37
+ # It's unique across all breaches but individual values may change in the future
38
+ # (i.e. if another breach occurs against an organisation already in the system).
39
+ # If a stable value is required to reference the breach, refer to the "Name" attribute instead.
40
+ #
41
+ # @option attributes [String] :domain -
42
+ # The domain of the primary website the breach occurred on.
43
+ # This may be used for identifying other assets external systems may have for the site.
44
+ #
45
+ # @option attributes [Date] :breach_date -
46
+ # The date (with no time) the breach originally occurred on in ISO 8601 format.
47
+ # This is not always accurate — frequently breaches are discovered and reported long after the original incident.
48
+ # Use this attribute as a guide only.
49
+ #
50
+ # @option attributes [DateTime] :added_date -
51
+ # The date and time (precision to the minute) the breach was added to the system in ISO 8601 format.
52
+ #
53
+ # @option attributes [DateTime] :modified_date -
54
+ # The date and time (precision to the minute) the breach was modified in ISO 8601 format.
55
+ # This will only differ from the AddedDate attribute if other attributes
56
+ # represented here are changed or data in the breach itself is changed
57
+ # (i.e. additional data is identified and loaded).
58
+ # It is always either equal to or greater then the AddedDate attribute, never less than.
59
+ #
60
+ # @option attributes [Integer] :pwn_count -
61
+ # The total number of accounts loaded into the system.
62
+ # This is usually less than the total number reported by the media due to
63
+ # duplication or other data integrity issues in the source data.
64
+ #
65
+ # @option attributes [String] :description -
66
+ # Contains an overview of the breach represented in HTML markup.
67
+ # The description may include markup such as emphasis and strong tags as well as hyperlinks.
68
+ #
69
+ # @option attributes [Array<String>] :data_classes -
70
+ # This attribute describes the nature of the data compromised in the breach and
71
+ # contains an alphabetically ordered string array of impacted data classes.
72
+ #
73
+ # @option attributes [Boolean] :is_verified -
74
+ # Indicates that the breach is considered unverified.
75
+ # An unverified breach may not have been hacked from the indicated website.
76
+ # An unverified breach is still loaded into HIBP when there's
77
+ # sufficient confidence that a significant portion of the data is legitimate.
78
+ #
79
+ # @option attributes [Boolean] :is_fabricated -
80
+ # Indicates that the breach is considered fabricated.
81
+ # A fabricated breach is unlikely to have been hacked from the
82
+ # indicated website and usually contains a large amount of manufactured data.
83
+ # However, it still contains legitimate email addresses and asserts that
84
+ # the account owners were compromised in the alleged breach.
85
+ #
86
+ # @option attributes [Boolean] :is_sensitive -
87
+ # Indicates if the breach is considered sensitive.
88
+ # The public API will not return any accounts for a breach flagged as sensitive.
89
+ #
90
+ # @option attributes [Boolean] :is_retired -
91
+ # Indicates if the breach has been retired.
92
+ # This data has been permanently removed and will not be returned by the API.
93
+ #
94
+ # @option attributes [Boolean] :is_spam_list -
95
+ # Indicates if the breach is considered a spam list.
96
+ # This flag has no impact on any other attributes but
97
+ # it means that the data has not come as a result of a security compromise.
98
+ #
99
+ # @option attributes [String] :logo_path -
100
+ # A URI that specifies where a logo for the breached service can be found.
101
+ # Logos are always in PNG format.
102
+ #
103
+ # @raise [ArgumentError]
104
+ # @raise [UnknownAttributeError]
105
+ #
106
+ def initialize(attributes)
107
+ assign_attributes(attributes)
108
+ end
109
+
110
+ %i[verified fabricated sensitive retired spam_list].each do |method|
111
+ define_method("#{method}?") { public_send("is_#{method}") }
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Models
5
+ # Hibp::Models::Password
6
+ #
7
+ # Represents password by the suffix of and
8
+ # a count of how many times it appears in the data set
9
+ #
10
+ class Password
11
+ include Helpers::AttributeAssignment
12
+
13
+ attr_accessor :suffix, :occurrences
14
+
15
+ # @param attributes [Hash]
16
+ #
17
+ # @option attributes [String] :suffix -
18
+ # Password suffix(password hash without first five symbols)
19
+ #
20
+ # @option attributes [Integer] :occurrences -
21
+ # Count of how many times suffix appears in the data set
22
+ #
23
+ def initialize(attributes)
24
+ assign_attributes(attributes)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Models
5
+ # Hibp::Models::Paste
6
+ #
7
+ # Used to construct a "paste" model
8
+ #
9
+ # A "paste" is information that has been "pasted" to a publicly facing
10
+ # website designed to share content such as Pastebin.
11
+ #
12
+ # These services are favoured by hackers due to the ease of anonymously
13
+ # sharing information and they're frequently the first place a breach appears.
14
+ #
15
+ # @note In the future, these attributes may expand without the API being versioned.
16
+ #
17
+ # @see https://haveibeenpwned.com/FAQs
18
+ #
19
+ class Paste
20
+ include Helpers::AttributeAssignment
21
+
22
+ attr_accessor :source, :id, :title, :date, :email_count
23
+
24
+ # @param attributes [Hash]
25
+ #
26
+ # @option attributes [String] :source -
27
+ # The paste service the record was retrieved from.
28
+ # Current values are:
29
+ # - Pastebin
30
+ # - Pastie
31
+ # - Slexy
32
+ # - Ghostbin
33
+ # - QuickLeak
34
+ # - JustPaste
35
+ # - AdHocUrl
36
+ # - PermanentOptOut
37
+ # - OptOut
38
+ #
39
+ # @option attributes [String] :id -
40
+ # The ID of the paste as it was given at the source service.
41
+ # Combined with the "Source" attribute, this can be used to resolve the URL of the paste.
42
+ #
43
+ # @option attributes [String] :title -
44
+ # The title of the paste as observed on the source site.
45
+ # This may be null.
46
+ #
47
+ # @option attributes [String] :date -
48
+ # The date and time (precision to the second) that the paste was posted.
49
+ # This is taken directly from the paste site when this information is
50
+ # available but may be null if no date is published.
51
+ #
52
+ # @option attributes [Integer] :email_count -
53
+ # The number of emails that were found when processing the paste.
54
+ # Emails are extracted by using the regular expression:
55
+ # \b+(?!^.{256})[a-zA-Z0-9\.\-_\+]+@[a-zA-Z0-9\.\-_]+\.[a-zA-Z]+\b
56
+ #
57
+ def initialize(attributes)
58
+ assign_attributes(attributes)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Parsers
5
+ # Hibp::Parsers::Breach
6
+ #
7
+ # Used to convert raw API response data to the breach entity or
8
+ # array of the entities in case if response data contains multiple breaches
9
+ #
10
+ class Breach < Json
11
+
12
+ # Convert raw data to the breach entity
13
+ #
14
+ # @param response [Faraday::Response] -
15
+ # Response that contains raw data for conversion
16
+ #
17
+ # @see https://haveibeenpwned.com/API/v3 (The breach model, Sample breach response)
18
+ #
19
+ # @return [Array<Hibp::Breach>, Hibp::Breach]
20
+ #
21
+ def parse_response(response)
22
+ super(response) do |attributes|
23
+ Models::Breach.new(convert_dates!(attributes))
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def convert_dates!(attributes)
30
+ %i[modified_date breach_date added_date].each do |attr_key|
31
+ next if attributes[attr_key].nil?
32
+
33
+ type = attr_key == :breach_date ? Date : Time
34
+
35
+ attributes[attr_key] = type.parse(attributes[attr_key])
36
+ end
37
+
38
+ attributes
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Parsers
5
+ # Hibp::Parsers::Json
6
+ #
7
+ # Used to parse API JSON response and transform(convert) this
8
+ # raw data to the model specified by converter
9
+ #
10
+ class Json
11
+ include Helpers::JsonConversion
12
+
13
+ # Parse API response
14
+ #
15
+ # @param response [Faraday::Response] -
16
+ # Response that contains raw data for conversion
17
+ #
18
+ # @yield [attributes] - (optional, default: nil)
19
+ # Converter that used to convert complex
20
+ # raw response data to the particular model representation.
21
+ #
22
+ # @note If block with conversion not set than parser returns raw data
23
+ # (useful in cases when response returns array of strings)
24
+ #
25
+ # @raise [Hibp::ServiceError]
26
+ #
27
+ def parse_response(response, &block)
28
+ return nil if empty_response?(response)
29
+
30
+ begin
31
+ _, body = prepare_response(response)
32
+
33
+ block_given? ? convert(body, &block) : body
34
+ rescue Oj::ParseError
35
+ raise_error(response.body)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def empty_response?(response)
42
+ response.body.nil? || response.body.empty?
43
+ end
44
+
45
+ def prepare_response(response)
46
+ headers = response.headers
47
+ body = Oj.load(response.body, symbolize_keys: true)
48
+
49
+ [headers, body]
50
+ end
51
+
52
+ def raise_error(payload)
53
+ error = ServiceError.new(
54
+ "Unparseable response: #{payload}",
55
+ title: 'UNPARSEABLE_RESPONSE', status_code: 500
56
+ )
57
+
58
+ raise error
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Parsers
5
+ # Parsers::Password
6
+ #
7
+ # Used to parse raw data and convert it to the password models
8
+ #
9
+ class Password
10
+ ROWS_SPLITTER = "\r\n"
11
+ ATTRIBUTES_SPLITTER = ':'
12
+
13
+ # Convert API response raw data to the passwords models.
14
+ #
15
+ # @param response [] -
16
+ # Contains the suffix of every hash beginning with the specified prefix,
17
+ # followed by a count of how many times it appears in the data set
18
+ #
19
+ # @return [Array<Hibp::Models::Password>]
20
+ #
21
+ def parse_response(response)
22
+ data = response.body
23
+
24
+ data.split(ROWS_SPLITTER).map(&method(:convert_to_password))
25
+ end
26
+
27
+ private
28
+
29
+ def convert_to_password(row)
30
+ suffix, occurrences = row.split(ATTRIBUTES_SPLITTER)
31
+
32
+ Models::Password.new(suffix: suffix, occurrences: occurrences.to_i)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hibp
4
+ module Parsers
5
+ # Hibp::Parsers::Paste
6
+ #
7
+ # Used to convert raw API response data to the array of the paste entities
8
+ #
9
+ class Paste < Json
10
+ # Convert raw data to the pastes entities
11
+ #
12
+ # @param response [Faraday::Response] -
13
+ # Response that contains raw data for conversion
14
+ #
15
+ # @see https://haveibeenpwned.com/API/v3 (The paste model, Sample paste response)
16
+ #
17
+ # @return [Array<Hibp::Paste>]
18
+ #
19
+ def parse_response(response)
20
+ super(response) { |attributes| Models::Paste.new(attributes) }
21
+ end
22
+ end
23
+ end
24
+ end