Skip to content
This repository has been archived by the owner on Aug 4, 2018. It is now read-only.

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
Fingercomp committed Apr 5, 2017
2 parents 8dc3ef3 + 8ebbaf0 commit fb1705f
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 18 deletions.
18 changes: 18 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
3.4.0
-----
- Salt passwords.

- Added ``user.salted`` field.
- Updated auth view to salt passwords when signing up.
- Updated auth view to replace old passwords in the database when logging in.
- Updated auth view to properly check for password validity.
- Updated user update view to salt password when changing it.

- Configured logging in ``development.ini``.
- Increased the maximum length of short description to 300 characters.
- Forbade using uppercase characters in package names.

3.3.1
-----
- Fixed the issue in ``list_users`` where only the left-most digit of ``offset`` was read.

3.3.0
-----
- Added ``versions.files.path``, deprected ``versions.files.dir`` and ``versions.files.name``.
Expand Down
4 changes: 2 additions & 2 deletions development.ini
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ handlers = console

[logger_hel]
level = DEBUG
handlers =
handlers = console
qualname = hel

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
level = DEBUG
formatter = generic

[formatter_generic]
Expand Down
5 changes: 5 additions & 0 deletions hel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ def main(global_config, **settings):
settings['controllers.users.list_length'] = int(settings.get(
'controllers.users.list_length', '20'))

settings['authentication.salt'] = (b'\xc4x\xc1\x1a\x0f\xa5$\xb1\x95AT\x03'
b'\x8d\x03bk\xfc\xe9\x1c\x88')
if 'AUTH_SALT' in os.environ:
settings['authentication.salt'] = os.environ['AUTH_SALT'].encode()

config = Configurator(settings=settings, root_factory=Root)

config.add_route_predicate('cors_preflight', CorsPreflightPredicate)
Expand Down
2 changes: 1 addition & 1 deletion hel/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json


VERSION = '3.3.0'
VERSION = '3.4.0'


def parse_search_phrase(s):
Expand Down
9 changes: 9 additions & 0 deletions hel/utils/authentication.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
import hashlib

from pyramid.authentication import AuthTktAuthenticationPolicy as Policy
from pyramid.httpexceptions import HTTPForbidden
from pyramid.security import ACLAllowed, Everyone, Authenticated
Expand Down Expand Up @@ -75,3 +77,10 @@ def has_permission(request, permission, context=None):
if request.has_permission(permission, context):
return True
raise HTTPForbidden


def salt(request, password, username):
hash_data = (username.encode() +
request.registry.settings['authentication.salt'] +
password.encode())
return hashlib.sha512(hash_data).hexdigest()
5 changes: 4 additions & 1 deletion hel/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ class Constants:
key_replace_char = '\uf123'

# The regexp used to validate a package name
name_pattern = re.compile('^[A-Za-z0-9-]+$')
name_pattern = re.compile('^[a-z0-9-]+$')

# The regexp used to validate a nickname
user_pattern = re.compile('^[A-Za-z0-9-_]+$')

date_format = '%Y-%m-%d %H:%M:%S'

# The maximum length of short description
sdesc_len = 300
3 changes: 2 additions & 1 deletion hel/utils/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class Messages:
no_values = 'No values given for %s.'
partial_ver = 'Version data you provided was partial.'
password_mismatch = 'Passwords do not match.'
pkg_bad_name = 'The name contains illegal characters.'
pkg_bad_name = ('The name contains illegal characters. Allowed are: '
'lowercase alphabetic characters, numbers and a dash (-).')
pkg_name_conflict = 'The name is already used by other package.'
too_many_values = 'Too many values (%s expected, got %s).'
type_mismatch = 'Wrong value for param "%s" given: expected %s!'
Expand Down
6 changes: 4 additions & 2 deletions hel/utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def __init__(self, strict=False, **kwargs):
self.data[k] = {parse_url(str(url)): str(desc)
for url, desc in v.items()}
elif k == 'short_description':
self.data[k] = str(v)[:140]
self.data[k] = str(v)[:Constants.sdesc_len]

@property
def json(self):
Expand All @@ -135,7 +135,9 @@ def __init__(self, strict=False, **kwargs):
'password': '',
'email': '',
'activation_phrase': '',
'activation_till': ''
'activation_till': '',
# TODO: remove in [email protected]
'salted': True
}

