hibp-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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