google-apis-securitycenter_v1 0.15.0 → 0.19.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1628e48e4704916ae60a85c0f263b41226b0f07f57eea8c5a214ea5d9dfb39f
4
- data.tar.gz: 062df5d8bca6bda49ef0d775305dd8c05032ea5e9dee52b5635348ea53e031f7
3
+ metadata.gz: 80485508ce897b7291af8f3ffee3eeae8e0394d1a951e314e924a48d2bac9bc2
4
+ data.tar.gz: 51b1a908cee074d1bdfea96c89ddff626fe2b40fe59b8e2c52769346067f7707
5
5
  SHA512:
6
- metadata.gz: 309bdfd2c23bfc11f8d1986af4ec3a0cd25436c555b56882cde2cda53b32743d4f002516e92ef81f81b6d797384c4e826dacf0745293ef62de2a3fc88533fd30
7
- data.tar.gz: 43b077e78747b63654e6c8b04f4e70f23934e53c325def7c7f88c4665aa0e7f43747d765c716ea1ed4657184e4a40beee2206d3a0f04f4ed281482e68b13256e
6
+ metadata.gz: 9b7c0ed1fd0475e63fd013891a368a239eab66012bd454fb2445963608c7f489c97abb035966983e8cd07d91de8209c0adf8909c860276363e6d855bb9fc8da2
7
+ data.tar.gz: a110f567c62b39769a6378b594f9e6dd71a1512067cf9e0fecbca70ca986f6607417c16de7eb28bb23151e9f4182b2e88ce83a824d5d314763d6d82227cacac4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Release history for google-apis-securitycenter_v1
2
2
 
3
+ ### v0.19.0 (2022-01-19)
4
+
5
+ * Regenerated from discovery document revision 20220113
6
+ * Regenerated using generator version 0.4.1
7
+
8
+ ### v0.18.0 (2021-12-16)
9
+
10
+ * Unspecified changes
11
+
12
+ ### v0.17.0 (2021-12-09)
13
+
14
+ * Regenerated from discovery document revision 20211207
15
+
16
+ ### v0.16.0 (2021-11-16)
17
+
18
+ * Regenerated from discovery document revision 20211112
19
+
3
20
  ### v0.15.0 (2021-11-09)
4
21
 
5
22
  * Regenerated from discovery document revision 20211103
data/OVERVIEW.md CHANGED
@@ -51,7 +51,7 @@ require "google/apis/securitycenter_v1"
51
51
  client = Google::Apis::SecuritycenterV1::SecurityCommandCenterService.new
52
52
 
53
53
  # Authenticate calls