if strict:
Expand Down
45 changes: 34 additions & 11 deletions hel/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from hel.resources import Package, Packages, User, Users
from hel.utils import update
from hel.utils.authentication import has_permission
from hel.utils.authentication import has_permission, salt
from hel.utils.constants import Constants
from hel.utils.messages import Messages
from hel.utils.models import ModelPackage, ModelUser
Expand Down Expand Up @@ -83,7 +83,8 @@ def auth(request):
email = ''
try:
params = request.json_body
except:
except Exception as e:
log.exception('1')
raise HTTPBadRequest(detail=Messages.bad_request)
if 'action' not in params:
raise HTTPBadRequest(detail=Messages.bad_request)
Expand All @@ -106,13 +107,32 @@ def auth(request):
raise HTTPBadRequest(detail=Messages.empty_nickname)
if password == '':
raise HTTPBadRequest(detail=Messages.empty_password)
pass_hash = hashlib.sha512(password.encode()).hexdigest()
user = request.db['users'].find_one({'nickname': nickname})
if not user:
raise HTTPBadRequest(detail=Messages.failed_login)
correct_hash = user['password']
if pass_hash != correct_hash:
raise HTTPBadRequest(detail=Messages.failed_login)

if 'salted' not in user or not user['salted']: # pragma: no cover
correct_hash = user['password']
pass_hash = hashlib.sha512(password.encode()).hexdigest()
if pass_hash != correct_hash:
raise HTTPBadRequest( # pragma: no cover
detail=Messages.failed_login)

salted = salt(request, password, nickname)
request.db['users'].find_one_and_update({
'nickname': nickname
}, {
'$set': {
'salted': True,
'password': salted
}
})
else:
correct_hash = user['password']
pass_hash = salt(request, password, nickname)
if pass_hash != correct_hash:
raise HTTPBadRequest(detail=Messages.failed_login)

headers = remember(request, nickname)
raise HTTPOk(body=Messages.logged_in, headers=headers)
elif params['action'] == 'register':
Expand All @@ -128,18 +148,21 @@ def auth(request):
raise HTTPBadRequest(detail=Messages.empty_email)
if password == '':
raise HTTPBadRequest(detail=Messages.empty_password)
pass_hash = hashlib.sha512(password.encode()).hexdigest()
user = request.db['users'].find_one({'nickname': nickname})
if user:
raise HTTPBadRequest(detail=Messages.nickname_in_use)
user = request.db['users'].find_one({'email': email})
if user:
raise HTTPBadRequest(detail=Messages.email_in_use)

pass_hash = salt(request, password, nickname)

act_phrase = ''.join('{:02x}'.format(x) for x in os.urandom(
request.registry.settings
['activation.length']))
act_till = (datetime.datetime.now() + datetime.timedelta(
seconds=request.registry.settings['activation.time']))

subrequest = Request.blank('/users', method='POST', POST=(
str(ModelUser(nickname=nickname,
email=email,
Expand All @@ -148,6 +171,7 @@ def auth(request):
activation_till=act_till))),
content_type='application/json')
subrequest.no_permission_check = True

response = request.invoke_subrequest(subrequest, use_tweens=True)
if response.status_code == 201:
# TODO: send activation email
Expand Down Expand Up @@ -195,7 +219,7 @@ def update_package(context, request):
elif k == 'short_description':
query[k] = check(
v, str,
Messages.type_mismatch % (k, 'str',))[:120]
Messages.type_mismatch % (k, 'str',))[:Constants.sdesc_len]
elif k == 'owners':
check_list_of_strs(
v, Messages.type_mismatch % (k, 'list of strs',))
Expand Down Expand Up @@ -550,7 +574,7 @@ def update_user(context, request):
# TODO: send email
if v == '':
raise HTTPBadRequest(detail=Messages.empty_password)
pass_hash = hashlib.sha512(v.encode()).hexdigest()
pass_hash = salt(request, v, context.retrieve()['nickname'])
query[k] = pass_hash
query = update(old, query)
context.update(query, True)
Expand Down Expand Up @@ -602,7 +626,7 @@ def create_user(context, request):
context=Users,
permission='user_list')
def list_users(context, request):
params = request.GET
params = request.GET.dict_of_lists()
offset = 0
length = request.registry.settings['controllers.users.list_length']
if 'offset' in params:
Expand All @@ -611,7 +635,6 @@ def list_users(context, request):
offset = int(offset)
except:
offset = 0
params = params.dict_of_lists()
groups = None
if 'groups' in params:
groups = params['groups']
Expand Down

0 comments on commit fb1705f

Please sign in to comment.