├── .clj-kondo ├── babashka │ └── fs │ │ └── config.edn ├── config.edn ├── etaoin │ └── etaoin │ │ ├── config.edn │ │ └── etaoin │ │ ├── api.clj_kondo │ │ ├── api2.clj_kondo │ │ └── hooks_util.clj_kondo └── potemkin │ └── potemkin │ ├── config.edn │ └── potemkin │ └── namespaces.clj ├── .dir-locals.el ├── .github ├── CODEOWNERS ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── linters.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── README.md ├── SECURITY.md ├── VERSION.txt ├── build.clj ├── check-for-reflection-warnings.sh ├── codecov.yml ├── deps.edn ├── docker-compose.yml ├── e2e └── saml20_clj │ └── e2e │ ├── entra.cert │ ├── keystore.jks │ ├── okta.cert │ ├── server.clj │ └── server_test.clj ├── keycloak └── realm.json ├── license └── LICENSE ├── src └── saml20_clj │ ├── coerce.clj │ ├── core.clj │ ├── crypto.clj │ ├── encode_decode.clj │ ├── sp │ ├── logout_response.clj │ ├── message.clj │ ├── metadata.clj │ ├── request.clj │ └── response.clj │ ├── specs.clj │ ├── state.clj │ └── xml.clj └── test └── saml20_clj ├── coerce_test.clj ├── crypto_test.clj ├── runners └── test.clj ├── sp ├── logout_response_test.clj ├── metadata_test.clj ├── request_test.clj └── response_test.clj ├── state_test.clj ├── test.clj ├── test ├── idp.cert ├── idp.private.key ├── keystore.jks ├── logout-response-authnfailure-with-signature.xml ├── logout-response-success-with-bad-signature.edn ├── logout-response-success-with-bad-signature.xml ├── logout-response-success-with-signature.edn ├── logout-response-success-with-signature.xml ├── logout-response-success-without-signature.edn ├── logout-response-success-without-signature.xml ├── metadata-with-keyinfo.xml ├── metadata-without-keyinfo.xml ├── response-invalid-confirmation-data.xml ├── response-no-issuer.xml ├── response-unsigned.xml ├── response-valid-confirmation-data.xml ├── response-with-encrypted-assertion.xml ├── response-with-signed-and-encrypted-assertion.xml ├── response-with-signed-and-encrypted-no-namespace-assertion.xml ├── response-with-signed-and-encrypted-saml2-assertion.xml ├── response-with-signed-assertion.xml ├── response-with-signed-message-and-assertion.xml ├── response-with-signed-message-and-encrypted-assertion.xml ├── response-with-signed-message-and-signed-and-encryped-assertion.xml ├── response-with-signed-message.xml ├── response-with-swapped-signature.xml ├── sp.cert └── sp.private.key └── xml_test.clj /.clj-kondo/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:linters 2 | {:aliased-namespace-symbol {:level :warning} 3 | :case-symbol-test {:level :warning} 4 | :condition-always-true {:level :warning} 5 | :def-fn {:level :warning} 6 | :dynamic-var-not-earmuffed {:level :warning} 7 | :equals-expected-position {:level :warning, :only-in-test-assertion true} 8 | :keyword-binding {:level :warning} 9 | :main-without-gen-class {:level :warning} 10 | :minus-one {:level :warning} 11 | :misplaced-docstring {:level :warning} 12 | :missing-body-in-when {:level :warning} 13 | :missing-docstring {:level :warning} 14 | :missing-else-branch {:level :warning} 15 | :namespace-name-mismatch {:level :warning} 16 | :non-arg-vec-return-type-hint {:level :warning} 17 | :plus-one {:level :warning} 18 | :reduce-without-init {:level :warning} 19 | :redundant-call {:level :warning} 20 | :redundant-fn-wrapper {:level :warning} 21 | :self-requiring-namespace {:level :warning} 22 | :single-key-in {:level :warning} 23 | :unsorted-imports {:level :warning} 24 | :unsorted-required-namespaces {:level :warning} 25 | :unused-alias {:level :warning} 26 | :use {:level :warning} 27 | :warn-on-reflection {:level :warning}} 28 | 29 | :config-in-comment {:linters {:unresolved-symbol {:level :off}}} 30 | :lint-as {saml20-clj.sp.response/validate-confirmation-datas clojure.core/let}} 31 | -------------------------------------------------------------------------------- /.clj-kondo/etaoin/etaoin/config.edn: -------------------------------------------------------------------------------- 1 | {:linters 2 | {:etaoin/with-x-action {:level :error} 3 | :etaoin/binding-sym {:level :error} 4 | :etaoin/opts-map-type {:level :error} 5 | :etaoin/opts-map-pos {:level :error} 6 | :etaoin/empty-opts {:level :warning}} 7 | :hooks 8 | {:analyze-call 9 | {etaoin.api/with-chrome etaoin.api/with-browser 10 | etaoin.api/with-chrome-headless etaoin.api/with-browser 11 | etaoin.api/with-firefox etaoin.api/with-browser 12 | etaoin.api/with-firefox-headless etaoin.api/with-browser 13 | etaoin.api/with-edge etaoin.api/with-browser 14 | etaoin.api/with-edge-headless etaoin.api/with-browser 15 | etaoin.api/with-safari etaoin.api/with-browser 16 | 17 | etaoin.api/with-driver etaoin.api/with-driver 18 | etaoin.api/with-key-down etaoin.api/with-key-down 19 | etaoin.api/with-pointer-btn-down etaoin.api/with-pointer-btn-down 20 | 21 | ;; api2 moves to a more conventional let-ish vector syntax 22 | etaoin.api2/with-chrome etaoin.api2/with-browser 23 | etaoin.api2/with-chrome-headless etaoin.api2/with-browser 24 | etaoin.api2/with-edge etaoin.api2/with-browser 25 | etaoin.api2/with-edge-headless etaoin.api2/with-browser 26 | etaoin.api2/with-firefox etaoin.api2/with-browser 27 | etaoin.api2/with-firefox-headless etaoin.api2/with-browser 28 | etaoin.api2/with-safari etaoin.api2/with-browser}} 29 | :lint-as 30 | {etaoin.api/with-pointer-left-btn-down clojure.core/->}} 31 | -------------------------------------------------------------------------------- /.clj-kondo/etaoin/etaoin/etaoin/api.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns etaoin.api 2 | (:require [clj-kondo.hooks-api :as api] 3 | [etaoin.hooks-util :as h])) 4 | 5 | (defn- nil-node? [n] 6 | (and (api/token-node? n) (nil? (api/sexpr n)))) 7 | 8 | (defn- with-bound-arg [node arg-offset] 9 | (let [macro-args (rest (:children node)) 10 | leading-args (take arg-offset macro-args) 11 | interesting-args (drop arg-offset macro-args) 12 | [opts binding-sym & body] (if (h/symbol-node? (second interesting-args)) 13 | interesting-args 14 | (cons nil interesting-args))] 15 | ;; if the user has specified nil or {} for options we can suggest that is not necessary 16 | (when (and opts 17 | (or (and (api/map-node? opts) (not (seq (:children opts)))) 18 | (nil-node? opts))) 19 | (api/reg-finding! (assoc (meta opts) 20 | :message "Empty or nil driver options can be omitted" 21 | :type :etaoin/empty-opts))) 22 | 23 | (cond 24 | (not (h/symbol-node? binding-sym)) 25 | ;; it makes more sense here to report on the incoming node position instead of what we expect to be the binding-sym 26 | (api/reg-finding! (assoc (meta node) 27 | :message "Expected binding symbol for driver" 28 | :type :etaoin/binding-sym)) 29 | 30 | ;; we don't want to explicitly expect a map because the map might come from 31 | ;; an evalution, but we can do some checks 32 | (and opts ;; we'll assume a list-node is a function call (eval) 33 | (not (nil-node? opts)) ;; nil is actually old-v1 syntax acceptable 34 | (not (api/list-node? opts)) ;; some fn call 35 | (not (h/symbol-node? opts)) ;; from a binding maybe 36 | ;; there are other eval node types... @(something) for example... maybe we'll add them in if folks ask 37 | (not (api/map-node? opts))) 38 | ;; we can report directly on the opts node, because at this point we know we expect 39 | ;; this arg position to be an opts map 40 | (api/reg-finding! (assoc (meta opts) 41 | :message "When specified, opts should be a map" 42 | :type :etaoin/opts-map-type)) 43 | 44 | ;; one last nicety, if the first form in body is a map, the user has accidentally swapped 45 | ;; binding and opt args 46 | (api/map-node? (first body)) 47 | (api/reg-finding! (assoc (meta (first body)) 48 | :message "When specified, opts must appear before binding symbol" 49 | :type :etaoin/opts-map-pos)) 50 | 51 | :else 52 | {:node (api/list-node 53 | (list* 54 | (api/token-node 'let) 55 | ;; simulate the effect, macro is creating a new thing (driver for example) 56 | ;; via binding it. I don't think the bound value matters for the linting process 57 | (api/vector-node [binding-sym (api/map-node [])]) 58 | ;; reference the other args so that they are not linted as unused 59 | (api/vector-node leading-args) 60 | opts ;; might be a binding, so ref it too 61 | body))}))) 62 | 63 | (defn- with-x-down 64 | "This is somewhat of a maybe an odd duck. 65 | I think it is assumed to be used within a threading macro. 66 | And itself employs a threadfirst macro. 67 | So each body form need to have an action (dummy or not) threaded into it." 68 | [node] 69 | (let [macro-args (rest (:children node)) 70 | [input x & body] macro-args 71 | dummy-action (api/map-node [])] 72 | {:node (api/list-node 73 | (apply list* 74 | (api/token-node 'do) 75 | ;; reference x and input just in case they contain something lint-relevant 76 | x input 77 | ;; dump the body, threading a dummy action in as first arg 78 | (map (fn [body-form] 79 | (cond 80 | ;; not certain this is absolutely what we want, but maybe close enough 81 | (h/symbol-node? body-form) (api/list-node (list* body-form dummy-action)) 82 | (api/list-node? body-form) (let [children (:children body-form)] 83 | (assoc body-form :children (apply list* 84 | (first children) 85 | dummy-action 86 | (rest children)))) 87 | :else 88 | (api/reg-finding! (assoc (meta body-form) 89 | :message "expected to be threaded through an action" 90 | :type :etaoin/with-x-action)))) 91 | body)))})) 92 | 93 | (defn with-browser 94 | "Covers etaoin.api/with-chrome and all its variants 95 | [opt? bind & body]" 96 | [{:keys [node]}] 97 | (with-bound-arg node 0)) 98 | 99 | (defn with-driver 100 | "Very similar to with-browser but bound arg is 1 deeper 101 | [type opt? bind & body]" 102 | [{:keys [node]}] 103 | (with-bound-arg node 1)) 104 | 105 | (defn with-key-down 106 | "[input key & body]" 107 | [{:keys [node]}] 108 | (with-x-down node)) 109 | 110 | (defn with-pointer-btn-down 111 | "[input button & body]" 112 | [{:keys [node]}] 113 | (with-x-down node)) 114 | -------------------------------------------------------------------------------- /.clj-kondo/etaoin/etaoin/etaoin/api2.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns etaoin.api2 2 | (:require [clj-kondo.hooks-api :as api] 3 | [etaoin.hooks-util :as h])) 4 | 5 | (defn with-browser 6 | "Newer variants for api2 7 | [[bind & [options]] & body]" 8 | [{:keys [node]}] 9 | (let [macro-args (rest (:children node)) 10 | binding-like-vector (first macro-args)] 11 | (if-not (api/vector-node? binding-like-vector) 12 | ;; could use clj-kondo findings, but I think this is good for now 13 | (throw (ex-info "Expected vector for first arg" {})) 14 | (let [binding-sym (-> binding-like-vector :children first)] 15 | (if-not (h/symbol-node? binding-sym) 16 | (throw (ex-info "Expected binding symbol for first arg in vector" {})) 17 | (let [other-args (rest binding-like-vector) 18 | body (rest macro-args)] 19 | {:node (api/list-node 20 | (list* 21 | (api/token-node 'let) 22 | ;; simulate the effect, macro is creating a new thing (driver for example) 23 | ;; via binding it. I don't think the bound value matters for the linting process 24 | (api/vector-node [binding-sym (api/map-node [])]) 25 | ;; reference the other args so that they are not linted as unused 26 | (api/vector-node other-args) 27 | body))})))))) 28 | -------------------------------------------------------------------------------- /.clj-kondo/etaoin/etaoin/etaoin/hooks_util.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns etaoin.hooks-util 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn symbol-node? [node] 5 | (and (api/token-node? node) 6 | (symbol? (api/sexpr node)))) 7 | -------------------------------------------------------------------------------- /.clj-kondo/potemkin/potemkin/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {potemkin.collections/compile-if clojure.core/if 2 | potemkin.collections/reify-map-type clojure.core/reify 3 | potemkin.collections/def-map-type clj-kondo.lint-as/def-catch-all 4 | potemkin.collections/def-derived-map clj-kondo.lint-as/def-catch-all 5 | 6 | potemkin.types/reify+ clojure.core/reify 7 | potemkin.types/defprotocol+ clojure.core/defprotocol 8 | potemkin.types/deftype+ clojure.core/deftype 9 | potemkin.types/defrecord+ clojure.core/defrecord 10 | potemkin.types/definterface+ clojure.core/defprotocol 11 | potemkin.types/extend-protocol+ clojure.core/extend-protocol 12 | potemkin.types/def-abstract-type clj-kondo.lint-as/def-catch-all 13 | 14 | potemkin.utils/doit clojure.core/doseq 15 | potemkin.utils/doary clojure.core/doseq 16 | potemkin.utils/condp-case clojure.core/condp 17 | potemkin.utils/fast-bound-fn clojure.core/bound-fn 18 | 19 | potemkin.walk/prewalk clojure.walk/prewalk 20 | potemkin.walk/postwalk clojure.walk/postwalk 21 | potemkin.walk/walk clojure.walk/walk 22 | 23 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 24 | ;;;; top-level from import-vars 25 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 26 | 27 | ;; Have hooks 28 | ;;potemkin/import-fn potemkin.namespaces/import-fn 29 | ;;potemkin/import-macro potemkin.namespaces/import-macro 30 | ;;potemkin/import-def potemkin.namespaces/import-def 31 | 32 | ;; Internal, not transitive 33 | ;;potemkin/unify-gensyms potemkin.macros/unify-gensyms 34 | ;;potemkin/normalize-gensyms potemkin.macros/normalize-gensyms 35 | ;;potemkin/equivalent? potemkin.macros/equivalent? 36 | 37 | potemkin/condp-case clojure.core/condp 38 | potemkin/doit potemkin.utils/doit 39 | potemkin/doary potemkin.utils/doary 40 | 41 | potemkin/def-abstract-type clj-kondo.lint-as/def-catch-all 42 | potemkin/reify+ clojure.core/reify 43 | potemkin/defprotocol+ clojure.core/defprotocol 44 | potemkin/deftype+ clojure.core/deftype 45 | potemkin/defrecord+ clojure.core/defrecord 46 | potemkin/definterface+ clojure.core/defprotocol 47 | potemkin/extend-protocol+ clojure.core/extend-protocol 48 | 49 | potemkin/reify-map-type clojure.core/reify 50 | potemkin/def-derived-map clj-kondo.lint-as/def-catch-all 51 | potemkin/def-map-type clj-kondo.lint-as/def-catch-all} 52 | 53 | ;; leave import-vars alone, kondo special-cases it 54 | :hooks {:macroexpand {#_#_potemkin.namespaces/import-vars potemkin.namespaces/import-vars 55 | potemkin.namespaces/import-fn potemkin.namespaces/import-fn 56 | potemkin.namespaces/import-macro potemkin.namespaces/import-macro 57 | potemkin.namespaces/import-def potemkin.namespaces/import-def 58 | 59 | #_#_potemkin/import-vars potemkin.namespaces/import-vars 60 | potemkin/import-fn potemkin.namespaces/import-fn 61 | potemkin/import-macro potemkin.namespaces/import-macro 62 | potemkin/import-def potemkin.namespaces/import-def}}} 63 | -------------------------------------------------------------------------------- /.clj-kondo/potemkin/potemkin/potemkin/namespaces.clj: -------------------------------------------------------------------------------- 1 | (ns potemkin.namespaces 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn import-macro* 5 | ([sym] 6 | `(def ~(-> sym name symbol) ~sym)) 7 | ([sym name] 8 | `(def ~name ~sym))) 9 | 10 | (defmacro import-fn 11 | ([sym] 12 | (import-macro* sym)) 13 | ([sym name] 14 | (import-macro* sym name))) 15 | 16 | (defmacro import-macro 17 | ([sym] 18 | (import-macro* sym)) 19 | ([sym name] 20 | (import-macro* sym name))) 21 | 22 | (defmacro import-def 23 | ([sym] 24 | (import-macro* sym)) 25 | ([sym name] 26 | (import-macro* sym name))) 27 | 28 | #_ 29 | (defmacro import-vars 30 | "Imports a list of vars from other namespaces." 31 | [& syms] 32 | (let [unravel (fn unravel [x] 33 | (if (sequential? x) 34 | (->> x 35 | rest 36 | (mapcat unravel) 37 | (map 38 | #(symbol 39 | (str (first x) 40 | (when-let [n (namespace %)] 41 | (str "." n))) 42 | (name %)))) 43 | [x])) 44 | syms (mapcat unravel syms) 45 | result `(do 46 | ~@(map 47 | (fn [sym] 48 | (let [vr (resolve sym) 49 | m (meta vr)] 50 | (cond 51 | (nil? vr) `(throw (ex-info (format "`%s` does not exist" '~sym) {})) 52 | (:macro m) `(def ~(-> sym name symbol) ~sym) 53 | (:arglists m) `(def ~(-> sym name symbol) ~sym) 54 | :else `(def ~(-> sym name symbol) ~sym)))) 55 | syms))] 56 | result)) 57 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((clojure-mode 2 | (cider-clojure-cli-aliases . "dev"))) 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | *.* @camsaul 2 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | inputs: 3 | java-version: 4 | required: true 5 | default: 21 6 | cache-key: 7 | required: true 8 | default: "deps" 9 | 10 | runs: 11 | using: composite 12 | steps: 13 | - name: Prepare JDK 14 | uses: actions/setup-java@v4 15 | with: 16 | java-version: ${{ inputs.java-version }} 17 | distribution: temurin 18 | - name: Setup Clojure 19 | uses: DeLaGuardo/setup-clojure@9.5 20 | with: 21 | cli: 1.11.1.1208 22 | - name: Cache Dependencies 23 | uses: actions/cache@v4 24 | with: 25 | path: | 26 | ~/.m2 27 | ~/.gitlibs 28 | key: v1-${{ hashFiles('deps.edn') }}-${{ inputs.cache-key }} 29 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | Check: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | steps: 14 | - uses: actions/checkout@v4.1.7 15 | - uses: ./.github/actions/setup 16 | with: 17 | cache-key: "check" 18 | - run: clojure -M:check 19 | 20 | Eastwood: 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 10 23 | steps: 24 | - uses: actions/checkout@v4.1.7 25 | - uses: ./.github/actions/setup 26 | with: 27 | cache-key: "eastwood" 28 | - run: clojure -X:dev:eastwood 29 | 30 | Reflection-Warnings: 31 | runs-on: ubuntu-latest 32 | timeout-minutes: 10 33 | steps: 34 | - uses: actions/checkout@v4.1.7 35 | - uses: ./.github/actions/setup 36 | with: 37 | cache-key: "reflection-warnings" 38 | - run: ./check-for-reflection-warnings.sh 39 | 40 | Whitespace: 41 | runs-on: ubuntu-latest 42 | timeout-minutes: 10 43 | steps: 44 | - uses: actions/checkout@v4.1.7 45 | - uses: ./.github/actions/setup 46 | with: 47 | cache-key: "whitespace" 48 | - run: clojure -T:whitespace-linter lint 49 | 50 | Kondo: 51 | runs-on: ubuntu-latest 52 | timeout-minutes: 10 53 | steps: 54 | - uses: actions/checkout@v4.1.7 55 | - uses: ./.github/actions/setup 56 | with: 57 | cache-key: "kondo" 58 | - run: clojure -M:kondo 59 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'VERSION.txt' 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | environment: Deployment 14 | timeout-minutes: 10 15 | steps: 16 | - uses: actions/checkout@v4.1.0 17 | with: 18 | fetch-depth: 0 19 | - name: Prepare JDK 21 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: 21 23 | distribution: 'temurin' 24 | - name: Setup Clojure 25 | uses: DeLaGuardo/setup-clojure@12.1 26 | with: 27 | cli: 1.11.1.1413 28 | - name: Build saml20 29 | run: clojure -T:build build 30 | env: 31 | GITHUB_SHA: ${{ env.GITHUB_SHA }} 32 | - name: Deploy saml20 33 | run: clojure -T:build deploy 34 | env: 35 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 36 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} 37 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | 11 | Test-Java-17: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 10 14 | steps: 15 | - uses: actions/checkout@v4.1.7 16 | - uses: ./.github/actions/setup 17 | with: 18 | java-version: 17 19 | cache-key: "test" 20 | - run: clojure -X:dev:test 21 | 22 | Test-Java-21: 23 | runs-on: ubuntu-latest 24 | timeout-minutes: 10 25 | steps: 26 | - uses: actions/checkout@v4.1.7 27 | - uses: ./.github/actions/setup 28 | with: 29 | java-version: 21 30 | cache-key: "test" 31 | - run: clojure -X:dev:test 32 | 33 | Test-Browser-e2e: 34 | runs-on: ubuntu-latest 35 | timeout-minutes: 10 36 | steps: 37 | - uses: actions/checkout@v4.1.7 38 | - uses: ./.github/actions/setup 39 | with: 40 | java-version: 21 41 | cache-key: "e2e" 42 | - run: docker compose up -d --wait 43 | - run: clojure -X:dev:e2e 44 | 45 | Cloverage: 46 | runs-on: ubuntu-latest 47 | timeout-minutes: 10 48 | steps: 49 | - uses: actions/checkout@v4.1.7 50 | - uses: ./.github/actions/setup 51 | with: 52 | java-version: 21 53 | cache-key: "cloverage" 54 | - run: clojure -X:dev:cloverage 55 | - name: Upload code coverage to codecov.io 56 | uses: codecov/codecov-action@v4 57 | with: 58 | token: ${{ secrets.CODECOV_TOKEN }} 59 | file: target/coverage/codecov.json 60 | flags: cloverage 61 | name: codecov-umbrella 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.jar 3 | .\#* 4 | .cpcache 5 | /*.iml 6 | /.clj-kondo/.cache 7 | /.eastwood 8 | /.env 9 | /.envrc 10 | /.idea 11 | /.lein-deps-sum 12 | /.lein-env 13 | /.lein-failures 14 | /.lein-plugins 15 | /.lein-repl-history 16 | /.lsp 17 | /.nrepl-port 18 | /build.xml 19 | /checkouts 20 | /classes 21 | /classes 22 | /config.edn 23 | /lib 24 | /pom.xml 25 | /pom.xml.asc 26 | /profiles.clj 27 | /resources/public/js/main.js 28 | /tags 29 | /target 30 | \#*\# 31 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Security is very important to us at Metabase. 4 | 5 | ## Supported Versions 6 | 7 | We typically only support the latest release of Metabase for maintenance updates, but depending on the nature of the security issue we may issue hotfixes for arbitrarily earlier versions of Metabase. 8 | 9 | ## Reporting a Vulnerability 10 | 11 | If you discover any issue regarding security, please disclose the information responsibly by sending an email to security@metabase.com and not by creating a GitHub issue. We'll get back to you ASAP and work with you to confirm and plan a fix for the issue. 12 | 13 | Please note that we do not currently offer a bug bounty program. 14 | -------------------------------------------------------------------------------- /VERSION.txt: -------------------------------------------------------------------------------- 1 | 4.2.0 2 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.java.shell :as sh] 3 | [clojure.string :as str] 4 | [clojure.tools.build.api :as b] 5 | [deps-deploy.deps-deploy :as dd])) 6 | 7 | (def scm-url "git@github.com:metabase/saml20-clj.git") 8 | (def github-url "https://github.com/metabase/saml20-clj/") 9 | (def lib 'metabase/saml20-clj) 10 | 11 | (def version (str/trim (slurp "VERSION.txt"))) 12 | 13 | (def target "target") 14 | (def class-dir "target/classes") 15 | (def jar-file (format "target/%s-%s.jar" lib version)) 16 | 17 | 18 | (def sha 19 | (or (not-empty (System/getenv "GITHUB_SHA")) 20 | (not-empty (-> (sh/sh "git" "rev-parse" "HEAD") 21 | :out 22 | str/trim)))) 23 | 24 | (def pom-template 25 | [[:description "A library for delightful database interaction."] 26 | [:url github-url] 27 | [:licenses 28 | [:license 29 | [:name "Eclipse Public License"] 30 | [:url "http://www.eclipse.org/legal/epl-v10.html"]]] 31 | [:developers 32 | [:developer 33 | [:name "Cam Saul"]]] 34 | [:scm 35 | [:url github-url] 36 | [:connection (str "scm:git:" scm-url)] 37 | [:developerConnection (str "scm:git:" scm-url)] 38 | [:tag sha]]]) 39 | 40 | (def default-options 41 | {:lib lib 42 | :version version 43 | :jar-file jar-file 44 | :basis (b/create-basis {}) 45 | :class-dir class-dir 46 | :target target 47 | :src-dirs ["src"] 48 | :pom-data pom-template}) 49 | 50 | (defn build [opts] 51 | (let [opts (merge default-options opts)] 52 | (b/delete {:path target}) 53 | (println "\nWriting pom.xml...") 54 | (b/write-pom opts) 55 | (println "\nCopying source...") 56 | (b/copy-dir {:src-dirs ["src" "resources"] 57 | :target-dir class-dir}) 58 | (printf "\nBuilding %s...\n" jar-file) 59 | (b/jar opts) 60 | (println "Done."))) 61 | 62 | (defn install [opts] 63 | (printf "Installing %s to local Maven repository...\n" version) 64 | (b/install (merge default-options opts))) 65 | 66 | (defn build-and-install [opts] 67 | (build opts) 68 | (install opts)) 69 | 70 | (defn deploy [opts] 71 | (let [opts (merge default-options opts)] 72 | (printf "Deploying %s...\n" jar-file) 73 | (dd/deploy {:installer :remote 74 | :artifact (b/resolve-path jar-file) 75 | :pom-file (b/pom-path (select-keys opts [:lib :class-dir]))}) 76 | (println "Done."))) 77 | -------------------------------------------------------------------------------- /check-for-reflection-warnings.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | printf "\e[1;34mChecking for reflection warnings. This may take a few minutes, so sit tight...\e[0m\n" 4 | 5 | warnings=`clojure -M:check 2>&1 | grep Reflection | grep saml20 | sort | uniq` 6 | 7 | if [ ! -z "$warnings" ]; then 8 | printf "\e[1;31mYour code has introduced some reflection warnings.\e[0m 😞\n" 9 | echo "$warnings"; 10 | exit -1; 11 | fi 12 | 13 | printf "\e[1;32mNo reflection warnings! Success.\e[0m\n" 14 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | bot: "codecov-io" 3 | require_ci_to_pass: no 4 | 5 | coverage: 6 | status: 7 | project: 8 | default: 9 | # Project must always have at least this much coverage (by line) 10 | target: 65% 11 | # Whole-project test coverage is allowed to drop up to 1%. (For situtations where we delete code with full coverage) 12 | threshold: 1% 13 | patch: 14 | default: 15 | # Changes must have at least 75% test coverage (by line) 16 | target: 70% 17 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:mvn/repos 2 | {"opensaml" {:url "https://build.shibboleth.net/nexus/content/repositories/releases/"}} 3 | 4 | :deps 5 | {org.clojure/spec.alpha {:mvn/version "0.5.238"} 6 | org.clojure/tools.logging {:mvn/version "1.3.0"} 7 | com.onelogin/java-saml {:mvn/version "2.9.0"} 8 | clojure.java-time/clojure.java-time {:mvn/version "1.4.2"} 9 | commons-io/commons-io {:mvn/version "2.16.1"} 10 | org.apache.santuario/xmlsec {:mvn/version "4.0.2"} ; use latest version and override transient dep from OpenSAML 11 | org.cryptacular/cryptacular {:mvn/version "1.2.7"} ; use latest version and override transient dep from OpenSAML 12 | org.opensaml/opensaml-core-api {:mvn/version "5.1.3"} 13 | org.opensaml/opensaml-core-impl {:mvn/version "5.1.3"} 14 | org.opensaml/opensaml-messaging-impl {:mvn/version "5.1.3"} 15 | org.opensaml/opensaml-saml-impl {:mvn/version "5.1.3"} 16 | org.opensaml/opensaml-xmlsec-api {:mvn/version "5.1.3"} 17 | org.opensaml/opensaml-xmlsec-impl {:mvn/version "5.1.3"} 18 | potemkin/potemkin {:mvn/version "0.4.7"} 19 | pretty/pretty {:mvn/version "1.0.5"} 20 | ring/ring-codec {:mvn/version "1.2.0"} 21 | jakarta.servlet/jakarta.servlet-api {:mvn/version "6.1.0"}} 22 | 23 | :aliases 24 | { 25 | :dev 26 | {:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1", :git/sha "dfb30dd6"} 27 | pjstadig/humane-test-output {:mvn/version "0.11.0"} 28 | org.clojure/tools.logging {:mvn/version "1.3.0"} 29 | org.apache.logging.log4j/log4j-core {:mvn/version "2.24.3"} 30 | org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.24.3"} 31 | ring/ring {:mvn/version "1.13.0"} 32 | etaoin/etaoin {:mvn/version "1.1.42"} 33 | ring/ring-jetty-adapter {:mvn/version "1.13.0"}} 34 | :extra-paths ["test" "e2e"]} 35 | 36 | ;; clojure -X:dev:test 37 | :test 38 | {:exec-fn saml20-clj.runners.test/test} 39 | 40 | ;; clojure -X:dev:e2e 41 | :e2e 42 | {:exec-fn saml20-clj.runners.test/test 43 | :jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"] 44 | :exec-args {:dirs ["e2e"]}} 45 | 46 | 47 | ;; clojure -M:check 48 | :check 49 | {:extra-deps {athos/clj-check {:git/url "https://github.com/athos/clj-check.git" 50 | :sha "d997df866b2a04b7ce7b17533093ee0a2e2cb729"}} 51 | :main-opts ["-m" "clj-check.check"]} 52 | 53 | ;; clojure -X:dev:eastwood 54 | :eastwood 55 | {:extra-deps {jonase/eastwood {:mvn/version "1.4.3"}} 56 | :exec-fn eastwood.lint/eastwood-from-cmdline 57 | :exec-args {:source-paths ["src"] 58 | :add-linters [:unused-fn-args 59 | :unused-locals] 60 | :exclude-linters [:deprecations 61 | :unused-ret-vals 62 | :implicit-dependencies]}} 63 | 64 | ;; clojure -X:dev:cloverage 65 | :cloverage 66 | {:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}} 67 | :exec-fn cloverage.coverage/run-project 68 | :exec-args {:fail-threshold 66 69 | :codecov? true 70 | ;; don't instrument logging forms, since they won't get executed as part of tests anyway 71 | ;; log calls expand to these 72 | :exclude-call [clojure.tools.logging/logf clojure.tools.logging/logp] 73 | :src-ns-path ["src"] 74 | :test-ns-path ["test"]}} 75 | 76 | ;; clojure -M:kondo 77 | :kondo 78 | {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2024.08.01"}} 79 | :main-opts ["-m" "clj-kondo.main" 80 | "--lint" "src"]} 81 | 82 | ;; clojure -T:whitespace-linter lint 83 | :whitespace-linter 84 | {:deps {com.github.camsaul/whitespace-linter {:sha "e35bc252ccf5cc74f7d543ef95ad8a3e5131f25b"}} 85 | :ns-default whitespace-linter 86 | :exec-args {:paths ["./.dir-locals.el" 87 | "./deps.edn" 88 | "src" 89 | "test"] 90 | :include-patterns ["\\.clj.?$" 91 | "\\.edn$" 92 | "\\.el$" 93 | "\\.xml$"]}} 94 | 95 | :include-license 96 | {:extra-paths ["license"]} 97 | 98 | ;; clojure -T:build build 99 | ;; clojure -T:build deploy 100 | :build 101 | {:deps {io.github.clojure/tools.build {:mvn/version "0.10.5"} 102 | slipset/deps-deploy {:mvn/version "0.2.2"}} 103 | :ns-default build}}} 104 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | keycloak: 3 | image: quay.io/keycloak/keycloak:latest 4 | command: ["start-dev", "--import-realm"] 5 | platform: linux/amd64 6 | environment: 7 | KC_LOG_LEVEL: INFO 8 | KC_REALM_NAME: test 9 | KC_BOOTSTRAP_ADMIN_USERNAME: admin 10 | KC_BOOTSTRAP_ADMIN_PASSWORD: thismustbethepassword 11 | ports: 12 | - 8080:8080 13 | volumes: 14 | - ./keycloak/:/opt/keycloak/data/import/:ro 15 | test-server: 16 | build: 17 | context: . 18 | dockerfile_inline: | 19 | FROM clojure:tools-deps 20 | COPY ./ /app 21 | platform: linux/amd64 22 | command: ["clj", "-M:dev:e2e", "-m", "saml20-clj.e2e.server"] 23 | working_dir: /app 24 | ports: 25 | - 3002:3002 26 | - 3001:3001 27 | volumes: 28 | - ./src:/app/src 29 | - ./test:/app/test 30 | - ./e2e:/app/e2e 31 | - ../java-opensaml/:/java-opensaml 32 | 33 | selenium: 34 | image: selenium/standalone-chrome:latest 35 | platform: linux/amd64 36 | ports: 37 | - 4444:4444 38 | - 7900:7900 39 | shm_size: '2gb' 40 | healthcheck-keycloak: 41 | restart: always 42 | image: curlimages/curl:latest 43 | entrypoint: ["/bin/sh", "-c", "--", "while true; do sleep 30; done;"] 44 | depends_on: 45 | - keycloak 46 | healthcheck: 47 | test: ["CMD", "curl", "-f", "http://keycloak:8080/"] 48 | interval: 3s 49 | timeout: 5s 50 | retries: 30 51 | healthcheck-test-server: 52 | restart: always 53 | image: curlimages/curl:latest 54 | entrypoint: ["/bin/sh", "-c", "--", "while true; do sleep 30; done;"] 55 | depends_on: 56 | - test-server 57 | healthcheck: 58 | test: ["CMD", "curl", "-f", "-k", "https://test-server:3001/"] 59 | interval: 3s 60 | timeout: 5s 61 | retries: 30 62 | healthcheck-selenium: 63 | restart: always 64 | image: curlimages/curl:latest 65 | entrypoint: ["/bin/sh", "-c", "--", "while true; do sleep 30; done;"] 66 | depends_on: 67 | - selenium 68 | healthcheck: 69 | test: ["CMD", "curl", "-f", "http://selenium:4444/"] 70 | interval: 3s 71 | timeout: 5s 72 | retries: 30 73 | -------------------------------------------------------------------------------- /e2e/saml20_clj/e2e/entra.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8DCCAdigAwIBAgIQcvxenSvSG6BD3L5RwyvV6jANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQD 3 | EylNaWNyb3NvZnQgQXp1cmUgRmVkZXJhdGVkIFNTTyBDZXJ0aWZpY2F0ZTAeFw0yNTAzMTIxNDUy 4 | NTVaFw0yODAzMTIxNDUyNTVaMDQxMjAwBgNVBAMTKU1pY3Jvc29mdCBBenVyZSBGZWRlcmF0ZWQg 5 | U1NPIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2crMOvNTAjYC 6 | 2LlbBRTBqJWbO+CW3IeiX5HHfgiQYiDcJSRV51junC/qrJ9gdUeJZ6vDivgbOA6z+a1yEK0gXQrV 7 | lZMBqb2OMFSzZOj+aI1jRRmuAzgkOUSL0C409oQpZCm62/Vg38cTAiyKiR6NLyDrAhtYllc+gOOM 8 | OnS1BhE/BzN6yMnR90csRMxfcX3MEUfgSz/RXalr06xrWS+uWpFE7I1bMrY/z3o23VLfDncesiIs 9 | jonEfAoGXl8A/WlVkNEe1J37tWZwVOocy0FfOgNtGgGlAA0TQe2vHkGfTmFPqRra1F/Dg/hR4lX5 10 | hGTao5NnhfkGNCQ1Ox/Pd8A6aQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAiUErRmsmKPvVHbns5 11 | jsjOtufsH69uZ8SF7YP8SIlteJdgtbVyMVi/mnQQfUXE/JY/+dbCeauzfNvaOUPDGzS64ghViAO9 12 | 5Rt01u9AfQkhzLpyOvQpMHfZkqI5M2yAg+AMMmawl08pWitZ4A00lwVOyThb29b+ohF6fA4ptueX 13 | ZMVvvlM6AktWBpPVyXTrmJ9A5TRHVr4aDNP4vQtO90IgpLBv/ql8I1R4bTAI2kO/QDY+XfZh7gQX 14 | uTakQzXFD9GtxBPwmLexXOzqMHKTvUujh/nclWuhHF+haaZv4isYEqgmcxeu8gTN3PXypCxYcOTV 15 | GBP7gtDKf+uhwHY+bhwQ 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /e2e/saml20_clj/e2e/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabase/saml20-clj/72b45702afe821e0c9f7781b0575b06414f594e2/e2e/saml20_clj/e2e/keystore.jks -------------------------------------------------------------------------------- /e2e/saml20_clj/e2e/okta.cert: -------------------------------------------------------------------------------- 1 | MIIDqDCCApCgAwIBAgIGAZVIaTWrMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi0wODU0ODIyNTEcMBoGCSqGSIb3DQEJ ARYNaW5mb0Bva3RhLmNvbTAeFw0yNTAyMjcxNzE1NDlaFw0zNTAyMjcxNzE2NDlaMIGUMQswCQYD VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsG A1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi0wODU0ODIyNTEc MBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBALOialzywPKChQZGU0LE4Og4e0kd4bCy3+QorhwrmRMGRhI4Vc91kDfBnEblH+R8Yqn28fMy sV9H7bwKls/CBljY/VwDUWLNupNPoRrmfOMwhe/X3wS3sLrq0cHw7Gpi8tKRgE9k6uXfNnSElj4+ by2wkgmLG+mb3S280SYZgfOKR+qtjDkdO+lxCpEHG8pHC7ayZhDsA8TgOeECI5Qia5v+Z+m+fMH3 RHUg7Zu51UKn2KN46T+dP9PdC34AoQ5oksUZ6Bu5+1eyzzFDgqHSJgOczb9JokGl6NnxoNkp9m8H fX15g9+UgVA+B5HsuPOS5WPOLZxYbiWJWV9MhgbZjxsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA NYYJ4NAnQUJmCMzZ6YGI2WWocHZcd5ivAo4TaHIs0UCCVXOtOHMJzCM4rHg/XUDzOWQZGApsJyJO 3h6m9LGJByJNXeuEZ1feP6pNePfE9ej3rf34GsiGVSk9T52atBQ0Uy4u491NE8j5FzB0fpK7zYFq 3BcrK7usaEozM1h+qVy3zMWnavuaxNFZNS/IF5Gbe61NtY1qvqG4JYb4XxymC0ljnr4R0KUQHVVu M1Fa+At4WLYieBE6urUAqpojCgIRWLCdtGFEImKRf0psmSxYCKBTL3kef+YI3dLQlkEo+PNNAx2t z61zG1baY1dckm3S7Bvbp1H4ljXZy1tZ0WgEKw== 2 | -------------------------------------------------------------------------------- /e2e/saml20_clj/e2e/server.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.e2e.server 2 | (:require 3 | [clojure.tools.logging :as logging] 4 | [ring.adapter.jetty :refer [run-jetty]] 5 | [ring.middleware.cookies :as ring.cookies] 6 | [ring.middleware.params :as ring.params] 7 | [ring.middleware.reload :as ring.reload] 8 | [ring.middleware.stacktrace :as ring.stacktrace] 9 | [ring.util.response :as ring.resp] 10 | [saml20-clj.sp.logout-response :as logout-response] 11 | [saml20-clj.sp.request :as request] 12 | [saml20-clj.sp.response :as response] 13 | [saml20-clj.test :as test])) 14 | 15 | (def home-page-logged-out 16 | (str "" 17 | "" 22 | "")) 23 | (def home-page-logged-in 24 | (str "" 25 | "" 30 | "")) 31 | (def cookie-name "COOKIE") 32 | 33 | (defn idp-login-config 34 | [idp-type] 35 | (condp = idp-type 36 | :entra {:sp-name "SAMLTest" 37 | :acs-url "https://test-server:3001/login" 38 | :issuer "SAMLTest" 39 | :idp-url "https://login.microsoftonline.com/baac9aeb-12ed-4fe9-844f-fde9aa3fc2c7/saml2" 40 | :request-id "a-test-request" 41 | :protocol-binding :post 42 | :credential test/sp-private-key} 43 | :okta {:sp-name "SAMLTest" 44 | :acs-url "https://test-server:3001/login" 45 | :issuer "SAMLTest" 46 | :idp-url "https://dev-08548225.okta.com/app/dev-08548225_mbcitest_1/exknlfxer1RcyaTAS5d7/sso/saml" 47 | :request-id "a-test-request" 48 | :credential test/sp-private-key} 49 | :keycloak {:sp-name "SAMLTest" 50 | :acs-url "https://test-server:3001/login" 51 | :issuer "SAMLTest" 52 | :idp-url "http://keycloak:8080/realms/test/protocol/saml" 53 | :request-id "a-test-request" 54 | :credential test/sp-private-key})) 55 | 56 | (defn idp-logout-config 57 | [idp-type] 58 | (condp = idp-type 59 | :entra {:sp-name "SAMLTest" 60 | :acs-url "https://test-server:3001/logout" 61 | :issuer "SAMLTest" 62 | :idp-url "https://login.microsoftonline.com/baac9aeb-12ed-4fe9-844f-fde9aa3fc2c7/saml2" 63 | :relay-state "entra" 64 | :user-email "metatest@example.com" 65 | :request-id "a-test-request" 66 | :credential test/sp-private-key} 67 | :okta {:sp-name "SAMLTest" 68 | :acs-url "https://test-server:3001/logout" 69 | :issuer "SAMLTest" 70 | :idp-url "https://dev-08548225.okta.com/app/dev-08548225_mbcitest_1/exknlfxer1RcyaTAS5d7/slo/saml" 71 | :relay-state "okta" 72 | :user-email "metatest@example.com" 73 | :request-id "a-test-request" 74 | :credential test/sp-private-key} 75 | :keycloak {:sp-name "SAMLTest" 76 | :acs-url "https://test-server:3001/logout" 77 | :issuer "SAMLTest" 78 | :idp-url "http://keycloak:8080/realms/test/protocol/saml" 79 | :relay-state "keycloak" 80 | :user-email "metatest@example.com" 81 | :request-id "a-test-request" 82 | :credential test/sp-private-key})) 83 | 84 | (defn validation-config 85 | [idp-type] 86 | (condp = idp-type 87 | :entra {:idp-cert (slurp "e2e/saml20_clj/e2e/entra.cert") 88 | :acs-url "https://test-server:3001/login" 89 | :issuer "https://sts.windows.net/baac9aeb-12ed-4fe9-844f-fde9aa3fc2c7/" 90 | :request-id "a-test-request"} 91 | :okta {:idp-cert (slurp "e2e/saml20_clj/e2e/okta.cert") 92 | :acs-url "https://test-server:3001/login" 93 | :issuer "http://www.okta.com/exknlfxer1RcyaTAS5d7" 94 | :request-id "a-test-request"} 95 | :keycloak {:idp-cert test/idp-cert 96 | :acs-url "https://test-server:3001/login" 97 | :issuer "http://keycloak:8080/realms/test" 98 | :request-id "a-test-request"})) 99 | 100 | (defn serve-home 101 | [cookie] 102 | (let [body (if (get cookie cookie-name) 103 | home-page-logged-in 104 | home-page-logged-out)] 105 | (logging/debug "Serving Home") 106 | (-> {:status 200 107 | :body body} 108 | (ring.resp/content-type "text/html") 109 | (ring.resp/charset "utf-8")))) 110 | 111 | (defmulti handle-login (fn [method _] method)) 112 | (defmulti handle-logout (fn [method _] method)) 113 | 114 | (defmethod handle-logout :get [_ request] 115 | (if-let [idp-type (get-in request [:params "idp-type"])] 116 | (request/idp-logout-redirect-response (idp-logout-config (keyword idp-type))) 117 | (handle-logout :post request))) 118 | 119 | (defmethod handle-logout :post [_ request] 120 | (when (logout-response/validate-logout request 121 | (-> (get-in request [:params "RelayState"]) 122 | keyword 123 | validation-config)) 124 | (-> (ring.resp/redirect "/") 125 | (ring.resp/set-cookie cookie-name nil {:expires "Thu, 1 Jan 1970 00:00:00 GMT"})))) 126 | 127 | (defmethod handle-login :get [_ request] 128 | (if-let [idp-type (get-in request [:params "idp-type"])] 129 | (request/idp-redirect-response (assoc (idp-login-config (keyword idp-type)) :relay-state idp-type)) 130 | (handle-login :post request))) 131 | 132 | (defmethod handle-login :post [_ request] 133 | (when (response/validate-response request 134 | (-> (get-in request [:params "RelayState"]) 135 | keyword 136 | validation-config)) 137 | (-> (ring.resp/redirect "/") 138 | (ring.resp/set-cookie cookie-name "true")))) 139 | 140 | (defn handler 141 | [{:keys [uri cookies request-method] :as request}] 142 | (condp = uri 143 | "/" (serve-home cookies) 144 | "/login" (handle-login request-method request) 145 | "/logout" (handle-logout request-method request) 146 | {:status 404})) 147 | 148 | (defn start-server 149 | [] 150 | (logging/debug "Starting server") 151 | (run-jetty (-> handler 152 | ring.cookies/wrap-cookies 153 | ring.params/wrap-params 154 | ring.stacktrace/wrap-stacktrace 155 | (ring.reload/wrap-reload {:dirs ["src" "test" "e2e"]})) 156 | {:ssl? true 157 | :ssl-port 3001 158 | :port 3002 159 | :keystore "e2e/saml20_clj/e2e/keystore.jks" 160 | :key-password "testpassword"})) 161 | 162 | (defn -main 163 | [] 164 | (start-server)) 165 | -------------------------------------------------------------------------------- /e2e/saml20_clj/e2e/server_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.e2e.server-test 2 | (:require [clojure.test :as t] 3 | [etaoin.api :as etaoin])) 4 | 5 | (def ^:private test-overrides 6 | {:entra {:username "metatest@luizarakakimetabase.onmicrosoft.com" 7 | :password "ThisMustBeThePassword2!" }}) 8 | 9 | (t/deftest test-saml-login-logout 10 | (doseq [provider [:okta 11 | :keycloak]] 12 | (etaoin/with-chrome 13 | {:port 4444 14 | :host "localhost" 15 | :args ["--no-sandbox" 16 | "--ignore-ssl-errors=yes" 17 | "--ignore-certificate-errors"] 18 | :capabilities {"acceptInsecureCerts" true}} 19 | driver 20 | (t/testing "full saml login/logout flow" 21 | (etaoin/go driver "https://test-server:3001") 22 | (t/is (etaoin/visible? driver {:tag :a :id provider :fn/has-text "Login"})) 23 | (etaoin/click driver {:tag :a :id provider}) 24 | (etaoin/wait-visible driver {:tag :input :name :username}) 25 | (etaoin/fill driver {:tag :input :name :username} 26 | (get-in test-overrides [provider :username] "metatest@example.com")) 27 | (etaoin/fill driver {:tag :input :name :password} 28 | (get-in test-overrides [provider :password] "thismustbetheotherpassword")) 29 | (etaoin/click driver {:type :submit}) 30 | (etaoin/wait-visible driver {:tag :a :id provider}) 31 | (t/is (etaoin/visible? driver {:tag :a :id provider :fn/has-text "Logout"})) 32 | (etaoin/click driver {:tag :a :id provider}) 33 | (etaoin/wait-visible driver {:tag :a :fn/has-text "Login"}) 34 | (t/is (etaoin/visible? driver {:tag :a :id provider :fn/has-text "Login"})))))) 35 | 36 | 37 | (t/deftest test-saml-login-logout-entra 38 | (let [provider :entra] 39 | (etaoin/with-chrome 40 | {:port 4444 41 | :host "localhost" 42 | :args ["--no-sandbox" 43 | "--ignore-ssl-errors=yes" 44 | "--ignore-certificate-errors"] 45 | :capabilities {"acceptInsecureCerts" true}} 46 | driver 47 | (t/testing "full saml login/logout flow" 48 | (etaoin/go driver "https://test-server:3001") 49 | (t/is (etaoin/visible? driver {:tag :a :id provider :fn/has-text "Login"})) 50 | (etaoin/click driver {:tag :a :id provider}) 51 | (etaoin/wait-visible driver {:tag :input :name :loginfmt}) 52 | (etaoin/fill driver {:tag :input :name :loginfmt} 53 | (get-in test-overrides [provider :username] "metatest@example.com")) 54 | (etaoin/click driver {:type :submit}) 55 | (etaoin/wait-visible driver {:tag :input :name :passwd}) 56 | (etaoin/fill driver {:tag :input :name :passwd} 57 | (get-in test-overrides [provider :password] "thismustbetheotherpassword")) 58 | (etaoin/click driver {:type :submit}) 59 | (etaoin/wait-visible driver {:type :submit}) 60 | (etaoin/click driver {:type :submit}) 61 | (etaoin/wait-visible driver {:tag :a :id provider}) 62 | (t/is (etaoin/visible? driver {:tag :a :id provider :fn/has-text "Logout"})) 63 | (etaoin/click driver {:tag :a :id provider}) 64 | (etaoin/wait-visible driver {:tag :a :fn/has-text "Login"}) 65 | (t/is (etaoin/visible? driver {:tag :a :id provider :fn/has-text "Login"})))))) 66 | -------------------------------------------------------------------------------- /src/saml20_clj/core.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.core 2 | "Main interface for saml20-clj SP functionality. The core functionality is broken out into several separate 3 | namespaces, but vars are made available here via Potemkin." 4 | (:require [potemkin :as p] 5 | [saml20-clj.coerce :as coerce] 6 | [saml20-clj.crypto :as crypto] 7 | [saml20-clj.sp.logout-response :as logout-response] 8 | [saml20-clj.sp.metadata :as metadata] 9 | [saml20-clj.sp.request :as request] 10 | [saml20-clj.sp.response :as response] 11 | [saml20-clj.state :as state])) 12 | 13 | ;; this is so the linter doesn't complain about unused namespaces. 14 | (comment 15 | coerce/keep-me 16 | crypto/keep-me 17 | metadata/keep-me 18 | request/keep-me 19 | response/keep-me 20 | state/keep-me 21 | logout-response/keep-me) 22 | 23 | (p/import-vars 24 | [coerce 25 | ->X509Certificate 26 | ->Response 27 | ->xml-string] 28 | 29 | [crypto 30 | has-private-key?] 31 | 32 | [metadata 33 | metadata] 34 | 35 | [request 36 | idp-redirect-response 37 | logout-redirect-location 38 | idp-logout-redirect-response] 39 | 40 | [response 41 | decrypt-response 42 | assertions 43 | validate-response] 44 | 45 | [logout-response 46 | logout-success? 47 | validate-logout] 48 | 49 | [state 50 | record-request! 51 | accept-response! 52 | in-memory-state-manager]) 53 | -------------------------------------------------------------------------------- /src/saml20_clj/crypto.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.crypto 2 | (:require [saml20-clj.coerce :as coerce]) 3 | (:import [org.opensaml.saml.common.messaging.context SAMLPeerEntityContext SAMLProtocolContext] 4 | [org.opensaml.security.credential BasicCredential Credential] 5 | org.apache.xml.security.Init 6 | org.opensaml.messaging.context.MessageContext 7 | org.opensaml.saml.common.binding.security.impl.SAMLProtocolMessageXMLSignatureSecurityHandler 8 | org.opensaml.saml.common.xml.SAMLConstants 9 | org.opensaml.saml.saml2.binding.security.impl.SAML2HTTPRedirectDeflateSignatureSecurityHandler 10 | org.opensaml.saml.saml2.metadata.SPSSODescriptor 11 | org.opensaml.security.credential.impl.CollectionCredentialResolver 12 | org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap 13 | org.opensaml.xmlsec.context.SecurityParametersContext 14 | org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine 15 | org.opensaml.xmlsec.SignatureValidationParameters)) 16 | 17 | (set! *warn-on-reflection* true) 18 | 19 | (defn has-private-key? 20 | "Will check if the provided keystore contains a private key or not." 21 | [credential] 22 | (when-let [^Credential credential (try 23 | (coerce/->Credential credential) 24 | (catch Throwable _ 25 | (coerce/->Credential (coerce/->PrivateKey credential))))] 26 | (some? (.getPrivateKey credential)))) 27 | 28 | (defn- decrypt! [sp-private-key element] 29 | (when-let [sp-private-key (coerce/->PrivateKey sp-private-key)] 30 | (when-let [element (coerce/->Element element)] 31 | (com.onelogin.saml2.util.Util/decryptElement element sp-private-key)))) 32 | 33 | (defn recursive-decrypt! 34 | "Mutates a SAML object to decrypt any encrypted Assertions present." 35 | [sp-private-key element] 36 | (when-let [sp-private-key (coerce/->PrivateKey sp-private-key)] 37 | (when-let [element (coerce/->Element element)] 38 | (when (and (= (.getLocalName element) "EncryptedAssertion") 39 | (= (.getNamespaceURI element) "urn:oasis:names:tc:SAML:2.0:assertion")) 40 | (decrypt! sp-private-key element)) 41 | (doseq [i (range (.. element getChildNodes getLength)) 42 | ;; Explict typehinting here required by Cloverage 43 | :let [^org.w3c.dom.NodeList nodes (.getChildNodes element) 44 | child (.item nodes i)] 45 | :when (instance? org.w3c.dom.Element child)] 46 | (recursive-decrypt! sp-private-key child))))) 47 | 48 | (defonce ^:private -init 49 | (delay 50 | (Init/init) 51 | nil)) 52 | 53 | @-init 54 | 55 | (defn authenticated? 56 | "True if the MessageContext's PeerEntity subcontext has isAuthenticated set" 57 | [^MessageContext msg-ctx] 58 | (let [^SAMLPeerEntityContext peer-entity-ctx (.. msg-ctx 59 | (getSubcontext SAMLPeerEntityContext))] 60 | (.isAuthenticated peer-entity-ctx))) 61 | 62 | (defn- signature [object] 63 | (when-let [object (coerce/->SAMLObject object)] 64 | (.getSignature object))) 65 | 66 | (defn signed? 67 | "Returns true when an xml object has a top-level Signature Element" 68 | [object] 69 | (when-let [object (coerce/->SAMLObject object)] 70 | (.isSigned object))) 71 | 72 | (defn assert-signature-valid-when-present 73 | "Attempts to validate any signatures in a SAML object. Raises if signature validation fails." 74 | [object credential] 75 | (when-let [signature (signature object)] 76 | (when-let [credential (coerce/->Credential credential)] 77 | ;; validate that the signature conforms to the SAML signature spec 78 | (try 79 | (.validate (org.opensaml.saml.security.impl.SAMLSignatureProfileValidator.) signature) 80 | (catch Throwable e 81 | (throw (ex-info "Signature does not conform to SAML signature spec" 82 | {:object (coerce/->xml-string object)} 83 | e)))) 84 | ;; validate that the signature matches the credential 85 | (try 86 | (org.opensaml.xmlsec.signature.support.SignatureValidator/validate signature credential) 87 | (catch Throwable e 88 | (throw (ex-info "Signature does not match credential" 89 | {:object (coerce/->xml-string object)} 90 | e)))) 91 | :valid))) 92 | 93 | (defn- prepare-for-signature-validation 94 | ^MessageContext [^MessageContext msg-ctx issuer credential] 95 | (let [credential (doto ^BasicCredential (coerce/->Credential credential) 96 | (.setEntityId issuer)) 97 | sig-trust-engine (ExplicitKeySignatureTrustEngine. 98 | (CollectionCredentialResolver. [credential]) 99 | (DefaultSecurityConfigurationBootstrap/buildBasicInlineKeyInfoCredentialResolver)) 100 | sig-val-parameters (doto (SignatureValidationParameters.) 101 | (.setSignatureTrustEngine sig-trust-engine)) 102 | ^SAMLPeerEntityContext peer-entity-ctx (.ensureSubcontext msg-ctx SAMLPeerEntityContext) 103 | ^SAMLProtocolContext protocol-ctx (.ensureSubcontext msg-ctx SAMLProtocolContext) 104 | ^SecurityParametersContext sec-params-ctx (.ensureSubcontext msg-ctx SecurityParametersContext)] 105 | (doto peer-entity-ctx 106 | (.setEntityId issuer) 107 | (.setRole SPSSODescriptor/DEFAULT_ELEMENT_NAME)) 108 | (.setProtocol protocol-ctx SAMLConstants/SAML20P_NS) 109 | (.setSignatureValidationParameters sec-params-ctx sig-val-parameters) 110 | msg-ctx)) 111 | 112 | (defn handle-signature-security 113 | "Uses OpenSAMLs security handlers to verify the signature of an incoming request for both 114 | GET and POST-based SAML flows. 115 | 116 | Returns the verified MessageContext for the request. 117 | 118 | The SAMLPeerEntityContext subcontext of the MessageContext will have a method isAuthenticated 119 | that returns true if the signature verification succeeded. 120 | 121 | It will raise if the verification fails and a signature was provided. 122 | 123 | It will return the message context if no sigature was provided but isAuthenticated will be 124 | false." 125 | ^MessageContext [^MessageContext msg-ctx issuer credential & [request]] 126 | 127 | ;; if we have a GET request we are dealing with a redirect where the signature is the query parameters 128 | ;; this uses a different security handler than POST requests where the signature is embedded in the 129 | ;; XML Document 130 | (if (and request (= (:request-method request) :get)) 131 | (let [http-req-supplier (coerce/ring-request->HttpServletRequestSupplier request)] 132 | (doto (SAML2HTTPRedirectDeflateSignatureSecurityHandler.) 133 | (.setHttpServletRequestSupplier http-req-supplier) 134 | (.initialize) 135 | (.invoke (prepare-for-signature-validation msg-ctx issuer credential)))) 136 | (doto (SAMLProtocolMessageXMLSignatureSecurityHandler.) 137 | (.initialize) 138 | (.invoke (prepare-for-signature-validation msg-ctx issuer credential)))) 139 | 140 | msg-ctx) 141 | -------------------------------------------------------------------------------- /src/saml20_clj/encode_decode.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.encode-decode 2 | "Utility functions for encoding/decoding and compressing byte arrays and strings." 3 | (:require [clojure.string :as str]) 4 | (:import [org.apache.commons.codec.binary Base64])) 5 | 6 | (set! *warn-on-reflection* true) 7 | 8 | (defn str->bytes 9 | "Return a byte array from a String." 10 | ^bytes [^String some-string] 11 | (when some-string 12 | (.getBytes some-string "UTF-8"))) 13 | 14 | (defn- strip-ascii-armor 15 | ^String [^String s] 16 | (when s 17 | (-> s 18 | (str/replace #"-----BEGIN [A-Z\s]+-----" "") 19 | (str/replace #"-----END [A-Z\s]+-----" "") 20 | (str/replace #"[\n ]" "")))) 21 | 22 | (defn decode-base64 23 | "Return a decoded byte array from a base64 encoded byte array." 24 | ^bytes [^bytes bs] 25 | (when bs 26 | (Base64/decodeBase64 bs))) 27 | 28 | (defn base64-credential->bytes 29 | "Return a byte array from a base64 encoded security credential string." 30 | ^bytes [^String s] 31 | (when s 32 | (decode-base64 (str->bytes (strip-ascii-armor s))))) 33 | -------------------------------------------------------------------------------- /src/saml20_clj/sp/logout_response.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.sp.logout-response 2 | "Handles parsing, validating and querying a LogoutResponse SAML message" 3 | (:require [saml20-clj.coerce :as coerce] 4 | [saml20-clj.sp.message :as message]) 5 | (:import [org.opensaml.saml.saml2.core LogoutResponse StatusCode] 6 | org.opensaml.saml.saml2.core.impl.LogoutRequestBuilder)) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | (defn logout-success? 11 | "Return true if a LogoutResponse object has a SUCCESS SAML status element." 12 | [^LogoutResponse response] 13 | (let [status-value (.. response getStatus getStatusCode getValue)] 14 | (= status-value StatusCode/SUCCESS))) 15 | 16 | (def ^:private default-logout-validation-options 17 | {:response-validators [:signature 18 | :issuer 19 | :in-response-to 20 | :require-authenticated]}) 21 | 22 | (defn validate-logout 23 | "Decode a ring request into a LogoutResponse SAML object and validate it. 24 | 25 | Throws if validation fails" 26 | (^LogoutResponse [req request-id issuer idp-cert] 27 | (validate-logout req {:issuer issuer 28 | :idp-cert idp-cert 29 | :request-id request-id})) 30 | (^LogoutResponse [req options] 31 | (let [options (-> (merge default-logout-validation-options options) 32 | (assoc :request req :request-builder (LogoutRequestBuilder.))) 33 | {:keys [response-validators]} options] 34 | (when-let [msg-ctx (coerce/ring-request->MessageContext req)] 35 | (doseq [validator response-validators] 36 | (message/validate-message validator msg-ctx options)) 37 | (coerce/->LogoutResponse msg-ctx))))) 38 | -------------------------------------------------------------------------------- /src/saml20_clj/sp/message.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.sp.message 2 | "Common validators for all SAML messages" 3 | (:require [saml20-clj.crypto :as crypto]) 4 | (:import [org.opensaml.messaging.context InOutOperationContext MessageContext] 5 | [org.opensaml.saml.saml2.core RequestAbstractType StatusResponseType Response] 6 | org.opensaml.messaging.handler.impl.CheckExpectedIssuer 7 | org.opensaml.saml.common.AbstractSAMLObjectBuilder 8 | org.opensaml.saml.common.binding.security.impl.InResponseToSecurityHandler)) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn- ->JavaFunction 13 | [func] 14 | (reify java.util.function.Function 15 | (apply [_ arg] 16 | (func arg)))) 17 | 18 | (defmulti validate-message 19 | "Peform a validation operation on a MessageCtx." 20 | (fn [validation _ _] 21 | (keyword validation))) 22 | 23 | (defmethod validate-message :signature 24 | [_ ^MessageContext msg-ctx {:keys [request issuer idp-cert] :or {issuer "unknown"}}] 25 | (assert (seq request) "Must provide original request") 26 | (assert (not (nil? idp-cert)) "Must provide a credential for the idp") 27 | (try 28 | (crypto/handle-signature-security msg-ctx issuer idp-cert request) 29 | (catch org.opensaml.messaging.handler.MessageHandlerException e 30 | (throw (ex-info "Message failed to validate signature" {:validator :signature} e))))) 31 | 32 | (defmethod validate-message :issuer 33 | [_ ^MessageContext msg-ctx {:keys [issuer]}] 34 | (assert (string? issuer) "Must provide issuer identifier for idp") 35 | (let [^StatusResponseType msg (.getMessage msg-ctx) 36 | incoming-issuer (when-let [issuer-element (.getIssuer msg)] 37 | (.getValue issuer-element))] 38 | (try 39 | (doto (CheckExpectedIssuer.) 40 | (.setExpectedIssuerLookupStrategy (->JavaFunction (constantly issuer))) 41 | (.setIssuerLookupStrategy (->JavaFunction (constantly incoming-issuer))) 42 | (.initialize) 43 | (.invoke msg-ctx)) 44 | (catch org.opensaml.messaging.handler.MessageHandlerException e 45 | (throw (ex-info "Message failed to validate issuer" 46 | {:validator :issuer 47 | :expected issuer 48 | :actual incoming-issuer} 49 | e)))))) 50 | 51 | ;; TODO: Replace this with usage of opensaml's client storage system 52 | (defmethod validate-message :in-response-to 53 | [_ ^MessageContext msg-ctx {:keys [request-id ^AbstractSAMLObjectBuilder request-builder]}] 54 | (assert (string? request-id) "Must provide the original request id") 55 | (assert (not (nil? request-builder)) "Must provide a request buidler") 56 | (let [^RequestAbstractType outgoing (.buildObject request-builder)] 57 | (.setID outgoing request-id) 58 | (InOutOperationContext. msg-ctx 59 | (doto (MessageContext.) 60 | (.setMessage outgoing)))) 61 | (try 62 | (doto (InResponseToSecurityHandler.) 63 | (.initialize) 64 | (.invoke msg-ctx)) 65 | (catch org.opensaml.messaging.handler.MessageHandlerException e 66 | (throw (ex-info "Message failed to validate InResponseTo" 67 | {:validator :in-response-to 68 | :original-request-id request-id 69 | :incoming-request-id (.getInResponseTo ^StatusResponseType (.getMessage msg-ctx))} 70 | e))))) 71 | 72 | (defn- maybe-get-assertions 73 | [response] 74 | (if (instance? Response response) 75 | (.getAssertions ^Response response) 76 | [])) 77 | 78 | (defmethod validate-message :require-authenticated 79 | ;; Requires the response be signed either in the query params (HTTP-Redirect) in the 80 | ;; XML body (HTTP-Post), must run after signature validation 81 | [_ ^MessageContext msg-ctx {:keys [decrypted-response]}] 82 | (when-not (crypto/authenticated? msg-ctx) 83 | (let [assertions (maybe-get-assertions decrypted-response)] 84 | (when (or (empty? assertions) 85 | (not (every? crypto/signed? assertions))) 86 | (throw (ex-info "Message is not Authenticated" 87 | {:is-authenticated (crypto/authenticated? msg-ctx) 88 | :validator :require-authenticated})))))) 89 | -------------------------------------------------------------------------------- /src/saml20_clj/sp/metadata.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.sp.metadata 2 | (:require [clojure.string :as str] 3 | [saml20-clj.coerce :as coerce]) 4 | (:import [org.opensaml.saml.saml2.metadata.impl AssertionConsumerServiceBuilder EntityDescriptorBuilder KeyDescriptorBuilder NameIDFormatBuilder SingleLogoutServiceBuilder SPSSODescriptorBuilder] 5 | org.opensaml.core.xml.util.XMLObjectSupport 6 | org.opensaml.saml.common.xml.SAMLConstants 7 | org.opensaml.saml.saml2.core.NameIDType 8 | org.opensaml.security.credential.UsageType 9 | org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory)) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (def ^:private name-id-formats 14 | [NameIDType/EMAIL NameIDType/TRANSIENT NameIDType/PERSISTENT NameIDType/UNSPECIFIED NameIDType/X509_SUBJECT]) 15 | 16 | (def ^:private cert-uses 17 | [UsageType/SIGNING UsageType/ENCRYPTION]) 18 | 19 | (defn metadata 20 | "Return string-encoded XML of this SAML SP's metadata." 21 | [{:keys [app-name acs-url slo-url sp-cert 22 | ^Boolean requests-signed 23 | ^Boolean want-assertions-signed] 24 | :or {want-assertions-signed true 25 | requests-signed true}}] 26 | (let [entity-descriptor (doto (.buildObject (EntityDescriptorBuilder.)) 27 | (.setID (str/replace acs-url #"[:/]" "_")) 28 | (.setEntityID app-name)) 29 | sp-sso-descriptor (doto (.buildObject (SPSSODescriptorBuilder.)) 30 | (.setAuthnRequestsSigned requests-signed) 31 | (.setWantAssertionsSigned want-assertions-signed) 32 | (.addSupportedProtocol SAMLConstants/SAML20P_NS))] 33 | 34 | (.. sp-sso-descriptor 35 | (getAssertionConsumerServices) 36 | (add (doto (.buildObject (AssertionConsumerServiceBuilder.)) 37 | (.setIndex (Integer. 0)) 38 | (.setIsDefault true) 39 | (.setLocation acs-url) 40 | (.setBinding SAMLConstants/SAML2_POST_BINDING_URI)))) 41 | (doseq [name-id-format name-id-formats] 42 | (.. sp-sso-descriptor 43 | (getNameIDFormats) 44 | (add (doto (.buildObject (NameIDFormatBuilder.)) 45 | (.setURI name-id-format))))) 46 | (when sp-cert 47 | (let [key-info-generator (.newInstance (doto (X509KeyInfoGeneratorFactory.) 48 | (.setEmitEntityCertificate true)))] 49 | (doseq [cert-use cert-uses] 50 | (.. sp-sso-descriptor 51 | (getKeyDescriptors) 52 | (add (doto (.buildObject (KeyDescriptorBuilder.)) 53 | (.setUse cert-use) 54 | (.setKeyInfo (.generate key-info-generator sp-cert)))))))) 55 | (when slo-url 56 | (.. sp-sso-descriptor 57 | (getSingleLogoutServices) 58 | (add (doto (.buildObject (SingleLogoutServiceBuilder.)) 59 | (.setBinding SAMLConstants/SAML2_POST_BINDING_URI) 60 | (.setLocation slo-url))))) 61 | 62 | (.. entity-descriptor 63 | (getRoleDescriptors) 64 | (add sp-sso-descriptor)) 65 | (coerce/->xml-string (XMLObjectSupport/marshall entity-descriptor)))) 66 | -------------------------------------------------------------------------------- /src/saml20_clj/sp/request.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.sp.request 2 | (:require [clojure.string :as str] 3 | [java-time.api :as t] 4 | [saml20-clj.coerce :as coerce] 5 | [saml20-clj.state :as state]) 6 | (:import [org.opensaml.saml.common.messaging.context SAMLBindingContext SAMLEndpointContext SAMLPeerEntityContext] 7 | [org.opensaml.saml.saml2.core AuthnRequest LogoutRequest NameIDType] 8 | [org.opensaml.saml.saml2.core.impl AuthnRequestBuilder IssuerBuilder LogoutRequestBuilder NameIDBuilder NameIDPolicyBuilder] 9 | org.opensaml.messaging.context.MessageContext 10 | org.opensaml.saml.common.xml.SAMLConstants 11 | org.opensaml.saml.saml2.binding.encoding.impl.HTTPRedirectDeflateEncoder 12 | org.opensaml.saml.saml2.metadata.impl.SingleSignOnServiceBuilder 13 | org.opensaml.xmlsec.context.SecurityParametersContext 14 | org.opensaml.xmlsec.SignatureSigningParameters)) 15 | 16 | (set! *warn-on-reflection* true) 17 | 18 | (defn- non-blank-string? [s] 19 | (and (string? s) 20 | (not (str/blank? s)))) 21 | 22 | (defn- random-request-id 23 | "Generates a random ID for a SAML request, if none is provided." 24 | [] 25 | (str "id" (random-uuid))) 26 | 27 | (def ^:private -sig-alg "http://www.w3.org/2000/09/xmldsig#rsa-sha1") 28 | 29 | (defn- keyword->protocol-binding 30 | [binding-kw] 31 | (condp = binding-kw 32 | :post SAMLConstants/SAML2_POST_BINDING_URI 33 | :redirect SAMLConstants/SAML2_REDIRECT_BINDING_URI 34 | (throw (ex-info "Unsupported protocol binding argument" {:arg binding-kw 35 | :allowed [:redirect :post]})))) 36 | 37 | (defn- setup-message-context 38 | [message credential sig-alg idp-url] 39 | (let [msgctx (doto (MessageContext.) (.setMessage message))] 40 | (when credential 41 | (let [decoded-credential (try 42 | (coerce/->Credential credential) 43 | (catch Throwable _ 44 | (coerce/->Credential (coerce/->PrivateKey credential)))) 45 | ^SecurityParametersContext security-context (.getSubcontext msgctx SecurityParametersContext true)] 46 | (.setSignatureSigningParameters security-context 47 | (doto (SignatureSigningParameters.) 48 | (.setSignatureAlgorithm sig-alg) 49 | (.setSigningCredential decoded-credential))))) 50 | 51 | (let [^SAMLPeerEntityContext peer-context (.getSubcontext msgctx SAMLPeerEntityContext true) 52 | ^SAMLEndpointContext endpoint-context (.getSubcontext peer-context SAMLEndpointContext true)] 53 | (.setEndpoint endpoint-context 54 | (doto (.buildObject (SingleSignOnServiceBuilder.)) 55 | (.setBinding SAMLConstants/SAML2_REDIRECT_BINDING_URI) 56 | (.setLocation idp-url)))) 57 | msgctx)) 58 | 59 | (defn- build-authn-obj 60 | ^AuthnRequest [request-id instant sp-name idp-url acs-url issuer protocol-binding] 61 | (doto (.buildObject (AuthnRequestBuilder.)) 62 | (.setID request-id) 63 | (.setIssueInstant instant) 64 | (.setDestination idp-url) 65 | (.setProtocolBinding (keyword->protocol-binding protocol-binding)) 66 | (.setIsPassive false) 67 | (.setProviderName sp-name) 68 | (.setAssertionConsumerServiceURL acs-url) 69 | (.setNameIDPolicy (doto (.buildObject (NameIDPolicyBuilder.)) 70 | (.setFormat NameIDType/UNSPECIFIED))) 71 | (.setIssuer (doto (.buildObject (IssuerBuilder.)) 72 | (.setValue issuer))))) 73 | 74 | (defn- authn-request 75 | "Return an OpenSAML MessageContext Object with a SAML AuthnRequest." 76 | ^MessageContext [request-id 77 | sp-name 78 | acs-url 79 | idp-url 80 | issuer 81 | state-manager 82 | credential 83 | sig-alg 84 | instant 85 | protocol-binding] 86 | (let [request (build-authn-obj request-id instant sp-name idp-url acs-url issuer protocol-binding)] 87 | (when state-manager 88 | (state/record-request! state-manager (.getID request))) 89 | (setup-message-context request credential sig-alg idp-url))) 90 | 91 | (defn- map-making-servlet 92 | "Implements a minimum HttpServletResponse for HTTPRedirectDeflateEncoder" 93 | [] 94 | (let [response (atom {:status 302 :body "" :headers {}}) 95 | servlet-wrapper (reify jakarta.servlet.http.HttpServletResponse 96 | (setHeader [_this name value] 97 | (swap! response update :headers assoc name value)) 98 | (^void setCharacterEncoding [_ ^String _]) 99 | (sendRedirect [this redirect] 100 | (.setHeader this "location" redirect))) 101 | wrapper-supplier (reify net.shibboleth.shared.primitive.NonnullSupplier 102 | (get [_] servlet-wrapper))] 103 | [wrapper-supplier #(deref response)])) 104 | 105 | (defn- redirect-response 106 | [^MessageContext saml-request relay-state] 107 | (let [[servlet ->ring-request] (map-making-servlet) 108 | ^SAMLBindingContext binding-context (.getSubcontext saml-request SAMLBindingContext true)] 109 | ;; set the relay state 110 | (.setRelayState binding-context relay-state) 111 | 112 | ;; Hand over to an opensaml encoder with a servletresponse implementation that allows us to 113 | ;; retrieve the result as a ring map 114 | (doto (HTTPRedirectDeflateEncoder.) 115 | (.setMessageContext saml-request) 116 | (.setHttpServletResponseSupplier servlet) 117 | (.initialize) 118 | (.encode)) 119 | (->ring-request))) 120 | 121 | (defn- build-logout-obj 122 | ^LogoutRequest [issuer user-email idp-url instant request-id] 123 | (assert (non-blank-string? idp-url) "idp-url is required") 124 | (assert (non-blank-string? issuer) "issuer is required") 125 | (assert (non-blank-string? user-email) "user-email is required") 126 | (doto (.buildObject (LogoutRequestBuilder.)) 127 | (.setID request-id) 128 | (.setIssueInstant instant) 129 | (.setDestination idp-url) 130 | (.setIssuer (doto (.buildObject (IssuerBuilder.)) 131 | (.setValue issuer))) 132 | (.setNameID (doto (.buildObject (NameIDBuilder.)) 133 | (.setValue user-email))))) 134 | 135 | (defn idp-redirect-response 136 | "Return Ring response for HTTP 302 redirect." 137 | [{:keys [;; e.g. something like a UUID. Random UUID will be used if no other ID is provided 138 | request-id 139 | ;; e.g. "Metabase" 140 | sp-name 141 | ;; e.g. http://sp.example.com/demo1/index.php?acs 142 | acs-url 143 | ;; e.g. http://idp.example.com/SSOService.php 144 | idp-url 145 | ;; e.g. http://sp.example.com/demo1/metadata.php 146 | issuer 147 | ;; If present, record the request 148 | state-manager 149 | ;; If present, we can sign the request 150 | credential 151 | ;; Signature Algorithm 152 | sig-alg 153 | ;; relay-state argument that will be returned by the provider 154 | relay-state 155 | ;; protocol binding specifying if IdP should use HTTP-Post or HTTP-Redirect to respond 156 | protocol-binding 157 | instant] 158 | :or {instant (t/instant) 159 | request-id (random-request-id) 160 | sig-alg -sig-alg 161 | protocol-binding :redirect}}] 162 | (assert (non-blank-string? acs-url) "acs-url is required") 163 | (assert (non-blank-string? idp-url) "idp-url is required") 164 | (assert (non-blank-string? sp-name) "sp-name is required") 165 | (assert (non-blank-string? issuer) "issuer is required") 166 | (assert (keyword? protocol-binding) "protocol binding must be a keyword") 167 | (redirect-response (authn-request request-id 168 | sp-name 169 | acs-url 170 | idp-url 171 | issuer 172 | state-manager 173 | credential 174 | sig-alg 175 | instant 176 | protocol-binding) 177 | relay-state)) 178 | 179 | (defn idp-logout-redirect-response 180 | "Return Ring response for HTTP 302 redirect." 181 | ([issuer user-email idp-url relay-state] 182 | (idp-logout-redirect-response issuer user-email idp-url relay-state (random-request-id))) 183 | ([issuer user-email idp-url relay-state request-id] 184 | (idp-logout-redirect-response {:issuer issuer 185 | :user-email user-email 186 | :idp-url idp-url 187 | :relay-state relay-state 188 | :request-id request-id})) 189 | ([{:keys [request-id instant idp-url issuer user-email credential relay-state sig-alg] 190 | :or {instant (t/instant) 191 | request-id (random-request-id) 192 | sig-alg -sig-alg}}] 193 | (let [logout-request (build-logout-obj issuer user-email idp-url instant request-id)] 194 | (redirect-response (setup-message-context logout-request credential sig-alg idp-url) relay-state)))) 195 | 196 | (defn logout-redirect-location 197 | "Return only the URI of the logout redirect." 198 | [& args] 199 | (get-in (idp-logout-redirect-response args) [:headers "location"])) 200 | -------------------------------------------------------------------------------- /src/saml20_clj/specs.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.specs 2 | (:require [clojure.spec.alpha :as s] 3 | [saml20-clj.coerce :as coerce] 4 | [saml20-clj.sp.metadata :as metadata] 5 | [saml20-clj.sp.request :as request] 6 | [saml20-clj.state :as state]) 7 | (:import java.net.URL 8 | javax.security.cert.X509Certificate 9 | org.opensaml.security.credential.Credential 10 | org.w3c.dom.Element)) 11 | 12 | (defn- url? [s] 13 | (try 14 | (URL. s) 15 | true 16 | (catch Exception _ 17 | false))) 18 | 19 | (s/def ::acs-url url?) 20 | (s/def ::idp-url url?) 21 | (s/def ::issuer url?) 22 | (s/def ::slo-url url?) 23 | 24 | (s/def ::sp-name string?) 25 | (s/def ::app-name string?) 26 | 27 | (s/def ::state-manager (partial satisfies? state/StateManager)) 28 | (s/def ::credential (partial instance? Credential)) 29 | (s/def ::instant inst?) 30 | 31 | (s/def ::saml-request (partial satisfies? coerce/SerializeXMLString)) 32 | (s/def ::relay-state string?) 33 | 34 | (s/def ::status int?) 35 | (s/def ::headers map?) 36 | (s/def ::body string?) 37 | 38 | (s/def ::sp-cert (partial instance? X509Certificate)) 39 | (s/def ::requests-signed boolean?) 40 | (s/def ::want-assertions-signed boolean?) 41 | 42 | (s/def ::request (s/keys :req-un [::sp-name 43 | ::acs-url 44 | ::idp-url 45 | ::issuer] 46 | :opt-un [::state-manager 47 | ::credential 48 | ::instant])) 49 | 50 | (s/def ::ring-response (s/keys :req-un [::status ::headers ::body])) 51 | 52 | (s/def ::metadata (s/keys :req-un [::acs-url 53 | ::app-name 54 | ::sp-cert] 55 | :opt-un [::requests-signed 56 | ::slo-url 57 | ::want-assertions-signed])) 58 | 59 | (s/fdef metadata/metadata 60 | :args (s/cat :args ::metadata) 61 | :ret string?) 62 | 63 | (s/fdef request/request 64 | :args (s/cat :request ::request) 65 | :ret (partial instance? Element)) 66 | 67 | (s/fdef request/id-redirect-response 68 | :args (s/cat :request ::saml-request 69 | :idp-url ::idp-url 70 | :relay-state ::relay-state) 71 | :ret ::ring-response) 72 | -------------------------------------------------------------------------------- /src/saml20_clj/state.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.state 2 | (:require [java-time.api :as t] 3 | [pretty.core :as pretty])) 4 | 5 | (set! *warn-on-reflection* true) 6 | 7 | (defprotocol StateManager 8 | "Protocol for managing state for recording which requests are in flight, so we can determine whether responses 9 | correspond to valid requests. This library ships with a simple in-memory implementation, but this interface is 10 | provided so that you can provide your own implementation if you need to do something more sophisticated (such as 11 | synchronizing across multiple instances)." 12 | (record-request! [this request-id] 13 | "Called whenever a new request to the IdP goes out. The state manager should record `request-id` (and probably the 14 | current timestamp as well) so it can be used for validating responses.") 15 | 16 | ;; TODO -- consider renaming this to handle-response! or something else clearer 17 | (accept-response! [this request-id] 18 | "Called whenever a new response from IdP is received. The state manager should verify that `request-id` was 19 | actually issued by us (e.g., one we've seen earlier when `record-request!`), and (hopefully) that it is not too old; 20 | if the response is not acceptable, it must throw an Exception. The state manager should remove the request from its 21 | state a response with the same ID cannot be used again (e.g. to prevent replay attacks).")) 22 | 23 | ;; in-memory-state-manager state works like this: 24 | ;; 25 | ;; - State consists of three buckets. After every timeout/2 seconds, the oldest bucket is dropped and a new one is 26 | ;; created. Buckets are thus: 27 | ;; 28 | ;; 1. Requests created after last rotation. Thus requests in this bucket are between 0 and timeout/2 seconds old. 29 | ;; 30 | ;; 2. Requests that have survived one rotation. Requests in this bucket are between ~0 and timeout seconds old. (They 31 | ;; can be ~0 if they were added to the bucket immediately before it was rotated, and rotation just occurred; or 32 | ;; ~timeout if they were added to the bucket when it was first created and the next rotation is about to occur). 33 | ;; 34 | ;; 3. Requests that have survived two rotations. Requests in this bucket are at least timeout/2 seconds old, and at 35 | ;; most (timeout*1.5) seconds old. 36 | ;; 37 | ;; Thus after the two rotations we know a request is at least timeout/2 seconds old, and after three we know it is 38 | ;; older than timeout and can drop it. 39 | ;; 40 | ;; buckets look like: [bucket-created-instant #{request-id}] 41 | ;; 42 | ;; Note that this means `timeout` means the earliest that a request ID gets dropped, but does not guarantee it will be 43 | ;; dropped by then; it make take up to timeout*1.5. 44 | 45 | (defn- prune-buckets [state request-timeout-seconds] 46 | (let [now (t/instant) 47 | [[bucket-1-created :as bucket-1] bucket-2] state] 48 | (letfn [(new-bucket [] 49 | [now #{}])] 50 | (cond 51 | ;; state not initialized yet. 52 | (not bucket-1) 53 | [(new-bucket)] 54 | 55 | ;; all buckets are too old 56 | (t/before? bucket-1-created (t/minus now (t/seconds request-timeout-seconds))) 57 | [(new-bucket)] 58 | 59 | ;; bucket 1 is past the threshold and it's time to rotate the buckets 60 | (t/before? bucket-1-created (t/minus now (t/seconds (int (/ request-timeout-seconds 2))))) 61 | [(new-bucket) bucket-1 bucket-2] 62 | 63 | ;; not time to rotate the buckets yet 64 | :else 65 | state)))) 66 | 67 | (defn- in-memory-state-manager-record-request [state request-timeout-seconds request-id] 68 | (let [state (prune-buckets state request-timeout-seconds)] 69 | (update-in state [0 1] conj request-id))) 70 | 71 | (defn- in-memory-state-manager-accept-response [state request-timeout-seconds request-id] 72 | (let [state (prune-buckets state request-timeout-seconds)] 73 | (or (some (fn [bucket-index] 74 | (when (contains? (get-in state [bucket-index 1]) request-id) 75 | (update-in state [bucket-index 1] disj request-id))) 76 | [0 1 2]) 77 | (throw (ex-info "Invalid request ID" {:request-id request-id}))))) 78 | 79 | ;; 5 minutes, in case people decide they want to sit around on the IdP page for a bit. 80 | (def ^:private default-request-timeout-seconds 300) 81 | 82 | (defn in-memory-state-manager 83 | "A simple in-memory state manager, suitable for a single instance. Requests IDs are considered valid for a minimum of 84 | `request-timeout-seconds`." 85 | ([] 86 | (in-memory-state-manager default-request-timeout-seconds)) 87 | 88 | ([request-timeout-seconds] 89 | (in-memory-state-manager request-timeout-seconds [])) 90 | 91 | ([request-timeout-seconds initial-state] 92 | (let [state (atom initial-state)] 93 | (reify 94 | pretty/PrettyPrintable 95 | (pretty [_] 96 | (list `in-memory-state-manager request-timeout-seconds @state)) 97 | 98 | StateManager 99 | (record-request! [_ request-id] 100 | (swap! state in-memory-state-manager-record-request request-timeout-seconds request-id)) 101 | (accept-response! [_ request-id] 102 | (swap! state in-memory-state-manager-accept-response request-timeout-seconds request-id)) 103 | 104 | ;; this is here mostly for convenience and testability: deref the state manager itself to see what's in the 105 | ;; state atom 106 | clojure.lang.IDeref 107 | (deref [_] 108 | @state))))) 109 | -------------------------------------------------------------------------------- /src/saml20_clj/xml.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.xml 2 | (:require [saml20-clj.encode-decode :as encode-decode]) 3 | (:import [javax.xml.parsers DocumentBuilder DocumentBuilderFactory] 4 | javax.xml.XMLConstants 5 | org.w3c.dom.Document)) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | (defn- document-builder 10 | ^DocumentBuilder [] 11 | (.newDocumentBuilder 12 | (doto (DocumentBuilderFactory/newInstance) 13 | (.setNamespaceAware true) 14 | (.setFeature "http://xml.org/sax/features/external-parameter-entities" false) 15 | (.setFeature "http://apache.org/xml/features/nonvalidating/load-external-dtd" false) 16 | (.setFeature "http://apache.org/xml/features/disallow-doctype-decl" true) 17 | (.setFeature XMLConstants/FEATURE_SECURE_PROCESSING true) 18 | (.setXIncludeAware false) 19 | (.setExpandEntityReferences false)))) 20 | 21 | (defn clone-document 22 | "Return a clone of the provided XML document." 23 | [^org.w3c.dom.Document document] 24 | (when document 25 | (let [clone (.. (DocumentBuilderFactory/newInstance) newDocumentBuilder newDocument) 26 | original-root (.getDocumentElement document) 27 | root-copy (.importNode clone original-root true)] 28 | (.appendChild clone root-copy) 29 | clone))) 30 | 31 | (defn str->xmldoc 32 | "Parse a string into an XML `Document`." 33 | ^Document [^String s] 34 | (let [document (document-builder)] 35 | (with-open [is (java.io.ByteArrayInputStream. (encode-decode/str->bytes s))] 36 | (.parse document is)))) 37 | -------------------------------------------------------------------------------- /test/saml20_clj/coerce_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.coerce-test 2 | (:require [clojure.test :refer :all] 3 | [saml20-clj.coerce :as coerce] 4 | [saml20-clj.test :as test])) 5 | 6 | (defn- key-fingerprint [^java.security.Key k] 7 | (when k 8 | (org.apache.commons.codec.digest.DigestUtils/md5Hex (.getEncoded k)))) 9 | 10 | (deftest ->PrivateKey-test 11 | (is (= nil (coerce/->PrivateKey nil))) 12 | (letfn [(is-key-with-fingerprint? [input] 13 | (let [k (coerce/->PrivateKey input)] 14 | (is (instance? java.security.PrivateKey k)) 15 | (is (= "af284d1f7bfa789c787f689a95604d31" 16 | (key-fingerprint k)))))] 17 | (testing "Should be able to get a private key from base-64-encoded string" 18 | (is-key-with-fingerprint? test/sp-private-key)) 19 | (testing "Should be able to get a private key from a Java keystore" 20 | (is-key-with-fingerprint? {:filename test/keystore-filename 21 | :password test/keystore-password 22 | :alias "sp"})) 23 | (testing "Should be able to get a private key from X509Credential" 24 | (is-key-with-fingerprint? (coerce/->Credential test/sp-cert test/sp-private-key))))) 25 | 26 | (def ^:private test-certificate-str-1 27 | "MIIDsjCCApqgAwIBAgIGAWtM1OOxMA0GCSqGSIb3DQEBCwUAMIGZMQswCQYDVQQGEwJVUzETMBEG 28 | A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU 29 | MBIGA1UECwwLU1NPUHJvdmlkZXIxGjAYBgNVBAMMEW1ldGFiYXNlLXZpY3RvcmlhMRwwGgYJKoZI 30 | hvcNAQkBFg1pbmZvQG9rdGEuY29tMB4XDTE5MDYxMjE3NTQ0OFoXDTI5MDYxMjE3NTU0OFowgZkx 31 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv 32 | MQ0wCwYDVQQKDARPa3RhMRQwEgYDVQQLDAtTU09Qcm92aWRlcjEaMBgGA1UEAwwRbWV0YWJhc2Ut 33 | dmljdG9yaWExHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wggEiMA0GCSqGSIb3DQEBAQUA 34 | A4IBDwAwggEKAoIBAQCJNDIHd05aBXALoQStEvErsnJZDx1PIHTYGDY30SGHad8vXANg+tpThny3 35 | ZMmGx8j3tDDwjsijPa8SQtL8I8GrTKO1h2zqM+3sKrgyLk6fcXnKWBqbFx9gpqz9bRxT76WKYTxV 36 | 3t71GtVb8fSfns1fv3u3thsUADDcJmOK65snwirtahie61IDIvoRxMIInu26kw1gCFtOcidoY0yL 37 | RhGgaMjgGYOd2auW5A7bQV9kxePLg8o8rU+KXhTbuHJg0dgW8gVNAv5IKEQQ1VZNTjALR+N6Mca1 38 | p0tuofEVggkA7x9t0O+xWXxUrbSs9C1DxKkxF4xI0z8M/ocqdtwPxNP5AgMBAAEwDQYJKoZIhvcN 39 | AQELBQADggEBAIO5cVa/P50nXuXaMK/klblZ+1MFbJ8Ti86TSPcdnxYO8nbWwQuUwKKuRHf6y5li 40 | 7ctaeXhMfyx/rGsYH4TDgzZhpZmGgZmAKGohDH4YxHctqyxNpRPwJe2kIkJN5yEqLUPNwqm2I7Dw 41 | PcmkewOYEf71Y/sBF0/vRJev5n3upo2nW9RzUz9ptAtWn7EoLsN+grcohJpygj7jiJmbicxblNqF 42 | uvuZkzz+X+qt2W/1mbVDyuIwsvUQOeRbpM+xv11dxheLRKt3kB8Gf6kqd8EjBtHmMFL8s4fdHyfM 43 | eRzAWU6exmsx49oEvw5LrBSTJ97ekvVFfrEASyd96sgeV2Nl0No=") 44 | 45 | (def ^:private test-certificate-str-2 46 | "-----BEGIN CERTIFICATE----- 47 | MIICEjCCAXsCAg36MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG 48 | A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE 49 | MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl 50 | YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw 51 | ODIyMDUyNjU0WhcNMTcwODIxMDUyNjU0WjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE 52 | CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs 53 | ZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAm/xmkHmEQrurE/0re/jeFRLl 54 | 8ZPjBop7uLHhnia7lQG/5zDtZIUC3RVpqDSwBuw/NTweGyuP+o8AG98HxqxTBwID 55 | AQABMA0GCSqGSIb3DQEBBQUAA4GBABS2TLuBeTPmcaTaUW/LCB2NYOy8GMdzR1mx 56 | 8iBIu2H6/E2tiY3RIevV2OW61qY2/XRQg7YPxx3ffeUugX9F4J/iPnnu1zAxxyBy 57 | 2VguKv4SWjRFoRkIfIlHX0qVviMhSlNy2ioFLy7JcPZb+v3ftDGywUqcBiVDoea0 58 | Hn+GmxZA 59 | -----END CERTIFICATE-----") 60 | 61 | (def ^:private test-certificate-str-3 62 | "-----BEGIN CERTIFICATE----- 63 | MIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG 64 | A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE 65 | MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl 66 | YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw 67 | ODIyMDUyNzQxWhcNMTcwODIxMDUyNzQxWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE 68 | CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs 69 | ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0z9FeMynsC8+u 70 | dvX+LciZxnh5uRj4C9S6tNeeAlIGCfQYk0zUcNFCoCkTknNQd/YEiawDLNbxBqut 71 | bMDZ1aarys1a0lYmUeVLCIqvzBkPJTSQsCopQQ9V8WuT252zzNzs68dVGNdCJd5J 72 | NRQykpwexmnjPPv0mvj7i8XgG379TyW6P+WWV5okeUkXJ9eJS2ouDYdR2SM9BoVW 73 | +FgxDu6BmXhozW5EfsnajFp7HL8kQClI0QOc79yuKl3492rH6bzFsFn2lfwWy9ic 74 | 7cP8EpCTeFp1tFaD+vxBhPZkeTQ1HKx6hQ5zeHIB5ySJJZ7af2W8r4eTGYzbdRW2 75 | 4DDHCPhZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQMv+BFvGdMVzkQaQ3/+2noVz 76 | /uAKbzpEL8xTcxYyP3lkOeh4FoxiSWqy5pGFALdPONoDuYFpLhjJSZaEwuvjI/Tr 77 | rGhLV1pRG9frwDFshqD2Vaj4ENBCBh6UpeBop5+285zQ4SI7q4U9oSebUDJiuOx6 78 | +tZ9KynmrbJpTSi0+BM= 79 | -----END CERTIFICATE-----") 80 | 81 | (def ^:private test-certificate-str-4 82 | "-----BEGIN CERTIFICATE----- 83 | MIID2jCCA0MCAg39MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG 84 | A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE 85 | MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl 86 | YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw 87 | ODIyMDUyODAwWhcNMTcwODIxMDUyODAwWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE 88 | CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs 89 | ZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwvWITOLeyTbS1 90 | Q/UacqeILIK16UHLvSymIlbbiT7mpD4SMwB343xpIlXN64fC0Y1ylT6LLeX4St7A 91 | cJrGIV3AMmJcsDsNzgo577LqtNvnOkLH0GojisFEKQiREX6gOgq9tWSqwaENccTE 92 | sAXuV6AQ1ST+G16s00iN92hjX9V/V66snRwTsJ/p4WRpLSdAj4272hiM19qIg9zr 93 | h92e2rQy7E/UShW4gpOrhg2f6fcCBm+aXIga+qxaSLchcDUvPXrpIxTd/OWQ23Qh 94 | vIEzkGbPlBA8J7Nw9KCyaxbYMBFb1i0lBjwKLjmcoihiI7PVthAOu/B71D2hKcFj 95 | Kpfv4D1Uam/0VumKwhwuhZVNjLq1BR1FKRJ1CioLG4wCTr0LVgtvvUyhFrS+3PdU 96 | R0T5HlAQWPMyQDHgCpbOHW0wc0hbuNeO/lS82LjieGNFxKmMBFF9lsN2zsA6Qw32 97 | Xkb2/EFltXCtpuOwVztdk4MDrnaDXy9zMZuqFHpv5lWTbDVwDdyEQNclYlbAEbDe 98 | vEQo/rAOZFl94Mu63rAgLiPeZN4IdS/48or5KaQaCOe0DuAb4GWNIQ42cYQ5TsEH 99 | Wt+FIOAMSpf9hNPjDeu1uff40DOtsiyGeX9NViqKtttaHpvd7rb2zsasbcAGUl+f 100 | NQJj4qImPSB9ThqZqPTukEcM/NtbeQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAIAi 101 | gU3My8kYYniDuKEXSJmbVB+K1upHxWDA8R6KMZGXfbe5BRd8s40cY6JBYL52Tgqd 102 | l8z5Ek8dC4NNpfpcZc/teT1WqiO2wnpGHjgMDuDL1mxCZNL422jHpiPWkWp3AuDI 103 | c7tL1QjbfAUHAQYwmHkWgPP+T2wAv0pOt36GgMCM 104 | -----END CERTIFICATE-----") 105 | 106 | (deftest ->X509Certificate-test 107 | (testing "from String" 108 | (testing "make sure we can parse a certificate, no armor" 109 | (coerce/->X509Certificate test-certificate-str-1) 110 | (is (instance? java.security.cert.X509Certificate 111 | (coerce/->X509Certificate test-certificate-str-1)))) 112 | (testing "make sure we can parse a certificate with armor 512b key" 113 | (is (instance? java.security.cert.X509Certificate 114 | (coerce/->X509Certificate test-certificate-str-2)))) 115 | (testing "make sure we can parse a certificate with armor 2048b key" 116 | (is (instance? java.security.cert.X509Certificate 117 | (coerce/->X509Certificate test-certificate-str-3)))) 118 | (testing "make sure we can parse a certificate with armor 4096b key" 119 | (is (instance? java.security.cert.X509Certificate 120 | (coerce/->X509Certificate test-certificate-str-4)))))) 121 | 122 | (defn- x509-credential-fingerprints [^org.opensaml.security.x509.X509Credential credential] 123 | {:public (key-fingerprint (.getPublicKey credential)) 124 | :private (key-fingerprint (.getPrivateKey credential))}) 125 | 126 | (deftest ->Credential-test 127 | (let [sp-fingerprints {:public "6e104aaa6daccb9c8f2b4d692441f3a5" 128 | :private "af284d1f7bfa789c787f689a95604d31"} 129 | idp-fingerprints {:public "b2648dc4aa28760eaf33c789d58ba262", :private nil}] 130 | (testing "Should be able to get an X509Credential from Strings" 131 | (is (= sp-fingerprints 132 | (x509-credential-fingerprints (coerce/->Credential test/sp-cert test/sp-private-key))))) 133 | (testing "Should accept a tuple of [public-key private-key]" 134 | (is (= sp-fingerprints 135 | (x509-credential-fingerprints (coerce/->Credential [test/sp-cert test/sp-private-key])))) 136 | (is (= idp-fingerprints 137 | (x509-credential-fingerprints (coerce/->Credential [test/idp-cert]))))) 138 | (testing "Should be able to get X509Credential from a keystore" 139 | (testing "public only" 140 | (is (= idp-fingerprints 141 | (x509-credential-fingerprints (coerce/->Credential {:filename test/keystore-filename 142 | :password test/keystore-password 143 | :alias "idp"}))))) 144 | (testing "public + private" 145 | (is (= sp-fingerprints 146 | (x509-credential-fingerprints (coerce/->Credential {:filename test/keystore-filename 147 | :password test/keystore-password 148 | :alias "sp"})))))))) 149 | 150 | (deftest ->LogoutResponse 151 | (let [logout-response-ring {:params {:SAMLResponse "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOkxvZ291dFJlc3BvbnNlIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjMwMDAvYXV0aC9zc28vaGFuZGxlX3NsbyIgSUQ9ImlkODYyMTQxMDMzODM0ODEzMDA4NTY4NzAiIEluUmVzcG9uc2VUbz0iaWQ2NjFiYWM5ZC0xYWMyLTQxNjctOTY0Ni05ZjEyMmY3ODhkMmYiIElzc3VlSW5zdGFudD0iMjAyNS0wMi0yNFQxNzoxOTo1Ny42MTBaIiBWZXJzaW9uPSIyLjAiIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48c2FtbDI6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5IiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+aHR0cDovL3d3dy5va3RhLmNvbS9leGtuZnpoMXA1TlhBTm96MTVkNzwvc2FtbDI6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48ZHM6UmVmZXJlbmNlIFVSST0iI2lkODYyMTQxMDMzODM0ODEzMDA4NTY4NzAiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxkczpEaWdlc3RWYWx1ZT5yeGt3RVJSRkNVVklyTXdBZDBoMnJ3bE5PeDVaK1UvZzZiWkUrVHpSVlNVPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5jeXdMRW5kdVF6b3VSa3k4K2hNVXkrMUtBVS9Xb2pRcDJDcTZmMmVrNlFyM2hvbGYvcUt6dkpjNVBOTzBSSjh3UTdvdVlGYmR4V0s0Q0VobzQ0Qy9sOTJSZTl6V3djcXdUWjA5WWdKRFNYUjU2NXRsT2VjQ2pqNS9kd05hRUkrNUEzdGVIbC9GMk5qMDdrUGRtSThhWlMyQ2tJOTk4aXoxclV4bVRqSTJIQm9td3QxZ04vQ1NaNys4d2lWZkRmOGVycmZ0SFhGUmhkMzdRTzBob0NmeVlUY2R0b0RGQitTZmxsSCtpRHVyeE8vV2NkMTZoUEJRQ0Z6bW9tdHAwZHkxMW80NlZmMVFwNUlhMEt4allKOU1tNmxkVUE2dHVYUW40aTYzZXI0MkVNZjAzRTFDYUZrZlowRXROU2ZmY1A3UUhZeTk0OHpIcG1vcEprU3UwV0NsNVE9PTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJRHFEQ0NBcENnQXdJQkFnSUdBWlVhd0VSWE1BMEdDU3FHU0liM0RRRUJDd1VBTUlHVU1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFRwpBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ3d05VMkZ1SUVaeVlXNWphWE5qYnpFTk1Bc0dBMVVFQ2d3RVQydDBZVEVVCk1CSUdBMVVFQ3d3TFUxTlBVSEp2ZG1sa1pYSXhGVEFUQmdOVkJBTU1ER1JsZGkwd09EVTBPREl5TlRFY01Cb0dDU3FHU0liM0RRRUoKQVJZTmFXNW1iMEJ2YTNSaExtTnZiVEFlRncweU5UQXlNVGd5TURJNE1qSmFGdzB6TlRBeU1UZ3lNREk1TWpKYU1JR1VNUXN3Q1FZRApWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzVqYVhOamJ6RU5NQXNHCkExVUVDZ3dFVDJ0MFlURVVNQklHQTFVRUN3d0xVMU5QVUhKdmRtbGtaWEl4RlRBVEJnTlZCQU1NREdSbGRpMHdPRFUwT0RJeU5URWMKTUJvR0NTcUdTSWIzRFFFSkFSWU5hVzVtYjBCdmEzUmhMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQwpnZ0VCQU8zbkhxZUMraXRINmdyUytvSEdMMEVvNXZUR2EycWQ5VzFyWkplcW1BcWJwZXk0WnY2c0VhSEVqL1FJTEVVbGVNOEl5YTJvClErdkxiWTFJU05Fb0R2TCt1MmZDM0NGWjE1VlRnb0hmdEhZOFF5K21vdW1pWjZyQWU2MzdSY1BQT0RmRXlSUzRhY2FZa29TQ0g1UDQKbEtkVnVOQTc2UXN6KzQrelNnbGNmMURmT0JhQ3FuRHJWWXUrbGVaTWxSSVJaL3ZZRW8zT012ejZXTGJnUy9KMXAra2xkZDJGanpFdQp5ZzdRYiszOGZCZ3pkREhSYmZUeGQzRVptTThFblpxQ0tIWklna3ZVZXBaWUp3TlVXM3FVR3dlR0Y0c0JQaWNnQnI2RU0rR2RJeWVmCnFZNzlEV2h4RVhIOEdhMzA2Yzk4L29KajBiUHBlRFVwb001OWZEN3QyekVDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEKQ3VmRnN5NHNGSkhvNk5XMFpSUy9RT0lFWTFVYjNZNGlHQytIM0tXemdsRDJqNTA1N0tqb3U1ZDNvSmIwSDB0OEpJK0tLOUhIMGk5YwpkRldyQXQ2OTZ1MFpmUEk2TVNWV2x5bVQ5WWY4ZkV1VW9xTmlqQ0RtcW96TlhINUpLQUM2TVZTNzdWZXY0amMxdHJFQmVxd0o5ZE5YCnpFMXBCUDh4YnpWSzBET0NQRW5EL0p4eHQyWmR4d1hiZjlCOWUyeGRTNWYrUG8vZjdCbDkrTVoxeWUyR1ZGV1J0cEJmZzUwU2pFdWYKMThMT2NsRjdibGhZZ1g0SnA4TFJVaGp4cVdUb0Qxc1B3QUxwTmw5SkJ5bGJNb2w2QUlLNkxURG8rMitScUNlQzdjU0FZcjY3SXY0cwppQ0RpbU9DMWlkR01vcU1QT1pOTXBmYXZUemxNeFptalAxQmhiUT09PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWwycDpTdGF0dXMgeG1sbnM6c2FtbDJwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxzYW1sMnA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1sMnA6U3RhdHVzPjwvc2FtbDJwOkxvZ291dFJlc3BvbnNlPg==" 152 | :RelayState "aHR0cDovL2xvY2FsaG9zdDozMDAwL2F1dGgvc3NvL2hhbmRsZV9zbG8="} 153 | :content-type "application/x-www-form-urlencoded" 154 | :request-method :post}] 155 | (testing "converts ring response into logout response object" 156 | (is (instance? org.opensaml.saml.saml2.core.LogoutResponse 157 | (coerce/->LogoutResponse logout-response-ring)))))) 158 | -------------------------------------------------------------------------------- /test/saml20_clj/crypto_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.crypto-test 2 | (:require [clojure.test :refer :all] 3 | [saml20-clj.coerce :as coerce] 4 | [saml20-clj.crypto :as crypto] 5 | [saml20-clj.test :as test]) 6 | (:import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext)) 7 | 8 | (deftest assert-signature-invalid-swapped-signature 9 | (doseq [{:keys [response], :as response-map} (test/responses) 10 | :when (test/malicious-signature? response-map)] 11 | (testing (test/describe-response-map response-map) 12 | (is (thrown-with-msg? 13 | clojure.lang.ExceptionInfo 14 | #"Signature does not match credential" 15 | (crypto/assert-signature-valid-when-present response test/idp-cert)))))) 16 | 17 | (deftest has-private-key-test 18 | (testing "has private key" 19 | (is (= true (crypto/has-private-key? {:filename test/keystore-filename 20 | :password test/keystore-password 21 | :alias "sp"}))) 22 | 23 | (is (= true (crypto/has-private-key? test/sp-private-key))) 24 | 25 | (testing "has only public key" 26 | (is (= false (crypto/has-private-key? {:filename test/keystore-filename 27 | :password test/keystore-password 28 | :alias "idp"})))))) 29 | 30 | (deftest handle-signature-security-test 31 | (testing "with signed LogoutResponse POST bindings" 32 | (let [request (test/ring-logout-response-post :success "relay-state" :signature true) 33 | msg-ctx (coerce/ring-request->MessageContext request)] 34 | (crypto/handle-signature-security msg-ctx "http://idp.example.com/metadata.php" test/idp-cert request) 35 | (is (.isAuthenticated (.getSubcontext msg-ctx SAMLPeerEntityContext))))) 36 | 37 | (testing "with signed LogoutResponse Redirect bindings" 38 | (let [request (test/ring-logout-response-get :success :signature true) 39 | msg-ctx (coerce/ring-request->MessageContext request)] 40 | (crypto/handle-signature-security msg-ctx "http://idp.example.com/metadata.php" test/idp-cert request) 41 | (is (.isAuthenticated (.getSubcontext msg-ctx SAMLPeerEntityContext))))) 42 | 43 | (testing "with unsigned LogoutResponse POST bindings" 44 | (let [request (test/ring-logout-response-post :success "relay-state" :signature false) 45 | msg-ctx (coerce/ring-request->MessageContext request)] 46 | (crypto/handle-signature-security msg-ctx "http://idp.example.com/metadata.php" test/idp-cert request) 47 | (is (not (.isAuthenticated (.getSubcontext msg-ctx SAMLPeerEntityContext)))))) 48 | 49 | (testing "with unsigned LogoutResponse Redirect bindings" 50 | (let [request (test/ring-logout-response-get :success :signature false) 51 | msg-ctx (coerce/ring-request->MessageContext request)] 52 | (crypto/handle-signature-security msg-ctx "http://idp.example.com/metadata.php" test/idp-cert request) 53 | (is (not (.isAuthenticated (.getSubcontext msg-ctx SAMLPeerEntityContext))))))) 54 | -------------------------------------------------------------------------------- /test/saml20_clj/runners/test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.runners.test 2 | (:refer-clojure :exclude [test]) 3 | (:require [cognitect.test-runner.api :as test-runner] 4 | [pjstadig.humane-test-output :as humane-test-output])) 5 | 6 | (humane-test-output/activate!) 7 | 8 | (defn test [& args] 9 | (apply test-runner/test args)) 10 | -------------------------------------------------------------------------------- /test/saml20_clj/sp/logout_response_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.sp.logout-response-test 2 | (:require [clojure.test :as t] 3 | [saml20-clj.sp.logout-response :as sut] 4 | [saml20-clj.test :as test])) 5 | 6 | (t/deftest test-validate-response 7 | (t/testing "raise with an incorrect issuer response" 8 | (let [response (test/ring-logout-response-post :success "relay-state") 9 | exception (try 10 | (sut/validate-logout response test/logout-request-id "http://idp.incorrect.example.org/" test/idp-cert) 11 | (catch clojure.lang.ExceptionInfo e 12 | {:msg (ex-message e) :data (ex-data e)}))] 13 | (t/is (not (instance? org.opensaml.saml.saml2.core.LogoutResponse 14 | exception))) 15 | (t/is (= {:msg "Message failed to validate issuer" 16 | :data {:validator :issuer 17 | :expected "http://idp.incorrect.example.org/" 18 | :actual "http://idp.example.com/metadata.php"}} 19 | exception)))) 20 | (t/testing "raise with a broken signature response get" 21 | (let [response (test/ring-logout-response-get :success :signature :bad) 22 | exception (try 23 | (sut/validate-logout response test/logout-request-id test/logout-issuer-id test/idp-cert) 24 | (catch clojure.lang.ExceptionInfo e 25 | {:msg (ex-message e) :data (ex-data e)}))] 26 | (t/is (not (instance? org.opensaml.saml.saml2.core.LogoutResponse 27 | exception))) 28 | (t/is (= {:msg "Message failed to validate signature" 29 | :data {:validator :signature}} 30 | exception)))) 31 | (t/testing "raise with a broken signature response" 32 | (let [response (test/ring-logout-response-post :success "relay-state" :signature :bad) 33 | exception (try 34 | (sut/validate-logout response test/logout-request-id test/logout-issuer-id test/idp-cert) 35 | (catch clojure.lang.ExceptionInfo e 36 | {:msg (ex-message e) :data (ex-data e)}))] 37 | (t/is (not (instance? org.opensaml.saml.saml2.core.LogoutResponse 38 | exception))) 39 | (t/is (= {:msg "Message failed to validate signature" 40 | :data {:validator :signature}} 41 | exception)))) 42 | (t/testing "raise with an unsigned response" 43 | (let [response (test/ring-logout-response-post :success "relay-state" :signature false) 44 | exception (try 45 | (sut/validate-logout response test/logout-request-id test/logout-issuer-id test/idp-cert) 46 | (catch clojure.lang.ExceptionInfo e 47 | {:msg (ex-message e) :data (ex-data e)}))] 48 | (t/is (not (instance? org.opensaml.saml.saml2.core.LogoutResponse 49 | exception))) 50 | (t/is (= {:msg "Message is not Authenticated" 51 | :data {:validator :require-authenticated 52 | :is-authenticated false}} 53 | exception)))) 54 | (t/testing "returns a logout-response without raising" 55 | (let [response (test/ring-logout-response-post :success "relay-state")] 56 | (t/is (instance? org.opensaml.saml.saml2.core.LogoutResponse 57 | (sut/validate-logout response test/logout-request-id test/logout-issuer-id test/idp-cert))))) 58 | (t/testing "returns a logout-response without raising with get" 59 | (let [response (test/ring-logout-response-get :success)] 60 | (t/is (instance? org.opensaml.saml.saml2.core.LogoutResponse 61 | (sut/validate-logout response test/logout-request-id test/logout-issuer-id test/idp-cert)))))) 62 | 63 | (t/deftest test-success? 64 | (t/testing "when the logout-response is successful" 65 | (let [response (-> (test/ring-logout-response-post :success "relay-state") 66 | (sut/validate-logout test/logout-request-id test/logout-issuer-id test/idp-cert))] 67 | (t/is (sut/logout-success? response)))) 68 | (t/testing "when the logout-response is not successful" 69 | (let [response (-> (test/ring-logout-response-post :authnfailed "relay-state") 70 | (sut/validate-logout test/logout-request-id test/logout-issuer-id test/idp-cert))] 71 | (t/is (not (sut/logout-success? response)))))) 72 | -------------------------------------------------------------------------------- /test/saml20_clj/sp/metadata_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.sp.metadata-test 2 | (:require [clojure.test :as t] 3 | [saml20-clj.coerce :as coerce] 4 | [saml20-clj.sp.metadata :as sut] 5 | [saml20-clj.test :as test])) 6 | 7 | (t/deftest metadata-generation 8 | (t/testing "generates metadata with keyinfo" 9 | (t/is (= test/metadata-with-key-info 10 | (saml20-clj.sp.metadata/metadata {:app-name "metabase" 11 | :acs-url "http://acs.example.com" 12 | :slo-url "http://slo.example.com" 13 | :sp-cert (coerce/->Credential test/sp-cert)})))) 14 | (t/testing "generates metadata with-out keyinfo" 15 | (t/is (= test/metadata-without-key-info 16 | (saml20-clj.sp.metadata/metadata {:app-name "metabase" 17 | :acs-url "http://acs.example.com" 18 | :slo-url "http://slo.example.com"}))))) 19 | -------------------------------------------------------------------------------- /test/saml20_clj/sp/request_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.sp.request-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [java-time.api :as t] 4 | [saml20-clj.sp.request :as request] 5 | [saml20-clj.test :as test])) 6 | 7 | (def target-uri "http://sp.example.com/demo1/index.php?acs") 8 | 9 | (deftest idp-redirect-response-test 10 | (t/with-clock (t/mock-clock (t/instant "2020-09-24T22:51:00.000Z")) 11 | (testing "without signature" 12 | (let [request {:request-id "ONELOGIN_809707f0030a5d00620c9d9df97f627afe9dcc24" 13 | :sp-name "SP test" 14 | :acs-url "http://sp.example.com/demo1/index.php?acs" 15 | :idp-url "http://idp.example.com/SSOService.php" 16 | :issuer "http://sp.example.com/demo1/metadata.php" 17 | :relay-state target-uri}] 18 | (is (= {:status 302, 19 | :body "", 20 | :headers 21 | {"Cache-control" "no-cache, no-store" 22 | "Pragma" "no-cache" 23 | "location" (str 24 | "http://idp.example.com/SSOService.php?SAMLRequest=" 25 | "fVLLbtswEPwVgndJFPNwRFg2nLppDbi2YDk59FIw5KomIJEqlz" 26 | "Lcv4%2BsKEFyiK%2B7OzuzMzudn5qaHMGjcTanacwoAaucNvZv" 27 | "Th%2F3D9Ednc%2BmKJuat2LRhYPdwb8OMJAeaFG8dnLaeSucRI" 28 | "PCygZQBCXKxa%2B14DETrXfBKVdTskAEH3qqb85i14AvwR%2BN" 29 | "gsfdOqeHEFqRJNjGcJJNW0OsXJNoaFyaGKvhFLeHdi4VUrLsBR" 30 | "grwyB6xBn9GViW23H7GUfJapnT7eb7evtjtflzx7IJm1SMXTF5" 31 | "oxm75UxlOtNVNqlu%2BURWkGml%2BHUPw0IimiPktJI1wrmCHa" 32 | "wsBmlDTjnjLGJZxK%2F3nIubVDAWM8Z%2BU1KMZ9%2F34gc7L3" 33 | "n0%2FDqE4ud%2BX0Q70MaDCsOSo9HgNz0ip2VBQn86JU9vifVY" 34 | "OuYjBmH%2BYzCXOeVbGnR2yfsGgtQyyLON0%2BQj1ftjnNWtlo" 35 | "WrjfpPHpxvZPiaOo3ToWJ0VA2jorPYgjKVAU2T2cjx%2Bd1mLw" 36 | "%3D%3D&RelayState=http%3A%2F%2Fsp.example.com%2Fde" 37 | "mo1%2Findex.php%3Facs")}} 38 | (request/idp-redirect-response request))))) 39 | (testing "with a signature" 40 | (let [request {:request-id "ONELOGIN_809707f0030a5d00620c9d9df97f627afe9dcc24" 41 | :sp-name "SP test" 42 | :acs-url "http://sp.example.com/demo1/index.php?acs" 43 | :idp-url "http://idp.example.com/SSOService.php" 44 | :issuer "http://sp.example.com/demo1/metadata.php" 45 | :credential test/sp-private-key 46 | :relay-state target-uri}] 47 | (is (= {:status 302, 48 | :body "", 49 | :headers 50 | {"Cache-control" "no-cache, no-store" 51 | "Pragma" "no-cache" 52 | "location" (str 53 | "http://idp.example.com/SSOService.php?SAMLRequest=" 54 | "fVLLbtswEPwVgndJFPNwRFg2nLppDbi2YDk59FIw5KomIJEqlz" 55 | "Lcv4%2BsKEFyiK%2B7OzuzMzudn5qaHMGjcTanacwoAaucNvZv" 56 | "Th%2F3D9Ednc%2BmKJuat2LRhYPdwb8OMJAeaFG8dnLaeSucRI" 57 | "PCygZQBCXKxa%2B14DETrXfBKVdTskAEH3qqb85i14AvwR%2BN" 58 | "gsfdOqeHEFqRJNjGcJJNW0OsXJNoaFyaGKvhFLeHdi4VUrLsBR" 59 | "grwyB6xBn9GViW23H7GUfJapnT7eb7evtjtflzx7IJm1SMXTF5" 60 | "oxm75UxlOtNVNqlu%2BURWkGml%2BHUPw0IimiPktJI1wrmCHa" 61 | "wsBmlDTjnjLGJZxK%2F3nIubVDAWM8Z%2BU1KMZ9%2F34gc7L3" 62 | "n0%2FDqE4ud%2BX0Q70MaDCsOSo9HgNz0ip2VBQn86JU9vifVY" 63 | "OuYjBmH%2BYzCXOeVbGnR2yfsGgtQyyLON0%2BQj1ftjnNWtlo" 64 | "WrjfpPHpxvZPiaOo3ToWJ0VA2jorPYgjKVAU2T2cjx%2Bd1mLw" 65 | "%3D%3D&RelayState=http%3A%2F%2Fsp.example.com%2Fde" 66 | "mo1%2Findex.php%3Facs&SigAlg=http%3A%2F%2Fwww.w3.o" 67 | "rg%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=KJSj" 68 | "oD6Mg7OH%2F2pCd6qEDmqSxqWZqOmBePLC5RemNjmLE2ElfnO0" 69 | "tPvTgWDbY7Io5ENEElvsa8eJziZz3TYtFJa1AUDtO2c6BQX627" 70 | "LA7Y0gCvhj035rxJZPPh8ucdTCjNA0roYFpdlQiKQZnUJmJgX2" 71 | "QvB9Zr7WTIEPXMNkb%2B0%3D")}} 72 | (request/idp-redirect-response request))))) 73 | (testing "with a signature and http post binding" 74 | (let [request {:request-id "ONELOGIN_809707f0030a5d00620c9d9df97f627afe9dcc24" 75 | :sp-name "SP test" 76 | :acs-url "http://sp.example.com/demo1/index.php?acs" 77 | :idp-url "http://idp.example.com/SSOService.php" 78 | :issuer "http://sp.example.com/demo1/metadata.php" 79 | :credential test/sp-private-key 80 | :relay-state target-uri 81 | :protocl-binding :post}] 82 | (is (= {:status 302, 83 | :body "", 84 | :headers 85 | {"Cache-control" "no-cache, no-store" 86 | "Pragma" "no-cache" 87 | "location" (str "http://idp.example.com/SSOService.php?SAMLRequest=" 88 | "fVLLbtswEPwVgndJFPNwRFg2nLppDbi2YDk59FIw5KomIJEqlz" 89 | "Lcv4%2BsKEFyiK%2B7OzuzMzudn5qaHMGjcTanacwoAaucNvZv" 90 | "Th%2F3D9Ednc%2BmKJuat2LRhYPdwb8OMJAeaFG8dnLaeSucRI" 91 | "PCygZQBCXKxa%2B14DETrXfBKVdTskAEH3qqb85i14AvwR%2BN" 92 | "gsfdOqeHEFqRJNjGcJJNW0OsXJNoaFyaGKvhFLeHdi4VUrLsBR" 93 | "grwyB6xBn9GViW23H7GUfJapnT7eb7evtjtflzx7IJm1SMXTF5" 94 | "oxm75UxlOtNVNqlu%2BURWkGml%2BHUPw0IimiPktJI1wrmCHa" 95 | "wsBmlDTjnjLGJZxK%2F3nIubVDAWM8Z%2BU1KMZ9%2F34gc7L3" 96 | "n0%2FDqE4ud%2BX0Q70MaDCsOSo9HgNz0ip2VBQn86JU9vifVY" 97 | "OuYjBmH%2BYzCXOeVbGnR2yfsGgtQyyLON0%2BQj1ftjnNWtlo" 98 | "WrjfpPHpxvZPiaOo3ToWJ0VA2jorPYgjKVAU2T2cjx%2Bd1mLw" 99 | "%3D%3D&RelayState=http%3A%2F%2Fsp.example.com%2Fde" 100 | "mo1%2Findex.php%3Facs&SigAlg=http%3A%2F%2Fwww.w3.o" 101 | "rg%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=KJSj" 102 | "oD6Mg7OH%2F2pCd6qEDmqSxqWZqOmBePLC5RemNjmLE2ElfnO0" 103 | "tPvTgWDbY7Io5ENEElvsa8eJziZz3TYtFJa1AUDtO2c6BQX627" 104 | "LA7Y0gCvhj035rxJZPPh8ucdTCjNA0roYFpdlQiKQZnUJmJgX2" 105 | "QvB9Zr7WTIEPXMNkb%2B0%3D")}} 106 | (request/idp-redirect-response request))))))) 107 | 108 | (deftest idp-logout-redirect-response-test 109 | (t/with-clock (t/mock-clock (t/instant "2020-09-24T22:51:00.000Z")) 110 | (let [req-id "ONELOGIN_109707f0030a5d00620c9d9df97f627afe9dcc24" 111 | idp-url "http://idp.example.com/SSOService.php" 112 | user-email "user@example.com" 113 | issuer "http://sp.example.com/demo1/metadata.php"] 114 | (testing "without signing" 115 | 116 | (is (= {:status 302 117 | :headers {"Cache-control" "no-cache, no-store" 118 | "Pragma" "no-cache" 119 | "location" (str 120 | "http://idp.example.com/SSOService.php?SAMLRequest=" 121 | "nZFNS8NAEIb%2FSth7k8naNmZpUoWqBGoLTfXgRZbdSRvIfpjd" 122 | "lP580y%2BIHjx4m2F455mHmc2PqgkO2Lra6IzEIZAAtTCy1ruM" 123 | "vG2fR%2Fdkns8cVw21bGl2pvMb%2FOrQ%2BaBPascuo4x0rWaG" 124 | "u9oxzRU65gUrH1%2BXjIbAbGu8EaYhwaIP1pr7M23vvWVRVEsb" 125 | "4pEr22AojIrKcl1ie6gFhnZvSVAsMrJePS3XL8XqM4Y0gaQCuA" 126 | "M%2BkQBTCiKVqazSpJrShFeYSiHouI8512GhnefaZ4QChRGkIz" 127 | "reUsomMQMIAeCDBO83%2Bf5SclVl53A7VPzbkDuH7cmK5Fcr91" 128 | "NKojJxpNBzyT0%2Fic2iIeoGXvWri8W%2FwF1fPQyYN8BlZX5t" 129 | "f30x%2FwY%3D&RelayState=aHR0cDovL3NwLmV4YW1wbGUuY2" 130 | "9tL2RlbW8xL21ldGFkYXRhLnBocA%3D%3D")} 131 | :body ""} 132 | (request/idp-logout-redirect-response 133 | {:issuer issuer 134 | :user-email user-email 135 | :idp-url idp-url 136 | :relay-state (test/str->base64 issuer) 137 | :request-id req-id})))) 138 | (testing "with signing" 139 | (is (= {:status 302 140 | :headers {"Cache-control" "no-cache, no-store" 141 | "Pragma" "no-cache" 142 | "location" (str 143 | "http://idp.example.com/SSOService.php?SAMLRequest=" 144 | "nZFNS8NAEIb%2FSth7k8naNmZpUoWqBGoLTfXgRZbdSRvIfpjd" 145 | "lP580y%2BIHjx4m2F455mHmc2PqgkO2Lra6IzEIZAAtTCy1ruM" 146 | "vG2fR%2Fdkns8cVw21bGl2pvMb%2FOrQ%2BaBPascuo4x0rWaG" 147 | "u9oxzRU65gUrH1%2BXjIbAbGu8EaYhwaIP1pr7M23vvWVRVEsb" 148 | "4pEr22AojIrKcl1ie6gFhnZvSVAsMrJePS3XL8XqM4Y0gaQCuA" 149 | "M%2BkQBTCiKVqazSpJrShFeYSiHouI8512GhnefaZ4QChRGkIz" 150 | "reUsomMQMIAeCDBO83%2Bf5SclVl53A7VPzbkDuH7cmK5Fcr91" 151 | "NKojJxpNBzyT0%2Fic2iIeoGXvWri8W%2FwF1fPQyYN8BlZX5t" 152 | "f30x%2FwY%3D&RelayState=aHR0cDovL3NwLmV4YW1wbGUuY2" 153 | "9tL2RlbW8xL21ldGFkYXRhLnBocA%3D%3D&SigAlg=http%3A%" 154 | "2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&S" 155 | "ignature=N1dSZcA1AO6Et3%2BHgYNlyvAGnPuflVWyCerVrES" 156 | "jMLNCt%2F%2BshuUkwI%2BkyHsffbRS0iO0lh1bkIcexOFU8ja" 157 | "%2B3t5YcWsr%2B3AkfUeeNOoReeogKh2qIcU9UaHU7tkUj4SQi" 158 | "B%2BnWqfpueLkI8WaSE2hVBCe0qwiLLY4hvkEI2%2Fz5BI%3D")} 159 | :body ""} 160 | (request/idp-logout-redirect-response 161 | {:issuer issuer 162 | :credential test/sp-private-key 163 | :user-email user-email 164 | :idp-url idp-url 165 | :relay-state (test/str->base64 issuer) 166 | :request-id req-id}))))))) 167 | -------------------------------------------------------------------------------- /test/saml20_clj/state_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.state-test 2 | (:require [clojure.test :refer :all] 3 | [java-time.api :as t] 4 | [saml20-clj.coerce :as coerce] 5 | [saml20-clj.sp.request :as request] 6 | [saml20-clj.sp.response :as response] 7 | [saml20-clj.state :as state] 8 | [saml20-clj.test :as test])) 9 | 10 | (deftest in-memory-state-manager-test 11 | (let [m (state/in-memory-state-manager)] 12 | (t/with-clock (t/mock-clock (t/instant "2020-09-25T08:00:00.000Z")) 13 | (testing "record some IDs" 14 | (state/record-request! m 1) 15 | (state/record-request! m 2) 16 | (is (= [[(t/instant "2020-09-25T08:00:00Z") #{1 2}]] 17 | @m)))) 18 | (testing "Move forward to t+1 minute" 19 | (t/with-clock (t/mock-clock (t/instant "2020-09-25T08:01:00.000Z")) 20 | (testing "consume one of the IDs" 21 | (state/accept-response! m 2) 22 | (is (= [[(t/instant "2020-09-25T08:00:00Z") #{1}]] 23 | @m))) 24 | (testing "trying to consume the ID a second time should throw an Exception" 25 | (is (thrown-with-msg? 26 | clojure.lang.ExceptionInfo 27 | #"Invalid request ID" 28 | (state/accept-response! m 2)))) 29 | (testing "Add a few more request IDs" 30 | (state/record-request! m 3) 31 | (state/record-request! m 4) 32 | (is (= [[(t/instant "2020-09-25T08:00:00Z") #{1 3 4}]] 33 | @m))))) 34 | (testing "Move forward to t+3 minutes" 35 | (t/with-clock (t/mock-clock (t/instant "2020-09-25T08:03:00.000Z")) 36 | (testing "Add an ID. Buckets should get rotated" 37 | (state/record-request! m 5) 38 | (is (= [[(t/instant "2020-09-25T08:03:00.000Z") #{5}] 39 | [(t/instant "2020-09-25T08:00:00Z") #{1 3 4}] 40 | nil] 41 | @m))) 42 | (testing "Should be able to consume ID in other bucket" 43 | (state/accept-response! m 3) 44 | (is (= [[(t/instant "2020-09-25T08:03:00.000Z") #{5}] 45 | [(t/instant "2020-09-25T08:00:00Z") #{1 4}] 46 | nil] 47 | @m))))) 48 | (testing "Move forward to t+6 minutes" 49 | (t/with-clock (t/mock-clock (t/instant "2020-09-25T08:06:00.000Z")) 50 | (testing "Consume an ID. Buckets should get rotated" 51 | (state/accept-response! m 1) 52 | (is (= [[(t/instant "2020-09-25T08:06:00.000Z") #{}] 53 | [(t/instant "2020-09-25T08:03:00.000Z") #{5}] 54 | [(t/instant "2020-09-25T08:00:00Z") #{4}]] 55 | @m))) 56 | (testing "Add some more IDs" 57 | (state/record-request! m 6) 58 | (state/record-request! m 7) 59 | (is (= [[(t/instant "2020-09-25T08:06:00.000Z") #{6 7}] 60 | [(t/instant "2020-09-25T08:03:00.000Z") #{5}] 61 | [(t/instant "2020-09-25T08:00:00Z") #{4}]] 62 | @m))))) 63 | (testing "Move forward to t+9 minutes" 64 | (t/with-clock (t/mock-clock (t/instant "2020-09-25T08:09:00.000Z")) 65 | (testing "Attempt to consume now-ancient ID" 66 | (is (thrown-with-msg? 67 | clojure.lang.ExceptionInfo 68 | #"Invalid request ID" 69 | (state/accept-response! m 4)))) 70 | (testing "(buckets won't have been rotated because an Exception was thrown)" 71 | (is (= [[(t/instant "2020-09-25T08:06:00.000Z") #{6 7}] 72 | [(t/instant "2020-09-25T08:03:00.000Z") #{5}] 73 | [(t/instant "2020-09-25T08:00:00Z") #{4}]] 74 | @m))) 75 | (testing "adding a new ID will cause the old bucket to get dropped" 76 | (state/record-request! m 8) 77 | (is (= [[(t/instant "2020-09-25T08:09:00.000Z") #{8}] 78 | [(t/instant "2020-09-25T08:06:00.000Z") #{6 7}] 79 | [(t/instant "2020-09-25T08:03:00.000Z") #{5}]] 80 | @m))))))) 81 | 82 | (deftest e2e-test 83 | (let [m (state/in-memory-state-manager)] 84 | (t/with-clock (t/mock-clock (t/instant "2020-09-25T08:00:00.000Z")) 85 | (testing "generate request" 86 | (request/idp-redirect-response 87 | {:request-id "ABC" 88 | :sp-name "SP test" 89 | :acs-url "http://sp.example.com/demo1/index.php?acs" 90 | :idp-url "http://idp.example.com/SSOService.php" 91 | :issuer "http://sp.example.com/demo1/metadata.php" 92 | :state-manager m})) 93 | (testing "ID should be recorded" 94 | (is (= [[(t/instant "2020-09-25T08:00:00Z") #{"ABC"}]] 95 | @m))) 96 | (testing "Handle response" 97 | (letfn [(handle-response! [] 98 | (-> (str "" 103 | "") 104 | coerce/->Response 105 | test/ring-response-post 106 | (response/validate-response {:state-manager m, :response-validators []})))] 107 | (handle-response!) 108 | (testing "ID should be removed" 109 | (is (= [[(t/instant "2020-09-25T08:00:00Z") #{}]] 110 | @m))) 111 | (testing "Shouldn't be allowed to use ID not recorded in state" 112 | (is (thrown-with-msg? 113 | clojure.lang.ExceptionInfo 114 | #"Invalid request ID" 115 | (handle-response!))))))))) 116 | -------------------------------------------------------------------------------- /test/saml20_clj/test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.test 2 | "Test utils." 3 | (:require [clojure.string :as str] 4 | ring.util.codec 5 | [saml20-clj.coerce :as coerce] 6 | [saml20-clj.encode-decode :as encode-decode]) 7 | (:import [org.apache.commons.codec.binary Base64])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | ;; keystore has SP x.509 cert and private keys under "sp" and IdP X.509 cert under "idp" 12 | (def keystore-filename "test/saml20_clj/test/keystore.jks") 13 | (def keystore-password "123456") 14 | 15 | (defn- bytes->str 16 | ^String [^bytes some-bytes] 17 | (when some-bytes 18 | (String. some-bytes "UTF-8"))) 19 | 20 | (defn- encode-base64 ^bytes [^bytes bs] 21 | (when bs 22 | (Base64/encodeBase64 bs))) 23 | 24 | (defn str->base64 25 | ^String [^String string] 26 | (-> string encode-decode/str->bytes encode-base64 bytes->str)) 27 | 28 | (defn- sample-file [file-name] 29 | (slurp (str "test/saml20_clj/test/" file-name))) 30 | 31 | (def idp-cert 32 | (sample-file "idp.cert")) 33 | 34 | (def sp-cert 35 | (sample-file "sp.cert")) 36 | 37 | (def sp-private-key 38 | (sample-file "sp.private.key")) 39 | 40 | (defmulti response 41 | "Return a sample response (as a raw XML string) with options. 42 | 43 | (response {:message-signed? true})" 44 | {:arglists '([options])} 45 | ;; dispatch value is options as a map with only truthy keys 46 | ;; e.g. (response {:message-signed? false}) -> {} 47 | (fn [options] 48 | (into {} (for [[k v] options 49 | :when v] 50 | [k true])))) 51 | 52 | ;; Metadata tests 53 | 54 | (def metadata-with-key-info (sample-file "metadata-with-keyinfo.xml")) 55 | (def metadata-without-key-info (sample-file "metadata-without-keyinfo.xml")) 56 | 57 | ;; Logout Response 58 | 59 | (def logout-issuer-id "http://idp.example.com/metadata.php") 60 | (def logout-request-id "ONELOGIN_21df91a89767879fc0f7df6a1490c6000c81644d") 61 | 62 | (defn ring-logout-response-post 63 | "Return a ring map of the logout response as an HTTP-Post binding." 64 | [status relay-state & {:keys [signature] :or {signature true}}] 65 | (let [response (sample-file (condp = [status signature] 66 | [:success true] "logout-response-success-with-signature.xml" 67 | [:success :bad] "logout-response-success-with-bad-signature.xml" 68 | [:authnfailed true] "logout-response-authnfailure-with-signature.xml" 69 | [:success false] "logout-response-success-without-signature.xml"))] 70 | {:params {:SAMLResponse (str->base64 response) 71 | :RelayState (str->base64 relay-state)} 72 | :request-method :post 73 | :content-type "application/x-www-form-urlencoded"})) 74 | 75 | (defn ring-logout-response-get 76 | "Return a ring map of the logout response as an HTTP-Redirect binding." 77 | [status & {:keys [signature] :or {signature true}}] 78 | (let [response (-> (condp = [status signature] 79 | [:success true] "logout-response-success-with-signature.edn" 80 | [:success :bad] "logout-response-success-with-bad-signature.edn" 81 | [:authnfailed true] "logout-response-authnfailure-with-signature.edn" 82 | [:success false] "logout-response-success-without-signature.edn") 83 | sample-file 84 | read-string)] 85 | {:query-string (->> (zipmap (->> response keys (map name)) 86 | (vals response)) 87 | (map (partial str/join "=")) 88 | (str/join "&")) 89 | :params (zipmap (->> response keys (map name)) 90 | (->> response vals (map ring.util.codec/url-decode))) 91 | :request-method :get})) 92 | 93 | ;; 94 | ;; Confirmation Data 95 | ;; 96 | 97 | (defn ring-response-post 98 | "Return a ring map of a response as an HTTP-Post binding" 99 | [response & [relay-state]] 100 | {:params {:SAMLResponse (str->base64 (coerce/->xml-string response)) 101 | :RelayState (str->base64 (or relay-state "test-relay-state"))} 102 | :request-method :post 103 | :content-type "application/x-www-form-urlencoded"}) 104 | 105 | (defmethod response {:invalid-confirmation-data? true} 106 | [_] 107 | (sample-file "response-invalid-confirmation-data.xml")) 108 | 109 | (defmethod response {:valid-confirmation-data? true} 110 | [_] 111 | (sample-file "response-valid-confirmation-data.xml")) 112 | 113 | ;; 114 | ;; Signing and Encryption 115 | ;; 116 | 117 | (defmethod response {} 118 | [_] 119 | (sample-file "response-unsigned.xml")) 120 | 121 | (defmethod response {:message-signed? true} 122 | [_] 123 | (sample-file "response-with-signed-message.xml")) 124 | 125 | (defmethod response {:malicious-signature? true} 126 | [_] 127 | (sample-file "response-with-swapped-signature.xml")) 128 | 129 | (defmethod response {:assertion-signed? true} 130 | [_] 131 | (sample-file "response-with-signed-assertion.xml")) 132 | 133 | (defmethod response {:message-signed? true, :assertion-signed? true} 134 | [_] 135 | (sample-file "response-with-signed-message-and-assertion.xml")) 136 | 137 | (defmethod response {:assertion-encrypted? true} 138 | [_] 139 | (sample-file "response-with-encrypted-assertion.xml")) 140 | 141 | (defmethod response {:message-signed? true, :assertion-encrypted? true} 142 | [_] 143 | (sample-file "response-with-signed-message-and-encrypted-assertion.xml")) 144 | 145 | (defmethod response {:assertion-signed? true, :assertion-encrypted? true} 146 | [_] 147 | (sample-file "response-with-signed-and-encrypted-assertion.xml")) 148 | 149 | (defmethod response {:assertion-signed? true, :assertion-encrypted? true :saml2-assertion? true} 150 | [_] 151 | (sample-file "response-with-signed-and-encrypted-saml2-assertion.xml")) 152 | 153 | (defmethod response {:assertion-signed? true, :assertion-encrypted? true :no-namespace-assertion? true} 154 | [_] 155 | (sample-file "response-with-signed-and-encrypted-no-namespace-assertion.xml")) 156 | 157 | (defmethod response {:message-signed? true, :assertion-signed? true, :assertion-encrypted? true} 158 | [_] 159 | (sample-file "response-with-signed-message-and-signed-and-encryped-assertion.xml")) 160 | 161 | (defmethod response {:no-issuer-information? true} 162 | [_] 163 | (sample-file "response-no-issuer.xml")) 164 | 165 | (defn responses 166 | "All the sample responses above but in a convenient format for writing test code that loops over them. 167 | 168 | TODO -- invalid responses with an `:invalid-reason`." 169 | [] 170 | (for [[dispatch-value f] (methods response)] 171 | (assoc dispatch-value :response (f dispatch-value)))) 172 | 173 | (defn signed-and-encrypted-assertion? [response-map] 174 | (or (= {:assertion-signed? true :assertion-encrypted? true} (dissoc response-map :response)) 175 | ((some-fn :saml2-assertion? :no-namespace-assertion?) response-map))) 176 | 177 | (defn assertion-signed? [response-map] 178 | ((some-fn :assertion-signed?) response-map)) 179 | 180 | (defn message-signed? [response-map] 181 | ((some-fn :message-signed?) response-map)) 182 | 183 | (defn assertions-encrypted? [response-map] 184 | ((some-fn :assertion-encrypted?) response-map)) 185 | 186 | (defn valid-confirmation-data? [response-map] 187 | ((some-fn :valid-confirmation-data?) response-map)) 188 | 189 | (defn invalid-confirmation-data? [response-map] 190 | ((some-fn :invalid-confirmation-data?) response-map)) 191 | 192 | (defn malicious-signature? [response-map] 193 | ((some-fn :malicious-signature?) response-map)) 194 | 195 | (defn describe-response-map 196 | "Human-readable string description of a response map (from `responses`), useful for `testing` context when writing 197 | test code that loops over various responses." 198 | [{:keys [message-signed? malicious-signature? assertion-signed? assertion-encrypted? valid-confirmation-data? invalid-confirmation-data?], :as m}] 199 | (format "Response with %s message, %s %s%s %s assertion\n%s" 200 | (if message-signed? "SIGNED" "unsigned") 201 | (if malicious-signature? "MALICIOUS" "not-malicious") 202 | (cond valid-confirmation-data? "VALID confirmation data, " 203 | invalid-confirmation-data? "INVALID confiration data, " 204 | :else "") 205 | (if assertion-signed? "SIGNED" "unsigned") 206 | (if assertion-encrypted? "ENCRYPTED" "unencrypted") 207 | (pr-str (list 'saml20-clj.test/response (dissoc m :response))))) 208 | -------------------------------------------------------------------------------- /test/saml20_clj/test/idp.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czET 3 | MBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNV 4 | BAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQy 5 | MjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQK 6 | DAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqG 7 | SIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOk 8 | Nb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkW 9 | rzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS 10 | /7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYD 11 | VR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkq 12 | hkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6mi 13 | JoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cB 14 | SC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /test/saml20_clj/test/idp.private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAM6qlNun1UmrRpdB 3 | tXp24QMabwN5/kXosy1KA6Q1vcZXDmcGuxC3EjZHpoHCBWmA1uRsbpYtDFFYSkSh 4 | JsozSRSV7dg3Tj6T97v4uRavMHHd3KR2ESoX6BDIrt9aN+PflQmpF4zxxLPFQdAo 5 | cFwv4Pn1UDIkLewDWa+4qxL/tEgzAgMBAAECgYEAgPdAF2a/mc5RKdiuaDLBLc+U 6 | EUZSn8ub7JowdYu31orDCRXRLJghvE+wH281M2ZcM4Va6UpJUoXXKsapzyp74kat 7 | yQMDzX6KcCD1Ghm6gKIoPd7+3FLQSULe3/B+dsP0Qd9fn5lCjXucISrmcWGm+6Cp 8 | Y/lQlbfY4PEqBcTKQDECQQDms4tXd2KER/4FAtgRuZNlDA2MSwsIKzZ+7lDGYHj/ 9 | gJFYz/QxgTUscxfyvOX4spni4dow3IqG02bnM2EVWj/LAkEA5VROAjSwMRAxQhS7 10 | ujLRf3Nm+bgHZvFqEINmzTCNjqrG+XFpQozz7r+UNEa5WXZ9nqJRzJE6kulYKQo7 11 | uOm8OQJBANoeyVDiTF/7XT1JqhZgGe7AzZJmZUDM8pJiKjbVgbE40rNgsmWX3zlu 12 | fCcB2IEiBAMSPORTsBm3iKRx3Il+sa8CQQDYV/nnttDo2D+/+VNISxy8QaBJwMSq 13 | 5uCKh4v50u4YQx/GvqjwcYzGFNG5p2a/8Tp85uCIPhGl+qCCxvakcUxxAkEApJnF 14 | PjjEtQvM8uCM1xHsNhvhhpqDCf+VSOjZipkkvU///ggDQTCiBiSrlrE3Lfwaa+jm 15 | SrSqjJ6d+SrA9sotYg== 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /test/saml20_clj/test/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabase/saml20-clj/72b45702afe821e0c9f7781b0575b06414f594e2/test/saml20_clj/test/keystore.jks -------------------------------------------------------------------------------- /test/saml20_clj/test/logout-response-authnfailure-with-signature.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | MupMW1Xq3fa+tSg21WjYf0AiNDo=RmIKpmDUheMJY2KG4XaZ4siD+dIYhiMqMNcHkl8jVrB+DcRqyNCHvNtYyV3Da4tMQVS0nAOAczGJnojD9EsZXiJkvEZmMDIJup/yaX2VZ4p1Wu0hXvVvucUObL6wEquKsYwWyXPwDXfgb1kqY2hgmUMImT5WwDyFfSwdT5E+GTg= 7 | MIICZjCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20wHhcNMjAwOTIzMTc0MzA2WhcNMzAwOTIxMTc0MzA2WjBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMCOR6lM1raadHr3MnDU7ydGHUmMhZ5ZImwSHcxYrY6/F3TW+S6CPMuAfHJsNQZ57nG4wUhNCbfXdumfVxzoPMzD7oivKKVxeMK6HaUuGsGg9OK4ON++EVxomWdmPyJdHpiUaGveGU0BQgzI7aqNibncPYPxJgK9DZEIfDjp05lDAgMBAAGjUDBOMB0GA1UdDgQWBBStKfCHxILkLbv2tAEK54+Wn/xF+zAfBgNVHSMEGDAWgBStKfCHxILkLbv2tAEK54+Wn/xF+zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAIRA7mJdPPmTWc3wsPLDv+nMeR0nr5a6r8dZU5lOTqGfC43YvJ1NEysO3AB6YuiG1KKXERxtlISyYvU9wNrna2IPDU0njcU/a3dEBqa32lD3GxfUvbpzIcZovBYqQ7Jhfa86GvNKxRoyUEExVqyHh6i44S4NCJvr8IdnRilYBksl 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/saml20_clj/test/logout-response-success-with-bad-signature.edn: -------------------------------------------------------------------------------- 1 | {:SAMLResponse "fZHLasMwEEV%2FxWhvW4pVP0ScUJpSAmkCTciim6JI48RgS8Ijl3x%2BnRdNoWQ5jzN35s54emyb4Bs6rK0pCYsoCcAoq2uzL0nvqzAn08kYZds4sbB72%2FsPQGcNQjCQBsW5NLR2RliJNQojW0DhlVg%2Fvy%2FEKKLCddZbZRtyhzwmJCJ0fliJBPNZSVx1hCR54kVRhZAluzCnOx2mVPGQpaO8yLkEzSsSbG%2BHjE6HzBF7mBv00vghRRkPaRayfEOZYImg6ScJZoC%2BNtKfqYP3TsQxugiOsnUNRMq2sYbWsrg2Go6RO7ipVDjMNjcfNrYkq%2BXrYvU2X36NmK4KJvMiS7M8KypFq0xXqWS8oCqllKqcpZxrcvFUnFfsJlfhWv9VbsFLLb08yY7je%2BD6kbWXvse%2F0YvVEGxl08Njj%2FHcLda9UoBI4slF4Xdo%2FN%2FXJz8%3D" 2 | :Signature "O8UEXl%2FdZDlMqIXoxF8LQvONrr%2BMIR76TCi9YfA3othNgwz3KRBF%2F%2FUqgioOrLq5hcN%2BMBEbdXESMb7bDoP8a3Yv%2B2T1E2JFNEBjcPJP2WPBxwrhIZTruG4QNOpGhgUYzrapekN%2F7lRzXfQXjaMxtwGZH9AWdjH0lYz05dAntig%3D" 3 | :SigAlg "http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1" 4 | :RelayState "test-relay-state"} 5 | -------------------------------------------------------------------------------- /test/saml20_clj/test/logout-response-success-with-bad-signature.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | rBtyl8hzcz1K+CjbfwV5xjjIxAA=KMdIWgijF47EpfBYi2/2qVZZh/TYaP6L3rzaC1hayLdpM65HBxWYU8OvmHAHlNXiXkNyaaH9UHYl/dPP17fsFyfiTn/ghERyA3PjPkCrapIHQpSjZ3UZezLevEcKYlig6mClptM+Y6WXbKeCH9JBNInPVeis8d5zXmLRY2qYFOc= 7 | MIICZjCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20wHhcNMjAwOTIzMTc0MzA2WhcNMzAwOTIxMTc0MzA2WjBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMCOR6lM1raadHr3MnDU7ydGHUmMhZ5ZImwSHcxYrY6/F3TW+S6CPMuAfHJsNQZ57nG4wUhNCbfXdumfVxzoPMzD7oivKKVxeMK6HaUuGsGg9OK4ON++EVxomWdmPyJdHpiUaGveGU0BQgzI7aqNibncPYPxJgK9DZEIfDjp05lDAgMBAAGjUDBOMB0GA1UdDgQWBBStKfCHxILkLbv2tAEK54+Wn/xF+zAfBgNVHSMEGDAWgBStKfCHxILkLbv2tAEK54+Wn/xF+zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAIRA7mJdPPmTWc3wsPLDv+nMeR0nr5a6r8dZU5lOTqGfC43YvJ1NEysO3AB6YuiG1KKXERxtlISyYvU9wNrna2IPDU0njcU/a3dEBqa32lD3GxfUvbpzIcZovBYqQ7Jhfa86GvNKxRoyUEExVqyHh6i44S4NCJvr8IdnRilYBksl 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/saml20_clj/test/logout-response-success-with-signature.edn: -------------------------------------------------------------------------------- 1 | {:SAMLResponse "fZHLasMwEEV%2FxWhvW4pVP0ScUJpSAmkCTciim6JI48RgS8Ijl3x%2BnRdNoWQ5jzN35s54emyb4Bs6rK0pCYsoCcAoq2uzL0nvqzAn08kYZds4sbB72%2FsPQGcNQjCQBsW5NLR2RliJNQojW0DhlVg%2Fvy%2FEKKLCddZbZRtyhzwmJCJ0fliJBPNZSVx1hCR54kVRhZAluzCnOx2mVPGQpaO8yLkEzSsSbG%2BHjE6HzBF7mBv00vghRRkPaRayfEOZYImg6ScJZoC%2BNtKfqYP3TsQxugiOsnUNRMq2sYbWsrg2Go6RO7ipVDjMNjcfNrYkq%2BXrYvU2X36NmK4KJvMiS7M8KypFq0xXqWS8oCqllKqcpZxrcvFUnFfsJlfhWv9VbsFLLb08yY7je%2BD6kbWXvse%2F0YvVEGxl08Njj%2FHcLda9UoBI4slF4Xdo%2FN%2FXJz8%3D" 2 | :Signature "O8UEXl%2FdoDlMqIXoxF8LQvONrr%2BMIR76TCi9YfA3othNgwz3KRBF%2F%2FUqgioOrLq5hcN%2BMBEbdXESMb7bDoP8a3Yv%2B2T1E2JFNEBjcPJP2WPBxwrhIZTruG4QNOpGhgUYzrapekN%2F7lRzXfQXjaMxtwGZH9AWdjH0lYz05dAntig%3D" 3 | :SigAlg "http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1" 4 | :RelayState "test-relay-state"} 5 | -------------------------------------------------------------------------------- /test/saml20_clj/test/logout-response-success-with-signature.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | m7XJQJUS2sYpvSii5girw9RHnMw=vz90lqoqtksqLgpxxt7pn29kykHZRONYoHcapZvp0Nc12k/jx+oZ4OpXOEf6YvktOup3BaB+t05VNFpuJckAa//HunwLkZvdfWt2eSyn+Bq5b0+5fnAVWjK2cqh1X0mfZFOXi7DvV1wLtXr8Tgb5QT4tD73YuTI29nT9Gs+ggkE= 7 | MIICZjCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20wHhcNMjAwOTIzMTc0MzA2WhcNMzAwOTIxMTc0MzA2WjBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMCOR6lM1raadHr3MnDU7ydGHUmMhZ5ZImwSHcxYrY6/F3TW+S6CPMuAfHJsNQZ57nG4wUhNCbfXdumfVxzoPMzD7oivKKVxeMK6HaUuGsGg9OK4ON++EVxomWdmPyJdHpiUaGveGU0BQgzI7aqNibncPYPxJgK9DZEIfDjp05lDAgMBAAGjUDBOMB0GA1UdDgQWBBStKfCHxILkLbv2tAEK54+Wn/xF+zAfBgNVHSMEGDAWgBStKfCHxILkLbv2tAEK54+Wn/xF+zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAIRA7mJdPPmTWc3wsPLDv+nMeR0nr5a6r8dZU5lOTqGfC43YvJ1NEysO3AB6YuiG1KKXERxtlISyYvU9wNrna2IPDU0njcU/a3dEBqa32lD3GxfUvbpzIcZovBYqQ7Jhfa86GvNKxRoyUEExVqyHh6i44S4NCJvr8IdnRilYBksl 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/saml20_clj/test/logout-response-success-without-signature.edn: -------------------------------------------------------------------------------- 1 | {:SAMLResponse "fZHLasMwEEV%2FxWhvW4pVP0ScUJpSAmkCTciim6JI48RgS8Ijl3x%2BnRdNoWQ5jzN35s54emyb4Bs6rK0pCYsoCcAoq2uzL0nvqzAn08kYZds4sbB72%2FsPQGcNQjCQBsW5NLR2RliJNQojW0DhlVg%2Fvy%2FEKKLCddZbZRtyhzwmJCJ0fliJBPNZSVx1hCR54kVRhZAluzCnOx2mVPGQpaO8yLkEzSsSbG%2BHjE6HzBF7mBv00vghRRkPaRayfEOZYImg6ScJZoC%2BNtKfqYP3TsQxugiOsnUNRMq2sYbWsrg2Go6RO7ipVDjMNjcfNrYkq%2BXrYvU2X36NmK4KJvMiS7M8KypFq0xXqWS8oCqllKqcpZxrcvFUnFfsJlfhWv9VbsFLLb08yY7je%2BD6kbWXvse%2F0YvVEGxl08Njj%2FHcLda9UoBI4slF4Xdo%2FN%2FXJz8%3D" 2 | :RelayState "test-relay-state"} 3 | -------------------------------------------------------------------------------- /test/saml20_clj/test/logout-response-success-without-signature.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/saml20_clj/test/metadata-with-keyinfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MIICZjCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwK 7 | Q2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20w 8 | HhcNMjAwOTIzMTc0MzA2WhcNMzAwOTIxMTc0MzA2WjBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwK 9 | Q2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20w 10 | gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMCOR6lM1raadHr3MnDU7ydGHUmMhZ5ZImwSHcxY 11 | rY6/F3TW+S6CPMuAfHJsNQZ57nG4wUhNCbfXdumfVxzoPMzD7oivKKVxeMK6HaUuGsGg9OK4ON++ 12 | EVxomWdmPyJdHpiUaGveGU0BQgzI7aqNibncPYPxJgK9DZEIfDjp05lDAgMBAAGjUDBOMB0GA1Ud 13 | DgQWBBStKfCHxILkLbv2tAEK54+Wn/xF+zAfBgNVHSMEGDAWgBStKfCHxILkLbv2tAEK54+Wn/xF 14 | +zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAIRA7mJdPPmTWc3wsPLDv+nMeR0nr5a6 15 | r8dZU5lOTqGfC43YvJ1NEysO3AB6YuiG1KKXERxtlISyYvU9wNrna2IPDU0njcU/a3dEBqa32lD3 16 | GxfUvbpzIcZovBYqQ7Jhfa86GvNKxRoyUEExVqyHh6i44S4NCJvr8IdnRilYBksl 17 | 18 | 19 | 20 | 21 | 22 | 23 | MIICZjCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwK 24 | Q2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20w 25 | HhcNMjAwOTIzMTc0MzA2WhcNMzAwOTIxMTc0MzA2WjBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwK 26 | Q2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20w 27 | gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMCOR6lM1raadHr3MnDU7ydGHUmMhZ5ZImwSHcxY 28 | rY6/F3TW+S6CPMuAfHJsNQZ57nG4wUhNCbfXdumfVxzoPMzD7oivKKVxeMK6HaUuGsGg9OK4ON++ 29 | EVxomWdmPyJdHpiUaGveGU0BQgzI7aqNibncPYPxJgK9DZEIfDjp05lDAgMBAAGjUDBOMB0GA1Ud 30 | DgQWBBStKfCHxILkLbv2tAEK54+Wn/xF+zAfBgNVHSMEGDAWgBStKfCHxILkLbv2tAEK54+Wn/xF 31 | +zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAIRA7mJdPPmTWc3wsPLDv+nMeR0nr5a6 32 | r8dZU5lOTqGfC43YvJ1NEysO3AB6YuiG1KKXERxtlISyYvU9wNrna2IPDU0njcU/a3dEBqa32lD3 33 | GxfUvbpzIcZovBYqQ7Jhfa86GvNKxRoyUEExVqyHh6i44S4NCJvr8IdnRilYBksl 34 | 35 | 36 | 37 | 38 | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress 39 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient 40 | urn:oasis:names:tc:SAML:2.0:nameid-format:persistent 41 | urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified 42 | urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/saml20_clj/test/metadata-without-keyinfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress 5 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient 6 | urn:oasis:names:tc:SAML:2.0:nameid-format:persistent 7 | urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified 8 | urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-invalid-confirmation-data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | 7 | 8 | idp.example.com 9 | 10 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 11 | 12 | 13 | 14 | 15 | 16 | 17 | sp.example.com 18 | 19 | 20 | 21 | 22 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 23 | 24 | 25 | 26 | 27 | test 28 | 29 | 30 | test@example.com 31 | 32 | 33 | users 34 | examplerole1 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-no-issuer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 9 | 10 | 11 | 12 | 13 | 14 | 15 | sp.example.com 16 | 17 | 18 | 19 | 20 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 21 | 22 | 23 | 24 | 25 | test 26 | 27 | 28 | test@example.com 29 | 30 | 31 | users 32 | examplerole1 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-unsigned.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | 7 | 8 | idp.example.com 9 | 10 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 11 | 12 | 13 | 14 | 15 | 16 | 17 | sp.example.com 18 | 19 | 20 | 21 | 22 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 23 | 24 | 25 | 26 | 27 | test 28 | 29 | 30 | test@example.com 31 | 32 | 33 | users 34 | examplerole1 35 | 36 | 37 | test-group1 38 | 39 | 40 | test-group2 41 | test-group3 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-valid-confirmation-data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | 7 | 8 | idp.example.com 9 | 10 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 11 | 12 | 13 | 14 | 15 | 16 | 17 | sp.example.com 18 | 19 | 20 | 21 | 22 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 23 | 24 | 25 | 26 | 27 | test 28 | 29 | 30 | test@example.com 31 | 32 | 33 | users 34 | examplerole1 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-encrypted-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | bq3XdFQAU2ngvVuJKdrgMPEsayQTOg5ezxz0Nk922WjA2AB4z9b+xgO1X6NCI6A6XHyplohFnnyOLM5XQaVRtzgIoF6GldOJboGaEroAV/2935cLhEeofPuM5CoQL3M91XVNhQ75l/EjbekAnwi2N05lCVCMG80I1WDqsxujzkk= 15 | 16 | 17 | 18 | 19 | 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 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-and-encrypted-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | idp.example.com 3 | 4 | 5 | 6 | 7 | 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 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-and-encrypted-no-namespace-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | idp.example.com 3 | 4 | 5 | 6 | 7 | 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 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-and-encrypted-saml2-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | idp.example.com 3 | 4 | 5 | 6 | 7 | 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 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | 7 | 8 | idp.example.com 9 | 10 | 11 | 82irlwcnm+MkNtGLhd5L80Jlv9k=EJ1xlCJ/jFikKbDq+WRBKp0dnPuwUvf94p87ecmTByY/k7YTLraxTjlsc1KzanA7kMzsY1mu2eYXsHhyCWaNfOlohouOJcTY+toaaeFQZIcY77VHuZslfB5/q+ua6yBk/xEpwvWcQbX4S8s74rKfcy5LHq4il4TGRvIlhgLimYw= 12 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNVBAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQyMjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOkNb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkWrzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS/7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYDVR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6miJoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cBSC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 13 | 14 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 15 | 16 | 17 | 18 | 19 | 20 | 21 | sp.example.com 22 | 23 | 24 | 25 | 26 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 27 | 28 | 29 | 30 | 31 | test 32 | 33 | 34 | test@example.com 35 | 36 | 37 | users 38 | examplerole1 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-message-and-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | ZRR3bO41L6Wy97z7gZryH9M+VUM=rCbA5V2cEiLOHUZ8/NvDfSwr/yoegEcRmCLGE7kYpHlvz2byVIhG0ZQ9eTthyYKbS8lUMDeuMV7BXdYA7j1YKac7OmvZbiP1z/u0DjgzI+BSXJwShsR98CJKEzCVkF50xqdNvMaOjNHrGC972lDf7UKVlBYBEPfl36J/+EvW9ro= 7 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNVBAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQyMjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOkNb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkWrzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS/7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYDVR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6miJoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cBSC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 8 | 9 | 10 | 11 | 12 | idp.example.com 13 | 14 | 15 | JAHmZU8QW7CYQbdp6wzgCYTYDfY=RCgMxMJpBZZHRd58gzZcJG/gz7cib8Q1IGh00saevd/FgBxncIOGN1VEcuLzh3FcgHAhP1yjRZWWEcNeVWR/3NJdkZB6fY8XBZg1GflxvLvV7ZYoaWCAjUy1Ug/BXifa8wpIKeNFSRyz7xEEtQ2iLC9Hmy8DEpA2mPkN7Hy9IC4= 16 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNVBAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQyMjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOkNb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkWrzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS/7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYDVR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6miJoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cBSC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 17 | 18 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 19 | 20 | 21 | 22 | 23 | 24 | 25 | sp.example.com 26 | 27 | 28 | 29 | 30 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 31 | 32 | 33 | 34 | 35 | test 36 | 37 | 38 | test@example.com 39 | 40 | 41 | users 42 | examplerole1 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-message-and-encrypted-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | ZwtYFyFCP+hh8yNdDQAC+1Ohbug=xtbQVFT1afSL3gcQZY1vwBFHV5bnHti3hbNlP0Um85vrKzV5hznJ2koAHxhOxqDEtdg4/17Y09X9Wxkr7g4CW9V8besfiEuVYWJoAF3QxQ5LKBxqU+VD38d5jg+vxBKu8iR9NPDghZ6Hvk5Sh1oXA7mDETdnjF/2ne8TwyP/zQE= 7 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNVBAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQyMjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOkNb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkWrzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS/7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYDVR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6miJoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cBSC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | bq3XdFQAU2ngvVuJKdrgMPEsayQTOg5ezxz0Nk922WjA2AB4z9b+xgO1X6NCI6A6XHyplohFnnyOLM5XQaVRtzgIoF6GldOJboGaEroAV/2935cLhEeofPuM5CoQL3M91XVNhQ75l/EjbekAnwi2N05lCVCMG80I1WDqsxujzkk= 19 | 20 | 21 | 22 | 23 | 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 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-message-and-signed-and-encryped-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | o+H48AO2W6gUqzeKMbkzGFEeiGw=XusPe0BSQiUsORxARxPzOP0u44i6eZgBAKdQfBz+G6Xe0sQ32FOy4R4bRIqei1lCqG7TlJ9S10H1zn5ZNKFLzAkbvMZV0eKutdoyTO7YnCMUwkmS1NFNU0gpu8H2bCpNOxsUmyKcsnhvN6XEoApJZb+E5deXi2fPqBoX2sc9K4U= 7 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNVBAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQyMjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOkNb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkWrzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS/7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYDVR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6miJoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cBSC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 8 | 9 | 10 | 11 | 12 | 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 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-signed-message.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | P8zGA0epxZYRLGW26rsNmqDagp4=YSBcClcbbNn5uZUEnh30nHC2QzNzckK0LWMgEDDB7XPLP17m+LOigkGTvUs7Z52dLXC8LU5pTYxSLvvPTfddi/JMBjwEO9oe2fAvrrA1MQj+W3zogL35ybynLBgpi5uf8yCLzFr6ssinWZZfuS/zgYlNbs97aPQC//0BJFwfdso= 7 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNVBAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQyMjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOkNb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkWrzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS/7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYDVR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6miJoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cBSC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 8 | 9 | 10 | 11 | 12 | idp.example.com 13 | 14 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 15 | 16 | 17 | 18 | 19 | 20 | 21 | sp.example.com 22 | 23 | 24 | 25 | 26 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 27 | 28 | 29 | 30 | 31 | test 32 | 33 | 34 | test@example.com 35 | 36 | 37 | users 38 | examplerole1 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/saml20_clj/test/response-with-swapped-signature.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | idp.example.com 4 | 5 | 6 | ZRR3bO41L6Wy97z7gZryH9M+VUM=rCbA5V2cEiLOHUZ8/NvDfSwr/yoegEcRmCLGE7kYpHlvz2byVIhG0ZQ9eTthyYKbS8lUMDeuMV7BXdYA7j1YKac7OmvZbiP1z/u0DjgzI+BSXJwShsR98CJKEzCVkF50xqdNvMaOjNHrGC972lDf7UKVlBYBEPfl36J/+EvW9ro= 7 | MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJZHAxGDAWBgNVBAMMD2lkcC5leGFtcGxlLmNvbTAeFw0yMDA5MjMxNzQyMjRaFw0zMDA5MjExNzQyMjRaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxlIElkcDEYMBYGA1UEAwwPaWRwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOqpTbp9VJq0aXQbV6duEDGm8Def5F6LMtSgOkNb3GVw5nBrsQtxI2R6aBwgVpgNbkbG6WLQxRWEpEoSbKM0kUle3YN04+k/e7+LkWrzBx3dykdhEqF+gQyK7fWjfj35UJqReM8cSzxUHQKHBcL+D59VAyJC3sA1mvuKsS/7RIMwIDAQABo1AwTjAdBgNVHQ4EFgQUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwHwYDVR0jBBgwFoAUviPb7EZV3vBqOlKlyxI/Ps2Z/9AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQBus/4hCWuEVWX882TaUXmIr3yMuICxm+5VEt4dmiQrj6miJoV6kNTPuiyskPe5SK/5VBJMSSm1eqbW7nTaUYrnwUaYT8UBAfkBgAEM/PhuI8cBSC3YoVPZwQbmywKMsprSMfE4K9eOyay9796lddrdMVkD8MgD8Z1i5+vvw7kpcg== 8 | 9 | 10 | 11 | 12 | idp.example.com 13 | 14 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 15 | 16 | 17 | 18 | 19 | 20 | 21 | sp.example.com 22 | 23 | 24 | 25 | 26 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 27 | 28 | 29 | 30 | 31 | test 32 | 33 | 34 | test@example.com 35 | 36 | 37 | users 38 | examplerole1 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/saml20_clj/test/sp.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICZjCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBQMQswCQYDVQQGEwJ1czET 3 | MBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKRXhhbXBsZSBTUDEXMBUGA1UE 4 | AwwOc3AuZXhhbXBsZS5jb20wHhcNMjAwOTIzMTc0MzA2WhcNMzAwOTIxMTc0MzA2 5 | WjBQMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwK 6 | RXhhbXBsZSBTUDEXMBUGA1UEAwwOc3AuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcN 7 | AQEBBQADgY0AMIGJAoGBAMCOR6lM1raadHr3MnDU7ydGHUmMhZ5ZImwSHcxYrY6/ 8 | F3TW+S6CPMuAfHJsNQZ57nG4wUhNCbfXdumfVxzoPMzD7oivKKVxeMK6HaUuGsGg 9 | 9OK4ON++EVxomWdmPyJdHpiUaGveGU0BQgzI7aqNibncPYPxJgK9DZEIfDjp05lD 10 | AgMBAAGjUDBOMB0GA1UdDgQWBBStKfCHxILkLbv2tAEK54+Wn/xF+zAfBgNVHSME 11 | GDAWgBStKfCHxILkLbv2tAEK54+Wn/xF+zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 12 | DQEBDQUAA4GBAIRA7mJdPPmTWc3wsPLDv+nMeR0nr5a6r8dZU5lOTqGfC43YvJ1N 13 | EysO3AB6YuiG1KKXERxtlISyYvU9wNrna2IPDU0njcU/a3dEBqa32lD3GxfUvbpz 14 | IcZovBYqQ7Jhfa86GvNKxRoyUEExVqyHh6i44S4NCJvr8IdnRilYBksl 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /test/saml20_clj/test/sp.private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMCOR6lM1raadHr3 3 | MnDU7ydGHUmMhZ5ZImwSHcxYrY6/F3TW+S6CPMuAfHJsNQZ57nG4wUhNCbfXdumf 4 | VxzoPMzD7oivKKVxeMK6HaUuGsGg9OK4ON++EVxomWdmPyJdHpiUaGveGU0BQgzI 5 | 7aqNibncPYPxJgK9DZEIfDjp05lDAgMBAAECgYAuxOAnAODN7WoJS+1WdoG3+lG2 6 | wYja0y+HLEdMkOVm3Icn++b+Iuf9TbHsYNfoQMlnmcZodz27B3x8dhVEkivh7HxA 7 | Z22hjSKfnqyeC1ZjGcFSZP3sGNePRFxp5mCd9JU1Ux6yMnhs9prcHMlgaMAzk3s/ 8 | wXDXkUplxDnoIpXawQJBAOLBXpliwgzubSqoUb078wwUQ3i/WpQ4z0G7ShEAdNi1 9 | 5q6Hy+Alpy3HSVFk8iPS7kdgDSRXb9D7UYXc3jPKd1MCQQDZY8QYcS614egt1Y01 10 | rhoy5YI67zGeeyzRw7w27+7TITniuZZ8D2IJxtJlqJvQ/ExXrJ4w3LXRoeEYrT5l 11 | 6chRAkB+4xgNmw7db5oU8cVzsBVMYBZ1fhawtK19qdFDfE7mabuhVIoIbumDG25y 12 | pps84q7FsAEKogcHGWtADh0lPBnTAkA2mvbQ5O1ExqAZk0DLMRZnnnrd7uNZ2bri 13 | XfZTHmWxJvFxYAjK1NpddlG3M2kgT5+ljjiWMXqCU9VE64927ghhAkAIpNockBpU 14 | ybtYxj6oSKJnzVjwktR+TLBCiIfCriZk7uPL2E2bzTQw7dzre/L9xiShNQ+7w4GY 15 | Y/hvjX3aBn5+ 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /test/saml20_clj/xml_test.clj: -------------------------------------------------------------------------------- 1 | (ns saml20-clj.xml-test 2 | (:require [clojure.test :refer :all] 3 | [saml20-clj.xml :as xml])) 4 | 5 | (deftest str->xmldoc-test 6 | (testing "str->xmldoc errors if the input XML contains a DOCTYPE declaration" 7 | (let [xml-str (str 8 | "" 9 | "]>" 10 | "&test;")] 11 | (is (thrown? org.xml.sax.SAXParseException 12 | (xml/str->xmldoc xml-str)))))) 13 | --------------------------------------------------------------------------------