├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── akamai-edgegrid.gemspec ├── examples ├── README.md ├── create-credentials.rb ├── delete-credentials.rb ├── get-credentials.rb └── update-credentials.rb ├── lib └── akamai │ └── edgegrid.rb └── test ├── sample_edgerc ├── test_edgegrid.rb ├── test_edgerc.rb └── testdata.json /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.gem 3 | coverage/ 4 | Gemfile.lock 5 | html/ 6 | .ruby-version 7 | .ruby-gemset 8 | .DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 1.0.6 2 | ----- 3 | * Fix bug with sections on `edgerc` (thanks Kirsten). 4 | 5 | 1.0.5 6 | ----- 7 | * Add `edgerc` support (thanks Kirsten). 8 | 9 | 1.0.4 10 | ----- 11 | * Fix bug with the nil `request.body`. 12 | 13 | 1.0.3 14 | ----- 15 | * Support the QA environment. 16 | 17 | 1.0.2 18 | ----- 19 | * Update link to the developer site. 20 | 21 | 1.0.1 22 | ----- 23 | * Change the `POST` behavior to truncate and `max_body` to `128kb` (GRID-236). 24 | 25 | 1.0 26 | --- 27 | * Initial release. 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2024 Akamai Technologies, Inc. All rights reserved. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use these files except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EdgeGrid for Ruby 2 | 3 | This library implements an Authentication handler for HTTP requests using the [Akamai EdgeGrid Authentication](https://techdocs.akamai.com/developer/docs/set-up-authentication-credentials) scheme for the Ruby [Net/Http](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html) library. 4 | 5 | ## Install 6 | 7 | This library requires Ruby v1.9 or later. To easily install we 8 | recommend using [rbenv](https://github.com/sstephenson/rbenv), [rubygems](http://rubygems.org/), and [bundler](http://bundler.io/). 9 | 10 | * Install from rubygems. 11 | 12 | ```bash 13 | gem install akamai-edgegrid 14 | ``` 15 | 16 | * Install from sources (we assume you already have rbenv going). 17 | 18 | ```bash 19 | rbenv local 2.5.3 20 | gem install bundler 21 | bundle install 22 | rake test 23 | gem build akamai-edgegrid.gemspec 24 | gem install akamai-edgegrid-1.0.gem 25 | ``` 26 | 27 | ## Authentication 28 | 29 | We provide authentication credentials through an API client. Requests to the API are signed with a timestamp and are executed immediately. 30 | 31 | 1. [Create authentication credentials](https://techdocs.akamai.com/developer/docs/set-up-authentication-credentials). 32 | 33 | 2. Place your credentials in an EdgeGrid resource file, `.edgerc`, under a heading of `[default]` at your local home directory or the home directory of a web-server user. 34 | 35 | ``` 36 | [default] 37 | client_secret = C113nt53KR3TN6N90yVuAgICxIRwsObLi0E67/N8eRN= 38 | host = akab-h05tnam3wl42son7nktnlnnx-kbob3i3v.luna.akamaiapis.net 39 | access_token = akab-acc35t0k3nodujqunph3w7hzp7-gtm6ij 40 | client_token = akab-c113ntt0k3n4qtari252bfxxbsl-yvsdj 41 | ``` 42 | 43 | 3. Use your local `.edgerc` by providing the path to your resource file and credentials' section header in the `.setup_from_edgerc()` method. 44 | 45 | ```ruby 46 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 47 | 48 | baseuri = URI('https://' + http.host) 49 | 50 | http.setup_from_edgerc( 51 | :filename => '~/.edgerc', 52 | :section => 'default' 53 | ) 54 | ``` 55 | 56 | Or hard code your credentials as variables in the `.setup_edgegrid()` method. 57 | 58 | ```ruby 59 | baseuri = URI('akab-h05tnam3wl42son7nktnlnnx-kbob3i3v.luna.akamaiapis.net/') # that's a `host` value from your `.edgerc` file 60 | 61 | http = Akamai::Edgegrid::HTTP.new( 62 | address=baseuri.host, 63 | port=baseuri.port 64 | ) 65 | 66 | http.setup_edgegrid( 67 | :client_secret => 'C113nt53KR3TN6N90yVuAgICxIRwsObLi0E67/N8eRN=', 68 | :client_token => 'akab-c113ntt0k3n4qtari252bfxxbsl-yvsdj', 69 | :access_token => 'akab-acc35t0k3nodujqunph3w7hzp7-gtm6ij', 70 | ) 71 | ``` 72 | 73 | ## Use 74 | 75 | To use the library, provide your credentials section header of your local `.edgerc` file, and the appropriate endpoint information. 76 | 77 | ```ruby 78 | require 'akamai/edgegrid' 79 | require 'net/http' 80 | require 'uri' 81 | 82 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 83 | https.use_ssl = true 84 | 85 | baseuri = URI('https://' + http.host) 86 | 87 | http.setup_from_edgerc( 88 | :filename => '~/.edgerc', 89 | :section => 'default' 90 | ) 91 | 92 | request = Net::HTTP::Get.new(URI.join(baseuri.to_s, 'identity-management/v3/user-profile').to_s) 93 | request["Accept"] = "application/json" 94 | 95 | response = http.request(request) 96 | puts response.read_body 97 | ``` 98 | 99 | ### Query string parameters 100 | 101 | When entering query parameters, you can pass them in the url after a question mark ("?"). 102 | 103 | ```ruby 104 | request = Net::HTTP::Get.new(URI.join(baseuri.to_s, 'identity-management/v3/user-profile?authGrants=true¬ifications=true&actions=true').to_s) 105 | 106 | response = http.request(request) 107 | puts response.read_body 108 | ``` 109 | 110 | Or you can pass them dynamically. 111 | 112 | ```ruby 113 | baseuri = URI('https://' + http.host) 114 | params = { 115 | :authGrants => true, 116 | :notifications => true, 117 | :actions => true 118 | } 119 | baseuri.query = URI.encode_www_form(params) 120 | 121 | request = Net::HTTP::Get.new URI.join(baseuri.to_s, 'identity-management/v3/user-profile').to_s 122 | 123 | response = http.request(request) 124 | puts response.read_body 125 | ``` 126 | 127 | ### Headers 128 | 129 | Enter request headers using the `request[]` property. In the square brackets, specify the header name and then its value. 130 | 131 | > **Note:** You don't need to include the `Content-Type` and `Content-Length` headers. The authentication layer adds these values. 132 | 133 | ```ruby 134 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 135 | 136 | baseuri = URI('https://' + http.host) 137 | 138 | http.setup_from_edgerc({:section => 'default'}) 139 | 140 | request = Net::HTTP::Get.new(URI.join(baseuri.to_s, 'identity-management/v3/user-profile').to_s) 141 | request["Accept"] = "application/json" 142 | 143 | response = http.request(request) 144 | puts response.read_body 145 | ``` 146 | 147 | Another way to pass headers using the `initheader` argument: 148 | 149 | ```ruby 150 | post_request = Net::HTTP::Get.new( 151 | URI.join(baseuri.to_s, 'identity-management/v3/user-profile').to_s, 152 | initheader = { 'Content-Type' => 'application/json' } 153 | ) 154 | ``` 155 | 156 | ### Body data 157 | 158 | Import `json` package and then provide the request body as an object in the `request.body` property. 159 | 160 | ```ruby 161 | require 'akamai/edgegrid' 162 | require "net/http" 163 | require "uri" 164 | require "json" 165 | 166 | 167 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 168 | http.use_ssl = true 169 | 170 | baseuri = URI('https://' + http.host) 171 | 172 | http.setup_from_edgerc({:section => 'default'}) 173 | 174 | request = Net::HTTP::Put.new(URI.join(baseuri.to_s, 'identity-management/v3/user-profile/basic-info').to_s) 175 | request.body = JSON.dump({ 176 | "contactType": "Billing", 177 | "country": "USA", 178 | "firstName": "John", 179 | "lastName": "Smith", 180 | "phone": "3456788765", 181 | "preferredLanguage": "English", 182 | "sessionTimeOut": 30, 183 | "timeZone": "GMT", 184 | }) 185 | 186 | response = http.request(request) 187 | puts response.read_body 188 | ``` 189 | 190 | Another way to pass request body data. 191 | 192 | ```ruby 193 | request.body = { 194 | "contactType": "Billing", 195 | "country": "USA", 196 | "firstName": "John", 197 | "lastName": "Smith", 198 | "phone": "3456788765", 199 | "preferredLanguage": "English", 200 | "sessionTimeOut": 30, 201 | "timeZone": "GMT", 202 | }.to_json 203 | ``` 204 | 205 | ## Reporting issues 206 | 207 | To report an issue or make a suggestion, create a new [GitHub issue](https://github.com/akamai/AkamaiOPEN-edgegrid-ruby/issues). 208 | 209 | ## License 210 | 211 | Copyright 2024 Akamai Technologies, Inc. All rights reserved. 212 | 213 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 214 | 215 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rdoc/task' 3 | 4 | Rake::TestTask.new do |t| 5 | t.libs << 'test' 6 | t.pattern = 'test/test_*.rb' 7 | t.verbose = true 8 | end 9 | 10 | desc "Run tests" 11 | task :default => [:test, :rdoc] 12 | 13 | desc 'Generates a coverage report' 14 | task :coverage do 15 | ENV['COVERAGE'] = 'true' 16 | Rake::Task['test'].execute 17 | end 18 | 19 | desc 'Generates documentation' 20 | Rake::RDocTask.new do |rd| 21 | rd.title = 'Akamai::Edgegrid::HTTP' 22 | rd.rdoc_files.include("lib/**/*.rb") 23 | end 24 | -------------------------------------------------------------------------------- /akamai-edgegrid.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'akamai-edgegrid' 3 | s.version = '1.0.7' 4 | s.date = '2019-04-09' 5 | s.summary = 'Akamai {OPEN} EdgeGrid Authenticator for net/http' 6 | s.description = 'Implements the Akamai {OPEN} EdgeGrid Authentication specified by https://developer.akamai.com/introduction/Client_Auth.html' 7 | s.files = ["lib/akamai/edgegrid.rb"] 8 | s.homepage = "https://github.com/akamai-open/AkamaiOPEN-edgegrid-ruby" 9 | s.license = 'Apache' 10 | s.add_development_dependency 'simplecov', '~> 0.16' 11 | s.add_development_dependency 'minitest', '~> 5.11' 12 | s.add_runtime_dependency 'inifile', '~> 3.0' 13 | s.required_ruby_version = '>= 1.9' 14 | end 15 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains executable CRUD examples for Akamai API using the EdgeGrid Ruby library. API calls used in these examples are available to all users. But, if you find one of the write examples doesn't work for you, talk with your account's admin about your privilege level. 4 | 5 | ## Run 6 | 7 | To run any of the files: 8 | 9 | 1. Specify the location of your `.edgerc`. The default is set to the home directory. 10 | 2. Provide the section header for the set of credentials you'd like to use. The default is `default`. 11 | 3. For update and delete operations, replace the dummy `credentialId` with your valid `credentialId`. 12 | 4. Open a Terminal or shell instance and run the .rb file. 13 | 14 | ``` 15 | $ ruby .rb 16 | ``` 17 | 18 | ## Sample files 19 | 20 | The example in each file contains a call to one of the Identity and Access Management (IAM) API endpoints. See the [IAM API reference](https://techdocs.akamai.com/iam-api/reference/api) doc for more information on each of the calls used. 21 | 22 | | Operation | Method | Endpoint | 23 | | --- | --- | --- | 24 | | [List your API client credentials.](/examples/get-credentials.rb) | `GET` | `/identity-management/v3/api-clients/self/credentials` | 25 | | [Create new API client credentials.](/examples/create-credentials.rb)
This is a *quick* client and grants you the default permissions associated with your account. | `POST` | `/identity-management/v3/api-clients/self/credentials` | 26 | | [Update your credentials by ID.](/examples/update-credentials.rb) | `PUT` | `/identity-management/v3/api-clients/self/credentials/{credentialId}` | 27 | | [Delete your credentials by ID.](/examples/delete-credentials.rb) | `DELETE` | `/identity-management/v3/api-clients/self/credentials/{credentialId}` | 28 | 29 | Suggested chained call order: 30 | 31 | 1. Get credentials to see your base information. 32 | 2. Create a client to create a new set of credentials. 33 | 3. Update credentials to inactivate the newly created set from step 2. 34 | 4. Delete a client to delete the inactivated credentials. 35 | 5. Get credentials to verify if they're gone (the status will be `DELETED`). -------------------------------------------------------------------------------- /examples/create-credentials.rb: -------------------------------------------------------------------------------- 1 | # # This example creates your new API client credentials. 2 | # 3 | # To run this example: 4 | # 5 | # 1. Specify the location of your .edgerc file and the section header of the set of credentials to use. 6 | # 7 | ## The defaults here expect the .edgerc at your home directory and use the credentials under the heading of default. 8 | # 9 | # 2. Open a Terminal or shell instance and run "ruby examples/create-credentials.rb". 10 | # 11 | # A successful call returns a new API client with its credentialId. Use this ID in both the update and delete examples. 12 | # 13 | # For more information on the call used in this example, see https://techdocs.akamai.com/iam-api/reference/post-self-credentials. 14 | 15 | require 'akamai/edgegrid' 16 | require 'net/http' 17 | require 'uri' 18 | 19 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 20 | https.use_ssl = true 21 | 22 | baseuri = URI('https://' + http.host) 23 | 24 | http.setup_from_edgerc( 25 | :filename => '~/.edgerc', 26 | :section => 'default' 27 | ) 28 | 29 | request = Net::HTTP::Post.new(URI.join(baseuri.to_s, 'identity-management/v3/api-clients/self/credentials').to_s) 30 | request["Accept"] = "application/json" 31 | 32 | response = http.request(request) 33 | puts response.read_body 34 | -------------------------------------------------------------------------------- /examples/delete-credentials.rb: -------------------------------------------------------------------------------- 1 | # This example deletes your API client credentials. 2 | # 3 | # To run this example: 4 | # 5 | # 1. Specify the location of your .edgerc file and the section header of the set of credentials to use. 6 | # 7 | # The defaults here expect the .edgerc at your home directory and use the credentials under the heading of default. 8 | # 9 | # 2. Add the credentialId from the update example to the path. You can only delete inactive credentials. Sending the request on an active set will return a 400. Use the update credentials example for deactivation. 10 | # 11 | # 3. Open a Terminal or shell instance and run "ruby examples/delete-credentials.rb". 12 | # 13 | # A successful call returns "" null. 14 | # 15 | # For more information on the call used in this example, see https://techdocs.akamai.com/iam-api/reference/delete-self-credential. 16 | 17 | require 'akamai/edgegrid' 18 | require 'net/http' 19 | require 'uri' 20 | 21 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 22 | https.use_ssl = true 23 | 24 | baseuri = URI('https://' + http.host) 25 | 26 | http.setup_from_edgerc( 27 | :filename => '~/.edgerc', 28 | :section => 'default' 29 | ) 30 | 31 | credential_id = 123456 32 | 33 | request = Net::HTTP::Delete.new(URI.join(baseuri.to_s, 'identity-management/v3/api-clients/self/credentials/#{credential_id}').to_s) 34 | request["Accept"] = "application/json" 35 | 36 | response = http.request(request) 37 | puts response.read_body -------------------------------------------------------------------------------- /examples/get-credentials.rb: -------------------------------------------------------------------------------- 1 | # This example returns a list of your API client credentials. 2 | # 3 | # To run this example: 4 | # 5 | # 1. Specify the location of your .edgerc file and the section header of the set of credentials to use. 6 | # 7 | # The defaults here expect the .edgerc at your home directory and use the credentials under the heading of default. 8 | # 9 | # 2. Open a Terminal or shell instance and run "ruby examples/get-credentials.rb". 10 | # 11 | # A successful call returns your credentials grouped by credentialId. 12 | # 13 | # For more information on the call used in this example, see https://techdocs.akamai.com/iam-api/reference/get-self-credentials. 14 | 15 | require 'akamai/edgegrid' 16 | require 'net/http' 17 | require 'uri' 18 | 19 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 20 | https.use_ssl = true 21 | 22 | baseuri = URI('https://' + http.host) 23 | 24 | http.setup_from_edgerc( 25 | :filename => '~/.edgerc', 26 | :section => 'default' 27 | ) 28 | 29 | request = Net::HTTP::Get.new(URI.join(baseuri.to_s, 'identity-management/v3/api-clients/self/credentials').to_s) 30 | request["Accept"] = "application/json" 31 | 32 | response = http.request(request) 33 | puts response.read_body -------------------------------------------------------------------------------- /examples/update-credentials.rb: -------------------------------------------------------------------------------- 1 | # This example updates the credentials from the create credentials example. 2 | # 3 | # To run this example: 4 | # 5 | # 1. Specify the location of your .edgerc file and the section header of the set of credentials to use. 6 | # 7 | # The defaults here expect the .edgerc at your home directory and use the credentials under the heading of default. 8 | # 9 | # 2. Add the credentialId for the set of credentials created using the create example as a path parameter. 10 | # 11 | # 3. Edit the expiresOn date to today's date. Optionally, you can change the description value. 12 | # 13 | # 4. Open a Terminal or shell instance and run "ruby examples/update-credentials.rb". 14 | # 15 | # A successful call returns. 16 | # 17 | # For more information on the call used in this example, see https://techdocs.akamai.com/iam-api/reference/put-self-credential. 18 | 19 | require 'akamai/edgegrid' 20 | require 'net/http' 21 | require 'uri' 22 | require "json" 23 | 24 | http = Akamai::Edgegrid::HTTP.new(get_host(), 443) 25 | https.use_ssl = true 26 | 27 | baseuri = URI('https://' + http.host) 28 | 29 | http.setup_from_edgerc( 30 | :filename => '~/.edgerc', 31 | :section => 'default' 32 | ) 33 | 34 | credential_id = 123456 35 | 36 | request = Net::HTTP::Put.new(URI.join(baseuri.to_s, 'identity-management/v3/api-clients/self/credentials/#{credential_id}').to_s) 37 | request["Accept"] = "application/json" 38 | request["Content-Type"] = "application/json" 39 | request.body = JSON.dump({ 40 | "description": "Update this credential", 41 | "expiresOn": "2024-06-11T23:06:59.000Z", # # the date cannot be more than two years out or it will return a 400 42 | "status": "ACTIVE" 43 | }) 44 | 45 | response = http.request(request) 46 | puts response.read_body 47 | -------------------------------------------------------------------------------- /lib/akamai/edgegrid.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | require 'base64' 3 | require 'logger' 4 | require 'securerandom' 5 | require 'uri' 6 | require 'net/http' 7 | require 'inifile' 8 | 9 | def get_host(filename="~/.edgerc", section="default") 10 | edgerc_path = File.expand_path(filename) 11 | file = IniFile.load(edgerc_path) 12 | data = file[section] 13 | address = data["host"] || "" 14 | address.gsub!('/','') 15 | return address 16 | end 17 | 18 | 19 | module Akamai #:nodoc: 20 | module Edgegrid #:nodoc: 21 | 22 | # == Akamai::Edgegrid::HTTP {OPEN} client 23 | # 24 | # Akamai::Edgegrid::HTTP provides a subclass of Net::HTTP that adds EdgeGrid 25 | # authentication support as specified at 26 | # https://developer.akamai.com/introduction/Client_Auth.html 27 | # 28 | # == Example: 29 | # >> require 'akamai/edgegrid' 30 | # >> require 'net/http' 31 | # >> require 'uri' 32 | # >> require 'json' 33 | # 34 | # >> baseuri = URI('https://akaa-xxxxxxxxxxxx.luna.akamaiapis.net/') 35 | # >> http = Akamai::Edgegrid::HTTP.new(address, port) 36 | # >> http.setup_edgegrid( 37 | # :client_token => 'ccccccccc', 38 | # :client_secret => 'ssssssssssssssssss', 39 | # :access_token => 'aaaaaaaaaaaaaa' 40 | # ) 41 | # >> request = Net::HTTP::Get.new URI.join( 42 | # baseuri.to_s, '/identity-management/v3/user-profile' 43 | # ).to_s 44 | # >> response = http.request(request) 45 | # >> puts JSON.parse(response.body)['locations'][0] 46 | # => "Hongkong, Hong Kong 47 | # 48 | class HTTP < Net::HTTP 49 | attr_accessor :host, :section 50 | 51 | private 52 | 53 | def self.base64_hmac_sha256(data, key) 54 | return Base64.encode64( 55 | OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, data) 56 | ).strip() 57 | end 58 | 59 | def self.base64_sha256(data) 60 | return Base64.encode64( 61 | OpenSSL::Digest::SHA256.new.digest(data) 62 | ).strip() 63 | end 64 | 65 | public 66 | 67 | # Creates a new Akamai::Edgegrid::HTTP object (takes same options as Net::HTTP) 68 | def initialize(address, port) 69 | @host = address 70 | super(address, port) 71 | if port == 80 72 | @use_ssl = false 73 | else 74 | @use_ssl = true 75 | @verify_mode = OpenSSL::SSL::VERIFY_PEER 76 | end 77 | end 78 | 79 | # Creates a signing key based on the secret and timestamp 80 | def make_signing_key(timestamp) 81 | signing_key = self.class.base64_hmac_sha256(timestamp, @client_secret) 82 | @log.debug("signing key: #{signing_key}") 83 | return signing_key 84 | end 85 | 86 | # Returns the @headers_to_sign in normalized form 87 | def canonicalize_headers(request) 88 | return @headers_to_sign.select { |header| 89 | request.key?(header) 90 | }.map { |header| 91 | "#{header.downcase}:#{request[header].strip.gsub(%r{\s+}, ' ')}" 92 | }.join("\t") 93 | end 94 | 95 | # Returns a hash of the HTTP POST body 96 | def make_content_hash(request) 97 | if request.method == 'POST' and request.body and request.body.length > 0 98 | body = request.body 99 | if body.length > @max_body 100 | @log.debug("data length #{body.length} is larger than maximum #{@max_body}") 101 | body = body[0..@max_body-1] 102 | @log.debug("data truncated to #{body.length} for computing the hash") 103 | end 104 | 105 | return self.class.base64_sha256(body) 106 | end 107 | return "" 108 | end 109 | 110 | # Returns a string with all data that will be signed 111 | def make_data_to_sign(request, auth_header) 112 | url = URI(request.path) 113 | data_to_sign = [ 114 | request.method, 115 | url.scheme, 116 | request.key?('host') ? request['host'] : url.host, 117 | url.request_uri, 118 | canonicalize_headers(request), 119 | make_content_hash(request), 120 | auth_header 121 | ].join("\t") 122 | 123 | @log.debug("data to sign: #{data_to_sign.gsub("\t", '\\t')}") 124 | return data_to_sign 125 | end 126 | 127 | # Returns a signature of the given request, timestamp and auth_header 128 | def sign_request(request, timestamp, auth_header) 129 | return self.class.base64_hmac_sha256( 130 | make_data_to_sign(request, auth_header), 131 | make_signing_key(timestamp) 132 | ) 133 | end 134 | 135 | alias_method :orig_request, :request 136 | 137 | # returns the current time in the format understood by Edgegrid 138 | def self.eg_timestamp() 139 | return Time.now.utc.strftime('%Y%m%dT%H:%M:%S+0000') 140 | end 141 | 142 | # returns a new nonce (unique identifier) 143 | def self.new_nonce() 144 | return SecureRandom.uuid 145 | end 146 | 147 | # Configures Akamai::Edgegrid::HTTP for use 148 | # 149 | # ==== Options 150 | # * +:client_token+ - Client Token from "Credentials" Manage API UI 151 | # * +:client_secret+ - Client Secret from "Credentials" Manage API UI 152 | # * +:access_token+ - Access Token from "Authorizations" Manage API UI 153 | # * +:headers_to_sign+ - List of headers (in order) that will be signed. This info is provided by individual APIs (default []) 154 | # * +:max_body+ - Maximum POST body size accepted. This info is provided by individual APIs (default 2048) 155 | # * +:debug+ - Enable extra logging (default 'false') 156 | def setup_edgegrid(opts) 157 | @client_token = opts[:client_token] 158 | @client_secret = opts[:client_secret] 159 | @access_token = opts[:access_token] 160 | 161 | @headers_to_sign = opts[:headers_to_sign] 162 | @headers_to_sign ||= [] 163 | 164 | @max_body = opts[:max_body] 165 | @max_body ||= 2048 166 | 167 | if opts[:debug] 168 | @log = Logger.new(STDERR) 169 | else 170 | @log = Logger.new('/dev/null') 171 | end 172 | end 173 | 174 | def setup_from_edgerc(opts) 175 | edgerc_path = opts[:filename] || File.expand_path('~/.edgerc') 176 | @section = opts[:section] || "default" 177 | if File.exist?(edgerc_path) 178 | file = IniFile.load(edgerc_path) 179 | data = file[@section] 180 | @client_token = data["client_token"] 181 | @client_secret = data["client_secret"] 182 | @access_token = data["access_token"] 183 | @max_body = data["max_body"] || 2048 184 | @headers_to_sign = opts[:headers_to_sign] || [] 185 | end 186 | 187 | if opts[:debug] 188 | @log = Logger.new(STDERR) 189 | else 190 | @log = Logger.new('/dev/null') 191 | end 192 | end 193 | 194 | # Returns the computed Authorization header for the given request, timestamp and nonce 195 | def make_auth_header(request, timestamp, nonce) 196 | auth_header = "EG1-HMAC-SHA256 " + [ 197 | "client_token" => @client_token, 198 | "access_token" => @access_token, 199 | "timestamp" => timestamp, 200 | "nonce" => nonce 201 | ].map {|kvp| 202 | kvp.keys.map { |k| k + "=" + kvp[k] } 203 | }.join(';') + ';' 204 | 205 | @log.debug("unsigned authorization header: #{auth_header}") 206 | 207 | signed_auth_header = auth_header + 'signature=' + sign_request( 208 | request, timestamp, auth_header 209 | ) 210 | @log.debug("signed authorization header: #{signed_auth_header}") 211 | 212 | return signed_auth_header 213 | end 214 | 215 | # Same as Net::HTTP.request but with 'Authorization' header for {OPEN} Edgegrid added 216 | # to the given request 217 | def request(req, body=nil, &block) 218 | timestamp = self.class.eg_timestamp() 219 | nonce = self.class.new_nonce() 220 | req['Authorization'] = make_auth_header(req, timestamp, nonce) 221 | return orig_request(req, body, &block) 222 | end 223 | end 224 | end 225 | end 226 | -------------------------------------------------------------------------------- /test/sample_edgerc: -------------------------------------------------------------------------------- 1 | [test_edgerc] 2 | client_secret = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx= 3 | host = akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net/ 4 | access_token = akab-access-token-xxx-xxxxxxxxxxxxxxxx 5 | client_token = akab-client-token-xxx-xxxxxxxxxxxxxxxx 6 | max-body = 131072 7 | -------------------------------------------------------------------------------- /test/test_edgegrid.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] 2 | require 'simplecov' 3 | SimpleCov.start do 4 | add_filter 'test' 5 | command_name 'Mintest' 6 | end 7 | end 8 | 9 | require 'minitest' 10 | require 'minitest/autorun' 11 | require 'json' 12 | require 'uri' 13 | require_relative '../lib/akamai/edgegrid' 14 | 15 | class EdgegridTest < Minitest::Test 16 | @@testdata = JSON.parse(File.read("#{File.dirname(__FILE__)}/testdata.json")) 17 | 18 | @@testdata['tests'].each do |testcase| 19 | define_method("test_#{testcase['testName'].downcase.tr(" ", "_")}") do 20 | baseuri = URI(@@testdata['base_url']) 21 | http = Akamai::Edgegrid::HTTP.new( 22 | address=baseuri.host, 23 | port=baseuri.port 24 | ) 25 | 26 | http.setup_edgegrid( 27 | :client_token => @@testdata['client_token'], 28 | :client_secret => @@testdata['client_secret'], 29 | :access_token => @@testdata['access_token'], 30 | :max_body => @@testdata['max_body'], 31 | :headers_to_sign => @@testdata['headers_to_sign'] 32 | ) 33 | 34 | request_class = Net::HTTP.const_get(testcase['request']['method'].capitalize) 35 | request = request_class.new URI.join(baseuri.to_s, testcase['request']['path']).to_s 36 | 37 | if testcase['request']['headers'] 38 | testcase['request']['headers'].each do |header| 39 | header.each do |k,v| 40 | request.add_field(k,v) 41 | end 42 | end 43 | end 44 | 45 | if testcase['request']['data'] 46 | request.body = testcase['request']['data'] 47 | end 48 | 49 | begin 50 | auth_header = http.make_auth_header( 51 | request, 52 | @@testdata['timestamp'], 53 | @@testdata['nonce'] 54 | ) 55 | assert_equal(testcase['expectedAuthorization'], auth_header) 56 | 57 | rescue RuntimeError => err 58 | assert_equal(testcase['failsWithMessage'], err.message) 59 | end 60 | end 61 | end 62 | 63 | def test_nonce 64 | count = 100 65 | nonces = {} 66 | while count > 0 do 67 | n = Akamai::Edgegrid::HTTP.new_nonce() 68 | refute_includes(nonces, n) 69 | nonces[n] = 1 70 | count -= 1 71 | end 72 | end 73 | 74 | def test_timestamp 75 | assert_match(/^ 76 | \d{4} # year 77 | [0-1][0-9] # month 78 | [0-3][0-9] # day 79 | T 80 | [0-2][0-9] # hour 81 | : 82 | [0-5][0-9] # minute 83 | : 84 | [0-5][0-9] # second 85 | [+]0000 # timezone 86 | $/x, Akamai::Edgegrid::HTTP.eg_timestamp()) 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/test_edgerc.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] 2 | require 'simplecov' 3 | SimpleCov.start do 4 | add_filter 'test' 5 | command_name 'Mintest' 6 | end 7 | end 8 | 9 | require 'minitest' 10 | require 'minitest/autorun' 11 | require 'json' 12 | require 'uri' 13 | require_relative '../lib/akamai/edgegrid' 14 | 15 | class EdgeRcTest < Minitest::Test 16 | @@testdata = JSON.parse(File.read("#{File.dirname(__FILE__)}/testdata.json")) 17 | 18 | @@testdata['tests'].each do |testcase| 19 | define_method("test_#{testcase['testName'].downcase.tr(" ", "_")}") do 20 | baseuri = URI(@@testdata['base_url']) 21 | address = get_host("test/sample_edgerc","test_edgerc") 22 | 23 | http = Akamai::Edgegrid::HTTP.new( 24 | address=address, 25 | 443 26 | ) 27 | 28 | http.setup_from_edgerc( 29 | :filename => 'test/sample_edgerc', 30 | :section => 'test_edgerc', 31 | :headers_to_sign => @@testdata['headers_to_sign'] 32 | ) 33 | 34 | request_class = Net::HTTP.const_get(testcase['request']['method'].capitalize) 35 | request = request_class.new URI.join(baseuri.to_s, testcase['request']['path']).to_s 36 | 37 | if testcase['request']['headers'] 38 | testcase['request']['headers'].each do |header| 39 | header.each do |k,v| 40 | request.add_field(k,v) 41 | end 42 | end 43 | end 44 | 45 | if testcase['request']['data'] 46 | request.body = testcase['request']['data'] 47 | end 48 | 49 | begin 50 | auth_header = http.make_auth_header( 51 | request, 52 | @@testdata['timestamp'], 53 | @@testdata['nonce'] 54 | ) 55 | assert_equal(testcase['expectedAuthorization'], auth_header) 56 | 57 | rescue RuntimeError => err 58 | assert_equal(testcase['failsWithMessage'], err.message) 59 | end 60 | end 61 | end 62 | 63 | def test_nonce 64 | count = 100 65 | nonces = {} 66 | while count > 0 do 67 | n = Akamai::Edgegrid::HTTP.new_nonce() 68 | refute_includes(nonces, n) 69 | nonces[n] = 1 70 | count -= 1 71 | end 72 | end 73 | 74 | def test_timestamp 75 | assert_match(/^ 76 | \d{4} # year 77 | [0-1][0-9] # month 78 | [0-3][0-9] # day 79 | T 80 | [0-2][0-9] # hour 81 | : 82 | [0-5][0-9] # minute 83 | : 84 | [0-5][0-9] # second 85 | [+]0000 # timezone 86 | $/x, Akamai::Edgegrid::HTTP.eg_timestamp()) 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/testdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_url": "https://akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net/", 3 | "access_token": "akab-access-token-xxx-xxxxxxxxxxxxxxxx", 4 | "client_token":"akab-client-token-xxx-xxxxxxxxxxxxxxxx", 5 | "client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=", 6 | "max_body": 2048, 7 | "headers_to_sign": [ "X-Test1", "X-Test2", "X-Test3" ], 8 | "nonce": "nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 9 | "timestamp": "20140321T19:34:21+0000", 10 | "tests": [ 11 | { 12 | "testName": "simple GET", 13 | "request": { 14 | "method": "GET", 15 | "path": "/", 16 | "headers": [ 17 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"} 18 | ] 19 | }, 20 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=tL+y4hxyHxgWVD30X3pWnGKHcPzmrIF+LThiAOhMxYU=" 21 | }, 22 | { 23 | "testName": "GET with querystring", 24 | "request": { 25 | "method": "GET", 26 | "path": "/testapi/v1/t1?p1=1&p2=2", 27 | "headers": [ 28 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"} 29 | ] 30 | }, 31 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=hKDH1UlnQySSHjvIcZpDMbQHihTQ0XyVAKZaApabdeA=" 32 | }, 33 | { 34 | "testName": "POST inside limit", 35 | "request": { 36 | "method": "POST", 37 | "path": "/testapi/v1/t3", 38 | "data": "datadatadatadatadatadatadatadata", 39 | "headers": [ 40 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"} 41 | ] 42 | }, 43 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=hXm4iCxtpN22m4cbZb4lVLW5rhX8Ca82vCFqXzSTPe4=" 44 | }, 45 | { 46 | "testName": "POST too large", 47 | "request": { 48 | "method": "POST", 49 | "path": "/testapi/v1/t3", 50 | "data": "ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", 51 | "headers": [ 52 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"} 53 | ] 54 | }, 55 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=6Q6PiTipLae6n4GsSIDTCJ54bEbHUBp+4MUXrbQCBoY=" 56 | }, 57 | { 58 | "testName": "POST length equals max_body", 59 | "request": { 60 | "method": "POST", 61 | "path": "/testapi/v1/t3", 62 | "data": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", 63 | "headers": [ 64 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"} 65 | ] 66 | }, 67 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=6Q6PiTipLae6n4GsSIDTCJ54bEbHUBp+4MUXrbQCBoY=" 68 | }, 69 | { 70 | "testName": "POST empty body", 71 | "request": { 72 | "method": "POST", 73 | "path": "/testapi/v1/t6", 74 | "data": "", 75 | "headers": [ 76 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"} 77 | ] 78 | }, 79 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=1gEDxeQGD5GovIkJJGcBaKnZ+VaPtrc4qBUHixjsPCQ=" 80 | }, 81 | { 82 | "testName": "POST nil body", 83 | "request": { 84 | "method": "POST", 85 | "path": "/testapi/v1/t6", 86 | "data": null, 87 | "headers": [ 88 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"} 89 | ] 90 | }, 91 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=1gEDxeQGD5GovIkJJGcBaKnZ+VaPtrc4qBUHixjsPCQ=" 92 | }, 93 | { 94 | "testName": "Simple header signing with GET", 95 | "request": { 96 | "method": "GET", 97 | "path": "/testapi/v1/t4", 98 | "headers": [ 99 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"}, 100 | {"X-Test1": "test-simple-header"} 101 | ] 102 | }, 103 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=8F9AybcRw+PLxnvT+H0JRkjROrrUgsxJTnRXMzqvcwY=" 104 | }, 105 | { 106 | "testName": "Header containing spaces", 107 | "request": { 108 | "method": "GET", 109 | "path": "/testapi/v1/t4", 110 | "headers": [ 111 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"}, 112 | {"X-Test1": "\" test-header-with-spaces \""} 113 | ] 114 | }, 115 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=ucq2AbjCNtobHfCTuS38fdkl5UDdWHZhQX46fYR8CqI=" 116 | }, 117 | { 118 | "testName": "Header with leading and interior spaces", 119 | "request": { 120 | "method": "GET", 121 | "path": "/testapi/v1/t4", 122 | "headers": [ 123 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"}, 124 | {"X-Test1": " first-thing second-thing"} 125 | ] 126 | }, 127 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=WtnneL539UadAAOJwnsXvPqT4Kt6z7HMgBEwAFpt3+c=" 128 | }, 129 | { 130 | "testName": "Headers out of order", 131 | "request": { 132 | "method": "GET", 133 | "path": "/testapi/v1/t4", 134 | "headers": [ 135 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"}, 136 | {"X-Test2": "t2"}, 137 | {"X-Test1": "t1"}, 138 | {"X-Test3": "t3"} 139 | ] 140 | }, 141 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=Wus73Nx8jOYM+kkBFF2q8D1EATRIMr0WLWwpLBgkBqY=" 142 | }, 143 | { 144 | "testName": "Extra header", 145 | "request": { 146 | "method": "GET", 147 | "path": "/testapi/v1/t5", 148 | "headers": [ 149 | {"Host": "akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net"}, 150 | {"X-Test2": "t2"}, 151 | {"X-Test1": "t1"}, 152 | {"X-Test3": "t3"}, 153 | {"X-Extra": "this won't be included"} 154 | ] 155 | }, 156 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=Knd/jc0A5Ghhizjayr0AUUvl2MZjBpS3FDSzvtq4Ixc=" 157 | }, 158 | { 159 | "testName": "PUT test", 160 | "request": { 161 | "method": "PUT", 162 | "path": "/testapi/v1/t6", 163 | "data": "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" 164 | }, 165 | "expectedAuthorization": "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=GNBWEYSEWOLtu+7dD52da2C39aX/Jchpon3K/AmBqBU=" 166 | } 167 | ] 168 | } 169 | --------------------------------------------------------------------------------