54
- client.authentication = # ... use the googleauth gem to create credentials
54
+ client.authorization = # ... use the googleauth gem to create credentials
55
55
  ```
56
56
 
57
57
  See the class reference docs for information on the methods you can call from a client.
@@ -22,6 +22,57 @@ module Google
22
22
  module Apis
23
23
  module SecuritycenterV1
24
24
 
25
+ # Represents an access event.
26
+ class Access
27
+ include Google::Apis::Core::Hashable
28
+
29
+ # Caller's IP address, such as "1.1.1.1".
30
+ # Corresponds to the JSON property `callerIp`
31
+ # @return [String]
32
+ attr_accessor :caller_ip
33
+
34
+ # Represents a geographical location for a given access.
35
+ # Corresponds to the JSON property `callerIpGeo`
36
+ # @return [Google::Apis::SecuritycenterV1::Geolocation]
37
+ attr_accessor :caller_ip_geo
38
+
39
+ # The method that the service account called, e.g. "SetIamPolicy".
40
+ # Corresponds to the JSON property `methodName`
41
+ # @return [String]
42
+ attr_accessor :method_name
43
+
44
+ # Associated email, such as "foo@google.com".
45
+ # Corresponds to the JSON property `principalEmail`
46
+ # @return [String]
47
+ attr_accessor :principal_email
48
+
49
+ # This is the API service that the service account made a call to, e.g. "iam.
50
+ # googleapis.com"
51
+ # Corresponds to the JSON property `serviceName`
52
+ # @return [String]
53
+ attr_accessor :service_name
54
+
55
+ # What kind of user agent is associated, e.g. operating system shells, embedded
56
+ # or stand-alone applications, etc.
57
+ # Corresponds to the JSON property `userAgentFamily`
58
+ # @return [String]
59
+ attr_accessor :user_agent_family
60
+
61
+ def initialize(**args)
62
+ update!(**args)
63
+ end
64
+
65
+ # Update properties of this object
66
+ def update!(**args)
67
+ @caller_ip = args[:caller_ip] if args.key?(:caller_ip)
68
+ @caller_ip_geo = args[:caller_ip_geo] if args.key?(:caller_ip_geo)
69
+ @method_name = args[:method_name] if args.key?(:method_name)
70
+ @principal_email = args[:principal_email] if args.key?(:principal_email)
71
+ @service_name = args[:service_name] if args.key?(:service_name)
72
+ @user_agent_family = args[:user_agent_family] if args.key?(:user_agent_family)
73
+ end
74
+ end
75
+
25
76
  # Security Command Center representation of a Google Cloud resource. The Asset
26
77
  # is a Security Command Center resource that captures information about a single
27
78
  # Google Cloud resource. All modifications to an Asset are only within the
@@ -273,6 +324,42 @@ module Google
273
324
  end
274
325
  end
275
326
 
327
+ # Request message for bulk findings update. Note: 1. If multiple bulk update
328
+ # requests match the same resource, the order in which they get executed is not
329
+ # defined. 2. Once a bulk operation is started, there is no way to stop it.
330
+ class BulkMuteFindingsRequest
331
+ include Google::Apis::Core::Hashable
332
+
333
+ # Expression that identifies findings that should be updated. The expression is
334
+ # a list of zero or more restrictions combined via logical operators `AND` and `
335
+ # OR`. Parentheses are supported, and `OR` has higher precedence than `AND`.
336
+ # Restrictions have the form ` ` and may have a `-` character in front of them
337
+ # to indicate negation. The fields map to those defined in the corresponding
338
+ # resource. The supported operators are: * `=` for all value types. * `>`, `<`, `
339
+ # >=`, `<=` for integer values. * `:`, meaning substring matching, for strings.
340
+ # The supported value types are: * string literals in quotes. * integer literals
341
+ # without quotes. * boolean literals `true` and `false` without quotes.
342
+ # Corresponds to the JSON property `filter`
343
+ # @return [String]
344
+ attr_accessor :filter
345
+
346
+ # This can be a mute configuration name or any identifier for mute/unmute of
347
+ # findings based on the filter.
348
+ # Corresponds to the JSON property `muteAnnotation`
349
+ # @return [String]
350
+ attr_accessor :mute_annotation
351
+
352
+ def initialize(**args)
353
+ update!(**args)
354
+ end
355
+
356
+ # Update properties of this object
357
+ def update!(**args)
358
+ @filter = args[:filter] if args.key?(:filter)
359
+ @mute_annotation = args[:mute_annotation] if args.key?(:mute_annotation)
360
+ end
361
+ end
362
+
276
363
  # CVE stands for Common Vulnerabilities and Exposures. More information: https://
277
364
  # cve.mitre.org
278
365
  class Cve
@@ -462,6 +549,11 @@ module Google
462
549
  class Finding
463
550
  include Google::Apis::Core::Hashable
464
551
 
552
+ # Represents an access event.
553
+ # Corresponds to the JSON property `access`
554
+ # @return [Google::Apis::SecuritycenterV1::Access]
555
+ attr_accessor :access
556
+
465
557
  # The canonical name of the finding. It's either "organizations/`organization_id`
466
558
  # /sources/`source_id`/findings/`finding_id`", "folders/`folder_id`/sources/`
467
559
  # source_id`/findings/`finding_id`" or "projects/`project_number`/sources/`
@@ -482,16 +574,22 @@ module Google
482
574
  # @return [String]
483
575
  attr_accessor :create_time
484
576
 
