;; ;; Copyright (c) Two Sigma Open Source, LLC ;; ;; 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. ;; (ns waiter.auth.basic (:require [clojure.string :as str] [clojure.tools.logging :as log] [metrics.counters :as counters] [metrics.meters :as meters] [ring.middleware.basic-authentication :as basic-authentication] [ring.middleware.cookies :as cookies] [waiter.auth.authentication :as auth] [waiter.metrics :as metrics] [waiter.util.utils :as utils])) (def ^:const basic-prefix "Basic ") (def ^:const decode-base64 #'basic-authentication/decode-base64) (defn- basic-token? "Predicate to determine if an authorization header represents a spnego negotiate token." [authorization] (str/starts-with? (str authorization) basic-prefix)) (defn authorization->principal "Authenticates the given request using provided validate-credentials. A non-empty string value indicates successful authentication, else indicates authentication failure. On successful authentication, returns the principal returned by invoking validate-credentials." [validate-credentials authorization] (try (when-let [credentials (some-> authorization (str/split #" " 2) last str decode-base64)] (let [[username password] (str/split (str credentials) #":" 2)] (validate-credentials username password))) (catch Throwable th (log/error th "error in performing basic authentication")))) (defn- respond-with-data "Tell the client you'd like them to use basic auth" [request {:keys [status] :as data-map}] (let [status-str (str status)] (log/info "triggering" status-str "response for basic authentication") (counters/inc! (metrics/waiter-counter "core" "response-status" status-str)) (meters/mark! (metrics/waiter-meter "core" "response-status-rate" status-str)) (-> data-map (utils/data->error-response request) (cookies/cookies-response)))) (defn respond-with-401-challenge "Tell the client you'd like them to use basic auth" [request] (let [realm (utils/request->host request) www-auth-header (if (str/blank? realm) (str/trim basic-prefix) (str basic-prefix "realm=\"" realm "\""))] (respond-with-data request {:headers {"www-authenticate" www-auth-header} :message "Missing credentials" :status 401}))) (defn respond-with-403-forbidden "Tell the client they are unauthorized." [request] (respond-with-data request {:message "Invalid credentials" :status 403})) ;; The Basic authenticator attaches the username as principal to the request. ;; In particular, this enables requests to launch processes as that user. (defrecord BasicAuthenticator [password validate-credentials] auth/Authenticator (wrap-auth-handler [_ request-handler] (fn anonymous-handler [request] (if-let [authorization (auth/select-auth-header request basic-token?)] (if-let [principal (authorization->principal validate-credentials authorization)] (auth/handle-request-auth request-handler request :basic principal password) (respond-with-403-forbidden request)) (respond-with-401-challenge request))))) (defn basic-authenticator "Factory function for creating single-user authenticator" [{:keys [password validate-credentials-factory] :as context}] {:pre [(some? password) (symbol? validate-credentials-factory)]} (log/info "initializing basic authenticator") (let [validate-credentials-factory-fn (utils/resolve-symbol! validate-credentials-factory) validate-credentials (validate-credentials-factory-fn context)] (->BasicAuthenticator password validate-credentials))) (defn allow-waiter-users "User validation function that allows all users who provide a 'waiter' password." [username password] (if (= "waiter" password) (do (log/info "successfully authenticated" username) username) (log/info "unable to authenticate" username))) (defn allow-waiter-users-factory "Factory function that returns allow-waiter-users." [_] allow-waiter-users)