Skip to content

Commit

Permalink
Merge pull request #15 from ps/ha1_password_option
Browse files Browse the repository at this point in the history
Ha1 password option
  • Loading branch information
miguelgrinberg committed Apr 26, 2015
2 parents 96b0391 + 4f4aed3 commit 2320d59
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 3 deletions.
15 changes: 12 additions & 3 deletions flask_httpauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ def authenticate(self, auth, stored_password):


class HTTPDigestAuth(HTTPAuth):
def __init__(self):
def __init__(self, use_ha1_pw = False):
super(HTTPDigestAuth, self).__init__()
self.use_ha1_pw = use_ha1_pw
self.random = SystemRandom()
try:
self.random.random()
Expand All @@ -116,6 +117,11 @@ def __init__(self):
def get_nonce(self):
return md5(str(self.random.random()).encode('utf-8')).hexdigest()

def generate_ha1(self, username, password):
a1 = username + ":" + self.realm + ":" + password
a1 = a1.encode('utf-8')
return md5(a1).hexdigest()

def authenticate_header(self):
session["auth_nonce"] = self.get_nonce()
session["auth_opaque"] = self.get_nonce()
Expand All @@ -129,8 +135,11 @@ def authenticate(self, auth, password):
if auth.nonce != session.get("auth_nonce") or \
auth.opaque != session.get("auth_opaque"):
return False
a1 = auth.username + ":" + auth.realm + ":" + password
ha1 = md5(a1.encode('utf-8')).hexdigest()
if self.use_ha1_pw:
ha1 = password
else:
a1 = auth.username + ":" + auth.realm + ":" + password
ha1 = md5(a1.encode('utf-8')).hexdigest()
a2 = request.method + ":" + auth.uri
ha2 = md5(a2.encode('utf-8')).hexdigest()
a3 = ha1 + ":" + auth.nonce + ":" + ha2
Expand Down
47 changes: 47 additions & 0 deletions test_httpauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def md5(str):
str = str.encode('utf-8')
return basic_md5(str)

def get_ha1(user, pw, realm):
a1 = user + ":" + realm + ":" + pw
return md5(a1).hexdigest()

class HTTPAuthTestCase(unittest.TestCase):
def setUp(self):
Expand All @@ -26,6 +29,16 @@ def setUp(self):
digest_auth = HTTPDigestAuth()
digest_auth_my_realm = HTTPDigestAuth()
digest_auth_my_realm.realm = 'My Realm'
digest_auth_ha1_pw = HTTPDigestAuth(use_ha1_pw = True)

@digest_auth_ha1_pw.get_password
def get_digest_password(username):
if username == 'susan':
return get_ha1(username, 'hello', digest_auth_ha1_pw.realm)
elif username == 'john':
return get_ha1(username, 'bye', digest_auth_ha1_pw.realm)
else:
return None

@basic_auth.get_password
def get_basic_password(username):
Expand Down Expand Up @@ -126,6 +139,11 @@ def basic_verify_auth_route():
def digest_auth_route():
return 'digest_auth:' + digest_auth.username()

@app.route('/digest_ha1_pw')
@digest_auth_ha1_pw.login_required
def digest_auth_ha1_pw_route():
return 'digest_auth:' + digest_auth.username()

@app.route('/digest-with-realm')
@digest_auth_my_realm.login_required
def digest_auth_my_realm_route():
Expand Down Expand Up @@ -268,6 +286,30 @@ def test_digest_auth_login_valid(self):
d['opaque'])})
self.assertEqual(response.data, b'digest_auth:john')

def test_digest_ha1_pw_auth_login_valid(self):
response = self.client.get('/digest_ha1_pw')
self.assertTrue(response.status_code == 401)
header = response.headers.get('WWW-Authenticate')
auth_type, auth_info = header.split(None, 1)
d = parse_dict_header(auth_info)

a1 = 'john:' + d['realm'] + ':bye'
ha1 = md5(a1).hexdigest()
a2 = 'GET:/digest_ha1_pw'
ha2 = md5(a2).hexdigest()
a3 = ha1 + ':' + d['nonce'] + ':' + ha2
auth_response = md5(a3).hexdigest()

response = self.client.get(
'/digest', headers={
'Authorization': 'Digest username="john",realm="{0}",'
'nonce="{1}",uri="/digest_ha1_pw",response="{2}",'
'opaque="{3}"'.format(d['realm'],
d['nonce'],
auth_response,
d['opaque'])})
self.assertEqual(response.data, b'digest_auth:john')

def test_digest_auth_login_bad_realm(self):
response = self.client.get('/digest')
self.assertTrue(response.status_code == 401)
Expand Down Expand Up @@ -338,6 +380,11 @@ def test_digest_auth_login_invalid2(self):
r'nonce="[0-9a-f]+",opaque="[0-9a-f]+"$',
response.headers['WWW-Authenticate']))

def test_digest_generate_ha1(self):
ha1 = self.digest_auth.generate_ha1('pawel', 'test')
ha1_expected = get_ha1('pawel', 'test', self.digest_auth.realm)
self.assertEqual(ha1, ha1_expected)


def suite():
return unittest.makeSuite(HTTPAuthTestCase)
Expand Down

0 comments on commit 2320d59

Please sign in to comment.