<commit_before>(ns puppetlabs.puppetdb.middleware
  "Ring middleware"
  (:require [puppetlabs.i18n.core :as i18n :refer [trs tru]]
            [puppetlabs.kitchensink.core :as kitchensink]
            [puppetlabs.puppetdb.jdbc :as jdbc :refer [with-transacted-connection]]
            [puppetlabs.puppetdb.query-eng :as qe]
            [puppetlabs.puppetdb.utils.metrics :refer [multitime!]]
            [puppetlabs.puppetdb.http :as http]
            [puppetlabs.puppetdb.schema :refer [defn-validated]]
            [ring.util.request :as request]
            [clojure.string :as str]
            [clojure.tools.logging :as log]
            [ring.middleware.params :refer [wrap-params]]
            [puppetlabs.puppetdb.query.paging :as paging]
            [clojure.set :as set]
            [puppetlabs.puppetdb.metrics.core :as metrics]
            [metrics.timers :refer [timer time!]]
            [metrics.meters :refer [meter mark!]]
            [metrics.histograms :refer [update!]]
            [clojure.walk :refer [keywordize-keys]]
            [puppetlabs.puppetdb.utils :as utils]
            [bidi.bidi :as bidi]
            [bidi.ring :as bring]
            [bidi.schema :as bidi-schema]
            [puppetlabs.puppetdb.schema :as pls]
            [schema.core :as s]
            [puppetlabs.puppetdb.command :as cmd]
            [puppetlabs.puppetdb.constants :as constants]
            [puppetlabs.puppetdb.command.constants :as const]))

(def handler-schema (s/=> s/Any {s/Any s/Any}))

(defn wrap-with-debug-logging
  "Ring middleware that logs incoming HTTP request URIs (at DEBUG level) as
  requests come in.  To enable, add this line to your logback.xml:

  `<logger name=\"puppetlabs.puppetdb.middleware\" level=\"debug\"/>`"
  [app]
  (fn [req]
    (log/debug (trs "Processing HTTP request to URI: ''{0}''" (:uri req)))
    (app req)))

(defn build-allowlist-authorizer
  "Build a function that will authorize requests based on the supplied
  certificate allowlist (see `cn-whitelist->authorizer` for more
  details). Returns :authorized if the request is allowed, otherwise a
  string describing the reason not."
  [allowlist]
  {:pre  [(string? allowlist)]
   :post [(fn? %)]}
  (let [allowed? (kitchensink/cn-whitelist->authorizer allowlist)]
    (fn [{:keys [ssl-client-cn] :as req}]
      (when-not (allowed? req)
        (when ssl-client-cn
          (log/warn (trs "{0} rejected by certificate allowlist {1}" ssl-client-cn allowlist)))
        (http/denied-response (tru "The client certificate name {0} doesn't appear in the certificate allowlist. Is your master''s (or other PuppetDB client''s) certname listed in PuppetDB''s certificate-allowlist file?" ssl-client-cn)
                         http/status-forbidden)))))

(defn wrap-cert-authn
  [app cert-allowlist]
  (if-let [cert-authorize-fn (some-> cert-allowlist build-allowlist-authorizer)]
    (fn [req]
      (if-let [cert-auth-result (cert-authorize-fn req)]
        cert-auth-result
        (app req)))
    app))

(defn wrap-with-certificate-cn
  "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."
  [app]
  (fn [{:keys [ssl-client-cert] :as req}]
    (let [cn  (some-> ssl-client-cert kitchensink/cn-for-cert)
          req (assoc req :ssl-client-cn cn)]
      (app req))))

(defn wrap-with-default-body
  "Ring middleware that will attach a default body based on the response code
  if no other body is supplied."
  [app]
  (fn [req]
    (let [{:keys [body] :as response} (app req)]
      (if body
        response
        (assoc response :body (http/default-body req response))))))