485
- # The time at which the event took place, or when an update to the finding
486
- # occurred. For example, if the finding represents an open firewall it would
487
- # capture the time the detector believes the firewall became open. The accuracy
488
- # is determined by the detector. If the finding were to be resolved afterward,
489
- # this time would reflect when the finding was resolved. Must not be set to a
490
- # value greater than the current timestamp.
577
+ # The time the finding was first detected. If an existing finding is updated,
578
+ # then this is the time the update occurred. For example, if the finding
579
+ # represents an open firewall, this property captures the time the detector
580
+ # believes the firewall became open. The accuracy is determined by the detector.
581
+ # If the finding is later resolved, then this time reflects when the finding was
582
+ # resolved. This must not be set to a value greater than the current timestamp.
491
583
  # Corresponds to the JSON property `eventTime`
492
584
  # @return [String]
493
585
  attr_accessor :event_time
494
586
 
587
+ # Output only. Third party SIEM/SOAR fields within SCC, contains external system
588
+ # information and external system finding fields.
589
+ # Corresponds to the JSON property `externalSystems`
590
+ # @return [Hash<String,Google::Apis::SecuritycenterV1::GoogleCloudSecuritycenterV1ExternalSystem>]
591
+ attr_accessor :external_systems
592
+
495
593
  # The URI that, if available, points to a web page outside of Security Command
496
594
  # Center where additional information about the finding can be found. This field
497
595
  # is guaranteed to be either empty or a well formed URL.
@@ -512,6 +610,30 @@ module Google
512
610
  # @return [Google::Apis::SecuritycenterV1::Indicator]
513
611
  attr_accessor :indicator
514
612
 
613
+ # MITRE ATT&CK tactics and techniques related to this finding. See: https://
614
+ # attack.mitre.org
615
+ # Corresponds to the JSON property `mitreAttack`
616
+ # @return [Google::Apis::SecuritycenterV1::MitreAttack]
617
+ attr_accessor :mitre_attack
618
+
619
+ # Indicates the mute state of a finding (either unspecified, muted, unmuted or
620
+ # undefined).
621
+ # Corresponds to the JSON property `mute`
622
+ # @return [String]
623
+ attr_accessor :mute
624
+
625
+ # First known as mute_annotation. Records additional information about the mute
626
+ # operation e.g. mute config that muted the finding, user who muted the finding,
627
+ # etc.
628
+ # Corresponds to the JSON property `muteInitiator`
629
+ # @return [String]
630
+ attr_accessor :mute_initiator
631
+
632
+ # Output only. The most recent time this finding was muted or unmuted.
633
+ # Corresponds to the JSON property `muteUpdateTime`
634
+ # @return [String]
635
+ attr_accessor :mute_update_time
636
+
515
637
  # The relative resource name of this finding. See: https://cloud.google.com/apis/
516
638
  # design/resource_names#relative_resource_name Example: "organizations/`
517
639
  # organization_id`/sources/`source_id`/findings/`finding_id`"
@@ -574,13 +696,19 @@ module Google
574
696
 
575
697
  # Update properties of this object
576
698
  def update!(**args)
699
+ @access = args[:access] if args.key?(:access)
577
700
  @canonical_name = args[:canonical_name] if args.key?(:canonical_name)
578
701
  @category = args[:category] if args.key?(:category)
579
702
  @create_time = args[:create_time] if args.key?(:create_time)
580
703
  @event_time = args[:event_time] if args.key?(:event_time)
704
+ @external_systems = args[:external_systems] if args.key?(:external_systems)
581
705
  @external_uri = args[:external_uri] if args.key?(:external_uri)
582
706
  @finding_class = args[:finding_class] if args.key?(:finding_class)
583
707
  @indicator = args[:indicator] if args.key?(:indicator)
708
+ @mitre_attack = args[:mitre_attack] if args.key?(:mitre_attack)
709
+ @mute = args[:mute] if args.key?(:mute)
710
+ @mute_initiator = args[:mute_initiator] if args.key?(:mute_initiator)
711
+ @mute_update_time = args[:mute_update_time] if args.key?(:mute_update_time)
584
712
  @name = args[:name] if args.key?(:name)
585
713
  @parent = args[:parent] if args.key?(:parent)
586
714
  @resource_name = args[:resource_name] if args.key?(:resource_name)
@@ -618,6 +746,25 @@ module Google
618
746
  end
619
747
  end
620
748
 
