moesif_aws_lambda 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f59e608ff68965ce70ee78cab1118c2b3ef2061b7959a2381a8980e7dd4e87ec
4
+ data.tar.gz: 9fda726c38ce6024ed5037dbd9d57265c1a1bedfc18e51f9bac1765fd8a7a427
5
+ SHA512:
6
+ metadata.gz: c47740859106ab0cecf1f92603237ce151118f0331db6471e6eb894a6c2c7f61b9eff32fafdd3454a2c85beb7b04e630482669d277325b0a5a34f2079c9f73a3
7
+ data.tar.gz: 01cee574368e225740b351c3410cd612b8c9cfecf0f28f2a97f7ac2674a60c3b389cf1b9d576d01a2806db8f40bdb92ae292918eea05a37ca647668057eba825
data/LICENSE ADDED
@@ -0,0 +1,216 @@
1
+ Copyright (c) 2022 Moesif, Inc
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
15
+
16
+ Apache License
17
+ Version 2.0, January 2004
18
+ http://www.apache.org/licenses/
19
+
20
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
21
+
22
+ 1. Definitions.
23
+
24
+ "License" shall mean the terms and conditions for use, reproduction,
25
+ and distribution as defined by Sections 1 through 9 of this document.
26
+
27
+ "Licensor" shall mean the copyright owner or entity authorized by
28
+ the copyright owner that is granting the License.
29
+
30
+ "Legal Entity" shall mean the union of the acting entity and all
31
+ other entities that control, are controlled by, or are under common
32
+ control with that entity. For the purposes of this definition,
33
+ "control" means (i) the power, direct or indirect, to cause the
34
+ direction or management of such entity, whether by contract or
35
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
36
+ outstanding shares, or (iii) beneficial ownership of such entity.
37
+
38
+ "You" (or "Your") shall mean an individual or Legal Entity
39
+ exercising permissions granted by this License.
40
+
41
+ "Source" form shall mean the preferred form for making modifications,
42
+ including but not limited to software source code, documentation
43
+ source, and configuration files.
44
+
45
+ "Object" form shall mean any form resulting from mechanical
46
+ transformation or translation of a Source form, including but
47
+ not limited to compiled object code, generated documentation,
48
+ and conversions to other media types.
49
+
50
+ "Work" shall mean the work of authorship, whether in Source or
51
+ Object form, made available under the License, as indicated by a
52
+ copyright notice that is included in or attached to the work
53
+ (an example is provided in the Appendix below).
54
+
55
+ "Derivative Works" shall mean any work, whether in Source or Object
56
+ form, that is based on (or derived from) the Work and for which the
57
+ editorial revisions, annotations, elaborations, or other modifications
58
+ represent, as a whole, an original work of authorship. For the purposes
59
+ of this License, Derivative Works shall not include works that remain
60
+ separable from, or merely link (or bind by name) to the interfaces of,
61
+ the Work and Derivative Works thereof.
62
+
63
+ "Contribution" shall mean any work of authorship, including
64
+ the original version of the Work and any modifications or additions
65
+ to that Work or Derivative Works thereof, that is intentionally
66
+ submitted to Licensor for inclusion in the Work by the copyright owner
67
+ or by an individual or Legal Entity authorized to submit on behalf of
68
+ the copyright owner. For the purposes of this definition, "submitted"
69
+ means any form of electronic, verbal, or written communication sent
70
+ to the Licensor or its representatives, including but not limited to
71
+ communication on electronic mailing lists, source code control systems,
72
+ and issue tracking systems that are managed by, or on behalf of, the
73
+ Licensor for the purpose of discussing and improving the Work, but
74
+ excluding communication that is conspicuously marked or otherwise
75
+ designated in writing by the copyright owner as "Not a Contribution."
76
+
77
+ "Contributor" shall mean Licensor and any individual or Legal Entity
78
+ on behalf of whom a Contribution has been received by Licensor and
79
+ subsequently incorporated within the Work.
80
+
81
+ 2. Grant of Copyright License. Subject to the terms and conditions of
82
+ this License, each Contributor hereby grants to You a perpetual,
83
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
84
+ copyright license to reproduce, prepare Derivative Works of,
85
+ publicly display, publicly perform, sublicense, and distribute the
86
+ Work and such Derivative Works in Source or Object form.
87
+
88
+ 3. Grant of Patent License. Subject to the terms and conditions of
89
+ this License, each Contributor hereby grants to You a perpetual,
90
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
91
+ (except as stated in this section) patent license to make, have made,
92
+ use, offer to sell, sell, import, and otherwise transfer the Work,
93
+ where such license applies only to those patent claims licensable
94
+ by such Contributor that are necessarily infringed by their
95
+ Contribution(s) alone or by combination of their Contribution(s)
96
+ with the Work to which such Contribution(s) was submitted. If You
97
+ institute patent litigation against any entity (including a
98
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
99
+ or a Contribution incorporated within the Work constitutes direct
100
+ or contributory patent infringement, then any patent licenses
101
+ granted to You under this License for that Work shall terminate
102
+ as of the date such litigation is filed.
103
+
104
+ 4. Redistribution. You may reproduce and distribute copies of the
105
+ Work or Derivative Works thereof in any medium, with or without
106
+ modifications, and in Source or Object form, provided that You
107
+ meet the following conditions:
108
+
109
+ (a) You must give any other recipients of the Work or
110
+ Derivative Works a copy of this License; and
111
+
112
+ (b) You must cause any modified files to carry prominent notices
113
+ stating that You changed the files; and
114
+
115
+ (c) You must retain, in the Source form of any Derivative Works
116
+ that You distribute, all copyright, patent, trademark, and
117
+ attribution notices from the Source form of the Work,
118
+ excluding those notices that do not pertain to any part of
119
+ the Derivative Works; and
120
+
121
+ (d) If the Work includes a "NOTICE" text file as part of its
122
+ distribution, then any Derivative Works that You distribute must
123
+ include a readable copy of the attribution notices contained
124
+ within such NOTICE file, excluding those notices that do not
125
+ pertain to any part of the Derivative Works, in at least one
126
+ of the following places: within a NOTICE text file distributed
127
+ as part of the Derivative Works; within the Source form or
128
+ documentation, if provided along with the Derivative Works; or,
129
+ within a display generated by the Derivative Works, if and
130
+ wherever such third-party notices normally appear. The contents
131
+ of the NOTICE file are for informational purposes only and
132
+ do not modify the License. You may add Your own attribution
133
+ notices within Derivative Works that You distribute, alongside
134
+ or as an addendum to the NOTICE text from the Work, provided
135
+ that such additional attribution notices cannot be construed
136
+ as modifying the License.
137
+
138
+ You may add Your own copyright statement to Your modifications and
139
+ may provide additional or different license terms and conditions
140
+ for use, reproduction, or distribution of Your modifications, or
141
+ for any such Derivative Works as a whole, provided Your use,
142
+ reproduction, and distribution of the Work otherwise complies with
143
+ the conditions stated in this License.
144
+
145
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
146
+ any Contribution intentionally submitted for inclusion in the Work
147
+ by You to the Licensor shall be under the terms and conditions of
148
+ this License, without any additional terms or conditions.
149
+ Notwithstanding the above, nothing herein shall supersede or modify
150
+ the terms of any separate license agreement you may have executed
151
+ with Licensor regarding such Contributions.
152
+
153
+ 6. Trademarks. This License does not grant permission to use the trade
154
+ names, trademarks, service marks, or product names of the Licensor,
155
+ except as required for reasonable and customary use in describing the
156
+ origin of the Work and reproducing the content of the NOTICE file.
157
+
158
+ 7. Disclaimer of Warranty. Unless required by applicable law or
159
+ agreed to in writing, Licensor provides the Work (and each
160
+ Contributor provides its Contributions) on an "AS IS" BASIS,
161
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
162
+ implied, including, without limitation, any warranties or conditions
163
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
164
+ PARTICULAR PURPOSE. You are solely responsible for determining the
165
+ appropriateness of using or redistributing the Work and assume any
166
+ risks associated with Your exercise of permissions under this License.
167
+
168
+ 8. Limitation of Liability. In no event and under no legal theory,
169
+ whether in tort (including negligence), contract, or otherwise,
170
+ unless required by applicable law (such as deliberate and grossly
171
+ negligent acts) or agreed to in writing, shall any Contributor be
172
+ liable to You for damages, including any direct, indirect, special,
173
+ incidental, or consequential damages of any character arising as a
174
+ result of this License or out of the use or inability to use the
175
+ Work (including but not limited to damages for loss of goodwill,
176
+ work stoppage, computer failure or malfunction, or any and all
177
+ other commercial damages or losses), even if such Contributor
178
+ has been advised of the possibility of such damages.
179
+
180
+ 9. Accepting Warranty or Additional Liability. While redistributing
181
+ the Work or Derivative Works thereof, You may choose to offer,
182
+ and charge a fee for, acceptance of support, warranty, indemnity,
183
+ or other liability obligations and/or rights consistent with this
184
+ License. However, in accepting such obligations, You may act only
185
+ on Your own behalf and on Your sole responsibility, not on behalf
186
+ of any other Contributor, and only if You agree to indemnify,
187
+ defend, and hold each Contributor harmless for any liability
188
+ incurred by, or claims asserted against, such Contributor by reason
189
+ of your accepting any such warranty or additional liability.
190
+
191
+ END OF TERMS AND CONDITIONS
192
+
193
+ APPENDIX: How to apply the Apache License to your work.
194
+
195
+ To apply the Apache License to your work, attach the following
196
+ boilerplate notice, with the fields enclosed by brackets "[]"
197
+ replaced with your own identifying information. (Don't include
198
+ the brackets!) The text should be enclosed in the appropriate
199
+ comment syntax for the file format. We also recommend that a
200
+ file or class name and description of purpose be included on the
201
+ same "printed page" as the copyright notice for easier
202
+ identification within third-party archives.
203
+
204
+ Copyright [2016 - 2017] Moesif Inc.
205
+
206
+ Licensed under the Apache License, Version 2.0 (the "License");
207
+ you may not use this file except in compliance with the License.
208
+ You may obtain a copy of the License at
209
+
210
+ http://www.apache.org/licenses/LICENSE-2.0
211
+
212
+ Unless required by applicable law or agreed to in writing, software
213
+ distributed under the License is distributed on an "AS IS" BASIS,
214
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
215
+ See the License for the specific language governing permissions and
216
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # Moesif AWS Lambda Middleware Ruby
2
+
3
+ [![Built For][ico-built-for]][link-built-for]
4
+ [![Software License][ico-license]][link-license]
5
+ [![Source Code][ico-source]][link-source]
6
+
7
+ Middleware (Ruby) to automatically log API calls from AWS Lambda functions and sends
8
+ and sends to [Moesif](https://www.moesif.com) for API analytics.
9
+
10
+ This middleware expects the
11
+ [Lambda proxy integration type.](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-set-up-lambda-proxy-integration-on-proxy-resource)
12
+ If you're using AWS Lambda with API Gateway, you are most likely using the proxy integration type.
13
+
14
+
15
+ ## How to install
16
+
17
+ ```shell
18
+ bundle install moesif_aws_lambda
19
+ ```
20
+
21
+ ## How to use
22
+
23
+ ### 1. Wrap your original lambda handler with Moesif Middleware
24
+
25
+
26
+ ```ruby
27
+
28
+ def your_original_handler(event:, context:)
29
+ # your code
30
+ end
31
+
32
+ moesif_options = {
33
+ # application_id is required
34
+ "application_id" => 'Your Moesif Application Id',
35
+ "debug" => true,
36
+ # see list of other options below.
37
+ }
38
+
39
+ # create moesif_middleware object.
40
+ $moesif_middleware = Moesif::MoesifAwsMiddleware.new(method(:your_original_handler), moesif_options)
41
+ ```
42
+
43
+
44
+ ### 2. create a wrapped handler and set AWS lambda to use this.
45
+
46
+
47
+ ```ruby
48
+ def wrapped_handler(event:, context:)
49
+ $moesif_middleware.handle(event: event, context: context);
50
+ end
51
+ ```
52
+
53
+ configure AWS Lambda handler to `wrapped_handler` instead.
54
+
55
+
56
+ ## Configuration Options
57
+
58
+ #### __`application_id`__
59
+
60
+ Required. String. This is the Moesif application_id under settings
61
+ from your [Moesif account.](https://www.moesif.com)
62
+
63
+
64
+ #### __`api_version`__
65
+
66
+ Optional. String. Tag requests with the version of your API.
67
+
68
+
69
+ #### __`identify_user`__
70
+
71
+ Optional.
72
+ identify_user is a Proc that takes event, context, and lambda result as arguments and returns a user_id string. This helps us attribute requests to unique users. Even though Moesif can automatically retrieve the user_id without this, this is highly recommended to ensure accurate attribution.
73
+
74
+ `event` and `context` are original lambda input params, and `result` is the return result from your own original lambda handler.
75
+
76
+ ```ruby
77
+ moesif_options['identify_user'] = Proc.new { |event, context, result|
78
+
79
+ # Add your custom code that returns a string for user id
80
+ '12345'
81
+ }
82
+
83
+ ```
84
+
85
+ #### __`identify_company`__
86
+
87
+ Optional.
88
+ identify_company returns a company_id string. This helps us attribute requests to unique company.
89
+
90
+ ```ruby
91
+ moesif_options['identify_company'] = Proc.new { |event, context, result|
92
+
93
+ # Add your custom code that returns a string for company id
94
+ '67890'
95
+ }
96
+
97
+ ```
98
+
99
+ #### __`identify_session`__
100
+
101
+ Optional. A Proc that takes env, headers, body and returns a string.
102
+
103
+ ```ruby
104
+
105
+ moesif_options['identify_session'] = Proc.new { |event, context, result|
106
+ # Add your custom code that returns a string for session/API token
107
+ 'XXXXXXXXX'
108
+ }
109
+ ```
110
+
111
+ #### __`get_metadata`__
112
+
113
+ Optional.
114
+ get_metadata is a Proc that takes env, headers, and body as arguments and returns a Hash that is
115
+ representation of a JSON object. This allows you to attach any
116
+ metadata to this event.
117
+
118
+ ```ruby
119
+
120
+ moesif_options['get_metadata'] = Proc.new { |event, context, result|
121
+ # Add your custom code that returns a dictionary
122
+ value = {
123
+ 'datacenter' => 'westus',
124
+ 'deployment_version' => 'v1.2.3'
125
+ }
126
+ value
127
+ }
128
+ ```
129
+
130
+
131
+ #### __`mask_data`__
132
+
133
+ Optional. A Proc that takes event_model as an argument and returns event_model.
134
+
135
+ With mask_data, you can make modifications to headers or body of the event before it is sent to Moesif.
136
+
137
+ ```ruby
138
+
139
+ moesif_options['mask_data'] = Proc.new { |event_model|
140
+ # Add your custom code that returns a event_model after modifying any fields
141
+ event_model.response.body.password = nil
142
+ event_model
143
+ }
144
+
145
+ ```
146
+
147
+ For details for the spec of moesif event model, please see the [moesifapi-ruby](https://github.com/Moesif/moesifapi-ruby) or [Moesif Ruby API Documentation](https://www.moesif.com/docs/api?ruby)
148
+
149
+
150
+ #### __`skip`__
151
+
152
+ Optional. A Proc that takes env, headers, body and returns a boolean.
153
+
154
+ ```ruby
155
+
156
+ moesif_options['skip'] = Proc.new { |event, context, result|
157
+ # Add your custom code that returns true to skip logging the API call
158
+ if event.key?("rawPath")
159
+ # Skip probes to health page
160
+ event["rawPath"].include? "/health"
161
+ else
162
+ false
163
+ end
164
+ }
165
+
166
+ ```
167
+
168
+
169
+
170
+ #### __`debug`__
171
+
172
+ Optional. Boolean. Default false. If true, it will print out debug messages. In debug mode, the processing is not done in backend thread.
173
+
174
+ #### __`log_body`__
175
+
176
+ Optional. Boolean. Default true. If false, will not log request and response body to Moesif.
177
+
178
+
179
+ ## Additional Methods for Updating User and Company Profile
180
+
181
+ If you wish to update User or Company profile when needed using this SDK, you can also use below methods:
182
+
183
+ ``` ruby
184
+ $moesif_middleware.update_user(user_profile)
185
+ $moesif_middleware.update_user_batch(user_profiles)
186
+ $moesif_middleware.update_company(company_profile)
187
+ $moesif_middleware.update_company_batch(company_profiles)
188
+ ```
189
+
190
+ For details regarding shape of the profiles, please see the [Moesif Ruby API Documentation](https://www.moesif.com/docs/api?ruby)
191
+
192
+
193
+ ## Notes Regarding Bundling Your Gem Files
194
+
195
+ For AWS lambda with ruby, you have to bundle the gem dependencies into the zip file.
196
+ https://docs.aws.amazon.com/lambda/latest/dg/ruby-package.html
197
+
198
+ In your ruby main file, you may have to specify where the dependencies are installed:
199
+
200
+ ```ruby
201
+ load_paths = Dir[
202
+ "./vendor/bundle/ruby/2.7.0/gems/**/lib"
203
+ ]
204
+ $LOAD_PATH.unshift(*load_paths)
205
+ ```
206
+
207
+ ## Example
208
+
209
+ `example_handler.rb` is an example file that implement this.
210
+
211
+
212
+ [ico-built-for]: https://img.shields.io/badge/built%20for-aws%20lambda-blue.svg
213
+ [ico-license]: https://img.shields.io/badge/License-Apache%202.0-green.svg
214
+ [ico-source]: https://img.shields.io/github/last-commit/moesif/moesif-aws-lambda-ruby.svg?style=social
215
+
216
+ [link-built-for]: https://aws.amazon.com/lambda/
217
+ [link-license]: https://raw.githubusercontent.com/Moesif/moesif-aws-lambda-ruby/master/LICENSE
218
+ [link-source]: https://github.com/moesif/moesif-aws-lambda-ruby
@@ -0,0 +1,83 @@
1
+ require "json"
2
+
3
+ def build_uri(event, context, payload_format_version_1_0)
4
+ headers = event["headers"] || {};
5
+ protocol = headers.fetch("X-Forwarded-Proto", headers.fetch("x-forwarded-proto", "http")) + "://"
6
+ host = headers.fetch("Host", headers.fetch("host", "localhost"))
7
+
8
+ uri = protocol + host
9
+
10
+ if payload_format_version_1_0
11
+ uri = uri + event.fetch.get("path", "/")
12
+ if event.fetch("multiValueQueryStringParameters", {})
13
+ uri = uri + "?" + urlencode(event["multiValueQueryStringParameters"], doseq = True)
14
+ elsif event.fetch("queryStringParameters", {})
15
+ uri = uri + "?" + urlencode(event["queryStringParameters"])
16
+ end
17
+ else
18
+ uri = uri + event.fetch("rawPath", "/")
19
+ if event["rawQueryString"]
20
+ uri = uri + "?" + event["rawQueryString"]
21
+ end
22
+ end
23
+
24
+ uri
25
+ end
26
+
27
+ def get_request_verb(event, context, payload_format_version_1_0)
28
+ verb = event.dig("requestContext", "http", "method") || "GET"
29
+ if payload_format_version_1_0
30
+ verb = event.fetch("httpMethod", "GET")
31
+ end
32
+ verb
33
+ end
34
+
35
+ def get_request_headers(event, context, payload_format_version_1_0)
36
+ req_headers = event["headers"] || {}
37
+ if payload_format_version_1_0
38
+ if event.include? "multiValueHeaders"
39
+ req_headers = (event["multiValueHeaders"] || {}).transform_values do |value|
40
+ value.join("\n")
41
+ end
42
+ end
43
+ end
44
+ req_headers
45
+ end
46
+
47
+ def get_ip_address(event, context, payload_format_version_1_0)
48
+ ip_address = event.dig("requestContext", "http", "sourceIp")
49
+ if payload_format_version_1_0
50
+ ip_address = event.dig("requestContext", "identity", "sourceIp")
51
+ end
52
+ ip_address
53
+ end
54
+
55
+ def get_response_info_from_lambda_result(lambda_result)
56
+ if lambda_result.is_a?(Hash) and lambda_result.include?("statusCode")
57
+ raw_status = lambda_result["statusCode"]
58
+ status = raw_status.to_i
59
+ rsp_body = lambda_result["body"]
60
+ rsp_headers = lambda_result["headers"]
61
+ if lambda_result.includes?("multiValueHeaders")
62
+ multi_value_headers = (lambda_result["multiValueHeaders"] || {}).transform_values do |value|
63
+ value.join("\n")
64
+ end
65
+ rsp_headers = multi_value_headers.merge(lambda_result["headers"] || {})
66
+ end
67
+ rsp_body_transfer_encoding = lambda_result["isBase64Encoded"] ? "base64" : nil
68
+ else
69
+ # see here on how API gateway interpreate lambda_results when no status code.
70
+ # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
71
+ status = 200
72
+ rsp_headers = { "content-type" => "application/json" }
73
+ begin
74
+ rsp_body = JSON.generate(lambda_result)
75
+ transfer_encoding = nil
76
+ rescue
77
+ rsp_body = nil
78
+ rsp_body_transfer_encoding = nil
79
+ end
80
+ end
81
+
82
+ [status, rsp_headers, rsp_body, rsp_body_transfer_encoding]
83
+ end
@@ -0,0 +1,290 @@
1
+ require "moesif_api"
2
+ require "json"
3
+ require "time"
4
+ require "base64"
5
+ require "zlib"
6
+ require "stringio"
7
+
8
+ require_relative "./update_user.rb"
9
+ require_relative "./update_company.rb"
10
+ require_relative "./moesif_helpers.rb"
11
+ require_relative "./lambda_utils.rb"
12
+
13
+ module Moesif
14
+ class MoesifAwsMiddleware
15
+ def initialize(handler, options = {})
16
+ @handler = handler
17
+ if not options["application_id"]
18
+ raise "application_id required for Moesif Middleware"
19
+ end
20
+ @api_client = MoesifApi::MoesifAPIClient.new(options["application_id"])
21
+ @api_controller = @api_client.api
22
+
23
+ @api_version = options["api_version"]
24
+ @identify_user = options["identify_user"]
25
+ @identify_company = options["identify_company"]
26
+ @get_metadata = options["get_metadata"]
27
+ @identify_session = options["identify_session"]
28
+ @mask_data = options["mask_data"]
29
+ @skip = options["skip"]
30
+ @debug = options["debug"]
31
+ @moesif_helpers = MoesifHelpers.new(@debug)
32
+ @disable_transaction_id = options["disable_transaction_id"] || false
33
+ @log_body = options.fetch("log_body", true)
34
+ @event_response_config_etag = nil
35
+ end
36
+
37
+ def update_user(user_profile)
38
+ UserHelper.new.update_user(@api_controller, @debug, user_profile)
39
+ end
40
+
41
+ def update_users_batch(user_profiles)
42
+ UserHelper.new.update_users_batch(@api_controller, @debug, user_profiles)
43
+ end
44
+
45
+ def update_company(company_profile)
46
+ CompanyHelper.new.update_company(@api_controller, @debug, company_profile)
47
+ end
48
+
49
+ def update_companies_batch(company_profiles)
50
+ CompanyHelper.new.update_companies_batch(@api_controller, @debug, company_profiles)
51
+ end
52
+
53
+ def start_with_json(body)
54
+ body.start_with?("{") || body.start_with?("[")
55
+ end
56
+
57
+ def decompress_body(body)
58
+ Zlib::GzipReader.new(StringIO.new(body)).read
59
+ end
60
+
61
+ def base64_encode_body(body)
62
+ return Base64.encode64(body), "base64"
63
+ end
64
+
65
+ def @moesif_helpers.log_debug(message)
66
+ if @debug
67
+ puts("#{Time.now.to_s} [Moesif Middleware] PID #{Process.pid} TID #{Thread.current.object_id} #{message}")
68
+ end
69
+ end
70
+
71
+ def parse_body(body, headers)
72
+ begin
73
+ if (body.instance_of?(Hash) || body.instance_of?(Array))
74
+ parsed_body = body
75
+ transfer_encoding = "json"
76
+ elsif start_with_json(body)
77
+ parsed_body = JSON.parse(body)
78
+ transfer_encoding = "json"
79
+ elsif headers.key?("content-encoding") && ((headers["content-encoding"].downcase).include? "gzip")
80
+ uncompressed_string = decompress_body(body)
81
+ parsed_body, transfer_encoding = base64_encode_body(uncompressed_string)
82
+ else
83
+ parsed_body, transfer_encoding = base64_encode_body(body)
84
+ end
85
+ rescue
86
+ parsed_body, transfer_encoding = base64_encode_body(body)
87
+ end
88
+ return parsed_body, transfer_encoding
89
+ end
90
+
91
+ def start_worker(event_model)
92
+ thr = Thread::new do
93
+ @moesif_helpers.log_debug("start worker")
94
+
95
+ begin
96
+ batch_events = [ event_model ]
97
+ @moesif_helpers.log_debug("Sending #{batch_events.size.to_s} events to Moesif")
98
+ event_api_response = @api_controller.create_events_batch(batch_events)
99
+ @event_response_config_etag = event_api_response[:x_moesif_config_etag]
100
+ @moesif_helpers.log_debug(event_api_response.to_s)
101
+ @moesif_helpers.log_debug("Events successfully sent to Moesif")
102
+ rescue MoesifApi::APIException => e
103
+ if e.response_code.between?(401, 403)
104
+ puts "Unathorized accesss sending event to Moesif. Please verify your Application Id."
105
+ @moesif_helpers.log_debug(e.to_s)
106
+ end
107
+ @moesif_helpers.log_debug("Error sending event to Moesif, with status code #{e.response_code.to_s} #{e.to_s}")
108
+ end
109
+
110
+ end
111
+
112
+ thr.join
113
+ end
114
+
115
+ def handle(event:, context:)
116
+ payload_format_version_1_0 = event["version"] == "1.0"
117
+
118
+ # Request Time
119
+ if payload_format_version_1_0
120
+ epoch = event.dig("requestContext", "requestTimeEpoch")
121
+ else
122
+ epoch = event.dig("requestContext", "timeEpoch")
123
+ end
124
+ if epoch.nil?
125
+ start_time = Time.now.utc.iso8601(3)
126
+ else
127
+ # divide by 1000 to preserve the milliseconds
128
+ start_time = Time.at(epoch / 1000.0).utc.iso8601(3)
129
+ end
130
+ # preserve request headers here in case it is changed down stream.
131
+ req_headers = get_request_headers(event, context, payload_format_version_1_0)
132
+
133
+ @moesif_helpers.log_debug("Calling Moesif middleware")
134
+
135
+ begin
136
+ lambda_result = @handler.call(event: event, context: context)
137
+ rescue Exception => customer_exception
138
+ execution_error = customer_exception
139
+ end
140
+
141
+ end_time = Time.now.utc.iso8601(3)
142
+
143
+ process_send = lambda do
144
+ # REQUEST
145
+ event_req = MoesifApi::EventRequestModel.new()
146
+
147
+ event_req.time = start_time
148
+ event_req.uri = build_uri(event, context, payload_format_version_1_0)
149
+ event_req.verb = get_request_verb(event, context, payload_format_version_1_0)
150
+
151
+ # extract below from lambda_result
152
+ if execution_error
153
+ # so we can still continue
154
+ @moesif_helpers.log_debug("an exception occured with original execution, so capture that.")
155
+ status = 500
156
+ rsp_body = { "message" => "Internal Server Error" }
157
+ rsp_headers = {}
158
+ rsp_body_transfer_encoding = nil
159
+ else
160
+ status, rsp_headers, rsp_body, rsp_body_transfer_encoding = get_response_info_from_lambda_result(lambda_result)
161
+ end
162
+
163
+ if @api_version
164
+ event_req.api_version = @api_version
165
+ end
166
+
167
+ # Add Transaction Id to the Request Header
168
+ if !@disable_transaction_id
169
+ req_trans_id = req_headers["X-MOESIF_TRANSACTION_ID"]
170
+ if !req_trans_id.nil?
171
+ transaction_id = req_trans_id
172
+ if transaction_id.strip.empty?
173
+ transaction_id = SecureRandom.uuid
174
+ end
175
+ else
176
+ transaction_id = SecureRandom.uuid
177
+ end
178
+ # Add Transaction Id to Request Header
179
+ req_headers["X-Moesif-Transaction-Id"] = transaction_id
180
+ # Filter out the old key as HTTP Headers case are not preserved
181
+ if req_headers.key?("X-MOESIF_TRANSACTION_ID")
182
+ req_headers = req_headers.except("X-MOESIF_TRANSACTION_ID")
183
+ end
184
+ end
185
+
186
+ event_req.ip_address = get_ip_address(event, context, payload_format_version_1_0)
187
+ event_req.headers = req_headers
188
+
189
+ if @log_body
190
+ event_req.body = event["body"]
191
+ event_req.transfer_encoding = event["isBase64Encoded"] ? "base64" : "json"
192
+ end
193
+
194
+ # RESPONSEE
195
+ event_rsp = MoesifApi::EventResponseModel.new()
196
+ event_rsp.time = end_time
197
+
198
+ # Add Transaction Id to the Response Header
199
+ if !transaction_id.nil?
200
+ rsp_headers["X-Moesif-Transaction-Id"] = transaction_id
201
+ end
202
+
203
+ event_rsp.status = status
204
+ event_rsp.headers = rsp_headers
205
+
206
+ if @log_body
207
+ event_rsp.body = rsp_body
208
+ event_rsp.transfer_encoding = rsp_body_transfer_encoding
209
+ end
210
+
211
+ event_model = MoesifApi::EventModel.new()
212
+ event_model.request = event_req
213
+ event_model.response = event_rsp
214
+ event_model.direction = "Incoming"
215
+
216
+ if @identify_user
217
+ @moesif_helpers.log_debug "calling identify user proc"
218
+ event_model.user_id = @identify_user.call(event, context, lambda_result)
219
+ end
220
+
221
+ if @identify_company
222
+ @moesif_helpers.log_debug "calling identify company proc"
223
+ event_model.company_id = @identify_company.call(event, context, lambda_result)
224
+ end
225
+
226
+ if @get_metadata
227
+ @moesif_helpers.log_debug "calling get_metadata proc"
228
+ event_model.metadata = @get_metadata.call(event, context, lambda_result)
229
+ else
230
+ @moesif_helpers.log_debug "create default metadata"
231
+ ## get default metadata from context object?
232
+ contextHash = {}
233
+ context.instance_variables.each {|var| contextHash[var.to_s.delete("@")] = context.instance_variable_get(var) }
234
+ event_model.metadata = {
235
+ "trace_id" => context.aws_request_id.to_s,
236
+ "function_name" => context.function_name,
237
+ "request_context" => event["requestContext"],
238
+ "context" => contextHash
239
+ }
240
+ @moesif_helpers.log_debug JSON.generate(event_model.metadata)
241
+ end
242
+
243
+ if @identify_session
244
+ @moesif_helpers.log_debug "calling identify session proc"
245
+ event_model.session_token = @identify_session.call(event, context, lambda_result)
246
+ end
247
+ if @mask_data
248
+ @moesif_helpers.log_debug "calling mask_data proc"
249
+ event_model = @mask_data.call(event_model)
250
+ end
251
+
252
+ @moesif_helpers.log_debug "sending data to moesif"
253
+ @moesif_helpers.log_debug event_model.to_json
254
+
255
+ # send event directly
256
+ start_worker(event_model)
257
+ end # end process_send
258
+
259
+ should_skip = false
260
+
261
+ if @skip
262
+ if @skip.call(event, context, lambda_result)
263
+ should_skip = true
264
+ end
265
+ end
266
+
267
+ if !should_skip
268
+ begin
269
+ process_send.call
270
+ rescue => exception
271
+ @moesif_helpers.log_debug "Error while logging event - "
272
+ @moesif_helpers.log_debug exception.to_s
273
+ @moesif_helpers.log_debug exception.backtrace
274
+ end
275
+ else
276
+ @moesif_helpers.log_debug "Skipped Event using should_skip configuration option."
277
+ end
278
+
279
+ # return original result
280
+ # or raise original exception
281
+ if execution_error
282
+ @moesif_helpers.log_debug "raise original exception that happened."
283
+ raise execution_error
284
+ else
285
+ @moesif_helpers.log_debug "return original lambda result."
286
+ lambda_result
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,14 @@
1
+ require 'time'
2
+
3
+ class MoesifHelpers
4
+
5
+ def initialize debug
6
+ @debug = debug
7
+ end
8
+
9
+ def log_debug(message)
10
+ if @debug
11
+ puts("#{Time.now.to_s} [Moesif Middleware] PID #{Process.pid} TID #{Thread.current.object_id} #{message}")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ class CompanyHelper
2
+ def update_company(api_controller, debug, company_profile)
3
+ if company_profile.any?
4
+ if company_profile.key?("company_id")
5
+ begin
6
+ api_controller.update_company(MoesifApi::CompanyModel.from_hash(company_profile))
7
+ if debug
8
+ puts "Update Company Successfully"
9
+ end
10
+ rescue MoesifApi::APIException => e
11
+ if e.response_code.between?(401, 403)
12
+ puts "Unathorized accesss updating company to Moesif. Please verify your Application Id."
13
+ end
14
+ if debug
15
+ puts "Error updating company to Moesif, with status code: "
16
+ puts e.response_code
17
+ end
18
+ end
19
+ else
20
+ puts "To update a company, a company_id field is required"
21
+ end
22
+ else
23
+ puts "Expecting the input to be of the type - dictionary while updating user"
24
+ end
25
+ end
26
+
27
+ def update_companies_batch(api_controller, debug, company_profiles)
28
+ companyModels = []
29
+ company_profiles.each { |company|
30
+ if company.key?("company_id")
31
+ companyModels << MoesifApi::CompanyModel.from_hash(company)
32
+ else
33
+ puts "To update a company, a company_id field is required"
34
+ end
35
+ }
36
+
37
+ if companyModels.any?
38
+ begin
39
+ api_controller.update_companies_batch(companyModels)
40
+ if debug
41
+ puts "Update Companies Successfully"
42
+ end
43
+ rescue MoesifApi::APIException => e
44
+ if e.response_code.between?(401, 403)
45
+ puts "Unathorized accesss updating companies to Moesif. Please verify your Application Id."
46
+ end
47
+ if debug
48
+ puts "Error updating companies to Moesif, with status code: "
49
+ puts e.response_code
50
+ end
51
+ end
52
+ else
53
+ puts "Expecting the input to be of the type - Array of hashes while updating companies in batch"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ class UserHelper
2
+ def update_user(api_controller, debug, user_profile)
3
+ if user_profile.any?
4
+ if user_profile.key?("user_id")
5
+ begin
6
+ api_controller.update_user(MoesifApi::UserModel.from_hash(user_profile))
7
+ if debug
8
+ puts "Update User Successfully"
9
+ end
10
+ rescue MoesifApi::APIException => e
11
+ if e.response_code.between?(401, 403)
12
+ puts "Unathorized accesss updating user to Moesif. Please verify your Application Id."
13
+ end
14
+ if debug
15
+ puts "Error updating user to Moesif, with status code: "
16
+ puts e.response_code
17
+ end
18
+ end
19
+ else
20
+ puts "To update an user, an user_id field is required"
21
+ end
22
+ else
23
+ puts "Expecting the input to be of the type - dictionary while updating user"
24
+ end
25
+ end
26
+
27
+ def update_users_batch(api_controller, debug, user_profiles)
28
+ userModels = []
29
+ user_profiles.each { |user|
30
+ if user.key?("user_id")
31
+ userModels << MoesifApi::UserModel.from_hash(user)
32
+ else
33
+ puts "To update an user, an user_id field is required"
34
+ end
35
+ }
36
+
37
+ if userModels.any?
38
+ begin
39
+ api_controller.update_users_batch(userModels)
40
+ if debug
41
+ puts "Update Users Successfully"
42
+ end
43
+ rescue MoesifApi::APIException => e
44
+ if e.response_code.between?(401, 403)
45
+ puts "Unathorized accesss updating user to Moesif. Please verify your Application Id."
46
+ end
47
+ if debug
48
+ puts "Error updating user to Moesif, with status code: "
49
+ puts e.response_code
50
+ end
51
+ end
52
+ else
53
+ puts "Expecting the input to be of the type - Array of hashes while updating users in batch"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,2 @@
1
+
2
+ require_relative 'moesif_aws_lambda/moesif_aws_middleware.rb'
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: moesif_aws_lambda
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Moesif, Inc
8
+ - Xing Wang
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2022-08-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: test-unit
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.5'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 3.5.0
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '3.5'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.5.0
34
+ - !ruby/object:Gem::Dependency
35
+ name: moesif_api
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.2.14
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.2.14
48
+ description: Moesif aws lambda middleware for Ruby to log API calls to Moesif API
49
+ analytics and monitoring
50
+ email: xing@moesif.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - LICENSE
56
+ - README.md
57
+ - lib/moesif_aws_lambda.rb
58
+ - lib/moesif_aws_lambda/lambda_utils.rb
59
+ - lib/moesif_aws_lambda/moesif_aws_middleware.rb
60
+ - lib/moesif_aws_lambda/moesif_helpers.rb
61
+ - lib/moesif_aws_lambda/update_company.rb
62
+ - lib/moesif_aws_lambda/update_user.rb
63
+ homepage: https://moesif.com
64
+ licenses:
65
+ - Apache-2.0
66
+ metadata:
67
+ bug_tracker_uri: https://github.com/Moesif/moesif-aws-lambda-ruby/issues
68
+ changelog_uri: https://github.com/Moesif/moesif-aws-lambda-ruby/releases
69
+ documentation_uri: https://github.com/Moesif/moesif-aws-lambda-ruby
70
+ homepage_uri: https://www.moesif.com
71
+ mailing_list_uri: https://github.com/Moesif/moesif-aws-lambda-ruby
72
+ source_code_uri: https://github.com/Moesif/moesif-aws-lambda-ruby
73
+ wiki_uri: https://github.com/Moesif/moesif-aws-lambda-ruby
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '2.5'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.3.3
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: moesif_aws_lambda
93
+ test_files: []