├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── build.clj ├── deps.edn ├── doc.clj ├── doc ├── Makefile └── user-guide.md ├── examples ├── httpbasic │ ├── .gitignore │ ├── resources │ │ ├── index.html │ │ └── login.html │ └── src │ │ └── authexample │ │ └── web.clj ├── jwe │ ├── .gitignore │ └── src │ │ └── authexample │ │ └── web.clj ├── jws │ ├── .gitignore │ └── src │ │ └── authexample │ │ └── web.clj ├── session │ ├── .gitignore │ ├── resources │ │ ├── index.html │ │ └── login.html │ └── src │ │ └── authexample │ │ └── web.clj └── token │ ├── .gitignore │ └── src │ └── authexample │ └── web.clj ├── mvn-upload.sh ├── src └── buddy │ ├── auth.clj │ └── auth │ ├── accessrules.clj │ ├── backends.clj │ ├── backends │ ├── httpbasic.clj │ ├── session.clj │ └── token.clj │ ├── http.clj │ ├── middleware.clj │ └── protocols.clj └── test ├── _files ├── privkey.3des.dsa.pem ├── privkey.3des.rsa.pem ├── privkey.ecdsa.pem ├── pubkey.3des.dsa.pem ├── pubkey.3des.rsa.pem └── pubkey.ecdsa.pem └── buddy └── auth ├── accessrules_tests.clj ├── backends ├── httpbasic_tests.clj ├── session_tests.clj └── token_tests.clj └── middleware_tests.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | *.swp 9 | /.lein-* 10 | /.nrepl-port 11 | /doc/dist 12 | \#*\# 13 | *~ 14 | .\#* 15 | *.iml 16 | .cpcache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein 3 | 4 | script: 5 | - lein test 6 | 7 | jdk: 8 | - openjdk8 9 | - openjdk11 10 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 3.0.323 4 | 5 | Date: 2022-01-14 6 | 7 | - Update dependencies. 8 | - Minimum JDK == 8. 9 | 10 | 11 | ## Version 3.0.1 12 | 13 | Date: 2021-05-02 14 | 15 | - Update buddy-sign to 3.4.1 16 | 17 | 18 | ## Version 3.0.0 19 | 20 | Date: 2021-05-02 21 | 22 | - Dependencies update 23 | - Documentation changes. 24 | 25 | 26 | ## Version 2.2.0 27 | 28 | Date: 2018-06-28 29 | 30 | - Add support for async ring handlers 31 | - Update deps. 32 | 33 | 34 | ## Version 2.1.0 35 | 36 | Date: 2017-08-29 37 | 38 | - Update buddy-sign to 2.2.0 39 | 40 | 41 | ## Version 2.0.0 42 | 43 | Date: 2017-08-10 44 | 45 | - Allow keywords for HTTP headers as well as strings 46 | - Update to use Clojure 1.9.0-alpha17 47 | - Update buddy-sign to 2.0.0 (implicit breaking change no longer handling `iat` validation) 48 | - Update cuerdas to 2.0.2 49 | 50 | ## Version 1.4.1 51 | 52 | Date: 2017-01-29 53 | 54 | - Fix some backward incompatibilities introduced in previous commit. 55 | 56 | 57 | ## Version 1.4.0 58 | 59 | Date: 2017-01-24 60 | 61 | - Add `authfn` parameter to the rest of backends (thanks to @rymndhng) 62 | - Respect the value of `:identity` on request when no auth backend has 63 | authenticated the request (usefull for tests). 64 | 65 | ## Version 1.3.0 66 | 67 | Date: 2016-11-15 68 | 69 | - Update buddy-sign to 1.3.0 70 | - Update cuerdas to 2.0.1 71 | 72 | 73 | ## Version 1.2.0 74 | 75 | Date: 2016-09-01 76 | 77 | - Update buddy-sign to 1.2.0 78 | - Update cuerdas to 1.0.1 79 | 80 | 81 | ## Version 1.1.0 82 | 83 | Date: 2016-06-11 84 | 85 | - Update buddy-sign to 1.1.0 86 | 87 | 88 | ## Version 1.0.0 89 | 90 | Date: 2016-05-21 91 | 92 | **Important**: This is an major release beacause it includes breaking api changes. 93 | 94 | - Update buddy-sign dependency to 1.0.0 that includes breaking changes. For 95 | more information, refer to buddy-sign release notes: 96 | https://github.com/funcool/buddy-sign/blob/master/CHANGES.adoc#version-100 97 | 98 | 99 | 100 | ## Version 0.13.0 101 | 102 | Date: 2016-04-23 103 | 104 | - Update buddy-sign dependency to 0.13.0. 105 | 106 | 107 | ## Version 0.12.0 108 | 109 | Date: 2016-04-09 110 | 111 | - Update buddy-sign dependency to 0.12.0 112 | - Improve backends api (fully backward compatible). 113 | 114 | 115 | ## Version 0.11.0 116 | 117 | Date: 2016-03-27 118 | 119 | - Update buddy-sign dependency to 0.11.0 120 | 121 | 122 | ## Version 0.10.0 123 | 124 | Date: 2016-03-26 125 | 126 | - Update buddy-sign dependency to 0.10.0 127 | 128 | 129 | ## Version 0.9.0 130 | 131 | Date: 2016-01-06 132 | 133 | - Update buddy-sign dependency to 0.9.0. 134 | 135 | 136 | ## Version 0.8.2 137 | 138 | Date: 2015-12-08 139 | 140 | - Fixed wrong handling passwords with colons (thanks @mitch-kile). 141 | 142 | 143 | ## Version 0.8.1 144 | 145 | Date: 2015-11-17 146 | 147 | - Update buddy-sign to 0.8.1 148 | 149 | 150 | ## Version 0.8.0 151 | 152 | Date: 2015-11-15 153 | 154 | - Update buddy-sign to 0.8.0 155 | - Implicit update to buddy-core 0.8.1 156 | 157 | 158 | ## Version 0.7.1 159 | 160 | Date: 2015-10-03 161 | 162 | - Fix wrong call to `throw` on `wrap-authorization` middleware. 163 | - Update buddy-sign version to 0.7.1 164 | 165 | 166 | ## Version 0.7.0 167 | 168 | Date: 2015-09-19 169 | 170 | - Response return value is now not supported in `parse` step of the authentication. 171 | - The `on-error` handler now receives plain exception info instance instead 172 | the error data. This maybe a little breaking change caused by exception handling 173 | changes on buddy-core and buddy-sign. 174 | 175 | 176 | ## Version 0.6.2 177 | 178 | Date: 2015-08-26 179 | 180 | - The regext access rule matcher now only uses the request `:uri` property. 181 | 182 | 183 | ## Version 0.6.1 184 | 185 | Date: 2015-08-02 186 | 187 | - Set default clojure version to 1.7.0 188 | - Update buddy-sign version to 0.6.1 189 | - Update cuerdas version to 0.6.0 190 | 191 | 192 | ## Version 0.6.0 193 | 194 | Date: 2015-06-28 195 | 196 | - Update to buddy-sign 0.6.0 197 | - Update to buddy-core 0.6.0 198 | - Update cuerdas to 0.5.0 199 | 200 | 201 | ## Version 0.5.3 202 | 203 | Date: 2015-05-16 204 | 205 | - Remove ring dependency. 206 | - Implement some http related functios as protocols for easy 207 | extensibility by third party. Making it more compatible with 208 | `funcool/catacumba` as example. 209 | 210 | ## Version 0.5.2 211 | 212 | Date: 2015-05-09 213 | 214 | - Update clout version to 2.1.2 215 | - Update buddy-sign version to 0.5.1 (that fixes unexpected exceptions on parsing wrong tokens) 216 | 217 | 218 | ## Version 0.5.1 219 | 220 | Date: 2015-04-16 221 | 222 | - Add support for access to uri matching tokens when clout url matching 223 | system is used in access rules. 224 | 225 | 226 | ## Version 0.5.0 227 | 228 | Date: 2015-04-03 229 | 230 | - Update buddy-sign to 0.5.0 231 | - Add JWE (Json Web Token) auth backend. 232 | - Improved exception based ahorization functions. 233 | - Add `on-error` parameter to JWS backend. 234 | - Add support for multiple backends. (thanks to @r0man) 235 | - Add support for match for http method for acces rules (thanks to @r0man) 236 | - Fix wrong behavior :or logic operator on access rules dsl (thanks to @r0man) 237 | - Removed any java source, now is 100% clojure. 238 | 239 | 240 | ## Version 0.4.2 241 | 242 | Date: 2015-03-29 243 | 244 | - Update buddy-sign to 0.4.2 245 | 246 | 247 | ## Version 0.4.1 248 | 249 | Date: 2015-03-14 250 | 251 | - Fix bug in uri handling in accessrules. 252 | - Remove unnecesary headers normalization. 253 | - Upgrade buddy-sign to 0.4.1 254 | - Upgrade buddy-core to 0.4.2 255 | - Upgrade cuerdas to 0.3.1 256 | 257 | 258 | ## Version 0.4.0 259 | 260 | Date: 2014-02-22 261 | 262 | - Removed signed token backend. 263 | - Add jws backend, as replacement for signed token backend. 264 | - Update buddy-core version to 0.4.0 265 | - Update buddy-sign vetsion to 0.4.0 266 | - Update slingshot to 0.12.2 267 | 268 | 269 | ## Version 0.3.0 270 | 271 | Date: 2015-01-24 272 | 273 | - First version splitted from monolitic buddy package. 274 | - Refactored auth access rules module with features from 275 | https://github.com/yogthos/ring-access-rules 276 | - Fix bugs on auth backends related to headers parsing. 277 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # buddy-auth 2 | 3 | [![Travis Badge](https://img.shields.io/travis/funcool/buddy-auth.svg?style=flat)](https://travis-ci.org/funcool/buddy-auth "Travis Badge") 4 | 5 | **NOTE**: this project is in maintencance mode, and **looking for a new maintainer**. 6 | 7 | *buddy-auth* module is dedicated to provide **Authentication** and **Authorization** facilities 8 | for ring and ring based web applications. 9 | 10 | [![Clojars Project](http://clojars.org/buddy/buddy-auth/latest-version.svg)](http://clojars.org/buddy/buddy-auth) 11 | 12 | See the [documentation](https://funcool.github.io/buddy-auth/latest/). 13 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:refer-clojure :exclude [compile]) 3 | (:require [clojure.tools.build.api :as b])) 4 | 5 | (def lib 'buddy/buddy-auth) 6 | (def version (format "3.0.%s" (b/git-count-revs nil))) 7 | (def class-dir "target/classes") 8 | (def basis (b/create-basis {:project "deps.edn"})) 9 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 10 | 11 | (defn clean [_] 12 | (b/delete {:path "target"})) 13 | 14 | (defn jar [_] 15 | (b/write-pom 16 | {:class-dir class-dir 17 | :lib lib 18 | :version version 19 | :basis basis 20 | :src-dirs ["src"]}) 21 | 22 | (b/copy-dir 23 | {:src-dirs ["src" "resources"] 24 | :target-dir class-dir}) 25 | 26 | (b/jar 27 | {:class-dir class-dir 28 | :jar-file jar-file})) 29 | 30 | (defn clojars [_] 31 | (b/process 32 | {:command-args ["mvn" 33 | "deploy:deploy-file" 34 | (str "-Dfile=" jar-file) 35 | "-DpomFile=target/classes/META-INF/maven/buddy/buddy-auth/pom.xml" 36 | "-DrepositoryId=clojars" 37 | "-Durl=https://clojars.org/repo/"]})) 38 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {buddy/buddy-sign {:mvn/version "3.4.333"} 3 | clout/clout {:mvn/version "2.2.1"}} 4 | :paths ["src" "resources" "target/classes"] 5 | :aliases 6 | {:dev 7 | {:extra-deps 8 | {org.clojure/tools.namespace {:mvn/version "RELEASE"} 9 | org.clojure/test.check {:mvn/version "RELEASE"} 10 | org.clojure/tools.deps.alpha {:mvn/version "RELEASE"} 11 | org.clojure/clojure {:mvn/version "1.11.0-alpha4"} 12 | com.bhauman/rebel-readline {:mvn/version "RELEASE"} 13 | criterium/criterium {:mvn/version "RELEASE"} 14 | } 15 | :extra-paths ["test" "dev"]} 16 | 17 | :test 18 | {:extra-paths ["test"] 19 | :extra-deps 20 | {io.github.cognitect-labs/test-runner 21 | {:git/tag "v0.5.0" :git/sha "b3fd0d2"}} 22 | :exec-fn cognitect.test-runner.api/test 23 | :exec-args {:patterns [".*-tests.*"]}} 24 | 25 | :codox 26 | {:extra-deps 27 | {codox/codox {:mvn/version "RELEASE"} 28 | org.clojure/tools.reader {:mvn/version "RELEASE"} 29 | codox-theme-rdash/codox-theme-rdash {:mvn/version "RELEASE"}}} 30 | 31 | :build 32 | {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.7.4" :git/sha "ac442da"}} 33 | :ns-default build} 34 | 35 | :outdated 36 | {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"} 37 | org.slf4j/slf4j-nop {:mvn/version "RELEASE"}} 38 | :main-opts ["-m" "antq.core"]}}} 39 | -------------------------------------------------------------------------------- /doc.clj: -------------------------------------------------------------------------------- 1 | (require '[codox.main :as codox]) 2 | 3 | (codox/generate-docs 4 | {:output-path "doc/dist/latest" 5 | :metadata {:doc/format :markdown} 6 | :language :clojure 7 | :name "buddy/buddy-auth" 8 | :themes [:rdash] 9 | :source-paths ["src"] 10 | :namespaces [#"^buddy\."] 11 | :source-uri "https://github.com/funcool/buddy-auth/blob/master/{filepath}#L{line}"}) 12 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | all: doc 2 | 3 | doc: 4 | mkdir -p dist/latest/ 5 | cd ..; clojure -A:dev:codox -M doc.clj; 6 | 7 | github: doc 8 | ghp-import -m "Generate documentation" -b gh-pages dist/ 9 | git push origin gh-pages 10 | -------------------------------------------------------------------------------- /doc/user-guide.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | ## Introduction 4 | 5 | _buddy-auth_ is a module that provides authentication and authorization 6 | facilites for ring and ring based web applications. 7 | 8 | 9 | ### Project Maturity 10 | 11 | Since _buddy-auth_ is in a maintenance mode and does not expect more changes. 12 | 13 | 14 | ### Install 15 | 16 | The simplest way to use _buddy-auth_ in a clojure project is by including it in 17 | your *_project.clj_* dependency vector: 18 | 19 | ```clojure 20 | {buddy/buddy-auth {:mvn/version "3.0.323"} 21 | ``` 22 | 23 | This package is intended to be used with *jdk>=8*. 24 | 25 | 26 | ## Authentication 27 | 28 | ### Introduction 29 | 30 | The buddy's approach for authentication is pretty simple and explicit. 31 | In contrast to the vast majority of authentication libraries that I know, 32 | _buddy_ does not mix authentication process with the authorization. 33 | 34 | It is implemented as a pluggable backend that can be picked as is or you can 35 | implement a new one with simple steps. This is a list of builtin backends: 36 | 37 | | Backend name | Namespace | 38 | |---------------|--------------------------------| 39 | | Http Basic | `buddy.auth.backends/basic` | 40 | | Session | `buddy.auth.backends/session` | 41 | | Token | `buddy.auth.backends/token` | 42 | | Signed JWT | `buddy.auth.backends/jws` | 43 | | Encrypted JWT | `buddy.auth.backends/jwe` | 44 | 45 | If you are not happy with the built-in backends, you can implement your own and 46 | use it with _buddy-auth_ middleware without any problems. 47 | 48 | The authentication process works mainly in two steps: 49 | 50 | 1. *parse*: that is responsible for analyzing the request and reading the auth 51 | related data (e.g. `Authorization` header, url params, etc..) 52 | 2. *auth*: with the data obtained from parse step, just try to authenticate the 53 | request (e.g. simple access to database for obtaining the possible user, using 54 | a self contained jws/jwe token, checking a key in the session, etc...) 55 | 56 | This step does not raise any exceptions and is completely transparent to the 57 | user. It is the responsibility of the authentication process to determine if a request 58 | is anonymous or authenticated, nothing more. 59 | 60 | ### Backends 61 | 62 | #### Http-Basic 63 | 64 | The HTTP Basic authentication backend is one of the simplest and most insecure 65 | authentication systems, but is a good first step to understanding how 66 | _buddy-auth_ authentication works. 67 | 68 | ```clojure 69 | (require '[ring.util.response :refer (response)]) 70 | 71 | ;; Simple ring handler. This can also be a compojure router handler 72 | ;; or anything else compatible with ring middleware. 73 | 74 | (defn my-handler 75 | [request] 76 | (if (:identity request) 77 | (response (format "Hello %s" (:identity request))) 78 | (response "Hello Anonymous"))) 79 | ``` 80 | 81 | The basic step to check if a request is authenticated or not is just to check 82 | if it comes with an `:identity` key and it contains a logical `true` (exists and 83 | contains something different to `nil` or `false`). 84 | 85 | This is how the authentication backend should be setup: 86 | 87 | ```clojure 88 | (require '[buddy.auth.backends :as backends]) 89 | 90 | (defn my-authfn 91 | [request authdata] 92 | (let [username (:username authdata) 93 | password (:password authdata)] 94 | username)) 95 | 96 | (def backend (backends/basic {:realm "MyApi" 97 | :authfn my-authfn})) 98 | ``` 99 | 100 | The `authfn` is responsible for the second step of authentication. It receives 101 | the parsed auth data from request and should return a logical true value (e.g a user 102 | id, user instance, mainly something different to `nil` and `false`). And it will 103 | be called only if step 1 (parse) returns something. 104 | 105 | And finally, you should wrap your ring handler with authentication and authorization 106 | middleware: 107 | 108 | ```clojure 109 | (require '[buddy.auth.middleware :refer [wrap-authentication 110 | wrap-authorization]]) 111 | 112 | ;; Define the main handler with *app* name wrapping it 113 | ;; with authentication middleware using an instance of the 114 | ;; just created http-basic backend. 115 | 116 | ;; Define app var with handler wrapped with _buddy-auth_'s authentication 117 | ;; and authorization middleware using the previously defined backend. 118 | 119 | (def app (-> my-handler 120 | (wrap-authentication backend) 121 | (wrap-authorization backend))) 122 | ``` 123 | 124 | From now, all requests that reach `my-handler` will be properly authenticated. 125 | 126 | 127 | #### Session 128 | 129 | The session backend has the simplest implementation because it relies entirely on 130 | ring session support. 131 | 132 | The authentication process of this backend consists of checking the `:identity` 133 | keyword in session. If it exists and is a logical true, it is automatically 134 | forwarded to the request under the `:identity` property. 135 | 136 | ```clojure 137 | (require '[buddy.auth.backends :as backends]) 138 | 139 | ;; Create an instance 140 | (def backend (backends/session)) 141 | 142 | ;; Wrap the ring handler. 143 | (def app (-> my-handler 144 | (wrap-authentication backend))) 145 | ``` 146 | 147 | 148 | #### Token 149 | 150 | This is a backend that uses tokens for authenticating the user. It behaves very 151 | similarly to the basic-auth backend with the difference that instead of 152 | authenticating with credentials it authenticates with a simple token. 153 | 154 | Let's see an example: 155 | 156 | ```clojure 157 | (require '[buddy.auth.backends :as backends]) 158 | 159 | ;; Define a in-memory relation between tokens and users: 160 | (def tokens {:2f904e245c1f5 :admin 161 | :45c1f5e3f05d0 :foouser}) 162 | 163 | ;; Define an authfn, function with the responsibility 164 | ;; to authenticate the incoming token and return an 165 | ;; identity instance 166 | 167 | (defn my-authfn 168 | [request token] 169 | (let [token (keyword token)] 170 | (get tokens token nil))) 171 | 172 | ;; Create an instance 173 | (def backend (backends/token {:authfn my-authfn})) 174 | 175 | ;; Wrap the ring handler. 176 | (def app (-> my-handler 177 | (wrap-authentication backend))) 178 | ``` 179 | 180 | The process of authentication of this backend consists in parsing the 181 | "Authorization" header, extracting the token and in case the token is extracted 182 | successfully, call the `authfn` with extracted token. 183 | 184 | ```clojure 185 | Authorization: Token 45c1f5e3f05d0 186 | ``` 187 | 188 | The `authfn` should return something that will be associated to the `:identity` 189 | key in the request. 190 | 191 | The responsability of _buddy_ is just parse request and call the user function to 192 | authenticate it. The token building and storage is a user responsability. 193 | 194 | You can see a complete example of using this backend <>. 195 | 196 | 197 | #### Signed JWT 198 | 199 | Is a backend that uses signed and self contained tokens to authenticate the user. 200 | 201 | It behaves very similarly to the _Token_ backend (previously explained) with the 202 | difference that this one does not need additional user defined logic to validate 203 | tokens, because as we said previously, everything is self contained. 204 | 205 | This type of token mechanism enables a completely stateless authentication because the 206 | server does not need to store the token and related information, the token will 207 | contain all the needed information for authentication. 208 | 209 | Let's see a demonstrative example: 210 | 211 | ``` 212 | (require '[buddy.auth.backends :as backends]) 213 | (require '[buddy.auth.middleware :refer (wrap-authentication)]) 214 | 215 | (def secret "mysecret") 216 | (def backend (backends/jws {:secret secret})) 217 | 218 | ;; and wrap your ring application with 219 | ;; the authentication middleware 220 | 221 | (def app (-> your-ring-app 222 | (wrap-authentication backend))) 223 | ``` 224 | 225 | Now you should have a login endpoint in your ring application that will have the 226 | responsibility of generating valid tokens: 227 | 228 | ```clojure 229 | (require '[buddy.sign.jwt :as jwt]) 230 | (require '[cheshire.core :as json]) 231 | 232 | (defn login-handler 233 | [request] 234 | (let [data (:form-params request) 235 | user (find-user (:username data) ;; (implementation ommited) 236 | (:password data)) 237 | token (jwt/sign {:user (:id user)} secret)] 238 | {:status 200 239 | :body (json/encode {:token token}) 240 | :headers {:content-type "application/json"}})) 241 | ``` 242 | 243 | For more details about jwt, see the 244 | link:https://funcool.github.io/buddy-sign/latest/#jwt[buddy-sign] documentation. 245 | 246 | Some valuable resources for learning about stateless authentication are: 247 | 248 | - http://lucumr.pocoo.org/2013/11/17/my-favorite-database/ 249 | - http://www.niwi.nz/2014/06/07/stateless-authentication-with-api-rest/ 250 | 251 | 252 | #### Encrypted JWT 253 | 254 | This backend is almost identical to the previous one (signed JWT). 255 | 256 | The main difference is that the backend uses JWE (Json Web Encryption) instead of 257 | JWS (Json Web Signature) and it has the advantage that the content of the token is 258 | encrypted instead of simply signed. This is useful when token may contain some 259 | additional user information that should not be public. 260 | 261 | It will look similar to the previous (jws) example but instead uses jwe with 262 | asymmetric key encryption algorithm: 263 | 264 | ```clojure 265 | (require '[buddy.auth.backends :as backends]) 266 | (require '[buddy.auth.middleware :refer (wrap-authentication)]) 267 | (require '[buddy.sign.jwe :as jwe]) 268 | (require '[buddy.core.keys :as keys]) 269 | 270 | (def pubkey (keys/public-key "pubkey.pem")) 271 | (def privkey (keys/private-key "privkey.pem")) 272 | 273 | (def backend 274 | (backends/jwe {:secret privkey 275 | :options {:alg :rsa-oaep 276 | :enc :a128-hs256}})) 277 | 278 | ;; and wrap your ring application with 279 | ;; the authentication middleware 280 | 281 | (def app (-> your-ring-app 282 | (wrap-authentication backend))) 283 | ``` 284 | 285 | The corresponding login endpoint should have a similar aspect to this: 286 | 287 | ```clojure 288 | (require '[buddy.sign.jwt :as jwt]) 289 | (require '[cheshire.core :as json]) 290 | 291 | (defn login-handler 292 | [request] 293 | (let [data (:form-params request) 294 | user (find-user (:username data) ;; (implementation ommited) 295 | (:password data)) 296 | token (jwt/encrypt {:user (:id user)} pubkey 297 | {:alg :rsa-oaep :enc :a128-hs256})] 298 | {:status 200 299 | :body (json/encode {:token token}) 300 | :headers {:content-type "application/json"}))) 301 | ``` 302 | 303 | In order to use any asymmetric encryption algorithm, you should have private/public 304 | key pair. If you don't have one, don't worry, it is very easy to generate it using 305 | *openssl*, see this link:https://funcool.github.io/buddy-sign/latest/#generate-keypairs[faq entry]. 306 | 307 | 308 | ## Authorization 309 | 310 | The second part of the auth process is authorization. 311 | 312 | The authorization system is split into two parts: generic authorization and 313 | access-rules (explained in the next section). 314 | 315 | The generic one is based on exceptions, and consists in raising an unauthorized 316 | exception in case the request is considered unauthorized. The access rules 317 | system is based on some kind of rules attached to the handler or an _URI_ and 318 | that rules determine if a request is authorized or not. 319 | 320 | 321 | ### Exception-Based 322 | 323 | This authorization approach is based on wrapping everything in a try/catch block 324 | which only handles specific exceptions. When an unauthorized exception is caught, 325 | it executes a specific function to handle it or reraises the exception. 326 | 327 | With this approach, you can define your own middlewares/decorators using custom 328 | authorization logic with fast skip, raising an unauthorized exception using the 329 | `throw-unauthorized` function. 330 | 331 | ```clojure 332 | (require '[buddy.auth :refer [authenticated? throw-unauthorized]]) 333 | (require '[ring.util.response :refer (response redirect)]) 334 | 335 | (defn home-controller 336 | [request] 337 | (when (not (authenticated? request)) 338 | (throw-unauthorized {:message "Not authorized"})) 339 | (response "Hello World")) 340 | ``` 341 | 342 | Just like the authentication system, authorization is also implemented using 343 | plugable backends. 344 | 345 | All built-in backends already implement the authorization protocol with default 346 | behavior. The default behavior can be overridden passing the `:unauthorized-handler` 347 | option to the backend constructor: 348 | 349 | ```clojure 350 | (require '[buddy.auth.backends :as backends]) 351 | (require '[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]) 352 | 353 | ;; Simple self defined handler for unauthorized requests. 354 | (defn my-unauthorized-handler 355 | [request metadata] 356 | (-> (response "Unauthorized request") 357 | (assoc :status 403))) 358 | 359 | (def backend (backends/basic 360 | {:realm "API" 361 | :authfn my-auth-fn 362 | :unauthorized-handler my-unauthorized-handler})) 363 | 364 | (def app (-> your-handler 365 | (wrap-authentication backend) 366 | (wrap-authorization backend))) 367 | ``` 368 | 369 | 370 | ### Access Rules 371 | 372 | The access rules system is another part of authorization. It consists of matching 373 | an url to specific access rules logic. 374 | 375 | The access rules consist of an ordered list that contains mappings between urls 376 | and rule handlers using link:https://github.com/weavejester/clout[clout] url 377 | matching syntax or regular expressions. 378 | 379 | ```clojure 380 | [{:uri "/foo" 381 | :handler user-access} 382 | ``` 383 | 384 | ```clojure 385 | [{:uris ["/foo" "/bar"] 386 | :handler user-access} 387 | ``` 388 | 389 | ```clojure 390 | [{:pattern #"^/foo$" 391 | :handler user-access} 392 | ``` 393 | 394 | An access rule can also match against certain HTTP methods, by using the 395 | *:request-method* option. *:request-method* can be a keyword or a set of keywords. 396 | 397 | An example of an access rule that matches only GET requests: 398 | 399 | ```clojure 400 | [{:uri "/foo" 401 | :handler user-access 402 | :request-method :get} 403 | ``` 404 | 405 | 406 | #### Rules Handlers 407 | 408 | The rule handler is a plain function that accepts a request as a parameter and 409 | should return `accessrules/success` or `accessrules/error`. 410 | 411 | The `success` is a simple mark that means that handlers pass the validation and 412 | `error` is a mark that means the opposite, that the handler does not pass the 413 | validation. Instead of returning plain boolean values, this approach allows handlers 414 | to return errors messages or even a ring response. 415 | 416 | This is a simple example of the aspect of one rule handler: 417 | 418 | ```clojure 419 | (require '[buddy.auth.accessrules :refer (success error)]) 420 | 421 | (defn authenticated-user 422 | [request] 423 | (if (:identity request) 424 | true 425 | (error "Only authenticated users allowed"))) 426 | ``` 427 | 428 | These values are considered success marks: *true* and *success* instances. These are 429 | considered error marks: *nil*, *false*, and *error* instances. Error instances may 430 | contain a string as an error message or a ring response hash-map. 431 | 432 | Also, a rule handler can be a composition of several rule handlers using logical 433 | operators. 434 | 435 | ```clojure 436 | {:and [authenticated-user other-handler]} 437 | {:or [authenticated-user other-handler]} 438 | 439 | ;; Logical expressions can be nested as deep as you wish 440 | ;; with hypotetical rule handlers with self descriptive name. 441 | {:or [should-be-admin 442 | {:and [should-be-safe 443 | should-be-authenticated]}]}} 444 | ``` 445 | 446 | This is an example of how a composed rule handler can be used in an 447 | access rules list: 448 | 449 | ```clojure 450 | [{:pattern #"^/foo$" 451 | :handler {:and [authenticated-user admin-user]}}] 452 | ``` 453 | 454 | Additionally, if you are using *clout* based syntax for matching access rules, the 455 | request in a rule handler will contain `:match-params` with clout matched uri params. 456 | 457 | 458 | #### Usage 459 | 460 | Now, knowing how access rules and rule handlers can be defined, it is time to see 461 | how we can use it in our ring applications. 462 | 463 | _buddy-auth_ exposes two ways to do it: 464 | 465 | * Using a _wrap-access-rules_ middleware. 466 | * Using a _restrict_ decorator for assigning specific rules handlers to concrete 467 | ring handler. 468 | 469 | Here are couple of examples of how we could do it: 470 | 471 | ```clojure 472 | ;; Rules handlers used on this example are ommited for code clarity 473 | ;; Each handler represents authorization logic indicated by its name. 474 | 475 | (def rules [{:pattern #"^/admin/.*" 476 | :handler {:or [admin-access operator-access]}} 477 | {:pattern #"^/login$" 478 | :handler any-access} 479 | {:pattern #"^/.*" 480 | :handler authenticated-access}]) 481 | 482 | ;; Define default behavior for not authorized requests 483 | ;; 484 | ;; This function works like a default ring compatible handler 485 | ;; and should implement the default behavior for requests 486 | ;; which are not authorized by any defined rule 487 | 488 | (defn on-error 489 | [request value] 490 | {:status 403 491 | :headers {} 492 | :body "Not authorized"}) 493 | 494 | ;; Wrap the handler with access rules (and run with jetty as example) 495 | (defn -main 496 | [& args] 497 | (let [options {:rules rules :on-error on-error} 498 | app (wrap-access-rules your-app-handler options)] 499 | (run-jetty app {:port 3000}))) 500 | ``` 501 | 502 | If a request uri does not match any regular expression then the default policy is 503 | used. The default policy in _buddy-auth_ is *allow* but you can change the default 504 | behavior specifying a `:reject` value in the `:policy` option. 505 | 506 | Additionally, instead of specifying the global _on-error_ handler, you can set a 507 | specific behavior on a specific access rule, or use the _:redirect_ option to 508 | simply redirect a user to specific url. 509 | 510 | ```clojure 511 | (def rules [{:pattern #"^/admin/.*" 512 | :handler {:or [admin-access operator-access]} 513 | :redirect "/notauthorized"} 514 | {:pattern #"^/login$" 515 | :handler any-access} 516 | {:pattern #"^/.*" 517 | :handler authenticated-access 518 | :on-error (fn [req _] (response "Not authorized ;)"))}]) 519 | ``` 520 | 521 | The access rule options always takes precedence over the global ones. 522 | 523 | Then, if you don't want an external rules list and simply want to apply some rules 524 | to specific ring views/handlers, you can use the `restrict` decorator. Let's see it 525 | in action: 526 | 527 | ```clojure 528 | (require '[buddy.auth.accessrules :refer [restrict]]) 529 | 530 | (defn home-controller 531 | [request] 532 | {:body "Hello World" :status 200}) 533 | 534 | (defroutes app 535 | (GET "/" [] (restrict home-controller {:handler should-be-authenticated 536 | :on-error on-error})) 537 | ``` 538 | 539 | 540 | ## Examples 541 | 542 | ### Http Basic Auth Example 543 | 544 | This example tries to show the way to setup http basic auth in a simple ring based 545 | application. 546 | 547 | Just run the following commands: 548 | 549 | ``` 550 | git clone https://github.com/funcool/buddy-auth.git 551 | cd ./buddy-auth/ 552 | lein with-profile +httpbasic-example run 553 | ``` 554 | 555 | And redirect your browser to http://localhost:3000/. 556 | 557 | The credentials are: `admin` / `secret` and `test` / `secret`. 558 | 559 | You can see the example code here: 560 | https://github.com/funcool/buddy-auth/tree/master/examples/httpbasic 561 | 562 | 563 | ### Session Auth Example 564 | 565 | This example tries to show the way to setup session based auth in a simple ring 566 | based application. 567 | 568 | Just run the following commands: 569 | 570 | ``` 571 | git clone https://github.com/funcool/buddy-auth.git 572 | cd ./buddy-auth/ 573 | lein with-profile +session-example run 574 | ``` 575 | 576 | And redirect your browser to http://localhost:3000/. 577 | 578 | The credentials are: `admin` / `secret` and `test` / `secret`. 579 | 580 | You can see the example code here: 581 | https://github.com/funcool/buddy-auth/tree/master/examples/session 582 | 583 | 584 | ### Token Auth Example 585 | 586 | This example tries to show the way to setup token based auth in a simple ring based 587 | application. 588 | 589 | Just run the following commands: 590 | 591 | ``` 592 | git clone https://github.com/funcool/buddy-auth.git 593 | cd ./buddy-auth/ 594 | lein with-profile +token-example run 595 | ``` 596 | 597 | You can use *curl* for play with the authentication example: 598 | 599 | ``` 600 | $ curl -v -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "secret"}' http://localhost:3000/login 601 | * Connected to localhost (::1) port 3000 (#0) 602 | > POST /login HTTP/1.1 603 | > Host: localhost:3000 604 | > User-Agent: curl/7.46.0 605 | > Accept: */* 606 | > Content-Type: application/json 607 | > Content-Length: 43 608 | > 609 | * upload completely sent off: 43 out of 43 bytes 610 | < HTTP/1.1 200 OK 611 | < Date: Mon, 04 Jan 2016 13:54:02 GMT 612 | < Content-Type: application/json; charset=utf-8 613 | < Content-Length: 44 614 | < Server: Jetty(9.2.10.v20150310) 615 | < 616 | * Connection #0 to host localhost left intact 617 | {"token":"fe562338bf1604bd175722e32a4d7115"} 618 | ``` 619 | 620 | ``` 621 | $ curl -v -X GET -H "Content-Type: application/json" -H "Authorization: Token fe562338bf1604bd175722e32a4d7115" http://localhost:3000/ 622 | * Connected to localhost (::1) port 3000 (#0) 623 | > GET / HTTP/1.1 624 | > Host: localhost:3000 625 | > User-Agent: curl/7.46.0 626 | > Accept: */* 627 | > Content-Type: application/json 628 | > Authorization: Token fe562338bf1604bd175722e32a4d7115 629 | > 630 | < HTTP/1.1 200 OK 631 | < Date: Mon, 04 Jan 2016 13:54:40 GMT 632 | < Content-Type: application/json; charset=utf-8 633 | < Content-Length: 55 634 | < Server: Jetty(9.2.10.v20150310) 635 | < 636 | * Connection #0 to host localhost left intact 637 | {"status":"Logged","message":"hello logged user:admin"} 638 | ``` 639 | 640 | You can see the example code here: 641 | https://github.com/funcool/buddy-auth/tree/master/examples/token 642 | 643 | 644 | ### JWE Token Auth Example 645 | 646 | This example tries to show the way to setup jwe stateless token based auth in a 647 | simple ring based application. 648 | 649 | Just run the following commands: 650 | 651 | ``` 652 | git clone https://github.com/funcool/buddy-auth.git 653 | cd ./buddy-auth/ 654 | lein with-profile +jwe-example run 655 | ``` 656 | 657 | You can use *curl* for play with the authentication example: 658 | 659 | ``` 660 | $ curl -v -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "secret"}' http://localhost:3000/login 661 | * Connected to localhost (::1) port 3000 (#0) 662 | > POST /login HTTP/1.1 663 | > Host: localhost:3000 664 | > User-Agent: curl/7.46.0 665 | > Accept: */* 666 | > Content-Type: application/json 667 | > Content-Length: 43 668 | > 669 | * upload completely sent off: 43 out of 43 bytes 670 | < HTTP/1.1 200 OK 671 | < Date: Mon, 04 Jan 2016 13:52:11 GMT 672 | < Content-Type: application/json; charset=utf-8 673 | < Content-Length: 189 674 | < Server: Jetty(9.2.10.v20150310) 675 | < 676 | * Connection #0 to host localhost left intact 677 | {"token":"eyJhbGciOiJBMjU2S1ciLCJ0eXAiOiJKV1MiLCJlbmMiOiJBMTI4R0NNIn0.Q672y_lD3bOU_qm5U0RDKS-YszRHfkFu.vDZaAJPz8uL5q1A4.LonJtHZMA_Ty53YBmr1zpE7-SIbTJgVgme--Tjj25dHN.goYEyM3JZgYlbARo8CDk0g"} 678 | ``` 679 | 680 | Perform an authenticated request (using previously obtained token): 681 | 682 | ``` 683 | $ curl -v -X GET -H "Content-Type: application/json" -H "Authorization: Token eyJhbGciOiJBMjU2S1ciLCJ0eXAiOiJKV1MiLCJlbmMiOiJBMTI4R0NNIn0.Q672y_lD3bOU_qm5U0RDKS-YszRHfkFu.vDZaAJPz8uL5q1A4.LonJtHZMA_Ty53YBmr1zpE7-SIbTJgVgme--Tjj25dHN.goYEyM3JZgYlbARo8CDk0g" http://localhost:3000/ 684 | * Connected to localhost (::1) port 3000 (#0) 685 | > GET / HTTP/1.1 686 | > Host: localhost:3000 687 | > User-Agent: curl/7.46.0 688 | > Accept: */* 689 | > Content-Type: application/json 690 | > Authorization: Token eyJhbGciOiJBMjU2S1ciLCJ0eXAiOiJKV1MiLCJlbmMiOiJBMTI4R0NNIn0.Q672y_lD3bOU_qm5U0RDKS-YszRHfkFu.vDZaAJPz8uL5q1A4.LonJtHZMA_Ty53YBmr1zpE7-SIbTJgVgme--Tjj25dHN.goYEyM3JZgYlbARo8CDk0g 691 | > 692 | < HTTP/1.1 200 OK 693 | < Date: Mon, 04 Jan 2016 13:52:59 GMT 694 | < Content-Type: application/json; charset=utf-8 695 | < Content-Length: 84 696 | < Server: Jetty(9.2.10.v20150310) 697 | < 698 | * Connection #0 to host localhost left intact 699 | {"status":"Logged","message":"hello logged user {:user \"admin\", :exp 1451919131}"} 700 | ``` 701 | 702 | You can see the example code here: 703 | https://github.com/funcool/buddy-auth/tree/master/examples/jwe 704 | 705 | 706 | ### Signed JWT Auth Example 707 | 708 | This example tries to show the way to setup jws stateless token based auth in a 709 | simple ring based application. 710 | 711 | Just run the following commands: 712 | 713 | ``` 714 | git clone https://github.com/funcool/buddy-auth.git 715 | cd ./buddy-auth/ 716 | lein with-profile +jws-example run 717 | ``` 718 | 719 | You can use *curl* for play with the authentication example: 720 | 721 | ``` 722 | $ curl -v -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "secret"}' http://localhost:3000/login 723 | > POST /login HTTP/1.1 724 | > Host: localhost:3000 725 | > User-Agent: curl/7.46.0 726 | > Accept: */* 727 | > Content-Type: application/json 728 | > Content-Length: 43 729 | > 730 | * upload completely sent off: 43 out of 43 bytes 731 | < HTTP/1.1 200 OK 732 | < Date: Mon, 04 Jan 2016 13:49:30 GMT 733 | < Content-Type: application/json; charset=utf-8 734 | < Content-Length: 180 735 | < Server: Jetty(9.2.10.v20150310) 736 | < 737 | * Connection #0 to host localhost left intact 738 | {"token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXUyJ9.eyJ1c2VyIjoiYWRtaW4iLCJleHAiOjE0NTE5MTg5NzB9.Kvpr1jW7JBCZYUlFjAf7xnqMZSTpSVggAgiZ6_RGZuTi1wUuP_-E8MJff23GuCwpT9bbbHNTk84uV2cdg7rKTw"} 739 | ``` 740 | 741 | Perform an authenticated request (using previously obtained token): 742 | 743 | ``` 744 | $ curl -v -X GET -H "Content-Type: application/json" -H "Authorization: Token eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXUyJ9.eyJ1c2VyIjoiYWRtaW4iLCJleHAiOjE0NTE5MTg5NzB9.Kvpr1jW7JBCZYUlFjAf7xnqMZSTpSVggAgiZ6_RGZuTi1wUuP_-E8MJff23GuCwpT9bbbHNTk84uV2cdg7rKTw" http://localhost:3000/ 745 | * Connected to localhost (::1) port 3000 (#0) 746 | > GET / HTTP/1.1 747 | > Host: localhost:3000 748 | > User-Agent: curl/7.46.0 749 | > Accept: */* 750 | > Content-Type: application/json 751 | > Authorization: Token eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXUyJ9.eyJ1c2VyIjoiYWRtaW4iLCJleHAiOjE0NTE5MTg5NzB9.Kvpr1jW7JBCZYUlFjAf7xnqMZSTpSVggAgiZ6_RGZuTi1wUuP_-E8MJff23GuCwpT9bbbHNTk84uV2cdg7rKTw 752 | > 753 | < HTTP/1.1 200 OK 754 | < Date: Mon, 04 Jan 2016 13:50:15 GMT 755 | < Content-Type: application/json; charset=utf-8 756 | < Content-Length: 84 757 | < Server: Jetty(9.2.10.v20150310) 758 | < 759 | * Connection #0 to host localhost left intact 760 | {"status":"Logged","message":"hello logged user {:user \"admin\", :exp 1451918970}"} 761 | ``` 762 | 763 | You can see the example code here: 764 | https://github.com/funcool/buddy-auth/tree/master/examples/jws 765 | 766 | 767 | ## FAQ 768 | 769 | *What is the difference with Friend?* 770 | 771 | _buddy-auth_ authorization/authentication facilities are more low level and less 772 | opinionated than friend, and allow you to easily build other high level abstractions 773 | over them. Technically, friend abstraction can be built on top of _buddy-auth_. 774 | 775 | 776 | *How can I use _buddy_ with link:http://clojure-liberator.github.io/liberator/[liberator]?* 777 | 778 | By design, _buddy_ has authorization and authentication well 779 | separated. This helps a lot if you want use only one part of it (ex: 780 | authentication only) without including the other. 781 | 782 | In summary: yes, you can use _buddy-auth_ with liberator. 783 | 784 | 785 | *Can I use _buddy-auth_ with pedestal?* 786 | 787 | Although is not mentioned in this documentation, you can use _buddy-auth_ with 788 | pedestal without any problems. 789 | 790 | https://juxt.pro/blog/posts/securing-your-clojurescript-app.html 791 | 792 | 793 | *Can I use _buddy-auth_ with catacumba?* 794 | 795 | Not directly. 796 | 797 | The design of _buddy-auth_ api is intrinsically blocking just because ring and ring 798 | based abstractions are also blocking. However _catacumba_ is asyncronous toolkit and 799 | it comes with its own, builtint variant of _buddy-auth_ designed for asynchronous 800 | workflow (reusing the underlying _buddy-sign_, _buddy-core_ and _buddy-hashers_ 801 | modules). 802 | 803 | 804 | ## Developers Guide 805 | 806 | ### Contributing 807 | 808 | Unlike Clojure and other Clojure contributed libraries _buddy-auth_ does not have many 809 | restrictions for contributions. Just open an issue or pull request. 810 | 811 | 812 | ### Philosophy 813 | 814 | Five most important rules: 815 | 816 | - Beautiful is better than ugly. 817 | - Explicit is better than implicit. 818 | - Simple is better than complex. 819 | - Complex is better than complicated. 820 | - Readability counts. 821 | 822 | All contributions to _buddy-auth_ should keep these important rules in mind. 823 | 824 | 825 | ### Get the Code 826 | 827 | _buddy-auth_ is open source and can be found on link:https://github.com/funcool/buddy-auth[github]. 828 | 829 | You can clone the public repository with this command: 830 | 831 | ``` 832 | git clone https://github.com/funcool/buddy-auth 833 | ``` 834 | 835 | 836 | ### Run tests 837 | 838 | For running tests just execute this: 839 | 840 | ```bash 841 | lein test 842 | ``` 843 | 844 | 845 | ### License 846 | 847 | _buddy-auth_ is licensed under Apache 2.0 License. You can see the complete text 848 | of the license on the root of the repository on `LICENSE` file. 849 | -------------------------------------------------------------------------------- /examples/httpbasic/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /examples/httpbasic/resources/index.html: -------------------------------------------------------------------------------- 1 | Hello World. 2 | -------------------------------------------------------------------------------- /examples/httpbasic/resources/login.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /examples/httpbasic/src/authexample/web.clj: -------------------------------------------------------------------------------- 1 | (ns authexample.web 2 | (:require [compojure.route :as route] 3 | [compojure.core :refer :all] 4 | [compojure.response :refer [render]] 5 | [clojure.java.io :as io] 6 | [ring.util.response :refer [response redirect content-type]] 7 | [ring.adapter.jetty :as jetty] 8 | 9 | [buddy.auth :refer [authenticated? throw-unauthorized]] 10 | [buddy.auth.backends.httpbasic :refer [http-basic-backend]] 11 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]) 12 | (:gen-class)) 13 | 14 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 15 | ;; Controllers 16 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 17 | 18 | ;; Home page controller (ring handler) 19 | ;; If incoming user is not authenticated it raises a not authenticated 20 | ;; exception, else it simply shows a hello world message. 21 | 22 | (defn home 23 | [req] 24 | (if-not (authenticated? req) 25 | (throw-unauthorized) 26 | (response (slurp (io/resource "index.html"))))) 27 | 28 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 29 | ;; Routes and Middlewares 30 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 31 | 32 | ;; User defined application routes using compojure routing library. 33 | ;; Note: there are no middleware for authorization, all authorization 34 | ;; system is totally decoupled from main routes. 35 | 36 | (defroutes app 37 | (GET "/" [] home)) 38 | 39 | ;; Global var that stores valid users with their 40 | ;; respective passwords. 41 | (def authdata 42 | {:admin "secret" 43 | :test "secret"}) 44 | 45 | ;; Define function that is responsible for authenticating requests. 46 | ;; In this case it receives a map with username and password and it 47 | ;; should return a value that can be considered a "user" instance 48 | ;; and should be a logical true. 49 | 50 | (defn my-authfn 51 | [req {:keys [username password]}] 52 | (when-let [user-password (get authdata (keyword username))] 53 | (when (= password user-password) 54 | (keyword username)))) 55 | 56 | ;; Create an instance of auth backend without explicit handler for 57 | ;; unauthorized request. (That leaves the responsibility to default 58 | ;; backend implementation. 59 | 60 | (def auth-backend 61 | (http-basic-backend {:realm "MyExampleSite" 62 | :authfn my-authfn})) 63 | 64 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 65 | ;; Entry Point 66 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 67 | 68 | (defn -main 69 | [& args] 70 | (as-> app $ 71 | (wrap-authorization $ auth-backend) 72 | (wrap-authentication $ auth-backend) 73 | (jetty/run-jetty $ {:port 3000}))) 74 | -------------------------------------------------------------------------------- /examples/jwe/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /examples/jwe/src/authexample/web.clj: -------------------------------------------------------------------------------- 1 | (ns authexample.web 2 | (:require [compojure.route :as route] 3 | [compojure.core :refer :all] 4 | [compojure.response :refer [render]] 5 | [clojure.java.io :as io] 6 | [ring.util.response :refer [response redirect content-type]] 7 | [ring.middleware.session :refer [wrap-session]] 8 | [ring.middleware.params :refer [wrap-params]] 9 | [ring.middleware.json :refer [wrap-json-response wrap-json-body]] 10 | [ring.adapter.jetty :as jetty] 11 | [clj-time.core :as time] 12 | [buddy.sign.jwt :as jwt] 13 | [buddy.core.nonce :as nonce] 14 | [buddy.auth :refer [authenticated? throw-unauthorized]] 15 | [buddy.auth.backends.token :refer [jwe-backend]] 16 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]) 17 | (:gen-class)) 18 | 19 | (def secret (nonce/random-bytes 32)) 20 | 21 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 22 | ;; Semantic response helpers 23 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 24 | 25 | (defn ok [d] {:status 200 :body d}) 26 | (defn bad-request [d] {:status 400 :body d}) 27 | 28 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 29 | ;; Controllers ;; 30 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 31 | 32 | ;; Home page controller (ring handler) 33 | ;; If incoming user is not authenticated it raises a not authenticated 34 | ;; exception, else it simply shows a hello world message. 35 | 36 | (defn home 37 | [request] 38 | (if-not (authenticated? request) 39 | (throw-unauthorized) 40 | (ok {:status "Logged" :message (str "hello logged user " 41 | (:identity request))}))) 42 | 43 | ;; Global var that stores valid users with their 44 | ;; respective passwords. 45 | 46 | (def authdata {:admin "secret" 47 | :test "secret"}) 48 | 49 | ;; Authenticate Handler 50 | ;; Responds to post requests in same url as login and is responsible for 51 | ;; identifying the incoming credentials and setting the appropriate authenticated 52 | ;; user into session. `authdata` will be used as source of valid users. 53 | 54 | (defn login 55 | [request] 56 | (let [username (get-in request [:body :username]) 57 | password (get-in request [:body :password]) 58 | valid? (some-> authdata 59 | (get (keyword username)) 60 | (= password))] 61 | (if valid? 62 | (let [claims {:user (keyword username) 63 | :exp (time/plus (time/now) (time/seconds 3600))} 64 | token (jwt/encrypt claims secret {:alg :a256kw :enc :a128gcm})] 65 | (ok {:token token})) 66 | (bad-request {:message "wrong auth data"})))) 67 | 68 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 69 | ;; Routes and Middlewares ;; 70 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 71 | 72 | ;; User defined application routes using compojure routing library. 73 | ;; Note: there are no middleware for authorization, all authorization 74 | ;; system is totally decoupled from main routes. 75 | 76 | (defroutes app 77 | (GET "/" [] home) 78 | (POST "/login" [] login)) 79 | 80 | ;; Create an instance of auth backend. 81 | (def auth-backend (jwe-backend {:secret secret 82 | :options {:alg :a256kw :enc :a128gcm}})) 83 | 84 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 85 | ;; Entry Point 86 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 87 | 88 | (defn -main 89 | [& args] 90 | (as-> app $ 91 | (wrap-authorization $ auth-backend) 92 | (wrap-authentication $ auth-backend) 93 | (wrap-json-response $ {:pretty false}) 94 | (wrap-json-body $ {:keywords? true :bigdecimals? true}) 95 | (jetty/run-jetty $ {:port 3000}))) 96 | -------------------------------------------------------------------------------- /examples/jws/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /examples/jws/src/authexample/web.clj: -------------------------------------------------------------------------------- 1 | (ns authexample.web 2 | (:require [compojure.route :as route] 3 | [compojure.core :refer :all] 4 | [compojure.response :refer [render]] 5 | [clojure.java.io :as io] 6 | [ring.util.response :refer [response redirect content-type]] 7 | [ring.middleware.session :refer [wrap-session]] 8 | [ring.middleware.params :refer [wrap-params]] 9 | [ring.middleware.json :refer [wrap-json-response wrap-json-body]] 10 | [ring.adapter.jetty :as jetty] 11 | [clj-time.core :as time] 12 | [buddy.sign.jwt :as jwt] 13 | [buddy.auth :refer [authenticated? throw-unauthorized]] 14 | [buddy.auth.backends.token :refer [jws-backend]] 15 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]) 16 | (:gen-class)) 17 | 18 | (def secret "mysupersecret") 19 | 20 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 21 | ;; Semantic response helpers 22 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 23 | 24 | (defn ok [d] {:status 200 :body d}) 25 | (defn bad-request [d] {:status 400 :body d}) 26 | 27 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 28 | ;; Controllers ;; 29 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 30 | 31 | ;; Home page controller (ring handler) 32 | ;; If incoming user is not authenticated it raises a not authenticated 33 | ;; exception, else it simply shows a hello world message. 34 | 35 | (defn home 36 | [request] 37 | (if-not (authenticated? request) 38 | (throw-unauthorized) 39 | (ok {:status "Logged" :message (str "hello logged user " 40 | (:identity request))}))) 41 | 42 | ;; Global var that stores valid users with their 43 | ;; respective passwords. 44 | 45 | (def authdata {:admin "secret" 46 | :test "secret"}) 47 | 48 | ;; Authenticate Handler 49 | ;; Responds to post requests in same url as login and is responsible for 50 | ;; identifying the incoming credentials and setting the appropriate authenticated 51 | ;; user into session. `authdata` will be used as source of valid users. 52 | 53 | (defn login 54 | [request] 55 | (let [username (get-in request [:body :username]) 56 | password (get-in request [:body :password]) 57 | valid? (some-> authdata 58 | (get (keyword username)) 59 | (= password))] 60 | (if valid? 61 | (let [claims {:user (keyword username) 62 | :exp (time/plus (time/now) (time/seconds 3600))} 63 | token (jwt/sign claims secret {:alg :hs512})] 64 | (ok {:token token})) 65 | (bad-request {:message "wrong auth data"})))) 66 | 67 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 68 | ;; Routes and Middlewares ;; 69 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 70 | 71 | ;; User defined application routes using compojure routing library. 72 | ;; Note: there are no middleware for authorization, all authorization 73 | ;; system is totally decoupled from main routes. 74 | 75 | (defroutes app 76 | (GET "/" [] home) 77 | (POST "/login" [] login)) 78 | 79 | ;; Create an instance of auth backend. 80 | (def auth-backend (jws-backend {:secret secret :options {:alg :hs512}})) 81 | 82 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 83 | ;; Entry Point 84 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 85 | 86 | (defn -main 87 | [& args] 88 | (as-> app $ 89 | (wrap-authorization $ auth-backend) 90 | (wrap-authentication $ auth-backend) 91 | (wrap-json-response $ {:pretty false}) 92 | (wrap-json-body $ {:keywords? true :bigdecimals? true}) 93 | (jetty/run-jetty $ {:port 3000}))) 94 | -------------------------------------------------------------------------------- /examples/session/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /examples/session/resources/index.html: -------------------------------------------------------------------------------- 1 |