749
+ # Represents a geographical location for a given access.
750
+ class Geolocation
751
+ include Google::Apis::Core::Hashable
752
+
753
+ # A CLDR.
754
+ # Corresponds to the JSON property `regionCode`
755
+ # @return [String]
756
+ attr_accessor :region_code
757
+
758
+ def initialize(**args)
759
+ update!(**args)
760
+ end
761
+
762
+ # Update properties of this object
763
+ def update!(**args)
764
+ @region_code = args[:region_code] if args.key?(:region_code)
765
+ end
766
+ end
767
+
621
768
  # Request message for `GetIamPolicy` method.
622
769
  class GetIamPolicyRequest
623
770
  include Google::Apis::Core::Hashable
@@ -665,6 +812,139 @@ module Google
665
812
  end
666
813
  end
667
814
 
815
+ # The response to a BulkMute request. Contains the LRO information.
816
+ class GoogleCloudSecuritycenterV1BulkMuteFindingsResponse
817
+ include Google::Apis::Core::Hashable
818
+
819
+ def initialize(**args)
820
+ update!(**args)
821
+ end
822
+
823
+ # Update properties of this object
824
+ def update!(**args)
825
+ end
826
+ end
827
+
828
+ # Representation of third party SIEM/SOAR fields within SCC.
829
+ class GoogleCloudSecuritycenterV1ExternalSystem
830
+ include Google::Apis::Core::Hashable
831
+
832
+ # References primary/secondary etc assignees in the external system.
833
+ # Corresponds to the JSON property `assignees`
834
+ # @return [Array<String>]
835
+ attr_accessor :assignees
836
+
837
+ # The most recent time when the corresponding finding's ticket/tracker was
838
+ # updated in the external system.
839
+ # Corresponds to the JSON property `externalSystemUpdateTime`
840
+ # @return [String]
841
+ attr_accessor :external_system_update_time
842
+
843
+ # Identifier that's used to track the given finding in the external system.
844
+ # Corresponds to the JSON property `externalUid`
845
+ # @return [String]
846
+ attr_accessor :external_uid
847
+
848
+ # External System Name e.g. jira, demisto, etc. e.g.: organizations/1234/sources/
849
+ # 5678/findings/123456/externalSystems/jira folders/1234/sources/5678/findings/
850
+ # 123456/externalSystems/jira projects/1234/sources/5678/findings/123456/
851
+ # externalSystems/jira
852
+ # Corresponds to the JSON property `name`
853
+ # @return [String]
854
+ attr_accessor :name
855
+
856
+ # Most recent status of the corresponding finding's ticket/tracker in the
857
+ # external system.
858
+ # Corresponds to the JSON property `status`
859
+ # @return [String]
860
+ attr_accessor :status
861
+
862
+ def initialize(**args)
863
+ update!(**args)
864
+ end
865
+
866
+ # Update properties of this object
867
+ def update!(**args)
868
+ @assignees = args[:assignees] if args.key?(:assignees)
869
+ @external_system_update_time = args[:external_system_update_time] if args.key?(:external_system_update_time)
870
+ @external_uid = args[:external_uid] if args.key?(:external_uid)
871
+ @name = args[:name] if args.key?(:name)
872
+ @status = args[:status] if args.key?(:status)
873
+ end
874
+ end
875
+
876
+ # A mute config is a Cloud SCC resource that contains the configuration to mute
877
+ # create/update events of findings.
878
+ class GoogleCloudSecuritycenterV1MuteConfig
879
+ include Google::Apis::Core::Hashable
880
+
881
+ # Output only. The time at which the mute config was created. This field is set
882
+ # by the server and will be ignored if provided on config creation.
883
+ # Corresponds to the JSON property `createTime`
884
+ # @return [String]
885
+ attr_accessor :create_time
886
+
887
+ # A description of the mute config.
888
+ # Corresponds to the JSON property `description`
889
+ # @return [String]
890
+ attr_accessor :description
891
+
892
+ # The human readable name to be displayed for the mute config.
893
+ # Corresponds to the JSON property `displayName`
894
+ # @return [String]
895
+ attr_accessor :display_name
896
+
897
+ # Required. An expression that defines the filter to apply across create/update
898
+ # events of findings. While creating a filter string, be mindful of the scope in
899
+ # which the mute configuration is being created. E.g., If a filter contains
900
+ # project = X but is created under the project = Y scope, it might not match any
901
+ # findings. The following field and operator combinations are supported: *
902
+ # severity: `=`, `:` * category: `=`, `:` * resource.name: `=`, `:` * resource.
903
+ # project_name: `=`, `:` * resource.project_display_name: `=`, `:` * resource.
904
+ # folders.resource_folder: `=`, `:` * resource.parent_name: `=`, `:` * resource.
905
+ # parent_display_name: `=`, `:` * resource.type: `=`, `:` * finding_class: `=`, `
906
+ # :` * indicator.ip_addresses: `=`, `:` * indicator.domains: `=`, `:`
907
+ # Corresponds to the JSON property `filter`
908
+ # @return [String]
909
+ attr_accessor :filter
910
+
911
+ # Output only. Email address of the user who last edited the mute config. This
912
+ # field is set by the server and will be ignored if provided on config creation
913
+ # or update.
914
+ # Corresponds to the JSON property `mostRecentEditor`
915
+ # @return [String]
916
+ attr_accessor :most_recent_editor
917
+
918
+ # This field will be ignored if provided on config creation. Format "
919
+ # organizations/`organization`/muteConfigs/`mute_config`" "folders/`folder`/
920
+ # muteConfigs/`mute_config`" "projects/`project`/muteConfigs/`mute_config`"
921
+ # Corresponds to the JSON property `name`
922
+ # @return [String]
923
+ attr_accessor :name
924
+
925
+ # Output only. The most recent time at which the mute config was updated. This
926
+ # field is set by the server and will be ignored if provided on config creation
927
+ # or update.
928
+ # Corresponds to the JSON property `updateTime`
929
+ # @return [String]
930
+ attr_accessor :update_time
931
+
932
+ def initialize(**args)
933
+ update!(**args)
934
+ end
935
+
936
+ # Update properties of this object
937
+ def update!(**args)
938
+ @create_time = args[:create_time] if args.key?(:create_time)
939
+ @description = args[:description] if args.key?(:description)
940
+ @display_name = args[:display_name] if args.key?(:display_name)
941
+ @filter = args[:filter] if args.key?(:filter)
942
+ @most_recent_editor = args[:most_recent_editor] if args.key?(:most_recent_editor)
943
+ @name = args[:name] if args.key?(:name)
944
+ @update_time = args[:update_time] if args.key?(:update_time)
945
+ end
946
+ end
947
+
668
948
  # Cloud SCC's Notification
