-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathflat_file.py
More file actions
145 lines (124 loc) · 5.41 KB
/
Copy pathflat_file.py
File metadata and controls
145 lines (124 loc) · 5.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import locale
import bcrypt
import base64
import hashlib
import crypt # deprecated in 3.11 and removed in 3.13.
from hmac import compare_digest as compare_hash
from . import apr1
# Reference: https://httpd.apache.org/docs/2.4/misc/password_encryptions.html
# https://akkadia.org/drepper/SHA-crypt.txt
# empirical format for htpasswd using apache utils 2.4.41
# md5sum:$apr1$AmfEURVX$U0A7kxYcofNn2J.lptuOn0
# bcrypt:$2y$05$LtdiiELPayMNfwk5PMWA2uOMNAWW9wacCrYgN.lXUR35YEG.kOPWO
# sha:{SHA}C5wmJdwh7wX2rU3fR8XyA4N6oyw=
# crypt:znExVsGU19vAQ
# plain:toto
__all__ = ["FlatFileAuthenticationBackend"]
LOG = logging.getLogger(__name__)
# dummy pw is "testpassword" (used when user not found to avoid timing attacks)
DUMMY_HASH_DATA = "$2y$05$Vhvhbk0SYN3ncn9BSvXEHunzztBWfrwqOpX1D0GhrFvM1TcADpKoO"
class HtpasswdFile(object):
"""
Custom HtpasswdFile implementation which supports comments
(lines starting with #).
"""
def __init__(self, filename):
self.filename = filename
self.entries = {}
self._load_file()
def _load_file(self):
"""
Load apache htpasswd formatted file with support for lines starting with "#"
as comments. The format is a single line per record as <username>:<hash>
Records are added to the 'entries' dictionary with the username as the key
and hash data as the value.
"""
data = None
with open(self.filename, "r") as f:
data = f.readlines()
for line in data:
line = line.strip()
if line.startswith("#"):
LOG.debug(f"Skip comment {line}")
continue
if ":" not in line:
LOG.debug(f"Malformed entry '{line}'.")
continue
username, hash_data = line.split(":", 1)
self.entries[username] = hash_data
def check_password(self, username, password):
encode_local = locale.getpreferredencoding()
pw = bytes(password, encoding=encode_local)
if username in self.entries:
hash_data = self.entries[username]
if hash_data.startswith("$apr1$"):
LOG.warning(
"%s uses MD5 algorithm to hash the password."
"Rehash the password with bcrypt is strongly recommended.",
username,
)
_, _, salt, md5hash = hash_data.split("$")
return apr1.hash_apr1(salt, password) == hash_data
elif hash_data.startswith("$2y$"):
return bcrypt.checkpw(pw, bytes(hash_data, encoding=encode_local))
elif hash_data.startswith("{SHA}"):
LOG.warning(
"%s uses deprecated SHA algorithm to hash password."
"Rehash the password with bcrypt.",
username,
)
return bytes(hash_data, encoding=encode_local) == b"{SHA}" + base64.b64encode(
hashlib.sha1(pw).digest()
)
else:
# crypt is deprecated and will be dropped in python 3.13.
LOG.warning(
"%s uses deprecated crypt algorithm to hash password."
"Rehash the password with bcrypt.",
username,
)
return compare_hash(crypt.crypt(password, hash_data), hash_data)
else:
# User not found. Do a dummy hash to avoid timing attacks.
_ = bcrypt.checkpw(pw, bytes(DUMMY_HASH_DATA, encoding=encode_local))
return None
class FlatFileAuthenticationBackend(object):
"""
Backend which reads authentication information from a local file.
Entries need to be in a htpasswd file like format. This means entries can be managed with
the htpasswd utility (https://httpd.apache.org/docs/current/programs/htpasswd.html) which
ships with Apache HTTP server.
"""
def __init__(self, file_path):
"""
:param file_path: Path to the file with authentication information.
:type file_path: ``str``
"""
self._file_path = file_path
def authenticate(self, username, password):
htpasswd_file = HtpasswdFile(self._file_path)
result = htpasswd_file.check_password(username, password)
if result is None:
LOG.debug('User "%s" doesn\'t exist' % (username))
elif result is False:
LOG.debug('Invalid password for user "%s"' % (username))
elif result is True:
LOG.debug('Authentication for user "%s" successful' % (username))
return bool(result)
def get_user(self, username):
pass