(defn wrap-with-globals
  "Ring middleware that adds a :globals attribute to each request that
   contains a map of the current shared-global settings."
  [app get-shared-globals]
  (fn [req]
    (let [new-req (update req :globals #(merge (get-shared-globals) %))]
      (app new-req))))

(defn wrap-with-illegal-argument-catch
  [app]
  (fn [req]
    (try
      (app req)
      (catch IllegalArgumentException e
        (http/error-response e)))))

(defn verify-accepts-content-type
  "Ring middleware that requires a request for the wrapped `app` 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."
  [app content-type]
  {:pre (string? content-type)}
  (fn [{:keys [headers] :as req}]
    (if (http/acceptable-content-type
         content-type
         (headers "accept"))
      (app req)
      (http/error-response (tru "must accept {0}" content-type)
                           http/status-not-acceptable))))

(defn verify-content-encoding
  "Verification for the specified list of content-encodings."
  [app allowed-encodings]
  {:pre [(coll? allowed-encodings)
         (every? string? allowed-encodings)]}
  (fn [{:keys [headers request-method] :as req}]
    (let [content-encoding (headers "content-encoding")]
      (if (or (not= request-method :post)
              (empty? content-encoding)
              (some #{content-encoding} allowed-encodings))
        (app req)
        (http/error-response (tru "content encoding {0} not supported"
                                  content-encoding)
                             http/status-unsupported-type)))))

(defn verify-content-type
  "Verification for the specified list of content-types."
  [app content-types]
  {:pre [(coll? content-types)
         (every? string? content-types)]}
  (fn [{:keys [headers] :as req}]
    (if (= (:request-method req) :post)<commit_msg>(PDB-5022) verify-accepts-content-type: put :pre condition in a vector<commit_after>(ns puppetlabs.puppetdb.middleware
  "Ring middleware"
  (:require [puppetlabs.i18n.core :as i18n :refer [trs tru]]
            [puppetlabs.kitchensink.core :as kitchensink]
            [puppetlabs.puppetdb.jdbc :as jdbc :refer [with-transacted-connection]]
            [puppetlabs.puppetdb.query-eng :as qe]
            [puppetlabs.puppetdb.utils.metrics :refer [multitime!]]
            [puppetlabs.puppetdb.http :as http]
            [puppetlabs.puppetdb.schema :refer [defn-validated]]
            [ring.util.request :as request]
            [clojure.string :as str]
            [clojure.tools.logging :as log]
            [ring.middleware.params :refer [wrap-params]]
            [puppetlabs.puppetdb.query.paging :as paging]
            [clojure.set :as set]
            [puppetlabs.puppetdb.metrics.core :as metrics]
            [metrics.timers :refer [timer time!]]
            [metrics.meters :refer [meter mark!]]
            [metrics.histograms :refer [update!]]
            [clojure.walk :refer [keywordize-keys]]
            [puppetlabs.puppetdb.utils :as utils]
            [bidi.bidi :as bidi]
            [bidi.ring :as bring]
            [bidi.schema :as bidi-schema]
            [puppetlabs.puppetdb.schema :as pls]
            [schema.core :as s]
            [puppetlabs.puppetdb.command :as cmd]
            [puppetlabs.puppetdb.constants :as constants]
            [puppetlabs.puppetdb.command.constants :as const]))

(def handler-schema (s/=> s/Any {s/Any s/Any}))

(defn wrap-with-debug-logging
  "Ring middleware that logs incoming HTTP request URIs (at DEBUG level) as
  requests come in.  To enable, add this line to your logback.xml:

  `<logger name=\"puppetlabs.puppetdb.middleware\" level=\"debug\"/>`"
  [app]
  (fn [req]
    (log/debug (trs "Processing HTTP request to URI: ''{0}''" (:uri req)))
    (app req)))

(defn build-allowlist-authorizer
  "Build a function that will authorize requests based on the supplied
  certificate allowlist (see `cn-whitelist->authorizer` for more
  details). Returns :authorized if the request is allowed, otherwise a
  string describing the reason not."
  [allowlist]
  {:pre  [(string? allowlist)]
   :post [(fn? %)]}
  (let [allowed? (kitchensink/cn-whitelist->authorizer allowlist)]
    (fn [{:keys [ssl-client-cn] :as req}]
      (when-not (allowed? req)
        (when ssl-client-cn
          (log/warn (trs "{0} rejected by certificate allowlist {1}" ssl-client-cn allowlist)))
        (http/denied-response (tru "The client certificate name {0} doesn't appear in the certificate allowlist. Is your master''s (or other PuppetDB client''s) certname listed in PuppetDB''s certificate-allowlist file?" ssl-client-cn)
                         http/status-forbidden)))))

(defn wrap-cert-authn
  [app cert-allowlist]
  (if-let [cert-authorize-fn (some-> cert-allowlist build-allowlist-authorizer)]
    (fn [req]
      (if-let [cert-auth-result (cert-authorize-fn req)]
        cert-auth-result
        (app req)))
    app))

(defn wrap-with-certificate-cn
  "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."
  [app]
  (fn [{:keys [ssl-client-cert] :as req}]
    (let [cn  (some-> ssl-client-cert kitchensink/cn-for-cert)
          req (assoc req :ssl-client-cn cn)]
      (app req))))

(defn wrap-with-default-body
  "Ring middleware that will attach a default body based on the response code
  if no other body is supplied."
  [app]
  (fn [req]
    (let [{:keys [body] :as response} (app req)]
      (if body
        response
        (assoc response :body (http/default-body req response))))))

(defn wrap-with-globals
  "Ring middleware that adds a :globals attribute to each request that
   contains a map of the current shared-global settings."
  [app get-shared-globals]
  (fn [req]
    (let [new-req (update req :globals #(merge (get-shared-globals) %))]
      (app new-req))))

(defn wrap-with-illegal-argument-catch
  [app]
  (fn [req]
    (try
      (app req)
      (catch IllegalArgumentException e
        (http/error-response e)))))

(defn verify-accepts-content-type
  "Ring middleware that requires a request for the wrapped `app` 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."
  [app content-type]
  {:pre [(string? content-type)]}
  (fn [{:keys [headers] :as req}]
    (if (http/acceptable-content-type
         content-type
         (headers "accept"))
      (app req)
      (http/error-response (tru "must accept {0}" content-type)
                           http/status-not-acceptable))))

(defn verify-content-encoding
  "Verification for the specified list of content-encodings."
  [app allowed-encodings]
  {:pre [(coll? allowed-encodings)
         (every? string? allowed-encodings)]}
  (fn [{:keys [headers request-method] :as req}]
    (let [content-encoding (headers "content-encoding")]
      (if (or (not= request-method :post)
              (empty? content-encoding)
              (some #{content-encoding} allowed-encodings))
        (app req)
        (http/error-response (tru "content encoding {0} not supported"
                                  content-encoding)
                             http/status-unsupported-type)))))

(defn verify-content-type
  "Verification for the specified list of content-types."
  [app content-types]
  {:pre [(coll? content-types)
         (every? string? content-types)]}
  (fn [{:keys [headers] :as req}]
    (if (= (:request-method req) :post)
{'fraction_non_alphanumeric': 0.12529324876183856, 'fraction_numerical': 0.00260665566078721, 'mean_word_length': 2.715300193673338, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4997068', 'n_tokens_mistral': 3760, 'n_tokens_neox': 3711, 'n_words': 1076}