669
949
  class GoogleCloudSecuritycenterV1NotificationMessage
670
950
  include Google::Apis::Core::Hashable
@@ -1622,6 +1902,32 @@ module Google
1622
1902
  end
1623
1903
  end
1624
1904
 
1905
+ # Response message for listing mute configs.
1906
+ class ListMuteConfigsResponse
1907
+ include Google::Apis::Core::Hashable
1908
+
1909
+ # The mute configs from the specified parent.
1910
+ # Corresponds to the JSON property `muteConfigs`
1911
+ # @return [Array<Google::Apis::SecuritycenterV1::GoogleCloudSecuritycenterV1MuteConfig>]
1912
+ attr_accessor :mute_configs
1913
+
1914
+ # A token, which can be sent as `page_token` to retrieve the next page. If this
1915
+ # field is omitted, there are no subsequent pages.
1916
+ # Corresponds to the JSON property `nextPageToken`
1917
+ # @return [String]
1918
+ attr_accessor :next_page_token
1919
+
1920
+ def initialize(**args)
1921
+ update!(**args)
1922
+ end
1923
+
1924
+ # Update properties of this object
1925
+ def update!(**args)
1926
+ @mute_configs = args[:mute_configs] if args.key?(:mute_configs)
1927
+ @next_page_token = args[:next_page_token] if args.key?(:next_page_token)
1928
+ end
1929
+ end
1930
+
1625
1931
  # Response message for listing notification configs.
1626
1932
  class ListNotificationConfigsResponse
1627
1933
  include Google::Apis::Core::Hashable
@@ -1699,6 +2005,56 @@ module Google
1699
2005
  end
1700
2006
  end
1701
2007
 
