;;; url-auth-tests.el --- Test suite for url-auth. -*- lexical-binding:t -*- ;; Copyright (C) 2015-2020 Free Software Foundation, Inc. ;; Author: Jarno Malmari ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; Test HTTP authentication methods. ;;; Code: (require 'ert) (require 'url-auth) (defvar url-auth-test-challenges nil "List of challenges for testing. Each challenge is a plist. Values are as presented by the server's WWW-Authenticate header field.") ;; Set explicitly for easier modification for re-runs. (setq url-auth-test-challenges (list (list :qop "auth" :nonce "uBr3+qkQBybTr/dKWkmpUqVO7SaEwWYzyTKO7g==$" :uri "/random/path" :method "GET" :realm "Some test realm" :cnonce "YWU4NDcxYWMxMDAxMjlkMjAwMDE4MjI5MDAwMGY4NGQ=" :nc "00000001" :username "jytky" :password "xi5Ac2HEfKt1lKKO05DCSqsK0u7hqqtsT" :expected-ha1 "af521db3a83abd91262fead04fa31892" :expected-ha2 "e490a6a147c79404b365d1f6059ddda5" :expected-response "ecb6396e93b9e09e31f19264cfd8f854") (list :nonce "a1be8a3065e00c5bf190ad499299aea5" :opaque "d7c2a27230fc8c74bb6e06be8c9cd189" :realm "The Test Realm" :username "user" :password "passwd" :uri "/digest-auth/auth/user/passwd" :method "GET" :expected-ha1 "19c41161a8720edaeb7922ef8531137d" :expected-ha2 "b44272ea65ee4af7fb26c5dba58f6863" :expected-response "46c47a6d8e1fa95a3efcf49724af3fe7") (list :nonce "servernonce" :username "user" :password "passwd" :realm "The Test Realm 1" :uri "/digest-auth/auth/user/passwd" :method "GET" :expected-ha1 "00f848f943c9a05dd06c932a7334f120" :expected-ha2 "b44272ea65ee4af7fb26c5dba58f6863" :expected-response "b8a48cdc9aa9e514509a5a5c53d4e8cf") (list :nonce "servernonce" :username "user" :password "passwd" :realm "The Test Realm 2" :uri "/digest-auth/auth/user/passwd" :method "GET" :expected-ha1 "74d6abd3651d6b8260733d8a4c37ec1a" :expected-ha2 "b44272ea65ee4af7fb26c5dba58f6863" :expected-response "0d84884d967e04440efc77e9e2b5b561"))) (ert-deftest url-auth-test-colonjoin () "Check joining strings with `:'." (should (string= (url-digest-auth-colonjoin) "")) (should (string= (url-digest-auth-colonjoin nil) "")) (should (string= (url-digest-auth-colonjoin nil nil nil) "::")) (should (string= (url-digest-auth-colonjoin "") "")) (should (string= (url-digest-auth-colonjoin "" "") ":")) (should (string= (url-digest-auth-colonjoin "one") "one")) (should (string= (url-digest-auth-colonjoin "one" "two" "three") "one:two:three"))) (ert-deftest url-auth-test-digest-ha1 () "Check HA1 computation." (dolist (row url-auth-test-challenges) (should (string= (url-digest-auth-make-ha1 (plist-get row :username) (plist-get row :realm) (plist-get row :password)) (plist-get row :expected-ha1) )))) (ert-deftest url-auth-test-digest-ha2 () "Check HA2 computation." (dolist (row url-auth-test-challenges) (should (string= (url-digest-auth-make-ha2 (plist-get row :method) (plist-get row :uri)) (plist-get row :expected-ha2))))) (ert-deftest url-auth-test-digest-request-digest () "Check digest response value." (dolist (row url-auth-test-challenges) (should (string= (plist-get row :expected-response) (if (plist-member row :qop) (url-digest-auth-make-request-digest-qop (plist-get row :qop) (plist-get row :expected-ha1) (plist-get row :expected-ha2) (plist-get row :nonce) (plist-get row :nc) (plist-get row :cnonce)) (url-digest-auth-make-request-digest (plist-get row :expected-ha1) (plist-get row :expected-ha2) (plist-get row :nonce))))))) (ert-deftest url-auth-test-digest-create-key () "Check user credentials in their hashed form." (dolist (challenge url-auth-test-challenges) (let ((key (url-digest-auth-create-key (plist-get challenge :username) (plist-get challenge :password) (plist-get challenge :realm) (plist-get challenge :method) (plist-get challenge :uri)))) (should (= (length key) 2)) (should (string= (nth 0 key) (plist-get challenge :expected-ha1))) (should (string= (nth 1 key) (plist-get challenge :expected-ha2))) ))) (ert-deftest url-auth-test-digest-auth-retrieve-cache () "Check how the entry point retrieves cached authentication. Essential is how realms and paths are matched." (let* ((url-digest-auth-storage '(("example.org:80" ("/path/auth1" "auth1user" "key") ("/path" "pathuser" "key") ("/" "rootuser" "key") ("realm1" "realm1user" "key") ("realm2" "realm2user" "key") ("/path/auth2" "auth2user" "key")) ("example.org:443" ("realm" "secure_user" "key")) ("rootless.org:80" ; no "/" entry for this on purpose ("/path" "pathuser" "key") ("realm" "realmuser" "key")))) (attrs (list (cons "nonce" "servernonce"))) auth) (dolist (row (list ;; If :expected-user is `nil' it indicates ;; authentication information shouldn't be found. ;; non-existent server (list :url "http://other.com/path" :realm nil :expected-user nil) ;; unmatched port (list :url "http://example.org:444/path" :realm nil :expected-user nil) ;; root, no realm (list :url "http://example.org/" :realm nil :expected-user "rootuser") ;; root, no realm, explicit port (list :url "http://example.org:80/" :realm nil :expected-user "rootuser") (list :url "http://example.org/unknown" :realm nil :expected-user "rootuser") ;; realm specified, overrides any path (list :url "http://example.org/" :realm "realm1" :expected-user "realm1user") ;; realm specified, overrides any path (list :url "http://example.org/" :realm "realm2" :expected-user "realm2user") ;; authentication determined by path (list :url "http://example.org/path/auth1/query" :realm nil :expected-user "auth1user") ;; /path shadows /path/auth2, hence pathuser is expected (list :url "http://example.org/path/auth2/query" :realm nil :expected-user "pathuser") (list :url "https://example.org/path" :realm nil :expected-user "secure_user") ;; not really secure user but using the same port (list :url "http://example.org:443/path" :realm nil :expected-user "secure_user") ;; preferring realm user over path, even though no ;; realm specified (not sure why) (list :url "http://rootless.org/" :realm nil :expected-user "realmuser") ;; second variant for the same case (list :url "http://rootless.org/unknown/path" :realm nil :expected-user "realmuser") ;; path match (list :url "http://rootless.org/path/query?q=a" :realm nil :expected-user "pathuser") ;; path match, realm match, prefer realm (list :url "http://rootless.org/path/query?q=a" :realm "realm" :expected-user "realmuser") )) (setq auth (url-digest-auth (plist-get row :url) nil nil (plist-get row :realm) attrs)) (if (plist-get row :expected-user) (progn (should auth) (should (string-match ".*username=\"\\(.*?\\)\".*" auth)) (should (string= (match-string 1 auth) (plist-get row :expected-user)))) (should-not auth))))) (ert-deftest url-auth-test-digest-auth () "Check common authorization string contents. Challenges with qop are not checked for response since a unique cnonce is used for generating them which is not mocked by the test and cannot be passed by arguments to `url-digest-auth'." (dolist (challenge url-auth-test-challenges) (let* ((attrs (append (list (cons "nonce" (plist-get challenge :nonce))) (if (plist-get challenge :qop) (list (cons "qop" (plist-get challenge :qop)))))) (url (concat "http://example.org" (plist-get challenge :uri))) url-digest-auth-storage auth) ;; Add authentication info to cache so `url-digest-auth' can ;; complete without prompting minibuffer input. (setq url-digest-auth-storage (list (list "example.org:80" (cons (or (plist-get challenge :realm) "/") (cons (plist-get challenge :username) (url-digest-auth-create-key (plist-get challenge :username) (plist-get challenge :password) (plist-get challenge :realm) (plist-get challenge :method) (plist-get challenge :uri))))))) (setq auth (url-digest-auth (url-generic-parse-url url) nil nil (plist-get challenge :realm) attrs)) (should auth) (should (string-prefix-p "Digest " auth)) (should (string-match ".*username=\"\\(.*?\\)\".*" auth)) (should (string= (match-string 1 auth) (plist-get challenge :username))) (should (string-match ".*realm=\"\\(.*?\\)\".*" auth)) (should (string= (match-string 1 auth) (plist-get challenge :realm))) (if (plist-member challenge :qop) (progn ;; We don't know these, just check that they exists. (should (string-match-p ".*response=\".*?\".*" auth)) (should (string-match-p ".*nc=\".*?\".*" auth)) (should (string-match-p ".*cnonce=\".*?\".*" auth))) (should (string-match ".*response=\"\\(.*?\\)\".*" auth)) (should (string= (match-string 1 auth) (plist-get challenge :expected-response)))) ))) (ert-deftest url-auth-test-digest-auth-opaque () "Check that `opaque' value is added to result when presented by the server." (let* ((url-digest-auth-storage '(("example.org:80" ("/" "user" "key")))) (attrs (list (cons "nonce" "anynonce"))) auth) ;; Get authentication info from cache without `opaque'. (setq auth (url-digest-auth "http://example.org/path" nil nil nil attrs)) (should auth) (should-not (string-match-p "opaque=" auth)) ;; Add `opaque' to attributes. (push (cons "opaque" "opaque-value") attrs) (setq auth (url-digest-auth "http://example.org/path" nil nil nil attrs)) (should auth) (should (string-match ".*opaque=\"\\(.*?\\)\".*" auth)) (should (string= (match-string 1 auth) "opaque-value")))) (provide 'url-auth-tests) ;;; url-auth-tests.el ends here