Tests for basic authentication

master
Bob Mottram 2019-07-03 19:24:44 +01:00
parent bf13764756
commit be9c2d68d2
3 changed files with 134 additions and 4 deletions

95
auth.py 100644
View File

@ -0,0 +1,95 @@
__filename__ = "auth.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "0.0.1"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
import base64
import hashlib
import binascii
import os
import shutil
def hashPassword(password: str) -> str:
"""Hash a password for storing
"""
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'), salt, 100000)
pwdhash = binascii.hexlify(pwdhash)
return (salt + pwdhash).decode('ascii')
def verifyPassword(storedPassword: str,providedPassword: str) -> bool:
"""Verify a stored password against one provided by user
"""
salt = storedPassword[:64]
storedPassword = storedPassword[64:]
pwdhash = hashlib.pbkdf2_hmac('sha512', \
providedPassword.encode('utf-8'), \
salt.encode('ascii'), \
100000)
pwdhash = binascii.hexlify(pwdhash).decode('ascii')
return pwdhash == storedPassword
def createBasicAuthHeader(nickname: str,password: str) -> str:
"""This is only used by tests
"""
authStr=nickname.replace('\n','')+':'+password.replace('\n','')
return 'Basic '+base64.b64encode(authStr.encode('utf8')).decode('utf8')
def authorizeBasic(baseDir: str,authHeader: str) -> bool:
"""HTTP basic auth
"""
if ' ' not in authHeader:
return False
base64Str = authHeader.split(' ')[1]
plain = base64.b64decode(base64Str).decode('utf8')
if ':' not in plain:
return False
nickname = plain.split(':')[0]
passwordFile=baseDir+'/accounts/passwords'
if not os.path.isfile(passwordFile):
return False
providedPassword = plain.split(':')[1]
passfile = open(passwordFile, "r")
for line in passfile:
if line.startswith(nickname+':'):
storedPassword=line.split(':')[1].replace('\n','')
return verifyPassword(storedPassword,providedPassword)
return False
def storeBasicCredentials(baseDir: str,nickname: str,password: str) -> bool:
if ':' in nickname or ':' in password:
return False
nickname=nickname.replace('\n','').strip()
password=password.replace('\n','').strip()
if not os.path.isdir(baseDir+'/accounts'):
os.mkdir(baseDir+'/accounts')
passwordFile=baseDir+'/accounts/passwords'
storeStr=nickname+':'+hashPassword(password)
if os.path.isfile(passwordFile):
if nickname+':' in open(passwordFile).read():
with open(passwordFile, "r") as fin:
with open(passwordFile+'.new', "w") as fout:
for line in fin:
if not line.startswith(nickname+':'):
fout.write(line)
else:
fout.write(storeStr+'\n')
os.rename(passwordFile+'.new', passwordFile)
else:
# append to password file
with open(passwordFile, "a") as passfile:
passfile.write(storeStr+'\n')
else:
with open(passwordFile, "w") as passfile:
passfile.write(storeStr+'\n')
return True
def authorize(baseDir: str,authHeader: str) -> bool:
if authHeader.lower().startswith('basic '):
return authorizeBasic(baseDir,authHeader)
return False

View File

@ -22,6 +22,7 @@ from posts import getPersonPubKey
from inbox import inboxPermittedMessage from inbox import inboxPermittedMessage
from inbox import inboxMessageHasParams from inbox import inboxMessageHasParams
from follow import getFollowingFeed from follow import getFollowingFeed
from auth import authorize
import os import os
import sys import sys
@ -216,7 +217,13 @@ class PubServer(BaseHTTPRequestHandler):
# TODO # TODO
if self.path=='/outbox': if self.path=='/outbox':
print('c2s posts not supported yet') if self.headers.get('Authorization'):
if authorize(self.server.baseDir,self.headers['Authorization']):
print('c2s posts not supported yet')
self.send_response(400)
self.end_headers()
self.server.POSTbusy=False
return
self.send_response(400) self.send_response(400)
self.end_headers() self.end_headers()
self.server.POSTbusy=False self.server.POSTbusy=False

View File

@ -32,6 +32,9 @@ from follow import unfollowerOfPerson
from person import createPerson from person import createPerson
from person import setPreferredNickname from person import setPreferredNickname
from person import setBio from person import setBio
from auth import createBasicAuthHeader
from auth import authorizeBasic
from auth import storeBasicCredentials
testServerAliceRunning = False testServerAliceRunning = False
testServerBobRunning = False testServerBobRunning = False
@ -295,13 +298,38 @@ def testCreatePerson():
os.chdir(currDir) os.chdir(currDir)
shutil.rmtree(baseDir) shutil.rmtree(baseDir)
def testAuthentication():
print('testAuthentication')
currDir=os.getcwd()
nickname='test8743'
password='SuperSecretPassword12345'
baseDir=currDir+'/.tests_authentication'
if os.path.isdir(baseDir):
shutil.rmtree(baseDir)
os.mkdir(baseDir)
os.chdir(baseDir)
assert storeBasicCredentials(baseDir,'othernick','otherpass')
assert storeBasicCredentials(baseDir,'bad:nick','otherpass')==False
assert storeBasicCredentials(baseDir,'badnick','otherpa:ss')==False
assert storeBasicCredentials(baseDir,nickname,password)
authHeader=createBasicAuthHeader(nickname,password)
assert authorizeBasic(baseDir,authHeader)
authHeader=createBasicAuthHeader(nickname,password+'1')
assert authorizeBasic(baseDir,authHeader)==False
os.chdir(currDir)
shutil.rmtree(baseDir)
def runAllTests(): def runAllTests():
print('Running tests...') print('Running tests...')
testHttpsig() testHttpsig()
testCache() testCache()
testThreads() testThreads()
testCreatePerson() testCreatePerson()
testAuthentication()
testFollows() testFollows()
print('Tests succeeded\n') print('Tests succeeded\n')