Hello User

2 | 3 |

Logout

4 | -------------------------------------------------------------------------------- /examples/session/resources/login.html: -------------------------------------------------------------------------------- 1 | 2 |

Login

3 | 4 |
5 | 6 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /examples/session/src/authexample/web.clj: -------------------------------------------------------------------------------- 1 | (ns authexample.web 2 | (:require [compojure.route :as route] 3 | [compojure.core :refer :all] 4 | [compojure.response :refer [render]] 5 | 6 | [clojure.java.io :as io] 7 | [ring.util.response :refer [response redirect content-type]] 8 | [ring.middleware.session :refer [wrap-session]] 9 | [ring.middleware.params :refer [wrap-params]] 10 | [ring.adapter.jetty :as jetty] 11 | 12 | [buddy.auth :refer [authenticated? throw-unauthorized]] 13 | [buddy.auth.backends.session :refer [session-backend]] 14 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]) 15 | (:gen-class)) 16 | 17 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 18 | ;; Controllers ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | 21 | ;; Home page controller (ring handler) 22 | ;; If incoming user is not authenticated it raises a 23 | ;; not authenticated exception, else it simply shows a 24 | ;; hello world message. 25 | 26 | (defn home 27 | [request] 28 | (if-not (authenticated? request) 29 | (throw-unauthorized) 30 | (let [content (slurp (io/resource "index.html"))] 31 | (render content request)))) 32 | 33 | ;; Login page controller 34 | ;; It returns a login page on get requests. 35 | 36 | (defn login 37 | [request] 38 | (let [content (slurp (io/resource "login.html"))] 39 | (render content request))) 40 | 41 | ;; Logout handler 42 | ;; Responsible for clearing the session. 43 | 44 | (defn logout 45 | [request] 46 | (-> (redirect "/login") 47 | (assoc :session {}))) 48 | 49 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 50 | ;; Authentication ;; 51 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 52 | 53 | (def authdata 54 | "Global var that stores valid users with their 55 | respective passwords." 56 | {:admin "secret" 57 | :test "secret"}) 58 | 59 | ;; Authentication Handler 60 | ;; Used to respond to POST requests to /login. 61 | 62 | (defn login-authenticate 63 | "Check request username and password against authdata 64 | username and passwords. 65 | 66 | On successful authentication, set appropriate user 67 | into the session and redirect to the value of 68 | (:next (:query-params request)). On failed 69 | authentication, renders the login page." 70 | [request] 71 | (let [username (get-in request [:form-params "username"]) 72 | password (get-in request [:form-params "password"]) 73 | session (:session request) 74 | found-password (get authdata (keyword username))] 75 | (if (and found-password (= found-password password)) 76 | (let [next-url (get-in request [:query-params "next"] "/") 77 | updated-session (assoc session :identity (keyword username))] 78 | (-> (redirect next-url) 79 | (assoc :session updated-session))) 80 | (let [content (slurp (io/resource "login.html"))] 81 | (render content request))))) 82 | 83 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 84 | ;; Routes and Middlewares ;; 85 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 86 | 87 | ;; User defined application routes using compojure 88 | ;; routing library. 89 | ;; 90 | ;; Note: We do not use middleware for authorization, 91 | ;; all of the authorization system is decoupled from 92 | ;; main routes. 93 | 94 | (defroutes app 95 | (GET "/" [] home) 96 | (GET "/login" [] login) 97 | (POST "/login" [] login-authenticate) 98 | (GET "/logout" [] logout)) 99 | 100 | ;; User defined unauthorized handler 101 | ;; 102 | ;; This function is responsible for handling 103 | ;; unauthorized requests (when unauthorized exception 104 | ;; is raised by some handler) 105 | 106 | (defn unauthorized-handler 107 | [request metadata] 108 | (cond 109 | ;; If request is authenticated, raise 403 instead 110 | ;; of 401 (because user is authenticated but permission 111 | ;; denied is raised). 112 | (authenticated? request) 113 | (-> (render (slurp (io/resource "error.html")) request) 114 | (assoc :status 403)) 115 | ;; In other cases, redirect the user to login page. 116 | :else 117 | (let [current-url (:uri request)] 118 | (redirect (format "/login?next=%s" current-url))))) 119 | 120 | ;; Create an instance of auth backend. 121 | 122 | (def auth-backend 123 | (session-backend {:unauthorized-handler unauthorized-handler})) 124 | 125 | (defn -main 126 | [& args] 127 | (as-> app $ 128 | (wrap-authorization $ auth-backend) 129 | (wrap-authentication $ auth-backend) 130 | (wrap-params $) 131 | (wrap-session $) 132 | (jetty/run-jetty $ {:port 3000}))) 133 | -------------------------------------------------------------------------------- /examples/token/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /examples/token/src/authexample/web.clj: -------------------------------------------------------------------------------- 1 | (ns authexample.web 2 | (:require [compojure.route :as route] 3 | [compojure.core :refer :all] 4 | [compojure.response :refer [render]] 5 | [clojure.java.io :as io] 6 | [ring.util.response :refer [response redirect content-type]] 7 | [ring.middleware.session :refer [wrap-session]] 8 | [ring.middleware.params :refer [wrap-params]] 9 | [ring.middleware.json :refer [wrap-json-response wrap-json-body]] 10 | [ring.adapter.jetty :as jetty] 11 | 12 | [buddy.core.nonce :as nonce] 13 | [buddy.core.codecs :as codecs] 14 | [buddy.auth :refer [authenticated? throw-unauthorized]] 15 | [buddy.auth.backends.token :refer [token-backend]] 16 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]) 17 | (:gen-class)) 18 | 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; Semantic response helpers 21 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 22 | 23 | (defn ok [d] {:status 200 :body d}) 24 | (defn bad-request [d] {:status 400 :body d}) 25 | 26 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 27 | ;; Token generator helpers 28 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 29 | 30 | (defn random-token 31 | [] 32 | (let [randomdata (nonce/random-bytes 16)] 33 | (codecs/bytes->hex randomdata))) 34 | 35 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 36 | ;; Controllers ;; 37 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 38 | 39 | ;; Home page controller (ring handler) 40 | ;; If incoming user is not authenticated it raises a not authenticated 41 | ;; exception, else it simply shows a hello world message. 42 | 43 | (defn home 44 | [request] 45 | (if-not (authenticated? request) 46 | (throw-unauthorized) 47 | (ok {:status "Logged" :message (str "hello logged user" 48 | (:identity request))}))) 49 | 50 | 51 | ;; Global var that stores valid users with their 52 | ;; respective passwords. 53 | 54 | (def authdata {:admin "secret" 55 | :test "secret"}) 56 | 57 | ;; Global storage for generated tokens. 58 | (def tokens (atom {})) 59 | 60 | ;; Authenticate Handler 61 | ;; Responds to post requests in same url as login and is responsible for 62 | ;; identifying the incoming credentials and setting the appropriate authenticated 63 | ;; user into session. `authdata` will be used as source of valid users. 64 | 65 | (defn login 66 | [request] 67 | (let [username (get-in request [:body :username]) 68 | password (get-in request [:body :password]) 69 | valid? (some-> authdata 70 | (get (keyword username)) 71 | (= password))] 72 | (if valid? 73 | (let [token (random-token)] 74 | (swap! tokens assoc (keyword token) (keyword username)) 75 | (ok {:token token})) 76 | (bad-request {:message "wrong auth data"})))) 77 | 78 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 79 | ;; Routes and Middlewares ;; 80 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 81 | 82 | ;; User defined application routes using compojure routing library. 83 | ;; Note: there are no middleware for authorization, all authorization 84 | ;; system is totally decoupled from main routes. 85 | 86 | (defroutes app 87 | (GET "/" [] home) 88 | (POST "/login" [] login)) 89 | 90 | (defn my-authfn 91 | [req token] 92 | (when-let [user (get @tokens (keyword token))] 93 | user)) 94 | 95 | ;; Create an instance of auth backend. 96 | 97 | (def auth-backend 98 | (token-backend {:authfn my-authfn})) 99 | 100 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 101 | ;; Entry Point 102 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 103 | 104 | (defn -main 105 | [& args] 106 | (as-> app $ 107 | (wrap-authorization $ auth-backend) 108 | (wrap-authentication $ auth-backend) 109 | (wrap-json-response $ {:pretty false}) 110 | (wrap-json-body $ {:keywords? true :bigdecimals? true}) 111 | (jetty/run-jetty $ {:port 3000}))) 112 | -------------------------------------------------------------------------------- /mvn-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mvn deploy:deploy-file -Dfile=target/buddy-auth.jar -DpomFile=pom.xml -DrepositoryId=clojars -Durl=https://clojars.org/repo/ 4 | -------------------------------------------------------------------------------- /src/buddy/auth.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) 2013-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth 16 | "Authorization and Authentication primitives for 17 | ring based applications." 18 | (:require [buddy.auth.protocols :as proto])) 19 | 20 | (defn authenticated? 21 | "Return `true` if the `request` is an 22 | authenticated request. 23 | 24 | This function checks the `:identity` key 25 | in the request." 26 | [request] 27 | (boolean (:identity request))) 28 | 29 | (defn throw-unauthorized 30 | "Throws a unauthorized exception. 31 | 32 | Used as fast skip exception based 33 | authorization primitive." 34 | ([] (throw-unauthorized {})) 35 | ([errordata] 36 | (throw (ex-info "Unauthorized." {::type ::unauthorized 37 | ::payload errordata})))) 38 | -------------------------------------------------------------------------------- /src/buddy/auth/accessrules.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2013-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.accessrules 16 | "Access Rules system for ring based applications." 17 | (:require [buddy.auth :refer [throw-unauthorized]] 18 | [buddy.auth.http :as http] 19 | [clojure.walk :refer [postwalk]] 20 | [clout.core :as clout])) 21 | 22 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 23 | ;; Rule Handler Protocol 24 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 25 | 26 | (defprotocol IRuleHandlerResponse 27 | "Abstraction for uniform handling of rule handler return values. 28 | It comes with default implementation for nil and boolean types." 29 | (success? [_] "Check if a response is a success.") 30 | (get-value [_] "Get a handler response value.")) 31 | 32 | (extend-protocol IRuleHandlerResponse 33 | nil 34 | (success? [_] false) 35 | (get-value [_] nil) 36 | 37 | Boolean 38 | (success? [v] v) 39 | (get-value [_] nil)) 40 | 41 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 42 | ;; Rule Handler Response Type 43 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 44 | 45 | (deftype RuleSuccess [v] 46 | IRuleHandlerResponse 47 | (success? [_] true) 48 | (get-value [_] v) 49 | 50 | Object 51 | (equals [self other] 52 | (if (instance? RuleSuccess other) 53 | (= v (.-v other)) 54 | false)) 55 | 56 | (toString [self] 57 | (with-out-str (print [v])))) 58 | 59 | (deftype RuleError [v] 60 | IRuleHandlerResponse 61 | (success? [_] false) 62 | (get-value [_] v) 63 | 64 | Object 65 | (equals [self other] 66 | (if (instance? RuleError other) 67 | (= v (.-v other)) 68 | false)) 69 | 70 | (toString [self] 71 | (with-out-str (print [v])))) 72 | 73 | (alter-meta! #'->RuleSuccess assoc :private true) 74 | (alter-meta! #'->RuleError assoc :private true) 75 | 76 | (defn success 77 | "Function that returns a success state 78 | from one access rule handler." 79 | ([] (RuleSuccess. nil)) 80 | ([v] (RuleSuccess. v))) 81 | 82 | (defn error 83 | "Function that returns a failure state 84 | from one access rule handler." 85 | ([] (RuleError. nil)) 86 | ([v] (RuleError. v))) 87 | 88 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 89 | ;; Implementation 90 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 91 | 92 | (defn compile-rule-handler 93 | "Receives a rule handler and returns a compiled version of it. 94 | 95 | The compiled version of a rule handler consists of 96 | one function that accepts a request as first parameter 97 | and returns the result of the evaluation of it. 98 | 99 | The rule can be a simple function or logical expression. Logical 100 | expression is expressed using a hashmap: 101 | 102 | {:or [f1 f2]} 103 | {:and [f1 f2]} 104 | 105 | Logical expressions can be nested as deep as you want: 106 | 107 | {:or [f1 {:and [f2 f3]}]} 108 | 109 | The rule handler as unit of work, should return a 110 | `success` or `error`. `success` is a simple mark that 111 | means that handler passes the validation and `error` 112 | is a mark that means that rule does not pass the 113 | validation. 114 | 115 | An error mark can return a ring response that will be 116 | returned to the http client or string message that will be 117 | passed to `on-error` handler if it exists, or returned as 118 | bad-request response with message as response body. 119 | 120 | Example of success marks: 121 | 122 | - `true` 123 | - `(success)` 124 | 125 | Example of error marks: 126 | 127 | - `nil` 128 | - `false` 129 | - `(error \"Error msg\")` 130 | - `(error {:status 400 :body \"Unauthorized\"})` 131 | " 132 | [rule] 133 | (postwalk (fn [form] 134 | (cond 135 | ;; In this case is a handler 136 | (fn? form) 137 | (fn [req] (form req)) 138 | 139 | (:or form) 140 | (fn [req] 141 | (let [rules (:or form) 142 | evals (map (fn [x] (x req)) rules) 143 | accepts (filter success? evals)] 144 | (if (seq accepts) 145 | (first accepts) 146 | (last evals)))) 147 | 148 | (:and form) 149 | (fn [req] 150 | (let [rules (:and form) 151 | evals (map (fn [x] (x req)) rules) 152 | rejects (filter (complement success?) evals)] 153 | (if (seq rejects) 154 | (first rejects) 155 | (first evals)))) 156 | 157 | :else form)) 158 | rule)) 159 | 160 | (defn- matches-request-method 161 | "Match the :request-method of `request` against `allowed` HTTP 162 | methods. `allowed` can be a keyword, a set of keywords or nil." 163 | [request allowed] 164 | (let [actual (:request-method request)] 165 | (cond 166 | (keyword? allowed) 167 | (= actual allowed) 168 | 169 | (set? allowed) 170 | (or (empty? allowed) 171 | (contains? allowed actual)) 172 | 173 | :else true))) 174 | 175 | (defn compile-access-rule 176 | "Receives an access rule and returns a compiled version of it. 177 | 178 | The plain version of access rule consists of one hash-map with 179 | with `:uri` and `:handler` keys. `:uri` is a url match syntax 180 | that will be used for matching the url and `:handler` is a rule 181 | handler. 182 | 183 | Little overview of aspect of access rules: 184 | 185 | [{:uri \"/foo\" 186 | :handler user-access} 187 | {:uris [\"/bar\" \"/baz\"] 188 | :handler admin-access}] 189 | 190 | The clout library (https://github.com/weavejester/clout) 191 | for matching the `:uri`. 192 | 193 | It also has support for more advanced matching using plain 194 | regular expressions, which are matched against the full 195 | request uri: 196 | 197 | [{:pattern #\"^/foo$\" 198 | :handler user-access} 199 | 200 | An access rule can also match against certain HTTP methods, by using 201 | the `:request-method` option. `:request-method` can be a keyword or 202 | a set of keywords. 203 | 204 | [{:pattern #\"^/foo$\" 205 | :handler user-access 206 | :request-method :get} 207 | 208 | The compilation process consists in transforming the plain version 209 | into an optimized one in order to avoid unnecessary overhead to the 210 | request process time. 211 | 212 | The compiled version of access rule has a very similar format with 213 | the plain one. The difference is that `:handler` is a compiled 214 | version, and `:pattern` or `:uri` is replaced by matcher function. 215 | 216 | Little overview of aspect of compiled version of acces rule: 217 | 218 | [{:matcher # 219 | :handler # 220 | " 221 | [accessrule] 222 | {:pre [(map? accessrule)]} 223 | (let [request-method (:request-method accessrule) 224 | handler (compile-rule-handler (:handler accessrule)) 225 | matcher (cond 226 | (:pattern accessrule) 227 | (fn [request] 228 | (let [pattern (:pattern accessrule) 229 | uri (:uri request)] 230 | (when (and (matches-request-method request request-method) 231 | (seq (re-matches pattern uri))) 232 | {}))) 233 | 234 | (:uri accessrule) 235 | (let [route (clout/route-compile (:uri accessrule))] 236 | (fn [request] 237 | (let [match-params (clout/route-matches route request)] 238 | (when (and (matches-request-method request request-method) match-params) 239 | match-params)))) 240 | 241 | (:uris accessrule) 242 | (let [routes (mapv clout/route-compile (:uris accessrule))] 243 | (fn [request] 244 | (let [match-params (->> (map #(clout/route-matches % request) routes) 245 | (filter identity) 246 | (first))] 247 | (when (and (matches-request-method request request-method) match-params) 248 | match-params)))) 249 | 250 | :else (fn [request] {}))] 251 | (assoc accessrule 252 | :matcher matcher 253 | :handler handler))) 254 | 255 | (defn compile-access-rules 256 | "Compile a list of access rules. 257 | 258 | For more information, see the docstring 259 | of `compile-access-rule` function." 260 | [accessrules] 261 | (mapv compile-access-rule accessrules)) 262 | 263 | (defn- match-access-rules 264 | "Iterates over all access rules and try to match each one 265 | in order. Return the first matched access rule or nil." 266 | [accessrules request] 267 | (reduce (fn [acc accessrule] 268 | (let [matcher (:matcher accessrule) 269 | match-result (matcher request)] 270 | (when match-result 271 | (reduced (assoc accessrule :match-params match-result))))) 272 | nil 273 | accessrules)) 274 | 275 | (defn handle-error 276 | "Handles the error situation when access rules are 277 | evaluated in `wrap-access-rules` middleware. 278 | 279 | It receives a handler response (anything that rule handler may 280 | return), a current request and a hashmap passwd to the access 281 | rule definition. 282 | 283 | The received response has to satisfy the 284 | IRuleHandlerResponse protocol." 285 | {:no-doc true} 286 | ([response request {:keys [reject-handler on-error redirect]}] 287 | {:pre [(satisfies? IRuleHandlerResponse response)]} 288 | (let [val (get-value response)] 289 | (cond 290 | (string? redirect) 291 | (http/redirect redirect) 292 | 293 | (fn? on-error) 294 | (on-error request val) 295 | 296 | (http/response? val) 297 | val 298 | 299 | (fn? reject-handler) 300 | (reject-handler request val) 301 | 302 | (string? val) 303 | (http/response val 400) 304 | 305 | :else 306 | (throw-unauthorized)))) 307 | ([response request rule respond raise] 308 | (try 309 | (let [err (handle-error response request rule)] 310 | (respond err)) 311 | (catch Exception e 312 | (raise e))))) 313 | 314 | (defn- apply-matched-access-rule 315 | "Simple helper that executes the rule handler 316 | of received access rule and returns the result." 317 | [match request] 318 | {:pre [(map? match) 319 | (contains? match :handler)]} 320 | (let [handler (:handler match) 321 | params (:match-params match)] 322 | (-> request 323 | (assoc :match-params params) 324 | (handler)))) 325 | 326 | (defn wrap-access-rules 327 | "A ring middleware that helps to define access rules for 328 | ring handler. 329 | 330 | This is an example of access rules list that `wrap-access-rules` 331 | middleware expects: 332 | 333 | [{:uri \"/foo/*\" 334 | :handler user-access} 335 | {:uri \"/bar/*\" 336 | :handler {:or [user-access admin-access]}} 337 | {:uri \"/baz/*\" 338 | :handler {:and [user-access {:or [admin-access operator-access]}]}}] 339 | 340 | All access rules are evaluated in order and the process stops when 341 | a match is found. 342 | 343 | See docstring of `compile-rule-handler` for documentation 344 | about rule handlers." 345 | [handler & [{:keys [policy rules] :or {policy :allow} :as opts}]] 346 | (when (nil? rules) 347 | (throw (IllegalArgumentException. "rules should not be empty."))) 348 | (let [accessrules (compile-access-rules rules)] 349 | (fn 350 | ([request] 351 | (if-let [match (match-access-rules accessrules request)] 352 | (let [res (apply-matched-access-rule match request)] 353 | (if (success? res) 354 | (handler request) 355 | (handle-error res request (merge opts match)))) 356 | (case policy 357 | :allow (handler request) 358 | :reject (handle-error (error nil) request opts)))) 359 | ([request respond raise] 360 | (if-let [match (match-access-rules accessrules request)] 361 | (let [res (apply-matched-access-rule match request)] 362 | (if (success? res) 363 | (handler request respond raise) 364 | (handle-error res request (merge opts match) respond raise))) 365 | (case policy 366 | :allow (handler request respond raise) 367 | :reject (handle-error (error nil) request opts respond raise))))))) 368 | 369 | (defn restrict 370 | "Like `wrap-access-rules` middleware but works as 371 | decorator. It is intended to be used with compojure routing 372 | library or similar. Example: 373 | 374 | (defn login-ctrl [req] ...) 375 | (defn admin-ctrl [req] ...) 376 | 377 | (defroutes app 378 | (ANY \"/login\" [] login-ctrl) 379 | (GET \"/admin\" [] (restrict admin-ctrl {:handler admin-access ;; Mandatory 380 | :on-error my-reject-handler) 381 | 382 | This decorator allows using the same access rules but without 383 | any url matching algorithm, however it has the disadvantage of 384 | accoupling your routers code with access rules." 385 | [handler rule] 386 | (let [match (compile-access-rule rule)] 387 | (fn 388 | ([request] 389 | (let [rsp (apply-matched-access-rule match request)] 390 | (if (success? rsp) 391 | (handler request) 392 | (handle-error rsp request rule)))) 393 | ([request respond raise] 394 | (let [rsp (apply-matched-access-rule match request)] 395 | (if (success? rsp) 396 | (handler request respond raise) 397 | (handle-error rsp request rule respond raise))))))) 398 | -------------------------------------------------------------------------------- /src/buddy/auth/backends.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2013-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.backends 16 | (:require [buddy.auth.backends.httpbasic :as httpbasic] 17 | [buddy.auth.backends.token :as token] 18 | [buddy.auth.backends.session :as session])) 19 | 20 | (defn basic 21 | "Create an instance of the http-basic based 22 | authentication backend. 23 | 24 | This backend also implements authorization 25 | workflow with some defaults. This means that 26 | you can provide your own unauthorized-handler hook 27 | if the default one does not satisfy you." 28 | ([] (basic nil)) 29 | ([opts] (httpbasic/http-basic-backend opts))) 30 | 31 | (def http-basic 32 | "Alias for `basic`." 33 | basic) 34 | 35 | (defn session 36 | "Create an instance of the http session based 37 | authentication backend. 38 | 39 | This backend also implements authorization 40 | workflow with some defaults. This means that 41 | you can provide your own unauthorized-handler hook 42 | if the default one does not satisfy you." 43 | ([] (session nil)) 44 | ([opts] (session/session-backend opts))) 45 | 46 | (defn jws 47 | "Create an instance of the jws (signed JWT) 48 | based authentication backend. 49 | 50 | This backend also implements authorization workflow 51 | with some defaults. This means that you can provide 52 | your own unauthorized-handler hook if the default one 53 | does not satisfy you." 54 | ([] (jws nil)) 55 | ([opts] (token/jws-backend opts))) 56 | 57 | (defn jwe 58 | "Create an instance of the jwe (encrypted JWT 59 | based authentication backend. 60 | 61 | This backend also implements authorization workflow 62 | with some defaults. This means that you can provide 63 | your own unauthorized-handler hook if the default one 64 | does not satisfy you." 65 | ([] (jwe nil)) 66 | ([opts] (token/jwe-backend opts))) 67 | 68 | (defn token 69 | "Create an instance of the generic token based 70 | authentication backend. 71 | 72 | This backend also implements authorization workflow 73 | with some defaults. This means that you can provide 74 | your own unauthorized-handler hook if the default one 75 | does not satisfy you." 76 | ([] (token nil)) 77 | ([opts] (token/token-backend opts))) 78 | 79 | -------------------------------------------------------------------------------- /src/buddy/auth/backends/httpbasic.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2013-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.backends.httpbasic 16 | "The http-basic authentication and authorization backend." 17 | (:require [buddy.auth.protocols :as proto] 18 | [buddy.auth.http :as http] 19 | [buddy.auth :refer [authenticated?]] 20 | [buddy.core.codecs :as codecs] 21 | [buddy.core.codecs.base64 :as b64] 22 | [clojure.string :as str])) 23 | 24 | (defn- parse-header 25 | "Given a request, try to extract and parse 26 | the http basic header." 27 | [request] 28 | (let [pattern (re-pattern "^Basic (.+)$") 29 | decoded (some->> (http/-get-header request "authorization") 30 | (re-find pattern) 31 | (second) 32 | (b64/decode) 33 | (codecs/bytes->str))] 34 | (when-let [[username password] (some-> decoded (str/split #":" 2))] 35 | {:username username 36 | :password password}))) 37 | 38 | (defn http-basic-backend 39 | [& [{:keys [realm authfn unauthorized-handler] :or {realm "Buddy Auth"}}]] 40 | {:pre [(ifn? authfn)]} 41 | (reify 42 | proto/IAuthentication 43 | (-parse [_ request] 44 | (parse-header request)) 45 | (-authenticate [_ request data] 46 | (authfn request data)) 47 | 48 | proto/IAuthorization 49 | (-handle-unauthorized [_ request metadata] 50 | (if unauthorized-handler 51 | (unauthorized-handler request (assoc metadata :realm realm)) 52 | (if (authenticated? request) 53 | (http/response "Permission denied" 403) 54 | (http/response "Unauthorized" 401 55 | {"WWW-Authenticate" (format "Basic realm=\"%s\"" realm)})))))) 56 | -------------------------------------------------------------------------------- /src/buddy/auth/backends/session.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2013-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.backends.session 16 | "The session based authentication and authorization backend." 17 | (:require [buddy.auth.protocols :as proto] 18 | [buddy.auth.http :as http] 19 | [buddy.auth :refer [authenticated?]])) 20 | 21 | (defn session-backend 22 | [& [{:keys [unauthorized-handler authfn] :or {authfn identity}}]] 23 | (reify 24 | proto/IAuthentication 25 | (-parse [_ request] 26 | (:identity (:session request))) 27 | (-authenticate [_ request data] 28 | (authfn data)) 29 | 30 | proto/IAuthorization 31 | (-handle-unauthorized [_ request metadata] 32 | (if unauthorized-handler 33 | (unauthorized-handler request metadata) 34 | (if (authenticated? request) 35 | (http/response "Permission denied" 403) 36 | (http/response "Unauthorized" 401)))))) 37 | -------------------------------------------------------------------------------- /src/buddy/auth/backends/token.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2013-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.backends.token 16 | "The token based authentication and authorization backend." 17 | (:require [buddy.auth.protocols :as proto] 18 | [buddy.auth.http :as http] 19 | [buddy.auth :refer [authenticated?]] 20 | [buddy.sign.jwt :as jwt])) 21 | 22 | (defn- handle-unauthorized-default 23 | "A default response constructor for an unauthorized request." 24 | [request] 25 | (if (authenticated? request) 26 | {:status 403 :headers {} :body "Permission denied"} 27 | {:status 401 :headers {} :body "Unauthorized"})) 28 | 29 | (defn- parse-header 30 | [request token-name] 31 | (some->> (http/-get-header request "authorization") 32 | (re-find (re-pattern (str "^" token-name " (.+)$"))) 33 | (second))) 34 | 35 | (defn jws-backend 36 | [{:keys [secret authfn unauthorized-handler options token-name on-error] 37 | :or {authfn identity token-name "Token"}}] 38 | {:pre [(ifn? authfn)]} 39 | (reify 40 | proto/IAuthentication 41 | (-parse [_ request] 42 | (parse-header request token-name)) 43 | 44 | (-authenticate [_ request data] 45 | (try 46 | (authfn (jwt/unsign data secret options)) 47 | (catch clojure.lang.ExceptionInfo e 48 | (let [data (ex-data e)] 49 | (when (fn? on-error) 50 | (on-error request e)) 51 | nil)))) 52 | 53 | proto/IAuthorization 54 | (-handle-unauthorized [_ request metadata] 55 | (if unauthorized-handler 56 | (unauthorized-handler request metadata) 57 | (handle-unauthorized-default request))))) 58 | 59 | (defn jwe-backend 60 | [{:keys [secret authfn unauthorized-handler options token-name on-error] 61 | :or {authfn identity token-name "Token"}}] 62 | {:pre [(ifn? authfn)]} 63 | (reify 64 | proto/IAuthentication 65 | (-parse [_ request] 66 | (parse-header request token-name)) 67 | (-authenticate [_ request data] 68 | (try 69 | (authfn (jwt/decrypt data secret options)) 70 | (catch clojure.lang.ExceptionInfo e 71 | (when (fn? on-error) 72 | (on-error request e)) 73 | nil))) 74 | 75 | proto/IAuthorization 76 | (-handle-unauthorized [_ request metadata] 77 | (if unauthorized-handler 78 | (unauthorized-handler request metadata) 79 | (handle-unauthorized-default request))))) 80 | 81 | (defn token-backend 82 | [{:keys [authfn unauthorized-handler token-name] :or {token-name "Token"}}] 83 | {:pre [(ifn? authfn)]} 84 | (reify 85 | proto/IAuthentication 86 | (-parse [_ request] 87 | (parse-header request token-name)) 88 | (-authenticate [_ request token] 89 | (authfn request token)) 90 | 91 | proto/IAuthorization 92 | (-handle-unauthorized [_ request metadata] 93 | (if unauthorized-handler 94 | (unauthorized-handler request metadata) 95 | (handle-unauthorized-default request))))) 96 | -------------------------------------------------------------------------------- /src/buddy/auth/http.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2015-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.http 16 | "The http request response abstraction for 17 | builtin auth/authz backends.") 18 | 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; Protocols Definition 21 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 22 | 23 | (defprotocol IRequest 24 | (-get-header [req name] "Get a value of header.")) 25 | 26 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 27 | ;; Implementation 28 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 29 | 30 | (defn response 31 | "A multi arity function that creates 32 | a ring compatible response." 33 | ([body] 34 | {:status 200 :body body :headers {}}) 35 | ([body status] 36 | {:status status :body body :headers {}}) 37 | ([body status headers] 38 | {:status status :body body :headers headers})) 39 | 40 | (defn response? 41 | [resp] 42 | (and (map? resp) 43 | (integer? (:status resp)) 44 | (map? (:headers resp)))) 45 | 46 | (defn redirect 47 | "Returns a Ring compatible response for an HTTP 302 redirect." 48 | ([url] (redirect url 302)) 49 | ([url status] 50 | {:status status :body "" :headers {"Location" url}})) 51 | 52 | (defn find-header 53 | "Looks up a header in a headers map case insensitively, 54 | returning the header map entry, or nil if not present." 55 | [headers ^String header-name] 56 | (first (filter #(.equalsIgnoreCase header-name (name (key %))) headers))) 57 | 58 | (extend-protocol IRequest 59 | clojure.lang.IPersistentMap 60 | (-get-header [request header-name] 61 | (some-> (:headers request) (find-header header-name) val))) 62 | -------------------------------------------------------------------------------- /src/buddy/auth/middleware.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2013-2017 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.middleware 16 | (:require [buddy.auth.protocols :as proto] 17 | [buddy.auth.accessrules :as accessrules] 18 | [buddy.auth.http :as http] 19 | [buddy.auth :refer [authenticated? throw-unauthorized]])) 20 | 21 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 22 | ;; Authentication 23 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 24 | 25 | (defn authenticate-request 26 | "A function that runs the authentication backend chain for 27 | the provided request and return the identity of the first 28 | matched backend (backend that properly authenticates the 29 | provided request). 30 | 31 | NOTE: this function is for internal use, it is public 32 | because it is helpful in environments different to ring." 33 | [request backends] 34 | (loop [[backend & backends] backends] 35 | (when backend 36 | (let [request (assoc request :auth-backend backend)] 37 | (or (some->> request 38 | (proto/-parse backend) 39 | (proto/-authenticate backend request)) 40 | (recur backends)))))) 41 | 42 | (defn authentication-request 43 | "Updates request with authentication. If multiple `backends` are 44 | given each of them gets a chance to authenticate the request. 45 | 46 | NOTE: this function is for internal use, it is public 47 | because it is helpful in environments different to ring." 48 | [request & backends] 49 | (if-let [authdata (authenticate-request request backends)] 50 | (assoc request :identity authdata) 51 | request)) 52 | 53 | (defn wrap-authentication 54 | "Ring middleware that enables authentication for your ring 55 | handler. When multiple `backends` are given each of them gets a 56 | chance to authenticate the request." 57 | [handler & backends] 58 | (fn 59 | ([request] 60 | (handler (apply authentication-request request backends))) 61 | ([request respond raise] 62 | (handler (apply authentication-request request backends) respond raise)))) 63 | 64 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 65 | ;; Authorization 66 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 67 | 68 | (defn- fn->authorization-backend 69 | "Given a function that receives two parameters 70 | return an anonymous object that implements 71 | IAuthorization protocol." 72 | [callable] 73 | {:pre [(fn? callable)]} 74 | (reify 75 | proto/IAuthorization 76 | (-handle-unauthorized [_ request errordata] 77 | (callable request errordata)))) 78 | 79 | (defn authorization-error 80 | "Handles authorization errors. 81 | 82 | The `backend` parameter should be a plain function 83 | that accepts two parameters: request and errordata hashmap, 84 | or an instance that satisfies IAuthorization protocol." 85 | [request e backend] 86 | (let [backend (cond 87 | (fn? backend) 88 | (fn->authorization-backend backend) 89 | 90 | (satisfies? proto/IAuthorization backend) 91 | backend)] 92 | (if (instance? clojure.lang.ExceptionInfo e) 93 | (let [data (ex-data e)] 94 | (if (= (:buddy.auth/type data) :buddy.auth/unauthorized) 95 | (->> (:buddy.auth/payload data) 96 | (proto/-handle-unauthorized backend request)) 97 | (throw e))) 98 | (if (satisfies? proto/IAuthorizationdError e) 99 | (->> (proto/-get-error-data e) 100 | (proto/-handle-unauthorized backend request)) 101 | (throw e))))) 102 | 103 | (defn wrap-authorization 104 | "Ring middleware that enables authorization 105 | workflow for your ring handler. 106 | 107 | The `backend` parameter should be a plain function 108 | that accepts two parameters: request and errordata 109 | hashmap, or an instance that satisfies IAuthorization 110 | protocol." 111 | [handler backend] 112 | (fn 113 | ([request] 114 | (try (handler request) 115 | (catch Exception e 116 | (authorization-error request e backend)))) 117 | ([request respond raise] 118 | (try (handler request respond raise) 119 | (catch Exception e 120 | (respond (authorization-error request e backend))))))) 121 | -------------------------------------------------------------------------------- /src/buddy/auth/protocols.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2013-2016 Andrey Antukh 2 | ;; 3 | ;; Licensed under the Apache License, Version 2.0 (the "License") 4 | ;; you may not use this file except in compliance with the License. 5 | ;; You may obtain a copy of the License at 6 | ;; 7 | ;; http://www.apache.org/licenses/LICENSE-2.0 8 | ;; 9 | ;; Unless required by applicable law or agreed to in writing, software 10 | ;; distributed under the License is distributed on an "AS IS" BASIS, 11 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ;; See the License for the specific language governing permissions and 13 | ;; limitations under the License. 14 | 15 | (ns buddy.auth.protocols 16 | "Main authentication and authorization abstractions 17 | defined as protocols.") 18 | 19 | (defprotocol IAuthentication 20 | "Protocol that defines unified workflow steps for 21 | all authentication backends." 22 | (-parse [_ request] 23 | "Parse token from the request. If it returns `nil` 24 | the `authenticate` phase will be skipped and the 25 | handler will be called directly.") 26 | (-authenticate [_ request data] 27 | "Given a request and parsed data (from previous step), 28 | try to authenticate this data. 29 | 30 | If this method returns not nil value, the request 31 | will be considered authenticated and the value will 32 | be attached to request under `:identity` attribute.")) 33 | 34 | (defprotocol IAuthorization 35 | "Protocol that defines unified workflow steps for 36 | authorization exceptions." 37 | (-handle-unauthorized [_ request metadata] 38 | "This function is executed when a `NotAuthorizedException` 39 | exception is intercepted by authorization wrapper. 40 | 41 | It should return a valid ring response.")) 42 | 43 | (defprotocol IAuthorizationdError 44 | "Abstraction that allows the user to extend the exception 45 | based authorization system with own types." 46 | (-get-error-data [_] "Get error information.")) 47 | -------------------------------------------------------------------------------- /test/_files/privkey.3des.dsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,A31C28E0698981D8 4 | 5 | BtDyvLjxvkLfHHvtX8Pe7w9fvCZNXnio6A1YR/cSurVU8HclUnSyILB6AMPEyrdf 6 | Cksb39jQHLPFm72wwhmeb1UUuISnoTRZxefUYshzljWUvYp1Gj2jmEO66QLutsgE 7 | Cgq27ZpZTE5oqfYvEjM8kxie/TiT0ZpHWuPXrezRHeDUV+hQAHU2QimyvXVGRLHg 8 | zpPdstwVdaesNCuoFqqkeVeum2O5OPz+0Z+TbMmDfXrCnK3y0nP6hS4pJcyjcH0B 9 | KejAC+r8IxGaMuyKpSG9PyX8AT0apgPvpl3SnalPF2+jUkFHIHQNEGRYdFiLuUTm 10 | idpMuq6rEhswUG0P7TITUizML3U+ObvmgzoZju13OEsLj45UamBd4mwIzwZZZPU6 11 | /t77uajpqPSJmo70+RnBHDArS1XIZtAx6d9BQN49Eu6rmnMgCW49TkIUeGwniRgx 12 | KhbghJVEpvTSSQoWMzaZpaP6BQmJCOd532upCrTHjA8rR5diTzEc7g6SjhJYW4Z5 13 | sHiCdV/++2EsqSyvzuC3yAdrWGZmLzxTAbI/E81U77UuHVAjkaKVwySH68SezRZy 14 | 22GDGdpXhgPQChZoUvtJMhexFNIZRM0tKG+9wySZA4PrY6WRkzxaaV0p68YwWCRY 15 | 4oWN6pwJuC6X7+9rCCCsuZU6VKHbqDE7UtCRZ5BzMca+g9Iv0Nhn3FHk2qzbgE74 16 | w0hzDYV1EuGZjIMYRvrf/djejNHgTem1REPg0WLNrXVmCr5s2LptnnnB3ixiLMRU 17 | vKguW4eI7WC2Ny820sqEuoHhTEoequnBcdUacqLCnJEtEkwZTfdc5ZTZtFHfLP5a 18 | T0qsSxxpQB+3lQlqqHIF7z3p0hdr3vmwpZSUTqojBZrH3Ot6Z+b9ugSwdHvbbRNt 19 | ozNHbffyBtJ63yhAfIjX0rZnOiLFsgDWns74/RrMJERY+w3FzNCDgLsJ7IF6tuQ9 20 | 74DZfuGjioWyWFM8N9vX6YC5HoNAOEqj8FF5wjyaxIejbZ7zj0yY//nE9Y/F2KUO 21 | k0Q+iWHtBHp+IAa+Ecic/UHVAQ4QsmNqBu+ZmVufGSm8uNDx6od9fElU4hth3rLT 22 | u3N2XxajcEfbIbWYZ/l/2Pyny7Zt0kdc5logB79b359MCtCVheNdrBtxRNa78Udn 23 | -----END DSA PRIVATE KEY----- 24 | -------------------------------------------------------------------------------- /test/_files/privkey.3des.rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,BE7DFD7A51E401E4 4 | 5 | huDKvjpMKFsmxp+XyiLUE8H75osPq9azTWZkH/Q/EmtIeKIoJQj1vlxJEDgw9PBu 6 | M2S/Uig9B3S6LuosqX2auZhyjBmMq+O0DcqNrsdZi9E4VkfeRaJyAfqiuLDB8GaZ 7 | i0gv7Z7JK2x34e7PfPXqRWkOTNt0KIcClqFEs37XwzE9jrOctaqP1EEeLdwxOizh 8 | T/5gy81YEfE8HK5Z4kQKX8SaA0MwFZIiFy0F6bPLv3gjX2ld4/L+bZOLLlN43kRO 9 | u01TEy0SFQp2Ga4BQYNuVuccUpfhrL6RaUD5HNyHdpsmmxQ99Ae201SkZOyQkzOz 10 | dLUN5L+HnLszSMoCyYcH/a+5kMTk5PtPNhmY0ZWE2D63e2cRiutCZvrBzIO6AEXK 11 | V5Wb9qM4BYY7YGLHSWk6YhvtI2U2p2k0Vpg9EeDJHsu9xaNJlQUa+KhVzbwtMKi+ 12 | kITGSY2Tvoqfn2j7Ek1MJlSYHICTVyh4peTA2q3Y3swQSWgiZfpXEailxz8c3uRC 13 | CzOLDscLCi4j3Edb3ssaik1DHiwVtSiT8aIMSqvoklp6W733Wzls2HST6aiFkvVP 14 | DOqX00xdeRaANVzL+/isn70p5SOCnwa71n9iNcL9q+3irmkpAZwi3zfNG35+WGBA 15 | 1muFJvYJf3MrlsoEDAVlmHzHv022OI817DgNcuc3oyB3OtKqiG/FJZQtrBs9tGvJ 16 | O/qp7tNeFt3Hyu+8OHNBVqbdsR0G5blWcDyvMkiJjaurcHmKp3jgv5XYHbEwzW2U 17 | hSFR++ZZjGy3xLQzMbEpjPBxPhENtrk2Tc6eiyW5f7imMY8Y/HusfpFgbKDJcRKi 18 | ndjqiUqwDdyrG2tYAjxzjaZ5/TFKgpS7mkuQtInMaCFhym/BbnYcriwKlk3PlUoU 19 | iCbKKBTqCwbhVxOwDQVOw6xC5uOFMtVMlsGGs7sFgkxFLcsZk3NswokjYFXO/rKs 20 | DLHVwCZLfAkgafw0gLV74VTQpgcWoSikZqNnj+SA+mFEw+R4VqdpL6i2M6a/Pewl 21 | k/yymf2hzmhlLcf2XnonsMHxT2mz6R29XytByeJJ5yrP/kVZKCDAu+HzqFVGRd/P 22 | 2nUvUAru0SxsbbAbeHbwL39J8ZbwO5K6i8oDW17y4f/xPShF8KbATgcaM0+KSjQC 23 | jzeeKh2TfwATA8XoQkjpQXz+Ob/vfsdnFUv0mKwZDz9v4Y1gS6CoWsPWLgFAL/g8 24 | qqYaE5mxifOQBJtfCUjsAhDTBw4x7OphWja/ozzXjSr94R7cmIoT8PzuqxKE55+W 25 | Du0zS6GYTl24dvo/eoAVy3CujidUDM7w5FHWWJwXdX+wOmzTzQ17HWi0RU9tkhlh 26 | fAUr9YhD9rQYlanPNxfPMzDvL83wI+NzeGzO5DF/s/KEe/of6gd5X23nAPApcTA7 27 | mvuEpGYABKvhXqw5X9YeHQ2cu4Ef2Qtw666/amDu+7J0DWv28/f5bgQ0kb1IdJR8 28 | 44nHu6pGZLe7O3SRDT1PLg/nkddogIXIMeX0PoGjl9yFzn2akKhcccOZmmnVbE3B 29 | 5YpbYX4UhgdK5K17KaX3g2P80Z9i/tGw/+klqVY64Bxhs8YOibWt7vlqLpUAYElI 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /test/_files/privkey.ecdsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHQCAQEEIOWRHY5IzngV6VPtZdaTppvr6olKi4QYKsGNnR0Am7OFoAcGBSuBBAAK 3 | oUQDQgAEl22Tjui9EvTe/bmRuEzdLPDYKbCkLhd4KjcE01C4NvLTArVjT8EiLq9d 4 | ztE6wPRHbvhBaHPETf3ieVcLeSQqeA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /test/_files/pubkey.3des.dsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIDSDCCAjoGByqGSM44BAEwggItAoIBAQDKfZUHqyXbL097Okme9U0nZTzBXrtf 3 | /+J5IzMkGVggfu1k6e5VvwFBSUc+hLFZ99TBuKOxKanu4yX/rYphYXunJPynO5Ht 4 | 8KDxmdmdjFrPR6yxTyRygBB5AHnUXp3e3PDjDC2jc2TCHgbP44ifpG3HcwHgzwKn 5 | dvRlIunTWq3QVweLm4RCcrbIVdNjq9alj7qlv3dgahwLEVqoTSWQQsxSlk7xW+QD 6 | goPWq609XoqPcazDHtlk5f5sRjsx/+9seGEXP1OJAqxzl/+KD6CpyPv5ZxO8P62Z 7 | xzyn+bgLjvAwQBbYsnu4knrPR27HPrLeM7BREt7BIqcEhVy2Bo6814trAiEA6RE4 8 | VS5F2Nf6Hfp6yZZLjIOui7tRMD1I1iVanDC3Z58CggEBALwT+UbaVXNk7NllIjEF 9 | +aK+7+hFHylz2sVq+n22pA8Wpaqjmk3Rg3crMIdWIFBHdRWJNTz7KTfUU6JQ/kmh 10 | qj6zWy4aXznxOD9H1EC5tzq1flhuUTNOGauOod+ppHozkoa5o8JxWw7rXj2Wqq9V 11 | K2tTDITncddQaWj7zdOY34Wb51pm+Adxt7vitORXRHWEpUSTNhy81JJBOkPCBm45 12 | xXr9QB0AMTGHIF2uycBi7FGPrNTCdxz/ve/2lZ21aSC/oEabnmEX6qF0JDuicHd4 13 | fnhvH4aH/CpDyUMpxDK69weiUpeAcZJQ8QjOa3/OPwZDjxlntOSNhmUVPS5gp49x 14 | VS8DggEGAAKCAQEAiU0vd1RovhsryNyPZvNSfTj0C7xng8s7UuAlEnmEmwTE98sC 15 | EBO8TeAvnqYPcNjTDZWnLtJ4vevISamrFH4yZOMfLYfcmKVipQpxfJm7ocJ+iVFR 16 | FTeueIOO5KvPsSk9z0DJyCZL++Pb4DBip+Gdf71BLCHX4aQPTvV8xkTopxQA5lsE 17 | oQueBHBJQ0nFz9e8oCiXxUdT3/5tKW9Y35lolN8eAU4M19U6x3GOPobZgVcXF6z+ 18 | r7LhaJXLrjF4LlNmXItuvdGcp/BBo7oZ0IfqpLDkRAuFlwjC6CKHZVW5Nob1CNK+ 19 | U+vPkJpMPsSr0UhaySCFn+4KBrGe5v9Dd8g9lA== 20 | -----END PUBLIC KEY----- 21 | -------------------------------------------------------------------------------- /test/_files/pubkey.3des.rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuyriayUirpQv+4MhsKMr 3 | /1DHwz2XSkXk8GgeJBSHW1RpjWTQOSmQWYj9D+k5KRqwvs7I5OQYLLve+eRZNvwR 4 | 2OlPPLGs+qEukY1eH5FFav8LNgcxbo0pAX7IgYqQC3fH2RKarppLGBc7Jm/O0S4D 5 | 1ENtLansjz1qwrMBvmWW51YTYw5h59RHiR/85jlMCC1kkHuE/00EtfXzlcXYLMAc 6 | f7ep7Wjozl9q2E7huHNTWNLtuQmVpDEr1i6LhVKLJRxofSMZn+SRcvn/pNxQrOQ5 7 | ivfCjWz87EOh4va4N1yWVzXwAbAy9ob75WUu7OAHTiRhHNjZiRaZgrmc2XKwyOpW 8 | mwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /test/_files/pubkey.ecdsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEl22Tjui9EvTe/bmRuEzdLPDYKbCkLhd4 3 | KjcE01C4NvLTArVjT8EiLq9dztE6wPRHbvhBaHPETf3ieVcLeSQqeA== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/buddy/auth/accessrules_tests.clj: -------------------------------------------------------------------------------- 1 | (ns buddy.auth.accessrules-tests 2 | (:require [clojure.test :refer :all] 3 | [buddy.auth.http :as http] 4 | [buddy.auth.accessrules :as acr :refer (success error restrict wrap-access-rules)])) 5 | 6 | (defn ok [v] (acr/success v)) 7 | (defn fail [v] 8 | (acr/error (if (and (map? v) (:msg v)) (:msg v) v))) 9 | 10 | (defn ok2 [v] true) 11 | (defn fail2 [v] false) 12 | 13 | (deftest compile-rule-handler 14 | (testing "compile access rules 1" 15 | (let [rule (#'acr/compile-rule-handler ok) 16 | result (rule 1)] 17 | (is (= (success 1) result)))) 18 | 19 | (testing "compile access rules 2" 20 | (let [rule (#'acr/compile-rule-handler {:or [ok fail]}) 21 | result (rule 1)] 22 | (is (= (success 1) result)))) 23 | 24 | (testing "compile access rules 3" 25 | (let [rule (#'acr/compile-rule-handler {:and [ok fail]}) 26 | result (rule 1)] 27 | (is (= (error 1) result)))) 28 | 29 | (testing "compile access rules 4" 30 | (let [rule (#'acr/compile-rule-handler {:or [fail fail {:and [ok ok]}]}) 31 | result (rule 1)] 32 | (is (= (success 1) result)))) 33 | 34 | (testing "compile access rules 5" 35 | (let [rule (#'acr/compile-rule-handler {:and [ok ok]}) 36 | result (rule 1)] 37 | (is (= (success 1) result)))) 38 | 39 | (testing "compile access rules 6" 40 | (let [rule (#'acr/compile-rule-handler {:and [ok2 ok2]}) 41 | result (rule 1)] 42 | (is (= true result)))) 43 | 44 | (testing "compile access rules 7" 45 | (let [rule (#'acr/compile-rule-handler {:or [fail2 ok2]}) 46 | result (rule 1)] 47 | (is (= true result)))) 48 | 49 | (testing "compile access rules 8" 50 | (let [rule (#'acr/compile-rule-handler {:or [fail2 fail]}) 51 | result (rule 1)] 52 | (is (= (error 1) result)))) 53 | ) 54 | 55 | (defn test-handler 56 | [req] 57 | (http/response req)) 58 | 59 | (defn async-test-handler 60 | [req respond _] 61 | (respond (test-handler req))) 62 | 63 | (deftest restrict-test 64 | (testing "restrict handler 1" 65 | (let [handler (restrict test-handler {:handler {:or [ok fail]}}) 66 | rsp (handler {:foo "bar"})] 67 | (is (= {:foo "bar"} (:body rsp))))) 68 | 69 | (testing "restrict async handler 1" 70 | (let [handler (restrict async-test-handler {:handler {:or [ok fail]}}) 71 | req {:foo "bar"} 72 | rsp (promise) 73 | ex (promise)] 74 | (handler req rsp ex) 75 | (is (= {:foo "bar"} (:body @rsp))) 76 | (is (not (realized? ex))))) 77 | 78 | (testing "restrict handler with failure 1" 79 | (let [handler (restrict test-handler {:handler {:or [fail fail]}})] 80 | (is (thrown? clojure.lang.ExceptionInfo (handler {:foo "bar"}))))) 81 | 82 | (testing "restrict async handler with failure 1" 83 | (let [handler (restrict async-test-handler {:handler {:or [fail fail]}}) 84 | req {:foo "bar"} 85 | rsp (promise) 86 | ex (promise)] 87 | (handler req rsp ex) 88 | (is (instance? clojure.lang.ExceptionInfo @ex)) 89 | (is (not (realized? rsp))))) 90 | 91 | (testing "restrict handler with failure 2" 92 | (let [handler (restrict test-handler {:handler {:or [fail fail]}}) 93 | rsp (handler {:msg "Failure message"})] 94 | (is (= "Failure message" (:body rsp))) 95 | (is (= 400 (:status rsp))))) 96 | 97 | (testing "restrict async handler with failure 2" 98 | (let [handler (restrict async-test-handler {:handler {:or [fail fail]}}) 99 | req {:msg "Failure message"} 100 | rsp (promise) 101 | ex (promise)] 102 | (handler req rsp ex) 103 | (is (= "Failure message" (:body @rsp))) 104 | (is (= 400 (:status @rsp))) 105 | (is (not (realized? ex))))) 106 | 107 | (testing "restrict handlerw with failure and explicit on-error handler" 108 | (let [handler (restrict test-handler 109 | {:handler {:or [fail fail]} 110 | :on-error (fn [req val] (http/response (str "onfail-" val)))}) 111 | rsp (handler {:msg "test"})] 112 | (is (= "onfail-test" (:body rsp))))) 113 | 114 | (testing "restrict async handlerw with failure and explicit on-error handler" 115 | (let [handler (restrict async-test-handler 116 | {:handler {:or [fail fail]} 117 | :on-error (fn [req val] (http/response (str "onfail-" val)))}) 118 | req {:msg "test"} 119 | rsp (promise) 120 | ex (promise)] 121 | (handler req rsp ex) 122 | (is (= "onfail-test" (:body @rsp))) 123 | (is (not (realized? ex))))) 124 | 125 | (testing "restrict handlerw with failure and redirect" 126 | (let [handler (restrict test-handler 127 | {:handler {:or [fail fail]} 128 | :redirect "/foobar"}) 129 | rsp (handler {:msg "test"})] 130 | (is (= 302 (:status rsp))) 131 | (is (= "/foobar" (get-in rsp [:headers "Location"]))))) 132 | 133 | (testing "restrict async handlerw with failure and redirect" 134 | (let [handler (restrict async-test-handler 135 | {:handler {:or [fail fail]} 136 | :redirect "/foobar"}) 137 | req {:msg "test"} 138 | rsp (promise) 139 | ex (promise)] 140 | (handler req rsp ex) 141 | (is (= 302 (:status @rsp))) 142 | (is (= "/foobar" (get-in @rsp [:headers "Location"]))) 143 | (is (not (realized? ex))))) 144 | ) 145 | 146 | (def params1 147 | {:rules [{:pattern #"^/path1$" 148 | :handler {:or [ok fail]}} 149 | {:pattern #"^/path2$" 150 | :handler ok} 151 | {:pattern #"^/path3$" 152 | :handler {:and [fail ok]}}]}) 153 | 154 | (def params2 155 | {:rules [{:uri "/path1" 156 | :handler {:or [ok fail]}} 157 | {:uri "/path2" 158 | :handler ok} 159 | {:uris ["/path3" "/path0" "/path/:param"] 160 | :handler {:and [fail ok]}}]}) 161 | 162 | (defn on-error 163 | [req val] 164 | (http/response val 400)) 165 | 166 | (def handler1 167 | (wrap-access-rules test-handler 168 | (assoc params1 :policy :reject))) 169 | 170 | (def async-handler1 171 | (wrap-access-rules async-test-handler 172 | (assoc params1 :policy :reject))) 173 | 174 | (def handler2 175 | (wrap-access-rules test-handler 176 | (assoc params1 177 | :policy :reject 178 | :on-error on-error))) 179 | 180 | (def async-handler2 181 | (wrap-access-rules async-test-handler 182 | (assoc params1 183 | :policy :reject 184 | :on-error on-error))) 185 | 186 | (def handler3 187 | (wrap-access-rules test-handler 188 | (assoc params2 :policy :reject))) 189 | 190 | (def async-handler3 191 | (wrap-access-rules async-test-handler 192 | (assoc params2 :policy :reject))) 193 | 194 | (deftest wrap-access-rules-test 195 | (testing "check access rules 1" 196 | (let [rsp (handler1 {:uri "/path1"})] 197 | (is (= {:uri "/path1"} (:body rsp))))) 198 | 199 | (testing "check access rules 1 async" 200 | (let [req {:uri "/path1"} 201 | rsp (promise) 202 | ex (promise)] 203 | (async-handler1 req rsp ex) 204 | (is (= {:uri "/path1"} (:body @rsp))) 205 | (is (not (realized? ex))))) 206 | 207 | (testing "check access rules 2" 208 | (let [rsp (handler1 {:uri "/path2"})] 209 | (is (= {:uri "/path2"} (:body rsp))))) 210 | 211 | (testing "check access rules 2 async" 212 | (let [req {:uri "/path2"} 213 | rsp (promise) 214 | ex (promise)] 215 | (async-handler1 req rsp ex) 216 | (is (= {:uri "/path2"} (:body @rsp))) 217 | (is (not (realized? ex))))) 218 | 219 | (testing "check access rules 3" 220 | (is (thrown? clojure.lang.ExceptionInfo (handler1 {:uri "/path3"})))) 221 | 222 | (testing "check access rules 3 async" 223 | (let [req {:uri "/path3"} 224 | rsp (promise) 225 | ex (promise)] 226 | (async-handler1 req rsp ex) 227 | (is (instance? clojure.lang.ExceptionInfo @ex)) 228 | (is (not (realized? rsp))))) 229 | 230 | (testing "check access rules 4" 231 | (is (thrown? clojure.lang.ExceptionInfo (handler1 {:uri "/path4"})))) 232 | 233 | (testing "check access rules 4 async" 234 | (let [req {:uri "/path4"} 235 | rsp (promise) 236 | ex (promise)] 237 | (async-handler1 req rsp ex) 238 | (is (instance? clojure.lang.ExceptionInfo @ex)) 239 | (is (not (realized? rsp))))) 240 | 241 | (testing "check access rules 5" 242 | (let [rsp (handler2 {:uri "/path3"})] 243 | (is (= 400 (:status rsp))) 244 | (is (= {:uri "/path3" :match-params {}} (:body rsp))))) 245 | 246 | (testing "check access rules 5 async" 247 | (let [req {:uri "/path3"} 248 | rsp (promise) 249 | ex (promise)] 250 | (async-handler2 req rsp ex) 251 | (is (= 400 (:status @rsp))) 252 | (is (= {:uri "/path3" :match-params {}} (:body @rsp))) 253 | (is (not (realized? ex))))) 254 | 255 | (testing "check access rules 6" 256 | (let [rsp (handler2 {:uri "/path4"})] 257 | (is (= 400 (:status rsp))) 258 | (is (= nil (:body rsp))))) 259 | 260 | (testing "check access rules 6 async" 261 | (let [req {:uri "/path4"} 262 | rsp (promise) 263 | ex (promise)] 264 | (async-handler2 req rsp ex) 265 | (is (= 400 (:status @rsp))) 266 | (is (= nil (:body @rsp))) 267 | (is (not (realized? ex))))) 268 | 269 | ;; Clout format 270 | 271 | (testing "check access rules 1" 272 | (let [rsp (handler3 {:uri "/path1"})] 273 | (is (= {:uri "/path1"} (:body rsp))))) 274 | 275 | (testing "check access rules 1 async" 276 | (let [req {:uri "/path1"} 277 | rsp (promise) 278 | ex (promise)] 279 | (async-handler3 req rsp ex) 280 | (is (= {:uri "/path1"} (:body @rsp))) 281 | (is (not (realized? ex))))) 282 | 283 | (testing "check access rules 2" 284 | (let [rsp (handler3 {:uri "/path2"})] 285 | (is (= {:uri "/path2"} (:body rsp))))) 286 | 287 | (testing "check access rules 2 async" 288 | (let [req {:uri "/path2"} 289 | rsp (promise) 290 | ex (promise)] 291 | (async-handler3 req rsp ex) 292 | (is (= {:uri "/path2"} (:body @rsp))) 293 | (is (not (realized? ex))))) 294 | 295 | (testing "check access rules 3" 296 | (let [rsp (handler3 {:uri "/path/foobar" :body "Fail" :status 400 :headers {}})] 297 | (is (= 400 (:status rsp))) 298 | (is (= {:param "foobar"} (:match-params rsp))) 299 | (is (= "Fail" (:body rsp))))) 300 | 301 | (testing "check access rules 3 async" 302 | (let [req {:uri "/path/foobar" :body "Fail" :status 400 :headers {}} 303 | rsp (promise) 304 | ex (promise)] 305 | (async-handler3 req rsp ex) 306 | (is (= 400 (:status @rsp))) 307 | (is (= {:param "foobar"} (:match-params @rsp))) 308 | (is (= "Fail" (:body @rsp))) 309 | (is (not (realized? ex))))) 310 | 311 | (testing "check access rules 4" 312 | (is (thrown? clojure.lang.ExceptionInfo (handler3 {:uri "/path3"})))) 313 | 314 | (testing "check access rules 4 async" 315 | (let [req {:uri "/path3"} 316 | rsp (promise) 317 | ex (promise)] 318 | (async-handler3 req rsp ex) 319 | (is (instance? clojure.lang.ExceptionInfo @ex)) 320 | (is (not (realized? rsp))))) 321 | 322 | (testing "check access rules 5" 323 | (is (thrown? clojure.lang.ExceptionInfo (handler3 {:uri "/path4"})))) 324 | 325 | (testing "check access rules 5 async" 326 | (let [req {:uri "/path4"} 327 | rsp (promise) 328 | ex (promise)] 329 | (async-handler3 req rsp ex) 330 | (is (instance? clojure.lang.ExceptionInfo @ex)) 331 | (is (not (realized? rsp))))) 332 | ) 333 | 334 | (defn method-handler [type type-param allowed] 335 | (wrap-access-rules 336 | test-handler 337 | {:rules [{type type-param 338 | :handler ok 339 | :request-method allowed}] 340 | :policy :reject})) 341 | 342 | (defn async-method-handler [type type-param allowed] 343 | (wrap-access-rules 344 | async-test-handler 345 | {:rules [{type type-param 346 | :handler ok 347 | :request-method allowed}] 348 | :policy :reject})) 349 | 350 | (deftest wrap-access-rules-method-test 351 | (let [allowed {:uri "/comments/1" :request-method :get} 352 | forbidden (assoc allowed :request-method :delete)] 353 | 354 | (testing "access rule pattern" 355 | (let [method-handler (partial method-handler :pattern #"/comments/\d+") 356 | async-method-handler (partial async-method-handler 357 | :pattern #"/comments/\d+")] 358 | 359 | (testing "with keyword as allowed method" 360 | (let [handler (method-handler :get)] 361 | (is (= (:body (handler allowed)) allowed)) 362 | (is (thrown? clojure.lang.ExceptionInfo (handler forbidden))))) 363 | 364 | (testing "async with keyword as allowed method" 365 | (let [handler (async-method-handler :get)] 366 | (let [rsp (promise) 367 | ex (promise)] 368 | (handler allowed rsp ex) 369 | (is (= (:body @rsp) allowed)) 370 | (is (not (realized? ex)))) 371 | (let [rsp (promise) 372 | ex (promise)] 373 | (handler forbidden rsp ex) 374 | (is (instance? clojure.lang.ExceptionInfo @ex)) 375 | (is (not (realized? rsp)))))) 376 | 377 | (testing "with set of keywords as allowed method" 378 | (let [handler (method-handler #{:get})] 379 | (is (= (:body (handler allowed)) allowed)) 380 | (is (thrown? clojure.lang.ExceptionInfo (handler forbidden))))) 381 | 382 | (testing "async with set of keywords as allowed method" 383 | (let [handler (async-method-handler #{:get})] 384 | (let [rsp (promise) 385 | ex (promise)] 386 | (handler allowed rsp ex) 387 | (is (= (:body @rsp) allowed)) 388 | (is (not (realized? ex)))) 389 | (let [rsp (promise) 390 | ex (promise)] 391 | (handler forbidden rsp ex) 392 | (is (instance? clojure.lang.ExceptionInfo @ex)) 393 | (is (not (realized? rsp)))))) 394 | 395 | (testing "with nil as allowed request method" 396 | (let [handler (method-handler nil)] 397 | (is (= (:body (handler allowed)) allowed)))) 398 | 399 | (testing "async with nil as allowed request method" 400 | (let [handler (async-method-handler nil) 401 | rsp (promise) 402 | ex (promise)] 403 | (handler allowed rsp ex) 404 | (is (= (:body @rsp) allowed)) 405 | (is (not (realized? ex))))))) 406 | 407 | (testing "access rule uri" 408 | (let [method-handler (partial method-handler :uri "/comments/:id") 409 | async-method-handler (partial async-method-handler 410 | :uri "/comments/:id")] 411 | 412 | (testing "with keyword as allowed method" 413 | (let [handler (method-handler :get)] 414 | (is (= (:body (handler allowed)) allowed)) 415 | (is (thrown? clojure.lang.ExceptionInfo (handler forbidden))))) 416 | 417 | (testing "async with keyword as allowed method" 418 | (let [handler (async-method-handler :get)] 419 | (let [rsp (promise) 420 | ex (promise)] 421 | (handler allowed rsp ex) 422 | (is (= (:body @rsp) allowed)) 423 | (is (not (realized? ex)))) 424 | (let [rsp (promise) 425 | ex (promise)] 426 | (handler forbidden rsp ex) 427 | (is (instance? clojure.lang.ExceptionInfo @ex)) 428 | (is (not (realized? rsp)))))) 429 | 430 | (testing "with set of keywords as allowed method" 431 | (let [handler (method-handler #{:get})] 432 | (is (= (:body (handler allowed)) allowed)) 433 | (is (thrown? clojure.lang.ExceptionInfo (handler forbidden))))) 434 | 435 | (testing "async with set of keywords as allowed method" 436 | (let [handler (async-method-handler #{:get})] 437 | (let [rsp (promise) 438 | ex (promise)] 439 | (handler allowed rsp ex) 440 | (is (= (:body @rsp) allowed)) 441 | (is (not (realized? ex)))) 442 | (let [rsp (promise) 443 | ex (promise)] 444 | (handler forbidden rsp ex) 445 | (is (instance? clojure.lang.ExceptionInfo @ex)) 446 | (is (not (realized? rsp)))))) 447 | 448 | (testing "with nil as allowed request method" 449 | (let [handler (method-handler nil)] 450 | (is (= (:body (handler allowed)) allowed)))) 451 | 452 | (testing "async with nil as allowed request method" 453 | (let [handler (async-method-handler nil) 454 | rsp (promise) 455 | ex (promise)] 456 | (handler allowed rsp ex) 457 | (is (= (:body @rsp) allowed)) 458 | (is (not (realized? ex))))))) 459 | 460 | (testing "access rule uris" 461 | (let [method-handler (partial method-handler :uris ["/comments/:id"]) 462 | async-method-handler (partial async-method-handler 463 | :uris ["/comments/:id"])] 464 | 465 | (testing "with keyword as allowed method" 466 | (let [handler (method-handler :get)] 467 | (is (= (:body (handler allowed)) allowed)) 468 | (is (thrown? clojure.lang.ExceptionInfo (handler forbidden))))) 469 | 470 | (testing "async with keyword as allowed method" 471 | (let [handler (async-method-handler :get)] 472 | (let [rsp (promise) 473 | ex (promise)] 474 | (handler allowed rsp ex) 475 | (is (= (:body @rsp) allowed)) 476 | (is (not (realized? ex)))) 477 | (let [rsp (promise) 478 | ex (promise)] 479 | (handler forbidden rsp ex) 480 | (is (instance? clojure.lang.ExceptionInfo @ex)) 481 | (is (not (realized? rsp)))))) 482 | 483 | (testing "with set of keywords as allowed method" 484 | (let [handler (method-handler #{:get})] 485 | (is (= (:body (handler allowed)) allowed)) 486 | (is (thrown? clojure.lang.ExceptionInfo (handler forbidden))))) 487 | 488 | (testing "async with set of keywords as allowed method" 489 | (let [handler (async-method-handler #{:get})] 490 | (let [rsp (promise) 491 | ex (promise)] 492 | (handler allowed rsp ex) 493 | (is (= (:body @rsp) allowed)) 494 | (is (not (realized? ex)))) 495 | (let [rsp (promise) 496 | ex (promise)] 497 | (handler forbidden rsp ex) 498 | (is (instance? clojure.lang.ExceptionInfo @ex)) 499 | (is (not (realized? rsp)))))) 500 | 501 | (testing "with nil as allowed request method" 502 | (let [handler (method-handler nil)] 503 | (is (= (:body (handler allowed)) allowed)))) 504 | 505 | (testing "async with nil as allowed request method" 506 | (let [handler (async-method-handler nil) 507 | rsp (promise) 508 | ex (promise)] 509 | (handler allowed rsp ex) 510 | (is (= (:body @rsp) allowed)) 511 | (is (not (realized? ex))))))))) 512 | -------------------------------------------------------------------------------- /test/buddy/auth/backends/httpbasic_tests.clj: -------------------------------------------------------------------------------- 1 | (ns buddy.auth.backends.httpbasic-tests 2 | (:require [clojure.test :refer :all] 3 | [buddy.core.codecs :refer :all] 4 | [buddy.core.codecs.base64 :as b64] 5 | [buddy.auth :refer [throw-unauthorized]] 6 | [buddy.auth.http :as http] 7 | [buddy.auth.backends :as backends] 8 | [buddy.auth.backends.httpbasic :as httpbasic] 9 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]])) 10 | 11 | (defn make-header 12 | [username password] 13 | (format "Basic %s" (-> (b64/encode (format "%s:%s" username password)) 14 | (bytes->str)))) 15 | 16 | (defn make-request 17 | ([] {:headers {}}) 18 | ([username password] 19 | (let [auth (make-header username password)] 20 | {:headers {"auThorIzation" auth "lala" "2"}}))) 21 | 22 | (defn auth-fn 23 | [request {:keys [username]}] 24 | (if (= username "foo") 25 | :valid 26 | :invalid)) 27 | 28 | (def backend 29 | (backends/http-basic 30 | {:authfn auth-fn :realm "Foo"})) 31 | 32 | (deftest httpbasic-parse-test 33 | (testing "Parse httpbasic header from request" 34 | (let [parse #'httpbasic/parse-header 35 | request (make-request "foo" "bar") 36 | parsed (parse request)] 37 | (is (not (nil? parsed))) 38 | (is (= (:password parsed) "bar")) 39 | (is (= (:username parsed) "foo")))) 40 | (testing "Parse httpbasic header from request with colon in password" 41 | (let [parse #'httpbasic/parse-header 42 | request (make-request "foo" "bar:baz") 43 | parsed (parse request)] 44 | (is (not (nil? parsed))) 45 | (is (= (:password parsed) "bar:baz")) 46 | (is (= (:username parsed) "foo"))))) 47 | 48 | (deftest httpbasic-auth-backend 49 | (testing "Testing anon request" 50 | (let [handler (wrap-authentication identity backend) 51 | request (make-request) 52 | response (handler request)] 53 | (is (= (:identity response) nil)))) 54 | 55 | (testing "Test wrong request" 56 | (let [handler (wrap-authentication identity backend) 57 | request (make-request "test" "test") 58 | response (handler request)] 59 | (is (= (:identity response) :invalid)))) 60 | 61 | (testing "Test auth request" 62 | (let [handler (wrap-authentication identity backend) 63 | request (make-request "foo" "bar") 64 | response (handler request)] 65 | (is (= (:identity response) :valid)))) 66 | 67 | (testing "Authorization middleware tests 01" 68 | (let [handler (-> (fn [req] (if (nil? (:identity req)) 69 | (throw-unauthorized {:msg "FooMsg"}) 70 | req)) 71 | (wrap-authorization backend) 72 | (wrap-authentication backend)) 73 | request (make-request "user" "pass") 74 | response (handler request)] 75 | (is (= (:identity response) :invalid)))) 76 | 77 | (testing "Authorization middleware tests 02 with httpbasic backend" 78 | (let [handler (-> (fn [req] (if (nil? (:identity req)) 79 | (throw-unauthorized {:msg "FooMsg"}) 80 | req)) 81 | (wrap-authorization backend) 82 | (wrap-authentication backend)) 83 | request (make-request "foo" "pass") 84 | response (handler request)] 85 | (is (= (:identity response) :valid)))) 86 | 87 | (testing "Authorization middleware tests 03 with httpbasic backend" 88 | (let [handler (-> (fn [req] (throw-unauthorized {:msg "FooMsg"})) 89 | (wrap-authorization backend) 90 | (wrap-authentication backend)) 91 | request (make-request "foo" "pass") 92 | response (handler request)] 93 | (is (= (:status response) 403))))) 94 | -------------------------------------------------------------------------------- /test/buddy/auth/backends/session_tests.clj: -------------------------------------------------------------------------------- 1 | (ns buddy.auth.backends.session-tests 2 | (:require [clojure.test :refer :all] 3 | [buddy.core.codecs :refer :all] 4 | [buddy.auth :refer [throw-unauthorized]] 5 | [buddy.auth.backends :as backends] 6 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]])) 7 | 8 | (defn make-request 9 | ([] {:session {}}) 10 | ([id] {:session {:identity {:userid 1}}})) 11 | 12 | (def backend (backends/session)) 13 | (def backend-with-authfn (backends/session {:authfn (constantly ::authorized)})) 14 | 15 | (deftest session-backend-test 16 | (testing "Simple backend authentication 01" 17 | (let [handler (wrap-authentication identity backend) 18 | request (make-request 1) 19 | response (handler request)] 20 | (is (= (:identity response) {:userid 1})))) 21 | 22 | (testing "Simple backend authentication 02" 23 | (let [handler (wrap-authentication identity backend) 24 | request (make-request) 25 | response (handler request)] 26 | (is (nil? (:identity response))))) 27 | 28 | (testing "Handle unauthenticated unauthorized requests without specifying unauthorized handler" 29 | (let [handler (-> (fn [req] (throw-unauthorized "FooMsg")) 30 | (wrap-authorization backend) 31 | (wrap-authentication backend)) 32 | request (make-request) 33 | response (handler request)] 34 | (is (= (:status response) 401)))) 35 | 36 | (testing "Handle unauthorized requests specifying unauthorized handler" 37 | (let [onerror (fn [request metadata] {:body "" :status 3000}) 38 | backend (backends/session {:unauthorized-handler onerror}) 39 | handler (-> (fn [req] (throw-unauthorized "FooMsg")) 40 | (wrap-authorization backend) 41 | (wrap-authentication backend)) 42 | request (make-request) 43 | response (handler request)] 44 | (is (= (:status response) 3000)))) 45 | 46 | (testing "Handle authenticated unauthorized requests without specifying unauthorized handler" 47 | (let [handler (-> (fn [req] (throw-unauthorized "FooMsg")) 48 | (wrap-authorization backend) 49 | (wrap-authentication backend)) 50 | request (make-request 1) 51 | response (handler request)] 52 | (is (= (:status response) 403)))) 53 | 54 | (testing "Uses custom authfn when provided" 55 | (let [handler (wrap-authentication identity backend-with-authfn) 56 | request (make-request 1) 57 | response (handler request)] 58 | (is (= ::authorized (:identity response)))))) 59 | -------------------------------------------------------------------------------- /test/buddy/auth/backends/token_tests.clj: -------------------------------------------------------------------------------- 1 | (ns buddy.auth.backends.token-tests 2 | (:require [clojure.test :refer :all] 3 | [buddy.core.codecs :refer :all] 4 | [buddy.core.hash :as hash] 5 | [buddy.core.keys :as keys] 6 | [buddy.sign.jwt :as jwt] 7 | [buddy.auth :refer [throw-unauthorized authenticated?]] 8 | [buddy.auth.backends :as backends] 9 | [buddy.auth.backends.token :as token] 10 | [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]])) 11 | 12 | (def secret "test-secret-key") 13 | 14 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 15 | ;; Helpers 16 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 17 | 18 | (defn make-request 19 | [token] 20 | (let [header (format "Token %s" token)] 21 | {:headers {"auThorIzation" header}})) 22 | 23 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 24 | ;; Tests: parse 25 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 26 | 27 | (deftest token-parse-test 28 | (testing "Parse authorization header" 29 | (let [request (make-request "foo") 30 | parse #'token/parse-header 31 | parsed (parse request "Token")] 32 | (is (= parsed "foo")))) 33 | 34 | (testing "Parse authorization header different header name yields nil" 35 | (let [parse #'token/parse-header 36 | parsed (parse (make-request "foo") "MyToken")] 37 | (is (= parsed nil))))) 38 | 39 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 40 | ;; Tests: JWS 41 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 42 | 43 | (def jws-secret "mysuperjwssecret") 44 | (def jws-backend (backends/jws {:secret jws-secret})) 45 | (def jws-backend-with-authfn (backends/jws {:secret jws-secret 46 | :authfn (constantly ::jws-authorized)})) 47 | (def jws-data {:userid 1}) 48 | 49 | (def rsa-privkey (keys/private-key "test/_files/privkey.3des.rsa.pem" "secret")) 50 | (def rsa-pubkey (keys/public-key "test/_files/pubkey.3des.rsa.pem")) 51 | (def jws-backend-rsa (backends/jws {:secret rsa-pubkey :options {:alg :ps512}})) 52 | 53 | (defn make-jws-request 54 | ([data secret] 55 | (make-jws-request data secret {})) 56 | ([data secret options] 57 | (let [header (->> (jwt/sign data secret options) 58 | (format "Token %s"))] 59 | {:headers {"authorization" header}}))) 60 | 61 | (deftest jws-tests 62 | (testing "Jws token backend authentication" 63 | (let [request (make-jws-request jws-data jws-secret) 64 | handler (wrap-authentication identity jws-backend) 65 | request' (handler request)] 66 | (is (authenticated? request')) 67 | (is (= (:identity request') jws-data)))) 68 | 69 | (testing "Jws token backend authentication with RSA key" 70 | (let [request (make-jws-request jws-data rsa-privkey {:alg :ps512}) 71 | handler (wrap-authentication identity jws-backend-rsa) 72 | request' (handler request)] 73 | (is (authenticated? request')) 74 | (is (= (:identity request') jws-data)))) 75 | 76 | (testing "Jws token backend authentication with wrong key yields nil" 77 | (let [request (make-jws-request jws-data "wrong-key") 78 | handler (wrap-authentication identity jws-backend) 79 | request' (handler request)] 80 | (is (not (authenticated? request'))) 81 | (is (nil? (:identity request'))))) 82 | 83 | (testing "Jws token backend authentication without token yields nil" 84 | (let [request {} 85 | handler (wrap-authentication identity jws-backend) 86 | request' (handler request)] 87 | (is (not (authenticated? request'))) 88 | (is (nil? (:identity request'))))) 89 | 90 | (testing "Jws token authorizaton with wrong key yields 401" 91 | (let [request (make-jws-request jws-data "wrong-key") 92 | handler (-> (fn [req] (throw-unauthorized)) 93 | (wrap-authorization jws-backend) 94 | (wrap-authentication jws-backend)) 95 | response (handler request)] 96 | (is (= (:status response) 401)) 97 | (is (= (:body response) "Unauthorized")))) 98 | 99 | (testing "Jws token authorization with authenticated but unathorized thrown yields 403" 100 | (let [request (make-jws-request {:userid 1} jws-secret) 101 | handler (-> (fn [req] (throw-unauthorized)) 102 | (wrap-authorization jws-backend) 103 | (wrap-authentication jws-backend)) 104 | response (handler request)] 105 | (is (= (:status response) 403)) 106 | (is (= (:body response) "Permission denied")))) 107 | 108 | (testing "Jws token unathorized with :unauthorized-handlercalled when provided" 109 | (let [request (make-jws-request jws-data "wrong-key") 110 | onerror (fn [_ _] {:status 3000}) 111 | backend (backends/jws {:secret jws-secret 112 | :unauthorized-handler onerror}) 113 | handler (-> (fn [req] (throw-unauthorized)) 114 | (wrap-authorization backend) 115 | (wrap-authentication backend)) 116 | response (handler request)] 117 | (is (= (:status response) 3000)))) 118 | 119 | (testing "Jws token wrongdata with onerror handler called when provided" 120 | (let [request (make-jws-request jws-data "wrong-key") 121 | p (promise) 122 | onerror (fn [_ _] (deliver p true)) 123 | backend (backends/jws {:secret jws-secret 124 | :on-error onerror}) 125 | handler (-> identity 126 | (wrap-authorization backend) 127 | (wrap-authentication backend)) 128 | response (handler request)] 129 | (is (deref p 1000 false)) 130 | (is (= response request))))) 131 | 132 | (testing "Jws token with wrong token" 133 | (let [request (assoc (make-request "xyz") 134 | :foo :bar) 135 | backend (backends/jws {:secret jws-secret}) 136 | handler (-> identity 137 | (wrap-authorization backend) 138 | (wrap-authentication backend)) 139 | response (handler request)] 140 | (is (nil? (:identity request))) 141 | (is (= :bar (:foo request))))) 142 | 143 | (testing "Jws with custom authfn" 144 | (let [request (make-jws-request jws-data jws-secret) 145 | handler (wrap-authentication identity jws-backend-with-authfn) 146 | request' (handler request)] 147 | (is (authenticated? request')) 148 | (is (= ::jws-authorized (:identity request')))) 149 | ) 150 | 151 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 152 | ;; Tests: JWE 153 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 154 | 155 | (def jwe-secret (hash/sha256 "mysupersecretkey")) 156 | (def jwe-backend (backends/jwe {:secret jwe-secret})) 157 | (def jwe-backend-with-authfn (backends/jwe {:secret jwe-secret :authfn (constantly ::jwe-authorized)})) 158 | (def jwe-data {:userid 1}) 159 | 160 | (defn make-jwe-request 161 | [data secret] 162 | (let [header (->> (jwt/encrypt data secret) 163 | (format "Token %s"))] 164 | {:headers {"authorization" header}})) 165 | 166 | (deftest jwe-backend-test 167 | (testing "Jwe token backend authentication" 168 | (let [request (make-jwe-request jwe-data jwe-secret) 169 | handler (wrap-authentication identity jwe-backend) 170 | request' (handler request)] 171 | (is (authenticated? request')) 172 | (is (= (:identity request') jwe-data)))) 173 | 174 | (testing "Jwe token backend authentication with wrong key yields nil" 175 | (let [request (make-jwe-request jwe-data (hash/sha256 "wrong-key")) 176 | handler (wrap-authentication identity jwe-backend) 177 | request' (handler request)] 178 | (is (not (authenticated? request'))) 179 | (is (nil? (:identity request'))))) 180 | 181 | (testing "Jwe token backend authentication with no token yields nil" 182 | (let [request {} 183 | handler (wrap-authentication identity jwe-backend) 184 | request' (handler request)] 185 | (is (not (authenticated? request'))) 186 | (is (nil? (:identity request'))))) 187 | 188 | (testing "Jwe token authorizaton with wrong key yields 401" 189 | (let [request (make-jwe-request jwe-data (hash/sha256 "wrong-key")) 190 | handler (-> (fn [req] (throw-unauthorized)) 191 | (wrap-authorization jwe-backend) 192 | (wrap-authentication jwe-backend)) 193 | response (handler request)] 194 | (is (= (:status response) 401)))) 195 | 196 | (testing "Jwe token authorization with authenticated but unathorized thrown yields 403" 197 | (let [request (make-jwe-request {:userid 1} jwe-secret) 198 | handler (-> (fn [req] (throw-unauthorized)) 199 | (wrap-authorization jwe-backend) 200 | (wrap-authentication jwe-backend)) 201 | response (handler request)] 202 | (is (= (:status response) 403)))) 203 | 204 | (testing "Jwe token unathorized with unauth handler called when provided" 205 | (let [request (make-jwe-request jwe-data (hash/sha256 "wrong-key")) 206 | onerror (fn [_ _] {:status 3000}) 207 | backend (backends/jwe {:secret jwe-secret 208 | :unauthorized-handler onerror}) 209 | handler (-> (fn [req] (throw-unauthorized)) 210 | (wrap-authorization backend) 211 | (wrap-authentication backend)) 212 | response (handler request)] 213 | (is (= (:status response) 3000)))) 214 | 215 | (testing "Jwe token wrongdata with onerror handler called when provided" 216 | (let [request (make-jwe-request jws-data (hash/sha256 "foobar")) 217 | p (promise) 218 | onerror (fn [_ _] (deliver p true)) 219 | backend (backends/jwe {:secret jwe-secret 220 | :on-error onerror}) 221 | handler (-> identity 222 | (wrap-authorization backend) 223 | (wrap-authentication backend)) 224 | response (handler request)] 225 | (is (deref p 1000 false)) 226 | (is (= response request)))) 227 | 228 | (testing "Jwe token backend authentication with custom authfn" 229 | (let [request (make-jwe-request jwe-data jwe-secret) 230 | handler (wrap-authentication identity jwe-backend-with-authfn) 231 | request' (handler request)] 232 | (is (authenticated? request')) 233 | (is (= ::jwe-authorized (:identity request')))))) 234 | 235 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 236 | ;; Tests: Token 237 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 238 | 239 | (defn token-authfn 240 | [request token] 241 | (let [data {:token1 {:userid 1} 242 | :token2 {:userid 2}}] 243 | (get data (keyword token)))) 244 | 245 | (def backend (backends/token {:authfn token-authfn})) 246 | 247 | (deftest token-backend-test 248 | (testing "Basic token backend authentication 01" 249 | (let [request (make-request "token1") 250 | handler (wrap-authentication #(:identity %) backend) 251 | response (handler request)] 252 | (is (= response {:userid 1})))) 253 | 254 | (testing "Basic token backend authentication 02" 255 | (let [request (make-request "token3") 256 | handler (wrap-authentication #(:identity %) backend) 257 | response (handler request)] 258 | (is (= response nil)))) 259 | 260 | (testing "Token backend with unauthorized requests 1" 261 | (let [request (make-request "token1") 262 | handler (-> (fn [request] (throw-unauthorized)) 263 | (wrap-authorization backend) 264 | (wrap-authentication backend)) 265 | response (handler request)] 266 | (is (= (:status response) 403)))) 267 | 268 | (testing "Token backend with unauthorized requests 2" 269 | (let [request (make-request "token3") 270 | handler (-> (fn [request] (throw-unauthorized)) 271 | (wrap-authorization backend) 272 | (wrap-authentication backend)) 273 | response (handler request)] 274 | (is (= (:status response) 401)))) 275 | 276 | (testing "Token backend with unauthorized requests 3" 277 | (let [request (make-request "token3") 278 | onerror (fn [_ _] {:status 3000}) 279 | backend (backends/token {:authfn token-authfn 280 | :unauthorized-handler onerror}) 281 | handler (-> (fn [request] (throw-unauthorized)) 282 | (wrap-authorization backend) 283 | (wrap-authentication backend)) 284 | response (handler request)] 285 | (is (= (:status response) 3000))))) 286 | -------------------------------------------------------------------------------- /test/buddy/auth/middleware_tests.clj: -------------------------------------------------------------------------------- 1 | (ns buddy.auth.middleware-tests 2 | (:require [clojure.test :refer :all] 3 | [buddy.core.codecs :refer :all] 4 | [buddy.auth :refer [throw-unauthorized]] 5 | [buddy.auth.protocols :as proto] 6 | [buddy.auth.middleware :as mw])) 7 | 8 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 9 | ;; Authentication middleware testing 10 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 11 | 12 | (defn auth-backend 13 | [secret token-name] 14 | (reify 15 | proto/IAuthentication 16 | (-parse [_ request] 17 | (get request token-name)) 18 | 19 | (-authenticate [_ request data] 20 | (assert data) 21 | (when (= data secret) 22 | :valid)))) 23 | 24 | (defn- async-identity [req respond _] 25 | (respond req)) 26 | 27 | (deftest wrap-authentication 28 | (testing "Using auth requests" 29 | (let [handler (mw/wrap-authentication identity (auth-backend ::ok ::authdata)) 30 | response (handler {::authdata ::ok})] 31 | (is (= (:identity response) :valid)) 32 | (is (= (::authdata response) ::ok)))) 33 | 34 | (testing "Using auth async requests" 35 | (let [handler (-> async-identity 36 | (mw/wrap-authentication (auth-backend ::ok ::authdata))) 37 | response (promise) 38 | exception (promise)] 39 | (handler {::authdata ::ok} response exception) 40 | (is (= (:identity @response) :valid)) 41 | (is (= (::authdata @response) ::ok)) 42 | (is (not (realized? exception))))) 43 | 44 | (testing "Using anon request" 45 | (let [handler (mw/wrap-authentication identity (auth-backend ::ok ::authdata)) 46 | response (handler {})] 47 | (is (= (:identity response) nil)) 48 | (is (= (::authdata response) nil)))) 49 | 50 | (testing "Using anon async request" 51 | (let [handler (-> async-identity 52 | (mw/wrap-authentication (auth-backend ::ok ::authdata))) 53 | response (promise) 54 | exception (promise)] 55 | (handler {} response exception) 56 | (is (= (:identity @response) nil)) 57 | (is (= (::authdata @response) nil)) 58 | (is (not (realized? exception))))) 59 | 60 | (testing "Using wrong request" 61 | (let [handler (mw/wrap-authentication identity (auth-backend ::ok ::authdata)) 62 | response (handler {::authdata ::fake})] 63 | (is (nil? (:identity response))) 64 | (is (= (::authdata response) ::fake)))) 65 | 66 | (testing "Using wrong async request" 67 | (let [handler (-> async-identity 68 | (mw/wrap-authentication (auth-backend ::ok ::authdata))) 69 | response (promise) 70 | exception (promise)] 71 | (handler {::authdata ::fake} response promise) 72 | (is (nil? (:identity @response))) 73 | (is (= (::authdata @response) ::fake)) 74 | (is (not (realized? exception)))))) 75 | 76 | (deftest wrap-authentication-with-multiple-backends 77 | (let [backends [(auth-backend ::ok-1 ::authdata) 78 | (auth-backend ::ok-2 ::authdata2)] 79 | handler (apply mw/wrap-authentication identity backends) 80 | async-handler (apply mw/wrap-authentication async-identity backends)] 81 | 82 | (testing "backend #1 succeeds" 83 | (let [response (handler {::authdata ::ok-1})] 84 | (is (= (:identity response) :valid)) 85 | (is (= (::authdata response) ::ok-1)))) 86 | 87 | (testing "backend #1 succeeds for async" 88 | (let [response (promise) 89 | exception (promise)] 90 | (async-handler {::authdata ::ok-1} response exception) 91 | (is (= (:identity @response) :valid)) 92 | (is (= (::authdata @response) ::ok-1)) 93 | (is (not (realized? exception))))) 94 | 95 | (testing "backend #2 succeeds" 96 | (let [response (handler {::authdata2 ::ok-2})] 97 | (is (= (:identity response) :valid)) 98 | (is (= (::authdata2 response) ::ok-2)))) 99 | 100 | (testing "backend #2 succeeds for async" 101 | (let [response (promise) 102 | exception (promise)] 103 | (async-handler {::authdata2 ::ok-2} response exception) 104 | (is (= (:identity @response) :valid)) 105 | (is (= (::authdata2 @response) ::ok-2)) 106 | (is (not (realized? exception))))) 107 | 108 | (testing "no backends succeeds" 109 | (let [response (handler {::authdata ::fake})] 110 | (is (nil? (:identity response))) 111 | (is (= (::authdata response) ::fake)))) 112 | 113 | (testing "no backends succeeds for async" 114 | (let [response (promise) 115 | exception (promise)] 116 | (async-handler {::authdata ::fake} response exception) 117 | (is (nil? (:identity @response))) 118 | (is (= (::authdata @response) ::fake)) 119 | (is (not (realized? exception))))) 120 | 121 | (testing "handler called exactly once" 122 | (let [state (atom 0) 123 | counter (fn [request] (swap! state inc) request) 124 | handler (apply mw/wrap-authentication counter backends) 125 | response (handler {::authdata ::fake})] 126 | (is (nil? (:identity response))) 127 | (is (= (::authdata response) ::fake)) 128 | (is (= @state 1)))) 129 | 130 | (testing "async handler called exactly once" 131 | (let [state (atom 0) 132 | counter (fn [request respond raise] 133 | (swap! state inc) 134 | (respond request)) 135 | handler (apply mw/wrap-authentication counter backends) 136 | response (promise) 137 | exception (promise)] 138 | (handler {::authdata ::fake} response exception) 139 | (is (nil? (:identity @response))) 140 | (is (= (::authdata @response) ::fake)) 141 | (is (= @state 1)) 142 | (is (not (realized? exception))))) 143 | 144 | (testing "with zero backends" 145 | (let [request {:uri "/"}] 146 | (is (= ((mw/wrap-authentication identity) request) request)))) 147 | 148 | (testing "with zero backends for async" 149 | (let [request {:uri "/"} 150 | response (promise) 151 | exception (promise)] 152 | ((mw/wrap-authentication async-identity) request response exception) 153 | (is (= @response request)))))) 154 | 155 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 156 | ;; Authorization middleware testing 157 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 158 | 159 | (def autz-backend 160 | (reify 161 | proto/IAuthorization 162 | (-handle-unauthorized [_ request data] 163 | {:body "error" :status 401 :data data}))) 164 | 165 | (deftest wrap-authorization 166 | (testing "Simple authorized request" 167 | (let [handler (mw/wrap-authorization identity autz-backend) 168 | response (handler {:foo :bar})] 169 | (is (= (:foo response) :bar)))) 170 | 171 | (testing "Simple authorized async request" 172 | (let [handler (mw/wrap-authorization async-identity autz-backend) 173 | response (promise) 174 | exception (promise)] 175 | (handler {:foo :bar} response exception) 176 | (is (= (:foo @response) :bar)) 177 | (is (not (realized? exception))))) 178 | 179 | (testing "Unauthorized request" 180 | (let [handler (fn [req] 181 | (throw-unauthorized {:foo :bar})) 182 | handler (mw/wrap-authorization handler autz-backend) 183 | response (handler {})] 184 | (is (= (:body response) "error")) 185 | (is (= (:status response) 401)) 186 | (is (= (:data response) {:foo :bar})))) 187 | 188 | (testing "Unauthorized async request" 189 | (let [handler (fn [req respond raise] 190 | (throw-unauthorized {:foo :bar})) 191 | handler (mw/wrap-authorization handler autz-backend) 192 | response (promise) 193 | exception (promise)] 194 | (handler {} response exception) 195 | (is (= (:body @response) "error")) 196 | (is (= (:status @response) 401)) 197 | (is (= (:data @response) {:foo :bar})) 198 | (is (not (realized? exception))))) 199 | 200 | ;; (testing "Unauthorized request with custom exception" 201 | ;; (let [handler (fn [req] 202 | ;; (throw (proxy [Exception proto/IAuthorization] [] 203 | ;; proto/IAuthorizationdError 204 | ;; (-get-error-data [_] 205 | ;; {:foo :bar})))) 206 | ;; handler (mw/wrap-authorization handler autz-backend) 207 | ;; response (handler {})] 208 | ;; (is (= (:body response) "error")) 209 | ;; (is (= (:status response) 401)) 210 | ;; (is (= (:data response) {:foo :bar})))) 211 | 212 | (testing "Unauthorized request with backend as function" 213 | (let [backend (fn [request data] {:body "error" :status 401 :data data}) 214 | handler (fn [req] 215 | (throw-unauthorized {:foo :bar})) 216 | handler (mw/wrap-authorization handler backend) 217 | response (handler {})] 218 | (is (= (:body response) "error")) 219 | (is (= (:status response) 401)) 220 | (is (= (:data response) {:foo :bar})))) 221 | 222 | (testing "Unauthorized async request with backend as function" 223 | (let [backend (fn [request data] {:body "error" :status 401 :data data}) 224 | handler (fn [req respond raise] 225 | (throw-unauthorized {:foo :bar})) 226 | handler (mw/wrap-authorization handler backend) 227 | response (promise) 228 | exception (promise)] 229 | (handler {} response exception) 230 | (is (= (:body @response) "error")) 231 | (is (= (:status @response) 401)) 232 | (is (= (:data @response) {:foo :bar})) 233 | (is (not (realized? exception)))))) 234 | --------------------------------------------------------------------------------