| import logging | |
| import os | |
| import sys | |
| import pytest | |
| from .env import AAATestEnv | |
| from pyhttpd.conf import HttpdConf | |
| sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) | |
| def pytest_report_header(config, start_path): | |
| env = AAATestEnv() | |
| return f"mod_auth_digest [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]" | |
| def _digest_dir(docs, path, extra_lines): | |
| lines = [ | |
| f'<Directory "{docs}/digest/{path}">', | |
| ' AuthType Digest', | |
| f' AuthName "{AAATestEnv.REALM}"', | |
| ] | |
| lines.extend(f" {l}" for l in extra_lines) | |
| lines.append(' Require valid-user') | |
| lines.append('</Directory>') | |
| return lines | |
| @pytest.fixture(scope="package") | |
| def env(pytestconfig) -> AAATestEnv: | |
| level = logging.INFO | |
| console = logging.StreamHandler() | |
| console.setLevel(level) | |
| console.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) | |
| logging.getLogger('').addHandler(console) | |
| logging.getLogger('').setLevel(level=level) | |
| env = AAATestEnv(pytestconfig=pytestconfig) | |
| env.setup_httpd() | |
| env.apache_access_log_clear() | |
| env.httpd_error_log.clear_log() | |
| docs = env.server_docs_dir | |
| pwfile = env.digest_pwfile | |
| conf = HttpdConf(env) | |
| # Pin the client table to its historical size, ~12 entries, rather than | |
| # the current default of ~140: the tests which need an entry to be | |
| # garbage collected (085) drive that by filling the table with bare | |
| # requests, and a table an order of magnitude larger makes them an order | |
| # of magnitude slower for nothing. | |
| conf.add('AuthDigestShmemSize 1000') | |
| conf.add(_digest_dir(docs, "default", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| ])) | |
| conf.add(_digest_dir(docs, "nccheck", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestNcCheck On', | |
| ])) | |
| conf.add(_digest_dir(docs, "nccheck-shortlife", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestNcCheck On', | |
| 'AuthDigestNonceLifetime 2', | |
| ])) | |
| conf.add(_digest_dir(docs, "shortlife", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestNonceLifetime 2', | |
| ])) | |
| conf.add(_digest_dir(docs, "neverexpire", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestNonceLifetime -1', | |
| ])) | |
| conf.add(_digest_dir(docs, "onetime", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestNonceLifetime 0', | |
| ])) | |
| conf.add(_digest_dir(docs, "onetime-nccheck", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestNonceLifetime 0', | |
| 'AuthDigestNcCheck On', | |
| ])) | |
| conf.add(_digest_dir(docs, "domain", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestDomain "/digest/domain/" "https://mirror.example.org/other/"', | |
| ])) | |
| # Tracks clients like "nccheck", but with a domain; a client crossing | |
| # between the two is still the same user (test_015). | |
| conf.add(_digest_dir(docs, "nccheck-domain", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{pwfile}"', | |
| 'AuthDigestNcCheck On', | |
| 'AuthDigestDomain "/digest/nccheck-domain/"', | |
| ])) | |
| conf.add(_digest_dir(docs, "noprovider", [ | |
| # AuthDigestProvider intentionally omitted: falls back to "file". | |
| f'AuthUserFile "{pwfile}"', | |
| ])) | |
| # A request which fails header validation (e.g. no Host on HTTP/1.1) | |
| # is answered before the Digest module's post_read_request runs, so it | |
| # has no per-request Digest record; an ErrorDocument redirecting it into | |
| # a Digest-protected location must not then dereference that record. | |
| # Only configured where that is fixed (r1939032): earlier code does | |
| # dereference the missing record. | |
| if env.httpd_is_at_least("2.5.0"): | |
| conf.add('ErrorDocument 400 /digest/default/secret.txt') | |
| # A reverse-proxied location which authenticates with Digest at the | |
| # proxy (so the backend is never reached and need not be reachable). | |
| # The challenge is an origin-style WWW-Authenticate, so AuthDigestDomain | |
| # applies to it just as for a non-proxied location. | |
| conf.add([ | |
| 'ProxyPass "/pxdomain/" "http://127.0.0.1:1/unused/"', | |
| '<Location "/pxdomain/">', | |
| ' AuthType Digest', | |
| f' AuthName "{AAATestEnv.REALM}"', | |
| ' AuthDigestProvider file', | |
| f' AuthUserFile "{pwfile}"', | |
| ' AuthDigestDomain "/pxdomain/"', | |
| ' Require valid-user', | |
| '</Location>', | |
| ]) | |
| # A second server with the same realm but its own accounts, on its own | |
| # port so that the main server keeps the rest. The main server's | |
| # <Directory> sections apply here too; this one overrides the password | |
| # file for the nccheck location. | |
| conf.add(f'Listen {env.http_port2}') | |
| conf.start_vhost(domains=[f"aaa2.{env.http_tld}"], port=env.http_port2, | |
| doc_root=docs) | |
| conf.add(_digest_dir(docs, "nccheck", [ | |
| 'AuthDigestProvider file', | |
| f'AuthUserFile "{env.digest_pwfile2}"', | |
| 'AuthDigestNcCheck On', | |
| ])) | |
| conf.end_vhost() | |
| conf.install() | |
| assert env.apache_restart() == 0 | |
| return env | |
| @pytest.fixture(autouse=True, scope="package") | |
| def _stop_package_scope(env): | |
| yield | |
| assert env.apache_stop() == 0 |