pax_global_header00006660000000000000000000000064152367047370014530gustar00rootroot0000000000000052 comment=967efb734f82efcbb9410f7f8f53a724114e66b2 OpenVoxProject-ring-middleware-df9a994/000077500000000000000000000000001523670473700201605ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/.github/000077500000000000000000000000001523670473700215205ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/.github/dependabot.yml000066400000000000000000000005751523670473700243570ustar00rootroot00000000000000version: 2 updates: # raise PRs for gem updates - package-ecosystem: bundler directory: "/" schedule: interval: daily time: "13:00" open-pull-requests-limit: 10 # Maintain dependencies for GitHub Actions - package-ecosystem: github-actions directory: "/" schedule: interval: daily time: "13:00" open-pull-requests-limit: 10 OpenVoxProject-ring-middleware-df9a994/.github/release.yml000066400000000000000000000013231523670473700236620ustar00rootroot00000000000000--- # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes changelog: exclude: labels: - duplicate - invalid - modulesync - question - skip-changelog - wont-fix - wontfix categories: - title: Breaking Changes 🛠 labels: - backwards-incompatible - title: New Features 🎉 labels: - enhancement - title: Bug Fixes 🐛 labels: - bug - title: Documentation Updates 📚 labels: - documentation - docs - title: Dependency Updates ⬆️ labels: - dependencies - title: Other Changes labels: - "*" OpenVoxProject-ring-middleware-df9a994/.github/workflows/000077500000000000000000000000001523670473700235555ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/.github/workflows/pr-testing.yaml000066400000000000000000000027341523670473700265430ustar00rootroot00000000000000--- name: lein_test on: pull_request: {} push: branches: - main # minimal permissions permissions: contents: read jobs: run-lein-tests: name: lein test - Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: java: - '25' - '21' - '17' steps: - name: Check out repository code uses: actions/checkout@v7 - name: Setup java uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ matrix.java }} - name: Install Clojure tools uses: DeLaGuardo/setup-clojure@13.5 with: cli: latest # Clojure CLI based on tools.deps lein: latest # Leiningen boot: latest # Boot.clj bb: latest # Babashka clj-kondo: latest # Clj-kondo cljstyle: latest # cljstyle zprint: latest # zprint - name: Run lein tests with update profile run: | set -x set -e lein version echo "Running tests" lein -U test tests: if: always() needs: - run-lein-tests runs-on: ubuntu-24.04 name: Test suite steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} OpenVoxProject-ring-middleware-df9a994/.github/workflows/release.yaml000066400000000000000000000007411523670473700260630ustar00rootroot00000000000000--- name: Release and publish package to clojars.org permissions: contents: write packages: write on: workflow_dispatch: jobs: release: uses: 'openvoxproject/shared-actions/.github/workflows/clojure_release.yml@main' secrets: github_pat: ${{ secrets.OPENVOXBOT_COMMIT_AND_PRS }} ssh_private_key: ${{ secrets.OPENVOXBOT_SSH_PRIVATE_KEY }} clojars_username: ${{ secrets.CLOJARS_USERNAME }} clojars_password: ${{ secrets.CLOJARS_PASSWORD }} OpenVoxProject-ring-middleware-df9a994/.gitignore000066400000000000000000000003271523670473700221520ustar00rootroot00000000000000.nrepl-port pom.xml pom.xml.asc *jar /lib/ /classes/ /target/ /checkouts/ .lein-deps-sum .lein-repl-history .lein-plugins/ .lein-failures /resources/locales.clj /dev-resources/i18n/bin /resources/**/Messages*.class OpenVoxProject-ring-middleware-df9a994/CHANGELOG.md000066400000000000000000000063361523670473700220010ustar00rootroot00000000000000# 2.0.4 * add calling method and uri to unhandled exception logging # 2.0.3 * released with no changes due to issues with licensing in project.clj # 2.0.2 - unreleased * update to clj parent 7.3.31 * optimize the `wrap-uncaught-errors` to avoid anonymous functions, and ensure streams are closed * add new wrappers: * `wrap-accepts-content-type` * `wrap-accepts-json` * `wrap-content-type` * `wrap-content-type-json` * `wrap-json-parse-exception-handler` # 2.0.1 * Updates tk-jetty-10 to 1.0.7 which includes a fix for ring handler's getRequestCharacterEncoding() function. # 2.0.0 * This version updates testing in the repo to use trapperkeeper-webserver-jetty10. The repo is now intended to be used with Jetty 10. # 1.3.0 * This version updates the `wrap-add-cache-headers` middleware so that it adds a `cache-control` header with the "no-store" directive instead of the "private, max-age=0, no-cache" directives. # 1.2.0 * This version adds `wrap-params` middleware with custom implementation of the `params-request` function. This was copied from the puppetserver repo to this more-central location. It is documented in the [README](./README.md). # 1.1.0 * This version adds two new middleware used in other puppetlabs projects, documented in the [README](./README.md): * `wrap-add-x-content-nosniff` * `wrap-add-csp` # 1.0.1 * This is a bug fix release that ensure stacktraces are correctly printed to the log when handling otherwise uncaught exceptions. # 1.0.0 #### Breaking Changes * Moves from `{:type ... :message ...}` to `{:kind ... :msg ...}` for exceptions and error responses. * Moves schemas and helpers previously defined in `core` namespace into new `utils` namespace. # 0.3.1 * This is a bug-fix release for a regression in wrap-proxy. * All middleware now have the `:always-validate` metadata set for schema validation. # 0.3.0 * This version adds many middleware that are used in other puppetlabs projects. These middleware are mostly for logging and error handling, and they are all documented in the [README](./README.md): * `wrap-request-logging` * `wrap-response-logging` * `wrap-service-unavailable` * `wrap-bad-request` * `wrap-data-errors` * `wrap-schema-errors` * `wrap-uncaught-errors` * Additionally, this version fixes [an issue](https://tickets.puppetlabs.com/browse/TK-228) with the behavior of `wrap-proxy` and its handling of redirects. # 0.2.1 * Add wrap-with-certificate-cn middleware that adds a `:ssl-client-cn` key to the request map if a `:ssl-client-cert` is present. * Add wrap-with-x-frame-options-deny middleware that adds `X-Frame-Options: DENY` # 0.2.0 * Modify behavior of regex support in the wrap-proxy function. Now, when a regex is given for the `proxied-path` argument, the entirety of the request uri's path will be appended onto the path of `remote-uri-base`. * Add a new utility middleware, `wrap-add-cache-headers`, that adds `cache-control` headers to `GET` and `PUT` requests. # 0.1.3 * Log proxied requests * Allow `proxied-path` argument in `wrap-proxy` function to be a regular expression * Bump http-client to v0.2.8 # 0.1.2 * Add support for redirect following on proxy requests * Fix issue where Gzipped proxy responses were being truncated OpenVoxProject-ring-middleware-df9a994/LICENSE000066400000000000000000000260741523670473700211760ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 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. OpenVoxProject-ring-middleware-df9a994/Makefile000066400000000000000000000000441523670473700216160ustar00rootroot00000000000000include dev-resources/Makefile.i18n OpenVoxProject-ring-middleware-df9a994/README.md000066400000000000000000000303311523670473700214370ustar00rootroot00000000000000# ring-middleware This project was originally adapted from tailrecursion's [ring-proxy](https://github.com/tailrecursion/ring-proxy) middleware, and is meant for use with the [Trapperkeeper Webservice](https://github.com/openvoxproject/trapperkeeper-webserver). It also contains common ring middleware between Puppet projects and helpers to be used with the middleware. ## Usage To use `ring-middleware`, add this project as a dependency in your leiningen project file: [![Clojars Project](http://clojars.org/org.openvoxproject/ring-middleware/latest-version.svg)](https://clojars.org/org.openvoxproject/ring-middleware) ## Schemas * `ResponseType` -- one of the two supported response types (`:json`, `:plain`) returned by many middleware. * `RingRequest` -- a map containing at least a `:uri`, optionally a valid certificate, and any number of keyword-Any pairs. * `RingResponse` -- a map with at least `:status`, `:headers`, and `:body` keys. ## Non-Middleware Helpers ### json-response ```clj (json-response status body) ``` Creates a basic ring response with `:status` of `status` and a `:body` of `body` serialized to json. ### plain-response ```clj (plain-response status body) ``` Creates a basic ring response with `:status` of `status` and a `:body` of `body` set to UTF-8 plain text. ### throw-bad-request! ```clj (throw-bad-request! "Error Message") ``` Throws a :bad-request type slingshot error with the supplied message. See `wrap-bad-request` for middleware designed to compliment this function, also `bad-request?` for a function to help implement your own error handling. ### throw-service-unavailable! ```clj (throw-service-unavailable! "Error Message") ``` Throws a :service-unavailable type slingshot error with the supplied message. See `wrap-service-unavailable` for middleware designed to compliment this function, also `service-unavailable?` for a function to help implement your own error handling. ### throw-data-invalid! ```clj (throw-data-invalid! "Error Message") ``` Throws a :data-invalid type slingshot error with the supplied message. See `wrap-data-errors` for middleware designed to compliment this function, also `data-invalid?` for a function to help implement your own error handling. ### bad-request? ```clj (try+ (handler request) (catch bad-request? e (...handle a bad request...))) ``` Determines if the supplied slingshot error map is for a bad request. ### service-unavailable? ```clj (try+ (handler request) (catch service-unavailable? e (...handle service unavailability...))) ``` Determines if the supplied slingshot error map is for the service being unavailable. ### data-invalid? ```clj (try+ (handler request) (catch data-invalid? e (...handle invalid data...))) ``` Determines if the supplied slingshot error map is for invalid data. ### schema-error? ```clj (try+ (handler request) (catch schema-error? e (...handle schema error...))) ``` Determines if the supplied slingshot error map is for a schema error. ## Middleware ### wrap-request-logging ```clj (wrap-request-logging handler) ``` Logs the `:request-method` and `:uri` at debug level, the full request at trace. At the trace level, attempts to remove sensitive auth information and replace client certificate with the client's common name. ### wrap-response-logging ```clj (wrap-response-logging handler) ``` Logs the response at the trace log level. ### wrap-proxy ```clj (wrap-proxy handler proxied-path remote-uri-base & [http-opts]) ``` This function returns a ring handler that, when given a URL with a certain prefix, proxies the request to a remote URL specified by the `remote-uri-base` argument. The arguments are as follows: * `handler`: A ring-handler that will be used if the provided url does not begin with the proxied-path prefix * `proxied-path`: The URL prefix of all requests that are to be proxied. This can be either a string or a regular expression pattern. Note that, when this is a regular expression, the entire request URI will be appended to `remote-uri-base` when the URI is being rewritten, whereas if this argument is a string, the `proxied-path` will not be included. * `remote-uri-base`: The base URL that you want to proxy requests with the `proxied-path` prefix to * `http-opts`: An optional list of options for an http client. This is used by the handler returned by `wrap-proxy` when it makes a proxied request to a remote URI. For a list of available options, please see the options defined for [clj-http-client](https://github.com/openvoxproject/clj-http-client). For example, the following: ```clj (wrap-proxy handler "/hello-world" "http://localhost:9000/hello") ``` would return a ring handler that proxies all requests with URL prefix "/hello-world" to `http://localhost:9000/hello`. The following: ```clj (wrap-proxy handler #"^/hello-world" "http://localhost:9000/hello") ``` would return a ring handler that proxies all requests with a URL path matching the regex `#^/hello-world"` to `http://localhost:9000/hello/[url-path]`. #### Proxy Redirect Support By default, all proxy requests using `wrap-proxy` will follow any redirects, including on POST and PUT requests. To allow redirects but restrict their use on POST and PUT requests, set the `:force-redirects` option to `false` in the `http-opts` map. To disable redirect following on proxy requests, set the `:follow-redirects` option to `false` in the `http-opts` map. Please not that if proxy redirect following is disabled, you may have to disable it on the client making the proxy request as well if the location returned by the redirect is relative. #### SSL Support `wrap-proxy` supports SSL. To add SSL support, you can set SSL options in the `http-opts` map as you would in a request made with [clj-http-client](https://github.com/openvoxproject/clj-http-client). Simply set the `:ssl-cert`, `:ssl-key`, and `:ssl-ca-cert` options in the `http-opts` map to be paths to your .pem files. ### wrap-with-certificate-cn This middleware adds a `:ssl-client-cn` key to the request map if a `:ssl-client-cert` is present. If no client certificate is present, the key's value is set to nil. This makes for easier certificate whitelisting (using the cert whitelisting function from pl/kitchensink) ### wrap-add-cache-headers A utility middleware with the following signature: ```clj (wrap-add-cache-headers handler) ``` This middleware adds `Cache-Control: no-store` headers to `GET` and `PUT` requests if they are handled by the handler. ### wrap-add-x-frame-options-deny A utility middleware with the following signature: ```clj (wrap-add-x-frame-options-deny handler) ``` This middleware adds `X-Frame-Options: DENY` headers to requests if they are handled by the handler. ### wrap-add-x-content-nosniff A utility middleware with the following signature: ```clj (wrap-add-x-content-nosniff handler) ``` This middleware adds `X-Content-Type-Options: nosniff` headers to requests if they are handled by the handler. ### wrap-add-csp A utility middleware with the following signature: ```clj (wrap-add-csp handler csp-val) ``` This middleware adds `Content-Security-Policy` headers to requests if they are handled by the handler. The value of the header will be equivalent to the second argument passed, `csp-val`. ### wrap-params A utility middleware with the following signature: ```clj (wrap-params handler & [options]) ``` This middleware parses url-encoded parameters from the query string and form body and adds the following keys to the request map: * `:query-params` - a map of parameters from the query string * `:form-params` - a map of parameters from the body * `:params` - a map of all types of parameter Accepts the following options: * `:encoding` - encoding to use for url-decoding. If not specified, uses the request character encoding, or "UTF-8" if no request charater encoding is set. ### wrap-data-errors ```clj (wrap-data-errors handler) ``` Always returns a status code of 400 to the client and logs the error message at the "error" log level. Catches and processes any exceptions thrown via `slingshot/throw+` with a `:type` of one of: * `:request-data-invalid` * `:user-data-invalid` * `:data-invalid` * `:service-status-version-not-found` Returns a basic ring response map with the `:body` set to the JSON serialized representation of the exception thrown wrapped in a map and accessible by the "error" key. Example return body: ```json { "error": { "type": "user-data-invalid", "message": "Error Message From Thrower" } } ``` Returns valid [`ResponseType`](#schemas)s, eg: ```clj (wrap-data-errors handler :plain) ``` ### wrap-bad-request ```clj (wrap-bad-request handler) ``` Always returns a status code of 400 to the client and logs the error message at the "error" log level. Catches and processes any exceptions thrown via `slingshot/throw+` with a `:type` of one of: * `:bad-request` Returns a basic ring response map with the `:body` set to the JSON serialized representation of the exception thrown wrapped in a map and accessible by the "error" key. Example return body: ```json { "error": { "type": "bad-request", "message": "Error Message From Thrower" } } ``` Returns valid [`ResponseType`](#schemas)s, eg: ```clj (wrap-bad-request handler :plain) ``` ### wrap-schema-errors ```clj (wrap-schema-errors handler) ``` Always returns a status code of 500 to the client and logs an message containing the schema error, expected value, and exception type at the "error" log level. Returns a basic ring response map with the `:body` as the JSON serialized representation of helpful exception information wrapped in a map and accessible by the "error" key. Always returns an error type of "application-error". Example return body: ```json { "error": { "type": "application-error", "message": "Something unexpected happened: {:error ... :value ... :type :schema.core/error}" } } ``` Returns valid [`ResponseType`](#schemas)s, eg: ```clj (wrap-schema-errors handler :plain) ``` ### wrap-uncaught-errors ```clj (wrap-uncaught-errors handler) ``` Always returns a status code of 500 to the client and logs a message with the serialized Exception at the "error" log level. Returns a basic ring response map with the `:body` set as the JSON serialized representation of helpful exception information wrapped in a map and accessible by the "error" key. Always returns an error type of "application-error". Example return body: ```json { "error": { "type": "application-error", "message": "Internal Server Error: " } } ``` Returns valid [`ResponseType`](#schemas)s, eg: ```clj (wrap-uncaught-errors handler :plain) ``` ### wrap-accepts-content-type ```clj (wrap-accepts-content-type handler content-type) ``` Returns a wrapper that evaluates the accept header of the incoming request and validate that it accepts a type that this endpoint will produce. For example, if the endpoint will accept json ```clj (wrap-accepts-content-type handler "application/json") ``` If the request supplies a result with `text/plain` a `406 not-acceptable` error will be returned ### wrap-accepts-json ```clj (wrap-accepts-json handler) ``` Returns a wrapper that evaluates the accept header of the incoming request and validates that it matches `application/json` If it doesn't, a response with a `406 not acceptable` is returned. ### wrap-content-type ```clj (wrap-content-type handler ) ``` Ensure that the content type of the body of the incoming request matches the acceptable content types this endpoint supports. For example: ```clj (wrap-content-type handler [json-encoding-type]) ``` If the content type is not matched, a `415 Unsupported Media Type` is returned. ### wrap-content-type-json ```clj (wrap-content-type-json handler) ``` Ensure that the content type of the body of the incoming request matches the a json type. If the content type is not matched, a `415 Unsupported Media Type` is returned. ### wrap-json-parse-exception-handler ```clj (wrap-json-parse-exception-handler handler) ``` Ensure that if a JsonParseException is thrown while the handler is being executed, an appropriate error is returned. a `400 Bad Request` with appropriate messaging will be returned. ## Support GitHub issues and PRs are welcome! Additionally, drop us a line in [the Vox Pupuli Slack](https://voxpupuli.org/connect/). OpenVoxProject-ring-middleware-df9a994/dev-resources/000077500000000000000000000000001523670473700227465ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/dev-resources/Makefile.i18n000066400000000000000000000141111523670473700251620ustar00rootroot00000000000000# -*- Makefile -*- # This file was generated by the i18n leiningen plugin # Do not edit this file; it will be overwritten the next time you run # lein i18n init # # The name of the package into which the translations bundle will be placed BUNDLE=puppetlabs.ring_middleware # The name of the POT file into which the gettext code strings (msgid) will be placed POT_NAME=ring-middleware.pot # The list of names of packages covered by the translation bundle; # by default it contains a single package - the same where the translations # bundle itself is placed - but this can be overridden - preferably in # the top level Makefile PACKAGES?=$(BUNDLE) LOCALES=$(basename $(notdir $(wildcard locales/*.po))) BUNDLE_DIR=$(subst .,/,$(BUNDLE)) BUNDLE_FILES=$(patsubst %,resources/$(BUNDLE_DIR)/Messages_%.class,$(LOCALES)) FIND_SOURCES=find src -name \*.clj # xgettext before 0.19 does not understand --add-location=file. Even CentOS # 7 ships with an older gettext. We will therefore generate full location # info on those systems, and only file names where xgettext supports it LOC_OPT=$(shell xgettext --add-location=file -f - /dev/null 2>&1 && echo --add-location=file || echo --add-location) LOCALES_CLJ=resources/locales.clj define LOCALES_CLJ_CONTENTS { :locales #{$(patsubst %,"%",$(LOCALES))} :packages [$(patsubst %,"%",$(PACKAGES))] :bundle $(patsubst %,"%",$(BUNDLE).Messages) } endef export LOCALES_CLJ_CONTENTS i18n: msgfmt # Update locales/.pot update-pot: locales/$(POT_NAME) locales/$(POT_NAME): $(shell $(FIND_SOURCES)) | locales @tmp=$$(mktemp $@.tmp.XXXX); \ $(FIND_SOURCES) \ | xgettext --from-code=UTF-8 --language=lisp \ --copyright-holder='Puppet ' \ --package-name="$(BUNDLE)" \ --package-version="$(BUNDLE_VERSION)" \ --msgid-bugs-address="docs@puppet.com" \ -k \ -kmark:1 -ki18n/mark:1 \ -ktrs:1 -ki18n/trs:1 \ -ktru:1 -ki18n/tru:1 \ -ktrun:1,2 -ki18n/trun:1,2 \ -ktrsn:1,2 -ki18n/trsn:1,2 \ $(LOC_OPT) \ --add-comments --sort-by-file \ -o $$tmp -f -; \ sed -i.bak -e 's/charset=CHARSET/charset=UTF-8/' $$tmp; \ sed -i.bak -e 's/POT-Creation-Date: [^\\]*/POT-Creation-Date: /' $$tmp; \ rm -f $$tmp.bak; \ if ! diff -q -I POT-Creation-Date $$tmp $@ >/dev/null 2>&1; then \ mv $$tmp $@; \ else \ rm $$tmp; touch $@; \ fi # Run msgfmt over all .po files to generate Java resource bundles # and create the locales.clj file msgfmt: $(BUNDLE_FILES) $(LOCALES_CLJ) clean-orphaned-bundles # Force rebuild of locales.clj if its contents is not the the desired one. The # shell echo is used to add a trailing newline to match the one from `cat` ifneq ($(shell cat $(LOCALES_CLJ) 2> /dev/null),$(shell echo '$(LOCALES_CLJ_CONTENTS)')) .PHONY: $(LOCALES_CLJ) endif $(LOCALES_CLJ): | resources @echo "Writing $@" @echo "$$LOCALES_CLJ_CONTENTS" > $@ # Remove every resource bundle that wasn't generated from a PO file. # We do this because we used to generate the english bundle directly from the POT. .PHONY: clean-orphaned-bundles clean-orphaned-bundles: @for bundle in resources/$(BUNDLE_DIR)/Messages_*.class; do \ locale=$$(basename "$$bundle" | sed -E -e 's/\$$?1?\.class$$/_class/' | cut -d '_' -f 2;); \ if [ ! -f "locales/$$locale.po" ]; then \ rm "$$bundle"; \ fi \ done resources/$(BUNDLE_DIR)/Messages_%.class: locales/%.po | resources msgfmt --java2 -d resources -r $(BUNDLE).Messages -l $(*F) $< # Use this to initialize translations. Updating the PO files is done # automatically through a CI job that utilizes the scripts in the project's # `bin` file, which themselves come from the `clj-i18n` project. locales/%.po: | locales @if [ ! -f $@ ]; then \ touch $@ && msginit --no-translator -l $(*F) -o $@ -i locales/$(POT_NAME); \ fi resources locales: @mkdir $@ help: $(info $(HELP)) @echo .PHONY: help define HELP This Makefile assists in handling i18n related tasks during development. Files that need to be checked into source control are put into the locales/ directory. They are locales/$(POT_NAME) - the POT file generated by 'make update-pot' locales/$$LANG.po - the translations for $$LANG Only the $$LANG.po files should be edited manually; this is usually done by translators. You can use the following targets: i18n: refresh all the files in locales/ and recompile resources update-pot: extract strings and update locales/$(POT_NAME) locales/LANG.po: create translations for LANG msgfmt: compile the translations into Java classes; this step is needed to make translations available to the Clojure code and produces Java class files in resources/ endef # @todo lutter 2015-04-20: for projects that use libraries with their own # translation, we need to combine all their translations into one big po # file and then run msgfmt over that so that we only have to deal with one # resource bundle OpenVoxProject-ring-middleware-df9a994/dev-resources/config/000077500000000000000000000000001523670473700242135ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/dev-resources/config/jetty/000077500000000000000000000000001523670473700253525ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/dev-resources/config/jetty/ssl/000077500000000000000000000000001523670473700261535ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/dev-resources/config/jetty/ssl/certs/000077500000000000000000000000001523670473700272735ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/dev-resources/config/jetty/ssl/certs/ca.pem000066400000000000000000000036401523670473700303640ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFdTCCA12gAwIBAgIUbkMWXOFBEfh6IByiV3WgnglzkwowDQYJKoZIhvcNAQEL BQAwVDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk9SMREwDwYDVQQHDAhQb3J0bGFu ZDEUMBIGA1UECgwLUHVwcGV0LCBJbmMxDzANBgNVBAMMBnB1cHBldDAeFw0yNDA3 MDkyMzQ3NDVaFw0yOTA3MDgyMzQ3NDVaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQI DAJPUjERMA8GA1UEBwwIUG9ydGxhbmQxFDASBgNVBAoMC1B1cHBldCwgSW5jMQ8w DQYDVQQDDAZwdXBwZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCm 3AHJwjrQW//zu3u3uQ9CRhIMp4jk8dJn8ASBDz5njcbu2L2YxOSYDEttPWIJmZ9f rNTvDsaQI7sqTCpPxBRKaOiIMKV7h1PgGagXfxUHYIycu3ktX+X0CRdSOFWwyXAX 9L+wRKvUvUdTIUgyNA/XvRTsvN8RDedKPuAfKGWY/b9kbYqz8YQG6ipgP3b4yAK7 Mrtq57X0eLDIqB59cmMFMl9u45L8dOGQSgSCxWhSSF+jyfnBeXLB1NXIMswQZoBV K7moUuKStWAPSbiBh3pPsELmSIm/KkbqouYQ3jLbI3iEeDC/aVtbYrxnmzYvonPo TkaiIQattLfv2ZxEUzbPGGL//sb1oOFIFtsuQUqiqtZ3lbGAV3AZH6EmcVsgBpN6 4uDgY3FcFYJqaLnWPN8EKTyzZQmgyNi+VV6TXFRjREQOZH9fKeC8axNqws+dC6E0 qjzooJSSO/n745GJ9YTOZxhscLib4tZV9jUNQ5qMV6gJeJcYE8GlK4TyHukZHXZU E3hul7owHR/e39SUsOhhShgEJ4+6gWpdwFDOe6g9RbYIDapOu011DwT75M9yn522 1UWX54MCNea0wlgiMOla92SdsioiLue757b1kyzzytAfZr6jJTem8INyAESsiqlD DdIZXfJIlGQr/y2kHPVPabPg2eUb0XCHQswvvlGkywIDAQABoz8wPTAdBgNVHQ4E FgQULuvbnqrPNCKKnfH/oSNmFHU7MBQwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8E BAMCAYYwDQYJKoZIhvcNAQELBQADggIBADBsOYGdQ7he5yqf3BIenRx3xAfXIFbM H74uqF3q3xfPkQbDgDdPbNpgCdyzUl5+hLWSOnyKSykl5FBVvnQUk07pQJvQdWcy KusK6fivUpzRVXvWuaB5ngXSsYP3SBrvS3SuFL2t7NzZnswHyTJiKH7H0b6Kt9wn POuWttD9ysfkACF9zHKCJyKXMjOQ+G+wK1/5oKyhJsNE71JxeN51KDugyPwY9aC7 TwEUykL1ua7DP6IiJme8exIWEr6LN0D1Gp47Lj/NQF8b960tqoPeB0YpB5aG+ew1 2hETZiJb1aBrn58v6QPodd3yj/bjz83FZKIy6VZKAiyK2ml+p+4bLOF3PBgQqIvp 2nxXgQNyWJY6AzjMqiaAV2i9HG1a8zjlMNfw4fofQqsaK6x/+0Sr6KyXZaIhLG8G 3tQJKrWB9MPNjUHgsjyK8d4SpaPcHrzpW5afInAu38DrVuJzj9MffSRVa7DvDu4A SLGFzrx+OKOQ6OX2oZMszioiHXPXZtYjFlvMtLiJMqCM2GtjVe3tjb0q4w8HcLLd zp9BwIb+GnJS3oL0qD8BuJNquo7JeShdOAPJ81baTePDOELeK6W6UcVNaBZgJw28 NsDaGyXFgPy2cHDB1gl9GuyeAieKnrWCkbiH1l/NRotEW/r4DBbNrYe43C4pdfY+ wl8jJh/tD/b9 -----END CERTIFICATE----- OpenVoxProject-ring-middleware-df9a994/dev-resources/config/jetty/ssl/certs/localhost.pem000066400000000000000000000031161523670473700317670ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEezCCAmOgAwIBAgIUJLNMHcPnF90Y6dhr39MCIhnkwiswDQYJKoZIhvcNAQEL BQAwVDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk9SMREwDwYDVQQHDAhQb3J0bGFu ZDEUMBIGA1UECgwLUHVwcGV0LCBJbmMxDzANBgNVBAMMBnB1cHBldDAeFw0yNDA3 MDkyMzQ3NDVaFw0yOTA3MDgyMzQ3NDVaMFcxCzAJBgNVBAYTAlVTMQswCQYDVQQI DAJPUjERMA8GA1UEBwwIUG9ydGxhbmQxFDASBgNVBAoMC1B1cHBldCwgSW5jMRIw EAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQCs1R5uSRH5xV6qxvMiz9X7lvAxwI55nZZmgxKHZz7EqWcVtR1VKlKW4K3khGAr 0Sm9ihpM2KvaPEhGBQq6hhLBRDMKLkt1JaUmSyTeoz2EGGYXWMLZaoFEMPlBDY2t Hz03CF4WSDPp8MIKW7PBwspmHdkizFrrtQqLRYDtf01w4tgMflqgwWhLVPuO2eOf 0XmGVScRlQnAcS8WVFPAKJCstZNF3aqK5cGzhRkGeiDYqEfq29HyRbjPSO3M9vyg WIGSetR9DBM8VIpaGO457iEBZOGdgHqG84Ma8pjBj4pdPuTwjavh9coYQp0Tongt xF5Undf+fmkiPUfEVC5cZy2jAgMBAAGjQjBAMB0GA1UdDgQWBBQpfykU4gAMP6Pm mBsC1LUYCcM96zAfBgNVHSMEGDAWgBQu69ueqs80Ioqd8f+hI2YUdTswFDANBgkq hkiG9w0BAQsFAAOCAgEAeM5ME8mvkznGiepHQqDojpxaNIT9N4QDCAxW8y1LVfEF S2ufMva22q4loK+NJetaTNjDDv0lcCwUtdAqGQxngrSW8mmVL3qZrm/fFKDsRp0T 7r7qp0+nZIM+QkovxqPvDO39G70yKATOeXo5ureUK7bbXoU3h8SuPjZNT58XMjKN VDZ7f70ejaxjA45wU8jJ2kkotm62jyY7Osh5hA4doE6zLhGYrZZLoE6+kHTiTW1x UVnLcm0+MHBGFtydnK3BnnB4muWlWJggJhsmvOxTXK4kuIt1SMxp4fI4F21A/FUi qnVG9+pJ/+bm3vFAH5a+fxy/1Jq3yEgKKmn/bVRc+1cP0zgNMFSx7IvC+h2AgbpL 74lMKd74a5YqbnQraV0FGVY2q0b9W7Ce95kOm//aoFtlxGiAnftwwuFgzOe/pIdb r0AtiNkzfBOvqnXAwTHBU16042LtD9kO4aNjw7E2C7E8v5IyUwrYQxBM9pcj0waB PvjaT6aVrepQl5+17YzuQGd9YaTucS2ikGMJCQuaFY6ZupNZsQY3AeD8FKWkAgLU lKEBbbNZlCLmHBIxk/DGUyiBy6Q3BsBMivOzdWDpkhnHwhwpumFPJCF8yh9BXeba Xu89BP1D0Dme9r0dD4daCsa9UOqkmaSELd9c8sstojcRK7rD0tCBQI8fFzdwGNo= -----END CERTIFICATE----- OpenVoxProject-ring-middleware-df9a994/dev-resources/config/jetty/ssl/private_keys/000077500000000000000000000000001523670473700306605ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/dev-resources/config/jetty/ssl/private_keys/localhost.pem000066400000000000000000000032501523670473700333530ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCs1R5uSRH5xV6q xvMiz9X7lvAxwI55nZZmgxKHZz7EqWcVtR1VKlKW4K3khGAr0Sm9ihpM2KvaPEhG BQq6hhLBRDMKLkt1JaUmSyTeoz2EGGYXWMLZaoFEMPlBDY2tHz03CF4WSDPp8MIK W7PBwspmHdkizFrrtQqLRYDtf01w4tgMflqgwWhLVPuO2eOf0XmGVScRlQnAcS8W VFPAKJCstZNF3aqK5cGzhRkGeiDYqEfq29HyRbjPSO3M9vygWIGSetR9DBM8VIpa GO457iEBZOGdgHqG84Ma8pjBj4pdPuTwjavh9coYQp0TongtxF5Undf+fmkiPUfE VC5cZy2jAgMBAAECggEAEuU1737DnVgLsoYPvOWWEmx9FCNmMDufXtPDqdQK07tl jsT/UPlQkDg+KraiQQgcFSHNIEur9i8TA7y3YI8Z69FF9z36d/NGq/oZLNIR/qgg OTs2CkkPmuHzzj3qGFxK+AJNLwhzzIbK4BEIhQ2DzUhEHf7TjeN8JJ/TqaN6VvX3 r8j5G++uc1XKvDs4j5l/TbGfDKCNc6SPMCuI4LpuKMx+egpUBZE5HXGZhlhIL7OH 91m8YjWd0N0YXgsORof8nXzfzlYowDs9q2ZEtFOxiKyUzGQEnAReu4Y3ruANq3BS 0J6jv6F2Az+6CCJsOtooaSTKrGOkgQhmqtvoDG25cQKBgQDtIVtNZY6lWpNwbdYH jHU9vQ2cZL/xjYKhjzpavYNdILlQchWDMV19qxIUMtelAq9H8Xaimgpf9G7aA7Kt EMyTJimAG5JNPfZftp0ASJDp6YlT+uEYMqmfsZ0Mnx8UeQ7sn4qV/4WcGbfw6cU8 FD3hodMm58Ek3SG6fNAhZ+Cu+QKBgQC6le9RzIQ+/eVOUSTYXb8ZjQHFjEwbFgbI hsEKePg0Ux3ofBLTWqGSyspzDap31bJpKlG41S1N+d42sFAN57eNL9ipVl15BNXK paFMbObQQFWx1Hiwd8S8AYG8r+++jAso6QYS1n9FKD888YDBJUQzMU1Av+fuY3H0 tMqDEGb8ewKBgQDJFctOA7wGPpve8FVaS2K0exgKsmkOlpjbFhE/F4xJMdHUBRp3 CSqlwabwF/lERdWL5Zhb5NK9chN6rz4agq9obSkuKLNU6yF9IudacS7qHQ9Gdu3g zj2HXV+3b0w02T+tqtEjx+5uZGTWV/bYrrWXG9pqGIdyEk9izCrW2TbwSQKBgAad F+2LVUiyUTV0dNzifcqXD/ADqBLxte3XsPIBFbMtGwtJkpVBSibc304yts8mmPtX T6xAiimQaMsBduT3SK9Ned2OvSN0A2v6cPw3g/rvvNnf0SNYK3YKi6G3jsTvS9n4 YIm8Zqh547vyR4ERJBi4b6eS5dKyXbCx09fPdgcPAoGACqY7TwE1SFf19GxDDudd sstRyphb6uuQyZHqvQFtriHti2GtaETIjfHz/wO5exjLGwo9RfCMzimhbxtbzOPx DSj7QiBHWlQrw1PD4x3n2SXTvj5BV9+Fe95mO1/+dH2BPCSdNw/sWSn0wMJ3csOA a7h0CZrZsfivsBQcvUXq5WY= -----END PRIVATE KEY----- OpenVoxProject-ring-middleware-df9a994/dev-resources/exts.cnf000066400000000000000000000017031523670473700244220ustar00rootroot00000000000000[ req ] #default_bits = 2048 #default_md = sha256 #default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) organizationalUnitName = Organizational Unit Name (eg, section) commonName = Common Name (eg, fully qualified host name) commonName_max = 64 [ req_attributes ] # This section should be referenced when building an x509v3 CA # Certificate. # The default path length and the key usage can be overridden # modified by setting the CERTPATHLEN and CERTUSAGE environment # variables. [x509v3_CA] subjectKeyIdentifier = hash basicConstraints=critical,CA:true keyUsage=digitalSignature,keyCertSign,cRLSign authorityKeyIdentifier=keyid OpenVoxProject-ring-middleware-df9a994/dev-resources/gen-pki.sh000077500000000000000000000026201523670473700246370ustar00rootroot00000000000000#!/bin/bash if ! [[ -d dev-resources/ssl ]]; then echo "This script must be called from the root of the project and dev-resources/ssl must already exist" exit 1 fi echo echo "Generating primary self-signed CA" openssl req -x509 \ -newkey rsa:4096 \ -keyout dev-resources/ssl/ca.key \ -out dev-resources/ssl/ca.pem \ -days 1825 -nodes \ -extensions x509v3_CA \ -config dev-resources/exts.cnf \ -subj "/C=US/ST=OR/L=Portland/O=Puppet, Inc/CN=puppet" echo echo "Generating node cert" openssl genrsa -out dev-resources/ssl/key.pem 2048 echo echo "Creating node CSR" openssl req -new -sha256 \ -key dev-resources/ssl/key.pem \ -out dev-resources/ssl/csr.pem \ -subj "/C=US/ST=OR/L=Portland/O=Puppet, Inc/CN=localhost" echo echo "Signing node CSR" openssl x509 -req \ -in dev-resources/ssl/csr.pem \ -CA dev-resources/ssl/ca.pem \ -CAkey dev-resources/ssl/ca.key \ -CAcreateserial \ -out dev-resources/ssl/cert.pem \ -days 1825 -sha256 echo echo "Generating alternate self-signed CA" openssl req -x509 \ -newkey rsa:4096 \ -keyout dev-resources/ssl/alternate-ca.key \ -out dev-resources/ssl/alternate-ca.pem \ -days 1825 -nodes \ -extensions x509v3_CA \ -config dev-resources/exts.cnf \ -subj "/C=US/ST=OR/L=Portland/O=Puppet, Inc/CN=alternate" echo echo "Cleaning up files that will not be used by the tests" rm dev-resources/ssl/{alternate-ca.key,ca.key,ca.srl,csr.pem} OpenVoxProject-ring-middleware-df9a994/dev-resources/logback-test.xml000066400000000000000000000004521523670473700260500ustar00rootroot00000000000000 %d %-5p [%c{2}] %m%n OpenVoxProject-ring-middleware-df9a994/dev-resources/ssl/000077500000000000000000000000001523670473700235475ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/dev-resources/ssl/cert.pem000066400000000000000000000031161523670473700252100ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEezCCAmOgAwIBAgIUJLNMHcPnF90Y6dhr39MCIhnkwiswDQYJKoZIhvcNAQEL BQAwVDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk9SMREwDwYDVQQHDAhQb3J0bGFu ZDEUMBIGA1UECgwLUHVwcGV0LCBJbmMxDzANBgNVBAMMBnB1cHBldDAeFw0yNDA3 MDkyMzQ3NDVaFw0yOTA3MDgyMzQ3NDVaMFcxCzAJBgNVBAYTAlVTMQswCQYDVQQI DAJPUjERMA8GA1UEBwwIUG9ydGxhbmQxFDASBgNVBAoMC1B1cHBldCwgSW5jMRIw EAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQCs1R5uSRH5xV6qxvMiz9X7lvAxwI55nZZmgxKHZz7EqWcVtR1VKlKW4K3khGAr 0Sm9ihpM2KvaPEhGBQq6hhLBRDMKLkt1JaUmSyTeoz2EGGYXWMLZaoFEMPlBDY2t Hz03CF4WSDPp8MIKW7PBwspmHdkizFrrtQqLRYDtf01w4tgMflqgwWhLVPuO2eOf 0XmGVScRlQnAcS8WVFPAKJCstZNF3aqK5cGzhRkGeiDYqEfq29HyRbjPSO3M9vyg WIGSetR9DBM8VIpaGO457iEBZOGdgHqG84Ma8pjBj4pdPuTwjavh9coYQp0Tongt xF5Undf+fmkiPUfEVC5cZy2jAgMBAAGjQjBAMB0GA1UdDgQWBBQpfykU4gAMP6Pm mBsC1LUYCcM96zAfBgNVHSMEGDAWgBQu69ueqs80Ioqd8f+hI2YUdTswFDANBgkq hkiG9w0BAQsFAAOCAgEAeM5ME8mvkznGiepHQqDojpxaNIT9N4QDCAxW8y1LVfEF S2ufMva22q4loK+NJetaTNjDDv0lcCwUtdAqGQxngrSW8mmVL3qZrm/fFKDsRp0T 7r7qp0+nZIM+QkovxqPvDO39G70yKATOeXo5ureUK7bbXoU3h8SuPjZNT58XMjKN VDZ7f70ejaxjA45wU8jJ2kkotm62jyY7Osh5hA4doE6zLhGYrZZLoE6+kHTiTW1x UVnLcm0+MHBGFtydnK3BnnB4muWlWJggJhsmvOxTXK4kuIt1SMxp4fI4F21A/FUi qnVG9+pJ/+bm3vFAH5a+fxy/1Jq3yEgKKmn/bVRc+1cP0zgNMFSx7IvC+h2AgbpL 74lMKd74a5YqbnQraV0FGVY2q0b9W7Ce95kOm//aoFtlxGiAnftwwuFgzOe/pIdb r0AtiNkzfBOvqnXAwTHBU16042LtD9kO4aNjw7E2C7E8v5IyUwrYQxBM9pcj0waB PvjaT6aVrepQl5+17YzuQGd9YaTucS2ikGMJCQuaFY6ZupNZsQY3AeD8FKWkAgLU lKEBbbNZlCLmHBIxk/DGUyiBy6Q3BsBMivOzdWDpkhnHwhwpumFPJCF8yh9BXeba Xu89BP1D0Dme9r0dD4daCsa9UOqkmaSELd9c8sstojcRK7rD0tCBQI8fFzdwGNo= -----END CERTIFICATE----- OpenVoxProject-ring-middleware-df9a994/ext/000077500000000000000000000000001523670473700207605ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/ext/travisci/000077500000000000000000000000001523670473700226045ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/ext/travisci/test.sh000077500000000000000000000000271523670473700241210ustar00rootroot00000000000000#!/bin/bash lein test OpenVoxProject-ring-middleware-df9a994/locales/000077500000000000000000000000001523670473700216025ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/locales/eo.po000066400000000000000000000026721523670473700225540ustar00rootroot00000000000000# Esperanto translations for puppetlabs.ring_middleware package. # Copyright (C) 2017 Puppet # This file is distributed under the same license as the puppetlabs.ring_middleware package. # Automatically generated, 2017. # msgid "" msgstr "" "Project-Id-Version: puppetlabs.ring_middleware \n" "Report-Msgid-Bugs-To: docs@puppet.com\n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: eo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/puppetlabs/ring_middleware/common.clj msgid "" "Proxying request to {0} to remote url {1}. Remote server responded with " "status {2}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Processing {0} {1}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Full request:" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Computed response: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Submitted data is invalid: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Service Unavailable:" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Bad Request:" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Something unexpected happened: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Internal Server Error: {0}" msgstr "" OpenVoxProject-ring-middleware-df9a994/locales/ring-middleware.pot000066400000000000000000000036111523670473700254010ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Puppet # This file is distributed under the same license as the puppetlabs.ring_middleware package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: puppetlabs.ring_middleware \n" "X-Git-Ref: ca6f15d0850241780dfadbfe464525e788a2b834\n" "Report-Msgid-Bugs-To: docs@puppet.com\n" "POT-Creation-Date: \n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/puppetlabs/ring_middleware/common.clj msgid "" "Proxying request to {0} to remote url {1}. Remote server responded with " "status {2}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Processing {0} {1}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Full request:" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Computed response: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Submitted data is invalid: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Service Unavailable:" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Bad Request:" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Something unexpected happened: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Internal Server Error: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Internal Server Error for {1} {2}: {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "accept header must include {0}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "content-type {0} is not a supported type for request of type {1} at {2}" msgstr "" #: src/puppetlabs/ring_middleware/core.clj msgid "Failed to parse json for request of type {0} at {1}" msgstr "" OpenVoxProject-ring-middleware-df9a994/project.clj000066400000000000000000000045041523670473700223230ustar00rootroot00000000000000(defproject org.openvoxproject/ring-middleware "2.2.1" ;; Generally, try to keep version pins in :managed-dependencies and the libraries ;; this project actually uses in :dependencies, inheriting the version from ;; :managed-dependencies. This prevents endless version conflicts due to deps of deps. ;; Renovate should keep the versions largely in sync between projects. :managed-dependencies [[org.clojure/clojure "1.12.5"] [org.clojure/tools.logging "1.3.1"] [org.clojure/tools.macro "0.2.2"] [cheshire "6.2.0"] [commons-codec "1.22.1"] [compojure "1.7.2"] [org.bouncycastle/bcpkix-jdk18on "1.84"] [org.openvoxproject/http-client "2.4.1"] [org.openvoxproject/kitchensink "3.5.8" :classifier "test" :scope "test"] [org.openvoxproject/trapperkeeper "5.0.5" :classifier "test" :scope "test"] [org.openvoxproject/trapperkeeper-webserver "12.1.0"] [org.slf4j/slf4j-api "2.0.18"] [ring/ring-codec "1.3.0"] [ring/ring-core "1.15.5"]] :dependencies [[cheshire] [org.openvoxproject/http-client]] :min-lein-version "2.9.1" :license {:name "Apache-2.0" :url "https://www.apache.org/licenses/LICENSE-2.0.txt"} ;; Abort when version ranges or version conflicts are detected in ;; dependencies. Also supports :warn to simply emit warnings. ;; requires lein 2.2.0+. :pedantic? :abort :plugins [[org.openvoxproject/i18n "1.0.5"]] :deploy-repositories [["releases" {:url "https://clojars.org/repo" :username :env/CLOJARS_USERNAME :password :env/CLOJARS_PASSWORD :sign-releases false}]] :profiles {:dev {:dependencies [[compojure] [org.bouncycastle/bcpkix-jdk18on] [org.openvoxproject/kitchensink :classifier "test" :scope "test"] [org.openvoxproject/trapperkeeper :classifier "test" :scope "test"] [org.openvoxproject/trapperkeeper-webserver]]}}) OpenVoxProject-ring-middleware-df9a994/renovate.json000066400000000000000000000004061523670473700226760ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "addLabels": ["renovate", "dependencies"], "assigneesFromCodeOwners": true, "automerge": true, "automergeType": "pr", "dependencyDashboard": true, "enabledManagers": ["leiningen"] } OpenVoxProject-ring-middleware-df9a994/src/000077500000000000000000000000001523670473700207475ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/src/puppetlabs/000077500000000000000000000000001523670473700231265ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/src/puppetlabs/ring_middleware/000077500000000000000000000000001523670473700262625ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/src/puppetlabs/ring_middleware/common.clj000066400000000000000000000046251523670473700302530ustar00rootroot00000000000000(ns puppetlabs.ring-middleware.common (:require [clojure.string :refer [replace-first]] [clojure.tools.logging :as log] [puppetlabs.http.client.sync :refer [request]] [puppetlabs.i18n.core :refer [trs]]) (:import (java.net URI) (java.util.regex Pattern))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Private utility functions (defn prepare-cookies "Removes the :domain and :secure keys and converts the :expires key (a Date) to a string in the ring response map resp. Returns resp with cookies properly munged." [resp] (let [prepare #(-> (update-in % [1 :expires] str) (update-in [1] dissoc :domain :secure))] (assoc resp :cookies (into {} (map prepare (:cookies resp)))))) (defn strip-trailing-slash [url] (if (.endsWith url "/") (.substring url 0 (- (count url) 1)) url)) (defn proxy-request [req proxied-path remote-uri-base & [http-opts]] ; Remove :decompress-body from the options map, as if this is ; ever set to true, the response returned to the client making the ; proxy request will be truncated (let [http-opts (dissoc http-opts :decompress-body) uri (URI. (strip-trailing-slash remote-uri-base)) remote-uri (URI. (.getScheme uri) (.getAuthority uri) (str (.getPath uri) (if (instance? Pattern proxied-path) (:uri req) (replace-first (:uri req) proxied-path ""))) nil nil) response (-> (merge {:method (:request-method req) :url (str remote-uri "?" (:query-string req)) :headers (dissoc (:headers req) "host" "content-length") :body (not-empty (slurp (:body req))) :as :stream :force-redirects false :follow-redirects false :decompress-body false} http-opts) request prepare-cookies)] (log/debug (trs "Proxying request to {0} to remote url {1}. Remote server responded with status {2}" (:uri req) (str remote-uri) (:status response))) response)) OpenVoxProject-ring-middleware-df9a994/src/puppetlabs/ring_middleware/core.clj000066400000000000000000000317421523670473700277130ustar00rootroot00000000000000(ns puppetlabs.ring-middleware.core (:require [clojure.string :as str] [clojure.tools.logging :as log] [puppetlabs.i18n.core :refer [trs]] [puppetlabs.kitchensink.core :as ks] [puppetlabs.ring-middleware.common :as common] [puppetlabs.ring-middleware.utils :as utils] [puppetlabs.ssl-utils.core :as ssl-utils] [ring.middleware.cookies :as cookies] [schema.core :as schema] [slingshot.slingshot :as sling]) (:import (clojure.lang IFn) (com.fasterxml.jackson.core JsonParseException) (java.io ByteArrayOutputStream PrintStream) (java.util.regex Pattern))) (def json-encoding-type "application/json") ;; HTTP error codes (def InternalServiceError 500) (def ServiceUnavailable 503) (def BadRequest 400) (def NotAcceptable 406) (def UnsupportedMediaType 415) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Private (defn sanitize-client-cert "Given a ring request, return a map which replaces the :ssl-client-cert with just the certificate's Common Name at :ssl-client-cert-cn. Also, remove the copy of the certificate put on the request by TK-auth." [req] (-> (if-let [client-cert (:ssl-client-cert req)] (-> req (dissoc :ssl-client-cert) (assoc :ssl-client-cert-cn (ssl-utils/get-cn-from-x509-certificate client-cert))) req) (ks/dissoc-in [:authorization :certificate]))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Middleware (schema/defn ^:always-validate wrap-request-logging :- IFn "A ring middleware that logs the request." [handler :- IFn] (fn [{:keys [request-method uri] :as req}] (log/debug (trs "Processing {0} {1}" request-method uri)) (log/trace (format "%s\n%s" (trs "Full request:") (ks/pprint-to-string (sanitize-client-cert req)))) (handler req))) (schema/defn ^:always-validate wrap-response-logging :- IFn "A ring middleware that logs the response." [handler :- IFn] (fn [req] (let [resp (handler req)] (log/trace (trs "Computed response: {0}" resp)) resp))) (schema/defn ^:always-validate wrap-proxy :- IFn "Proxies requests to proxied-path, a local URI, to the remote URI at remote-uri-base, also a string." [handler :- IFn proxied-path :- (schema/either Pattern schema/Str) remote-uri-base :- schema/Str & [http-opts]] (let [proxied-path (if (instance? Pattern proxied-path) (re-pattern (str "^" (.pattern proxied-path))) proxied-path)] (cookies/wrap-cookies (fn [req] (if (or (and (string? proxied-path) (.startsWith ^String (:uri req) (str proxied-path "/"))) (and (instance? Pattern proxied-path) (re-find proxied-path (:uri req)))) (common/proxy-request req proxied-path remote-uri-base http-opts) (handler req)))))) (schema/defn ^:always-validate wrap-add-cache-headers :- IFn "Adds cache control invalidation headers to GET and PUT requests if they are handled by the handler" [handler :- IFn] (fn [request] (let [request-method (:request-method request) response (handler request)] (when-not (nil? response) (if (or (= request-method :get) (= request-method :put)) (assoc-in response [:headers "cache-control"] "no-store") response))))) (schema/defn ^:always-validate wrap-add-x-frame-options-deny :- IFn "Adds 'X-Frame-Options: DENY' headers to requests if they are handled by the handler" [handler :- IFn] (fn [request] (let [response (handler request)] (when response (assoc-in response [:headers "X-Frame-Options"] "DENY"))))) (schema/defn ^:always-validate wrap-add-x-content-nosniff :- IFn "Adds 'X-Content-Type-Options: nosniff' headers to request." [handler :- IFn] (fn [request] (let [response (handler request)] (when response (assoc-in response [:headers "X-Content-Type-Options"] "nosniff"))))) (schema/defn ^:always-validate wrap-add-csp :- IFn "Adds 'Content-Security-Policy: default-src 'self'' headers to request." [handler :- IFn csp-val] (fn [request] (let [response (handler request)] (when response (assoc-in response [:headers "Content-Security-Policy"] csp-val))))) (schema/defn ^:always-validate wrap-with-certificate-cn :- IFn "Ring middleware that will annotate the request with an :ssl-client-cn key representing the CN contained in the client certificate of the request. If no client certificate is present, the key's value is set to nil." [handler :- IFn] (fn [{:keys [ssl-client-cert] :as req}] (let [cn (some-> ssl-client-cert ssl-utils/get-cn-from-x509-certificate) req (assoc req :ssl-client-cn cn)] (handler req)))) (schema/defn ^:always-validate wrap-data-errors :- IFn "A ring middleware that catches a slingshot error thrown by throw-data-invalid! or a :kind of slingshot error of one of: :request-data-invalid :user-data-invalid :data-invalid :service-status-version-not-found logs the error and returns a BadRequest ring response." ([handler :- IFn] (wrap-data-errors handler :json)) ([handler :- IFn type :- utils/ResponseType] (let [response (fn [e] (log/error e (trs "Submitted data is invalid: {0}" (:msg e))) (case type :json (utils/json-response BadRequest e) :plain (utils/plain-response BadRequest (:msg e))))] (fn [request] (sling/try+ (handler request) (catch #(contains? #{:request-data-invalid :user-data-invalid :data-invalid :service-status-version-not-found} (:kind %)) e (response e))))))) (schema/defn ^:always-validate wrap-service-unavailable :- IFn "A ring middleware that catches slingshot errors thrown by utils/throw-service-unavailabe!, logs the error and returns a ServiceUnavailable ring response." ([handler :- IFn] (wrap-service-unavailable handler :json)) ([handler :- IFn type :- utils/ResponseType] (let [response (fn [e] (log/error e (trs "Service Unavailable:" (:msg e))) (case type :json (utils/json-response ServiceUnavailable e) :plain (utils/plain-response ServiceUnavailable (:msg e))))] (fn [request] (sling/try+ (handler request) (catch utils/service-unavailable? e (response e))))))) (schema/defn ^:always-validate wrap-bad-request :- IFn "A ring middleware that catches slingshot errors thrown by utils/throw-bad-request!, logs the error and returns a BadRequest ring response." ([handler :- IFn] (wrap-bad-request handler :json)) ([handler :- IFn type :- utils/ResponseType] (let [response (fn [e] (log/error e (trs "Bad Request:" (:msg e))) (case type :json (utils/json-response BadRequest e) :plain (utils/plain-response BadRequest (:msg e))))] (fn [request] (sling/try+ (handler request) (catch utils/bad-request? e (response e))))))) (schema/defn ^:always-validate wrap-schema-errors :- IFn "A ring middleware that catches schema errors and returns a InternalServiceError response with the details" ([handler :- IFn] (wrap-schema-errors handler :json)) ([handler :- IFn type :- utils/ResponseType] (let [response (fn [e] (let [msg (trs "Something unexpected happened: {0}" (select-keys e [:error :value :type]))] (log/error e msg) (case type :json (utils/json-response InternalServiceError {:kind :application-error :msg msg}) :plain (utils/plain-response InternalServiceError msg))))] (fn [request] (sling/try+ (handler request) (catch utils/schema-error? e (response e))))))) (defn- handle-error-response [request e type] (with-open [baos (ByteArrayOutputStream.) print-stream (PrintStream. baos)] (.printStackTrace e print-stream) (let [msg (trs "Internal Server Error: {0}" (.toString e)) method (-> request :request-method name str/upper-case)] (log/error (trs "Internal Server Error for {1} {2}: {0}" (.toString baos) method (:uri request))) (case type :json (utils/json-response InternalServiceError {:kind :application-error :msg msg}) :plain (utils/plain-response InternalServiceError msg))))) (schema/defn ^:always-validate wrap-uncaught-errors :- IFn "A ring middleware that catches all otherwise uncaught errors and returns a InternalServiceError response with the error message" ([handler :- IFn] (wrap-uncaught-errors handler :json)) ([handler :- IFn type :- utils/ResponseType] (fn [request] (try (handler request) (catch Throwable e (handle-error-response request e type)))))) (schema/defn ^:always-validate wrap-add-referrer-policy :- IFn "Adds referrer policy to the header as 'Referrer-Policy: no-referrer' or 'Referrer-Policy: same-origin'" [policy-option :- schema/Str handler :- IFn] (fn [request] (let [response (handler request)] (when-not (nil? response) (assoc-in response [:headers "Referrer-Policy"] policy-option))))) (def superwildcard "*/*") (defn acceptable-content-type "Returns a boolean indicating whether the `candidate` mime type matches any of those listed in `header`, an Accept header." [candidate header] (if-not (string? header) true (let [[prefix] (.split ^String candidate "/") wildcard (str prefix "/*") types (->> (str/split header #",") (map #(.trim ^String %)) (set))] (or (types superwildcard) (types wildcard) (types candidate))))) (defn wrap-accepts-content-type "Ring middleware that requires a request for the wrapped `handler` to accept the provided `content-type`. If the content type isn't acceptable, a 406 Not Acceptable status is returned, with a message informing the client it must accept the content type." [handler content-type] (fn [{:keys [headers] :as req}] (if (acceptable-content-type content-type (get headers "accept")) (handler req) (utils/json-response NotAcceptable {:kind "not-acceptable" :msg (trs "accept header must include {0}" content-type)})))) (def wrap-accepts-json "Ring middleware which requires a request for `handler` to accept application/json as a content type. If the request doesn't accept application/json, a 406 Not Acceptable status is returned with an error message indicating the problem." (fn [handler] (wrap-accepts-content-type handler json-encoding-type))) (defn wrap-content-type "Verification for the specified list of content-types." [handler content-types] {:pre [(coll? content-types) (every? string? content-types)]} (fn [{:keys [headers] :as req}] (if (or (= (:request-method req) :post) (= (:request-method req) :put)) (let [content-type (get headers "content-type") media-type (when-not (nil? content-type) (ks/base-type content-type))] (if (or (nil? media-type) (some #{media-type} content-types)) (handler req) (utils/json-response UnsupportedMediaType {:kind "unsupported-type" :msg (trs "content-type {0} is not a supported type for request of type {1} at {2}" media-type (:request-method req) (:uri req))}))) (handler req)))) (def wrap-content-type-json "Ring middleware which requires a request for `handler` to accept application/json as a content-type. If the request doesn't specify a content-type of application/json a 415 Unsupported Type status is returned." (fn [handler] (wrap-content-type handler [json-encoding-type]))) (def wrap-json-parse-exception-handler "Ring middleware which catches JsonParseExceptions and returns a predictable result" (fn [handler] (fn [req] (try (handler req) (catch JsonParseException e (log/debug e (trs "Failed to parse json for request of type {0} at {1}" (:request-method req) (:uri req))) (utils/json-response BadRequest {:kind :json-parse-exception :msg (.getMessage e)})))))) OpenVoxProject-ring-middleware-df9a994/src/puppetlabs/ring_middleware/params.clj000066400000000000000000000115671523670473700302510ustar00rootroot00000000000000(ns puppetlabs.ring-middleware.params (:require [ring.util.codec :as codec] [ring.util.request :as req])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; COPY OF RELEVANT FUNCTIONS FROM UPSTREAM ring.middleware.params LIBRARY ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This namespace is basically just here to provide an implementation of ;; the `params-request` middleware function that supports a String representation ;; of the body of a request. The upstream library requires the body to be of ;; a type that is compatible with Clojure's IOFactory, which forces us to read ;; the request body into memory twice for requests that we have to pass down ;; into the JRuby layer. (Technically, the Ring specification states that the ;; body must be an InputStream, so the maintainer of the upstream library was ;; reluctant to accept any sort of upstream PR to work around this issue.) ;; ;; All of this code is copied from the upstream library, and there is just ;; one very slight modification (see comment in `assoc-form-params` function) ;; that allows us to avoid reading the body into memory twice. ;; ;; In the future, if we can handle the query parameter parsing strictly on the ;; Clojure side and remove that code from the Ruby side, we should be able to ;; get rid of this. That will be much easier to consider doing once we're able ;; to get rid of the Rack/Webrick support. ;; ;; If that happens, we should delete this namespace :) ;; (defn parse-params [params encoding] (let [params (codec/form-decode params encoding)] (if (map? params) params {}))) (defn content-type "Return the content-type of the request, or nil if no content-type is set." [request] ;; NOTE: in the latest version of ring-core, they only look in ;; the headers map for the content type. They no longer fall ;; back to looking for it in the main request map. (if-let [type (or (get-in request [:headers "content-type"]) (get request :content-type))] (second (re-find #"^(.*?)(?:;|$)" type)))) (defn urlencoded-form? "True if a request contains a urlencoded form in the body." [request] (if-let [^String type (content-type request)] (.startsWith type "application/x-www-form-urlencoded"))) (defn assoc-query-params "Parse and assoc parameters from the query string with the request." [request encoding] (merge-with merge request (if-let [query-string (:query-string request)] (let [params (parse-params query-string encoding)] {:query-params params, :params params}) {:query-params {}, :params {}}))) (defn assoc-form-params "Parse and assoc parameters from the request body with the request." [request encoding] (merge-with merge request (if-let [body (and (urlencoded-form? request) (:body request))] (let [params (parse-params ;; NOTE: this is the main difference between our ;; copy of this code and the upstream version: ;; the upstream always does a slurp here, while ;; we only do a slurp if the body is not already ;; a string. (if (string? body) body (slurp body :encoding encoding)) encoding)] {:form-params params, :params params}) {:form-params {}, :params {}}))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Public (defn params-request "Adds parameters from the query string and the request body to the request map. See: wrap-params." {:arglists '([request] [request options])} [request & [opts]] (let [encoding (or (:encoding opts) (req/character-encoding request) "UTF-8") request (if (:form-params request) request (assoc-form-params request encoding))] (if (:query-params request) request (assoc-query-params request encoding)))) (defn wrap-params "Middleware to parse urlencoded parameters from the query string and form body (if the request is a url-encoded form). Adds the following keys to the request map: :query-params - a map of parameters from the query string :form-params - a map of parameters from the body :params - a merged map of all types of parameter Accepts the following options: :encoding - encoding to use for url-decoding. If not specified, uses the request character encoding, or \"UTF-8\" if no request character encoding is set." {:arglists '([handler] [handler options])} [handler & [options]] (fn [request] (handler (params-request request options)))) OpenVoxProject-ring-middleware-df9a994/src/puppetlabs/ring_middleware/utils.clj000066400000000000000000000045661523670473700301270ustar00rootroot00000000000000(ns puppetlabs.ring-middleware.utils (:require [schema.core :as schema] [ring.util.response :as rr] [slingshot.slingshot :as sling] [cheshire.core :as json]) (:import (java.security.cert X509Certificate))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Schemas (def ResponseType (schema/enum :json :plain)) (def RingRequest {:uri schema/Str (schema/optional-key :ssl-client-cert) (schema/maybe X509Certificate) schema/Keyword schema/Any}) (def RingResponse {:status schema/Int :headers {schema/Str schema/Any} :body schema/Any schema/Keyword schema/Any}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Helpers (schema/defn ^:always-validate json-response :- RingResponse [status :- schema/Int body :- schema/Any] (-> body json/encode rr/response (rr/status status) (rr/content-type "application/json; charset=utf-8"))) (schema/defn ^:always-validate plain-response :- RingResponse [status :- schema/Int body :- schema/Str] (-> body rr/response (rr/status status) (rr/content-type "text/plain; charset=utf-8"))) (defn throw-bad-request! "Throw a :bad-request type slingshot error with the supplied message" [message] (sling/throw+ {:kind :bad-request :msg message})) (defn bad-request? [e] "Determine if the supplied slingshot error is for a bad request" (when (map? e) (= (:kind e) :bad-request))) (defn throw-service-unavailable! "Throw a :service-unavailable type slingshot error with the supplied message" [message] (sling/throw+ {:kind :service-unavailable :msg message})) (defn service-unavailable? [e] "Determine if the supplied slingshot error is for an unavailable service" (when (map? e) (= (:kind e) :service-unavailable))) (defn throw-data-invalid! "Throw a :data-invalid type slingshot error with the supplied message" [message] (sling/throw+ {:kind :data-invalid :msg message})) (defn data-invalid? [e] "Determine if the supplied slingshot error is for invalid data" (when (map? e) (= (:kind e) :data-invalid))) (defn schema-error? [e] "Determine if the supplied slingshot error is for a schema mismatch" (when (map? e) (= (:type e) :schema.core/error))) OpenVoxProject-ring-middleware-df9a994/test/000077500000000000000000000000001523670473700211375ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/test/puppetlabs/000077500000000000000000000000001523670473700233165ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/test/puppetlabs/ring_middleware/000077500000000000000000000000001523670473700264525ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/test/puppetlabs/ring_middleware/core_test.clj000066400000000000000000001146311523670473700311410ustar00rootroot00000000000000(ns puppetlabs.ring-middleware.core-test (:require [cheshire.core :as json] [clojure.test :refer :all] [compojure.core :refer :all] [compojure.handler :as handler] [compojure.route :as route] [puppetlabs.ring-middleware.core :as core] [puppetlabs.ring-middleware.utils :as utils] [puppetlabs.ring-middleware.testutils.common :refer :all] [puppetlabs.ssl-utils.core :refer [pem->cert]] [puppetlabs.ssl-utils.simple :as ssl-simple] [puppetlabs.trapperkeeper.app :refer [get-service]] [puppetlabs.trapperkeeper.services.webserver.jetty-service :refer :all] [puppetlabs.trapperkeeper.testutils.bootstrap :refer [with-app-with-config]] [puppetlabs.trapperkeeper.testutils.logging :as logutils] [ring.util.response :as rr] [schema.core :as schema] [slingshot.slingshot :as slingshot]) (:import (com.fasterxml.jackson.core JsonFactory JsonLocation JsonParseException JsonParser))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Testing Helpers (def WackSchema [schema/Str]) (schema/defn ^:always-validate cause-schema-error [request :- WackSchema] (throw (IllegalStateException. "The test should have never gotten here..."))) (defn throwing-handler [kind msg] (fn [_] (slingshot/throw+ {:kind kind :msg msg}))) (defn basic-request ([] (basic-request "foo-agent" :get "https://example.com")) ([subject method uri] {:request-method method :uri uri :ssl-client-cert (:cert (ssl-simple/gen-self-signed-cert subject 1 {:keylength 512})) :authorization {:certificate "foo"}})) (defn post-target-handler [req] (if (= (:request-method req) :post) {:status 200 :body (slurp (:body req))} {:status 404 :body "Z'oh"})) (defn proxy-target-handler [req] (condp = (:uri req) "/hello" {:status 302 :headers {"Location" "/hello/world"}} "/hello/" {:status 302 :headers {"Location" "/hello/world"}} "/hello/world" {:status 200 :body "Hello, World!"} "/hello/wrong-host" {:status 302 :headers {"Location" "http://localhost:4/fake"}} "/hello/fully-qualified" {:status 302 :headers {"Location" "http://localhost:9000/hello/world"}} "/hello/different-path" {:status 302 :headers {"Location" "http://localhost:9000/different/"}} {:status 404 :body "D'oh"})) (defn non-proxy-target [_] {:status 200 :body "Non-proxied path"}) (def gzip-body (apply str (repeat 1000 "f"))) (defn proxy-gzip-response [_] (-> gzip-body (rr/response) (rr/status 200) (rr/content-type "text/plain") (rr/charset "UTF-8"))) (defn proxy-error-handler [_] {:status 404 :body "N'oh"}) (defn proxy-regex-response [req] {:status 200 :body (str "Proxied to " (:uri req))}) (defroutes fallthrough-routes (GET "/hello/world" [] "Hello, World! (fallthrough)") (GET "/goodbye/world" [] "Goodbye, World! (fallthrough)") (route/not-found "Not Found (fallthrough)")) (def proxy-regex-fallthrough (handler/site fallthrough-routes)) (def proxy-wrapped-app (-> proxy-error-handler (core/wrap-proxy "/hello-proxy" "http://localhost:9000/hello"))) (def proxy-wrapped-app-ssl (-> proxy-error-handler (core/wrap-proxy "/hello-proxy" "https://localhost:9001/hello" {:ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem" :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem" :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem"}))) (def proxy-wrapped-app-redirects (-> proxy-error-handler (core/wrap-proxy "/hello-proxy" "http://localhost:9000/hello" {:force-redirects true :follow-redirects true}))) (def proxy-wrapped-app-regex (-> proxy-regex-fallthrough (core/wrap-proxy #"^/([^/]+/certificate.*)$" "http://localhost:9000/hello"))) (def proxy-wrapped-app-regex-alt (-> proxy-regex-fallthrough (core/wrap-proxy #"/hello-proxy" "http://localhost:9000/hello"))) (def proxy-wrapped-app-regex-no-prepend (-> proxy-regex-fallthrough (core/wrap-proxy #"^/([^/]+/certificate.*)$" "http://localhost:9000"))) (def proxy-wrapped-app-regex-trailing-slash (-> proxy-regex-fallthrough (core/wrap-proxy #"^/([^/]+/certificate.*)$" "http://localhost:9000/"))) (defmacro with-target-and-proxy-servers [{:keys [target proxy proxy-handler ring-handler endpoint target-endpoint]} & body] `(with-app-with-config proxy-target-app# [jetty-service] {:webserver ~target} (let [target-webserver# (get-service proxy-target-app# :WebserverService)] (add-ring-handler target-webserver# ~ring-handler ~target-endpoint) (add-ring-handler target-webserver# non-proxy-target "/different") (add-ring-handler target-webserver# post-target-handler "/hello/post")) (with-app-with-config proxy-app# [jetty-service] {:webserver ~proxy} (let [proxy-webserver# (get-service proxy-app# :WebserverService)] (add-ring-handler proxy-webserver# ~proxy-handler ~endpoint)) ~@body))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Core Helpers (deftest sanitize-client-cert-test (testing "sanitize-client-cert" (let [subject "foo-client" cert (:cert (ssl-simple/gen-self-signed-cert subject 1)) request {:ssl-client-cert cert :authorization {:certificate "stuff"}} response (core/sanitize-client-cert request)] (testing "adds the CN at :ssl-client-cert-cn" (is (= subject (response :ssl-client-cert-cn)))) (testing "removes :ssl-client-cert key from response" (is (nil? (response :ssl-client-cert)))) (testing "remove tk-auth cert info" (is (nil? (get-in response [:authorization :certificate]))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Core Middleware (deftest test-proxy (let [common-ssl-config {:ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem" :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem" :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem"}] (testing "basic proxy support" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "http://localhost:9000/hello/world")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-get "http://localhost:10000/hello-proxy/world")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-get "http://localhost:10000/hello-proxy/world" {:as :stream})] (is (= (slurp (:body response)) "Hello, World!"))) (let [response (http-post "http://localhost:10000/hello-proxy/post/" {:as :stream :body "I'm posted!"})] (is (= (:status response) 200)) (is (= (slurp (:body response)) "I'm posted!"))))) (testing "basic https proxy support" (with-target-and-proxy-servers {:target (merge common-ssl-config {:ssl-host "0.0.0.0" :ssl-port 9001}) :proxy (merge common-ssl-config {:ssl-host "0.0.0.0" :ssl-port 10001}) :proxy-handler proxy-wrapped-app-ssl :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "https://localhost:9001/hello/world" default-options-for-https-client)] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))))) (testing "basic http->https proxy support" (with-target-and-proxy-servers {:target (merge common-ssl-config {:ssl-host "0.0.0.0" :ssl-port 9001}) :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-ssl :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "https://localhost:9001/hello/world" default-options-for-https-client)] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-get "http://localhost:10000/hello-proxy/world")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))))) (testing "basic https->http proxy support" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy (merge common-ssl-config {:ssl-host "0.0.0.0" :ssl-port 10001}) :proxy-handler proxy-wrapped-app :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "http://localhost:9000/hello/world")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-get "https://localhost:10001/hello-proxy/world" default-options-for-https-client)] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))))) (testing "redirect test with proxy" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "http://localhost:9000/hello")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-get "http://localhost:9000/hello/world")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-get "http://localhost:10000/hello-proxy/" {:follow-redirects false :as :text})] (is (= (:status response) 302)) (is (= "/hello/world" (get-in response [:headers "location"])))) (let [response (http-post "http://localhost:10000/hello-proxy/" {:follow-redirects false :as :text})] (is (= (:status response) 302)) (is (= "/hello/world" (get-in response [:headers "location"])))) (let [response (http-get "http://localhost:10000/hello-proxy/world")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))))) (testing "proxy redirect succeeds on POST if :force-redirects set true" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-redirects :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "http://localhost:10000/hello-proxy/" {:follow-redirects false :as :text})] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))) (let [response (http-post "http://localhost:10000/hello-proxy/" {:follow-redirects false})] (is (= (:status response) 200))))) (testing "redirect test with fully qualified url, correct host, and proxied path" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-redirects :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "http://localhost:10000/hello-proxy/fully-qualified" {:follow-redirects false :as :text})] (is (= (:status response) 200)) (is (= (:body response) "Hello, World!"))))) (testing "redirect test with correct host on non-proxied path" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-redirects :ring-handler proxy-target-handler :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "http://localhost:9000/different")] (is (= (:status response) 200)) (is (= (:body response) "Non-proxied path"))) (let [response (http-get "http://localhost:10000/different")] (is (= (:status response) 404))) (let [response (http-get "http://localhost:10000/hello-proxy/different-path" {:follow-redirects false :as :text})] (is (= (:status response) 200)) (is (= (:body response) "Non-proxied path"))))) (testing "gzipped responses not truncated" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app :ring-handler proxy-gzip-response :endpoint "/hello-proxy" :target-endpoint "/hello"} (let [response (http-get "http://localhost:9000/hello")] (is (= gzip-body (:body response))) (is (= "gzip" (:orig-content-encoding response)))) (let [response (http-get "http://localhost:10000/hello-proxy/")] (is (= gzip-body (:body response))) (is (= "gzip" (:orig-content-encoding response)))))) (testing "proxy works with regex" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-regex :ring-handler proxy-regex-response :endpoint "/" :target-endpoint "/hello"} (let [response (http-get "http://localhost:10000/production/certificate/foo")] (is (= (:status response) 200)) (is (= (:body response) "Proxied to /hello/production/certificate/foo"))) (let [response (http-get "http://localhost:10000/hello/world")] (is (= (:status response) 200)) (is (= (:body response) "Hello, World! (fallthrough)"))) (let [response (http-get "http://localhost:10000/goodbye/world")] (is (= (:status response) 200)) (is (= (:body response) "Goodbye, World! (fallthrough)"))) (let [response (http-get "http://localhost:10000/production/cert/foo")] (is (= (:status response) 404)) (is (= (:body response) "Not Found (fallthrough)"))))) (testing "proxy regex matches beginning of string" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-regex-alt :ring-handler proxy-regex-response :endpoint "/" :target-endpoint "/hello"} (let [response (http-get "http://localhost:10000/hello-proxy")] (is (= (:status response) 200)) (is (= (:body response) "Proxied to /hello/hello-proxy"))) (let [response (http-get "http://localhost:10000/production/hello-proxy")] (is (= (:status response) 404)) (is (= (:body response) "Not Found (fallthrough)"))))) (testing "proxy regex does not need to match entire request uri" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-regex-alt :ring-handler proxy-regex-response :endpoint "/" :target-endpoint "/hello"} (let [response (http-get "http://localhost:10000/hello-proxy/world")] (is (= (:status response) 200)) (is (= (:body response) "Proxied to /hello/hello-proxy/world"))))) (testing "proxy works with regex and no prepended path" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-regex-no-prepend :ring-handler proxy-regex-response :endpoint "/" :target-endpoint "/"} (let [response (http-get "http://localhost:10000/production/certificate/foo")] (is (= (:status response) 200)) (is (= (:body response) "Proxied to /production/certificate/foo"))))) (testing "no repeat slashes exist in rewritten uri" (with-target-and-proxy-servers {:target {:host "0.0.0.0" :port 9000} :proxy {:host "0.0.0.0" :port 10000} :proxy-handler proxy-wrapped-app-regex-trailing-slash :ring-handler proxy-regex-response :endpoint "/" :target-endpoint "/"} (let [response (http-get "http://localhost:10000/production/certificate/foo")] (is (= (:status response) 200)) (is (= (:body response) "Proxied to /production/certificate/foo"))))))) (deftest test-wrap-add-cache-headers (let [put-request {:request-method :put} get-request {:request-method :get} post-request {:request-method :post} delete-request {:request-method :delete} no-cache-header "no-store"] (testing "wrap-add-cache-headers ignores nil response" (let [handler (constantly nil) wrapped-handler (core/wrap-add-cache-headers handler)] (is (nil? (wrapped-handler put-request))) (is (nil? (wrapped-handler get-request))) (is (nil? (wrapped-handler post-request))) (is (nil? (wrapped-handler delete-request))))) (testing "wrap-add-cache-headers observes handled response" (let [handler (constantly {}) wrapped-handler (core/wrap-add-cache-headers handler) handled-response {:headers {"cache-control" no-cache-header}} not-handled-response {}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= not-handled-response (wrapped-handler post-request))) (is (= not-handled-response (wrapped-handler delete-request))))) (testing "wrap-add-cache-headers doesn't stomp on existing headers" (let [fake-response {:headers {:something "Hi mom"}} handler (constantly fake-response) wrapped-handler (core/wrap-add-cache-headers handler) handled-response {:headers {:something "Hi mom" "cache-control" no-cache-header}} not-handled-response fake-response] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= not-handled-response (wrapped-handler post-request))) (is (= not-handled-response (wrapped-handler delete-request))))))) (deftest test-wrap-add-x-content-nosniff (let [put-request {:request-method :put} get-request {:request-method :get} post-request {:request-method :post} delete-request {:request-method :delete}] (testing "wrap-add-x-content-nosniff ignores nil response" (let [handler (constantly nil) wrapped-handler (core/wrap-add-x-content-nosniff handler)] (is (nil? (wrapped-handler put-request))) (is (nil? (wrapped-handler get-request))) (is (nil? (wrapped-handler post-request))) (is (nil? (wrapped-handler delete-request))))) (testing "wrap-add-x-content-nosniff observes handled response" (let [handler (constantly {}) wrapped-handler (core/wrap-add-x-content-nosniff handler) handled-response {:headers {"X-Content-Type-Options" "nosniff"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))) (testing "wrap-add-x-content-nosniff doesn't stomp on existing headers" (let [fake-response {:headers {:something "Hi mom"}} handler (constantly fake-response) wrapped-handler (core/wrap-add-x-content-nosniff handler) handled-response {:headers {:something "Hi mom" "X-Content-Type-Options" "nosniff"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))))) (deftest test-wrap-add-csp (let [put-request {:request-method :put} get-request {:request-method :get} post-request {:request-method :post} delete-request {:request-method :delete}] (testing "wrap-add-csp ignores nil response" (let [handler (constantly nil) wrapped-handler (core/wrap-add-csp handler "foo")] (is (nil? (wrapped-handler put-request))) (is (nil? (wrapped-handler get-request))) (is (nil? (wrapped-handler post-request))) (is (nil? (wrapped-handler delete-request))))) (testing "wrap-add-csp observes handled response" (let [handler (constantly {}) wrapped-handler (core/wrap-add-csp handler "foo") handled-response {:headers {"Content-Security-Policy" "foo"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))) (testing "wrap-add-csp doesn't stomp on existing headers" (let [fake-response {:headers {:something "Hi mom"}} handler (constantly fake-response) wrapped-handler (core/wrap-add-csp handler "foo") handled-response {:headers {:something "Hi mom" "Content-Security-Policy" "foo"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))) (testing "wrap-add-csp takes arg for header value" (let [handler (constantly {}) wrapped-handler (core/wrap-add-csp handler "bar") handled-response {:headers {"Content-Security-Policy" "bar"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))))) (deftest test-wrap-with-cn (testing "When extracting a CN from a cert" (testing "and there is no cert" (let [mw-fn (core/wrap-with-certificate-cn identity) post-req (mw-fn {})] (testing "ssl-client-cn is set to nil" (is (= post-req {:ssl-client-cn nil}))))) (testing "and there is a cert" (let [mw-fn (core/wrap-with-certificate-cn identity) post-req (mw-fn {:ssl-client-cert (pem->cert "dev-resources/ssl/cert.pem")})] (testing "ssl-client-cn is set properly" (is (= (:ssl-client-cn post-req) "localhost"))))))) (deftest test-wrap-add-x-frame-options-deny (let [get-request {:request-method :get} put-request {:request-method :put} post-request {:request-method :post} delete-request {:request-method :delete} x-frame-header "DENY"] (testing "wrap-add-x-frame-options-deny ignores nil response" (let [handler (constantly nil) wrapped-handler (core/wrap-add-x-frame-options-deny handler)] (is (nil? (wrapped-handler get-request))) (is (nil? (wrapped-handler put-request))) (is (nil? (wrapped-handler post-request))) (is (nil? (wrapped-handler delete-request))))) (testing "wrap-add-x-frame-options-deny observes handled response" (let [handler (constantly {}) wrapped-handler (core/wrap-add-x-frame-options-deny handler) handled-response {:headers {"X-Frame-Options" x-frame-header}} not-handled-response {}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))) (testing "wrap-add-x-frame-options-deny doesn't stomp on existing headers" (let [fake-response {:headers {:something "Hi mom"}} handler (constantly fake-response) wrapped-handler (core/wrap-add-x-frame-options-deny handler) handled-response {:headers {:something "Hi mom" "X-Frame-Options" x-frame-header}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))))) (deftest wrap-response-logging-test (testing "wrap-response-logging" (logutils/with-test-logging (let [stack (core/wrap-response-logging identity) response (stack (basic-request))] (is (logged? #"Computed response.*" :trace)))))) (deftest wrap-request-logging-test (testing "wrap-request-logging" (logutils/with-test-logging (let [subject "foo-agent" method :get uri "https://example.com" stack (core/wrap-request-logging identity) request (basic-request subject method uri) response (stack request)] (is (logged? (format "Processing %s %s" method uri) :debug)) (is (logged? #"Full request" :trace)))))) (deftest wrap-data-errors-test (testing "wrap-data-errors" (testing "default behavior" (logutils/with-test-logging (let [stack (core/wrap-data-errors (throwing-handler :user-data-invalid "Error Message")) response (stack (basic-request)) json-body (json/parse-string (response :body))] (is (= 400 (response :status))) (is (= "Error Message" (get json-body "msg"))) (is (logged? #"Error Message" :error)) (is (logged? #"Submitted data is invalid" :error))))) (doseq [error [:request-data-invalid :user-data-invalid :service-status-version-not-found]] (testing (str "handles errors of " error) (logutils/with-test-logging (let [stack (core/wrap-data-errors (throwing-handler error "Error Message")) response (stack (basic-request)) json-body (json/parse-string (response :body))] (is (= 400 (response :status))) (is (= (name error) (get json-body "kind"))))))) (testing "handles errors thrown by `throw-data-invalid!`" (logutils/with-test-logging (let [stack (core/wrap-data-errors (fn [_] (utils/throw-data-invalid! "Error Message"))) response (stack (basic-request)) json-body (json/parse-string (response :body))] (is (= 400 (response :status))) (is (= (name "data-invalid") (get json-body "kind")))))) (testing "can be plain text" (logutils/with-test-logging (let [stack (core/wrap-data-errors (throwing-handler :user-data-invalid "Error Message") :plain) response (stack (basic-request))] (is (re-matches #"text/plain.*" (get-in response [:headers "Content-Type"])))))))) (deftest wrap-bad-request-test (testing "wrap-bad-request" (testing "default behavior" (logutils/with-test-logging (let [stack (core/wrap-bad-request (fn [_] (utils/throw-bad-request! "Error Message"))) response (stack (basic-request)) json-body (json/parse-string (response :body))] (is (= 400 (response :status))) (is (logged? #".*Bad Request.*" :error)) (is (re-matches #"Error Message.*" (get json-body "msg" ""))) (is (= "bad-request" (get json-body "kind")))))) (testing "can be plain text" (logutils/with-test-logging (let [stack (core/wrap-bad-request (fn [_] (utils/throw-bad-request! "Error Message")) :plain) response (stack (basic-request))] (is (re-matches #"text/plain.*" (get-in response [:headers "Content-Type"])))))))) (deftest wrap-schema-errors-test (testing "wrap-schema-errors" (testing "default behavior" (logutils/with-test-logging (let [stack (core/wrap-schema-errors cause-schema-error) response (stack (basic-request)) json-body (json/parse-string (response :body))] (is (= 500 (response :status))) (is (logged? #".*Something unexpected.*" :error)) (is (re-matches #"Something unexpected.*" (get json-body "msg" ""))) (is (= "application-error" (get json-body "kind")))))) (testing "can be plain text" (logutils/with-test-logging (let [stack (core/wrap-schema-errors cause-schema-error :plain) response (stack (basic-request))] (is (re-matches #"text/plain.*" (get-in response [:headers "Content-Type"])))))))) (deftest wrap-service-unavailable-test (testing "wrap-service-unavailable" (testing "default behavior" (logutils/with-test-logging (let [stack (core/wrap-service-unavailable (fn [_] (utils/throw-service-unavailable! "Test Service is DOWN!"))) response (stack (basic-request)) json-body (json/parse-string (response :body))] (is (= 503 (response :status))) (is (logged? #".*Service Unavailable.*" :error)) (is (= "Test Service is DOWN!" (get json-body "msg"))) (is (= "service-unavailable" (get json-body "kind")))))) (testing "can be plain text" (logutils/with-test-logging (let [stack (core/wrap-service-unavailable (fn [_] (utils/throw-service-unavailable! "Test Service is DOWN!")) :plain) response (stack (basic-request))] (is (re-matches #"text/plain.*" (get-in response [:headers "Content-Type"])))))))) (deftest wrap-uncaught-errors-test (testing "wrap-uncaught-errors" (testing "default behavior" (logutils/with-test-logging (let [stack (core/wrap-uncaught-errors (fn [_] (throw (IllegalStateException. "Woah...")))) response (stack (basic-request)) json-body (json/parse-string (response :body))] (is (= 500 (response :status))) ;; Note the "(?s)" uses Java's DOTALL matching mode, so that a "." will match newlines ;; This with the "at puppetlabs" that should be in a stacktrace ensures we're logging ;; the stacktrace... (is (logged? #"(?s).*Internal Server Error.*at puppetlabs.*" :error)) ;; ...while this ending anchor and lack of DOTALL ensures that we are not sending a ;; stacktrace (technically nothing with new lines). (is (re-matches #"Internal Server Error.*$" (get json-body "msg" "")))))) (testing "can be plain text" (logutils/with-test-logging (let [stack (core/wrap-uncaught-errors (fn [_] (throw (IllegalStateException. "Woah..."))) :plain) response (stack (basic-request))] (is (re-matches #"text/plain.*" (get-in response [:headers "Content-Type"])))))))) (deftest test-wrap-add-referrer-policy (let [get-request {:request-method :get} put-request {:request-method :put} post-request {:request-method :post} delete-request {:request-method :delete}] (testing "wrap-add-referrer-policy observes handled response" (let [handler (constantly {}) wrapped-handler (core/wrap-add-referrer-policy "no-referrer" handler) handled-response {:headers {"Referrer-Policy" "no-referrer"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))) (testing "wrap-add-referrer-policy observes handled dynamic response" (let [handler (constantly {}) wrapped-handler (core/wrap-add-referrer-policy "same-origin" handler) handled-response {:headers {"Referrer-Policy" "same-origin"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))) (testing "wrap-add-referrer-policy ignores nil response" (let [handler (constantly nil) wrapped-handler (core/wrap-add-referrer-policy "same-origin" handler)] (is (nil? (wrapped-handler get-request))) (is (nil? (wrapped-handler put-request))) (is (nil? (wrapped-handler post-request))) (is (nil? (wrapped-handler delete-request))))) (testing "wrap-add-referrer-policy doesn't stomp existing headers" (let [fake-response {:headers {:something "hi there"}} handler (constantly fake-response) wrapped-handler (core/wrap-add-referrer-policy "same-origin" handler) handled-response {:headers {:something "hi there" "Referrer-Policy" "same-origin"}}] (is (= handled-response (wrapped-handler get-request))) (is (= handled-response (wrapped-handler put-request))) (is (= handled-response (wrapped-handler post-request))) (is (= handled-response (wrapped-handler delete-request))))))) (deftest wrap-accepts-json-test (let [request (fn [method a] {:request-method method :headers {"accept" a}}) handler (constantly nil) wrapped-handler (core/wrap-accepts-json handler)] (testing "wrap-accepts-json allows json" (let [] (doseq [method [:get :put :post :delete]] (is (nil? (wrapped-handler (request method "application/json")))) (is (nil? (wrapped-handler (request method "text/html, application/json")))) (is (nil? (wrapped-handler (request method "application/*")))) (is (nil? (wrapped-handler (request method "*/*"))))))) (testing "wrap-accepts-json rejects non-json" (let [rejection-expectation {:body "{\"kind\":\"not-acceptable\",\"msg\":\"accept header must include application/json\"}" :headers {"Content-Type" "application/json; charset=utf-8"} :status 406}] (doseq [method [:get :put :post :delete]] (let [result (wrapped-handler (request method "text/html"))] (is (= rejection-expectation result)))))))) (deftest wrap-content-type-json-test (let [request (fn [method a] {:request-method method :headers {"content-type" a} :uri "http://example.com"}) handler (constantly nil) wrapped-handler (core/wrap-content-type-json handler)] (testing "wrap-content-type-json allows json" (let [] (doseq [method [:put :post]] (is (nil? (wrapped-handler (request method "application/json"))))) ;; content-type is ignored for get and delete (doseq [method [:get :delete]] (is (nil? (wrapped-handler (request method "text/html, application/json")))) (is (nil? (wrapped-handler (request method "application/*")))) (is (nil? (wrapped-handler (request method "*/*")))))) (testing "wrap-content-type-json rejects non-json" (let [rejection-expectation (fn [method] {:body (str "{\"kind\":\"unsupported-type\",\"msg\":\"content-type text/html is not a supported type for request of type " method " at http://example.com\"}") :headers {"Content-Type" "application/json; charset=utf-8"} :status 415})] (doseq [method [:put :post]] (let [result (wrapped-handler (request method "text/html"))] (is (= (rejection-expectation method) result))))))))) (deftest wrap-json-parse-exception-handler-test (let [factory (JsonFactory.) wrapped-handler (core/wrap-json-parse-exception-handler (fn [_] (throw (JsonParseException. (.createParser factory "") "Error Message")))) response (wrapped-handler (basic-request))] (is (= 400 (:status response))) (is (= {"Content-Type" "application/json; charset=utf-8"} (:headers response))) (let [body (json/parse-string (:body response))] (is (= "json-parse-exception" (get body "kind"))) (is (clojure.string/starts-with? (get body "msg") "Error Message")) (is (clojure.string/includes? (get body "msg") "line: 1, column: 1"))))) OpenVoxProject-ring-middleware-df9a994/test/puppetlabs/ring_middleware/testutils/000077500000000000000000000000001523670473700305125ustar00rootroot00000000000000OpenVoxProject-ring-middleware-df9a994/test/puppetlabs/ring_middleware/testutils/common.clj000066400000000000000000000012621523670473700324750ustar00rootroot00000000000000(ns puppetlabs.ring-middleware.testutils.common (:require [puppetlabs.http.client.sync :as http-client])) (def default-options-for-https-client {:ssl-cert "./dev-resources/config/jetty/ssl/certs/localhost.pem" :ssl-key "./dev-resources/config/jetty/ssl/private_keys/localhost.pem" :ssl-ca-cert "./dev-resources/config/jetty/ssl/certs/ca.pem" :as :text}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Testing utility functions (defn http-get ([url] (http-get url {:as :text})) ([url options] (http-client/get url options))) (defn http-post ([url] (http-post url {:as :text})) ([url options] (http-client/post url options)))OpenVoxProject-ring-middleware-df9a994/test/puppetlabs/ring_middleware/utils_test.clj000066400000000000000000000017541523670473700313520ustar00rootroot00000000000000(ns puppetlabs.ring-middleware.utils-test (:require [cheshire.core :as json] [clojure.test :refer :all] [puppetlabs.ring-middleware.utils :as utils])) (deftest json-response-test (testing "json response" (let [source {:key 1} response (utils/json-response 200 source)] (testing "has 200 status code" (is (= 200 (:status response)))) (testing "has json content-type" (is (re-matches #"application/json.*" (get-in response [:headers "Content-Type"])))) (testing "is properly converted to a json string" (is (= 1 ((json/parse-string (:body response)) "key"))))))) (deftest plain-response-test (testing "json response" (let [message "Response message" response (utils/plain-response 200 message)] (testing "has 200 status code" (is (= 200 (:status response)))) (testing "has plain content-type" (is (re-matches #"text/plain.*" (get-in response [:headers "Content-Type"])))))))