2008
+ # MITRE ATT&CK tactics and techniques related to this finding. See: https://
2009
+ # attack.mitre.org
2010
+ class MitreAttack
2011
+ include Google::Apis::Core::Hashable
2012
+
2013
+ # Additional MITRE ATT&CK tactics related to this finding, if any.
2014
+ # Corresponds to the JSON property `additionalTactics`
2015
+ # @return [Array<String>]
2016
+ attr_accessor :additional_tactics
2017
+
2018
+ # Additional MITRE ATT&CK techniques related to this finding, if any, along with
2019
+ # any of their respective parent techniques.
2020
+ # Corresponds to the JSON property `additionalTechniques`
2021
+ # @return [Array<String>]
2022
+ attr_accessor :additional_techniques
2023
+
2024
+ # The MITRE ATT&CK tactic most closely represented by this finding, if any.
2025
+ # Corresponds to the JSON property `primaryTactic`
2026
+ # @return [String]
2027
+ attr_accessor :primary_tactic
2028
+
2029
+ # The MITRE ATT&CK technique most closely represented by this finding, if any.
2030
+ # primary_techniques is a repeated field because there are multiple levels of
2031
+ # MITRE ATT&CK techniques. If the technique most closely represented by this
2032
+ # finding is a sub-technique (e.g. SCANNING_IP_BLOCKS), both the sub-technique
2033
+ # and its parent technique(s) will be listed (e.g. SCANNING_IP_BLOCKS,
2034
+ # ACTIVE_SCANNING).
2035
+ # Corresponds to the JSON property `primaryTechniques`
2036
+ # @return [Array<String>]
2037
+ attr_accessor :primary_techniques
2038
+
2039
+ # The MITRE ATT&CK version referenced by the above fields. E.g. "8".
2040
+ # Corresponds to the JSON property `version`
2041
+ # @return [String]
2042
+ attr_accessor :version
2043
+
2044
+ def initialize(**args)
2045
+ update!(**args)
2046
+ end
2047
+
2048
+ # Update properties of this object
2049
+ def update!(**args)
2050
+ @additional_tactics = args[:additional_tactics] if args.key?(:additional_tactics)
2051
+ @additional_techniques = args[:additional_techniques] if args.key?(:additional_techniques)
2052
+ @primary_tactic = args[:primary_tactic] if args.key?(:primary_tactic)
2053
+ @primary_techniques = args[:primary_techniques] if args.key?(:primary_techniques)
2054
+ @version = args[:version] if args.key?(:version)
2055
+ end
2056
+ end
2057
+
1702
2058
  # Cloud Security Command Center (Cloud SCC) notification configs. A notification
1703
2059
  # config is a Cloud SCC resource that contains the configuration to send
1704
2060
  # notifications for create/update events of findings, assets and etc.
@@ -2248,6 +2604,25 @@ module Google
2248
2604
  end
2249
2605
  end
2250
2606
 
2607
+ # Request message for updating a finding's mute status.
2608
+ class SetMuteRequest
2609
+ include Google::Apis::Core::Hashable
2610
+
2611
+ # Required. The desired state of the Mute.
2612
+ # Corresponds to the JSON property `mute`
2613
+ # @return [String]
2614
+ attr_accessor :mute
2615
+
2616
+ def initialize(**args)
2617
+ update!(**args)
2618
+ end
2619
+
2620
+ # Update properties of this object
2621
+ def update!(**args)
2622
+ @mute = args[:mute] if args.key?(:mute)
2623
+ end
2624
+ end
2625
+
2251
2626
  # Security Command Center finding source. A finding source is an entity or a
2252
2627
  # mechanism that can produce a finding. A source is like a container of findings
2253
2628
  # that come from the same scanner, logger, monitor, and other tools.
@@ -16,13 +16,13 @@ module Google
16
16
  module Apis
17
17
  module SecuritycenterV1
18
18
  # Version of the google-apis-securitycenter_v1 gem
19
- GEM_VERSION = "0.15.0"
19
+ GEM_VERSION = "0.19.0"
20
20
 
21
21
  # Version of the code generator used to generate this client
22
- GENERATOR_VERSION = "0.4.0"
22
+ GENERATOR_VERSION = "0.4.1"
23
23
 
24
24
  # Revision of the discovery document this client was generated from
25
- REVISION = "20211103"
25
+ REVISION = "20220113"
26
26
  end
27
27
  end
28
28
  end