|
|
# Copyright (C) 2010-2024 RhodeCode GmbH
|
|
|
#
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
# it under the terms of the GNU Affero General Public License, version 3
|
|
|
# (only), as published by the Free Software Foundation.
|
|
|
#
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
# GNU General Public License for more details.
|
|
|
#
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
#
|
|
|
# This program is dual-licensed. If you wish to learn more about the
|
|
|
# RhodeCode Enterprise Edition, including its added features, Support services,
|
|
|
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
|
|
|
|
|
import mock
|
|
|
import pytest
|
|
|
|
|
|
from rhodecode.lib.auth import _RhodeCodeCryptoBCrypt
|
|
|
from rhodecode.authentication.base import RhodeCodeAuthPluginBase
|
|
|
from rhodecode.authentication.plugins.auth_ldap import RhodeCodeAuthPlugin
|
|
|
from rhodecode.model import db
|
|
|
|
|
|
|
|
|
class RcTestAuthPlugin(RhodeCodeAuthPluginBase):
|
|
|
|
|
|
def name(self):
|
|
|
return 'stub_auth'
|
|
|
|
|
|
|
|
|
def test_authenticate_returns_from_auth(stub_auth_data):
|
|
|
plugin = RcTestAuthPlugin('stub_id')
|
|
|
with mock.patch.object(plugin, 'auth') as auth_mock:
|
|
|
auth_mock.return_value = stub_auth_data
|
|
|
result = plugin._authenticate(mock.Mock(), 'test', 'password', {})
|
|
|
assert stub_auth_data == result
|
|
|
|
|
|
|
|
|
def test_authenticate_returns_empty_auth_data():
|
|
|
auth_data = {}
|
|
|
plugin = RcTestAuthPlugin('stub_id')
|
|
|
with mock.patch.object(plugin, 'auth') as auth_mock:
|
|
|
auth_mock.return_value = auth_data
|
|
|
result = plugin._authenticate(mock.Mock(), 'test', 'password', {})
|
|
|
assert auth_data == result
|
|
|
|
|
|
|
|
|
def test_authenticate_skips_hash_migration_if_mismatch(stub_auth_data):
|
|
|
stub_auth_data['_hash_migrate'] = 'new-hash'
|
|
|
plugin = RcTestAuthPlugin('stub_id')
|
|
|
with mock.patch.object(plugin, 'auth') as auth_mock:
|
|
|
auth_mock.return_value = stub_auth_data
|
|
|
result = plugin._authenticate(mock.Mock(), 'test', 'password', {})
|
|
|
|
|
|
user = db.User.get_by_username(stub_auth_data['username'])
|
|
|
assert user.password != 'new-hash'
|
|
|
assert result == stub_auth_data
|
|
|
|
|
|
|
|
|
def test_authenticate_migrates_to_new_hash(stub_auth_data):
|
|
|
new_password = b'new-password'
|
|
|
new_hash = _RhodeCodeCryptoBCrypt().hash_create(new_password)
|
|
|
stub_auth_data['_hash_migrate'] = new_hash
|
|
|
plugin = RcTestAuthPlugin('stub_id')
|
|
|
with mock.patch.object(plugin, 'auth') as auth_mock:
|
|
|
auth_mock.return_value = stub_auth_data
|
|
|
result = plugin._authenticate(
|
|
|
mock.Mock(), stub_auth_data['username'], new_password, {})
|
|
|
|
|
|
user = db.User.get_by_username(stub_auth_data['username'])
|
|
|
assert user.password == new_hash
|
|
|
assert result == stub_auth_data
|
|
|
|
|
|
|
|
|
@pytest.fixture()
|
|
|
def stub_auth_data(user_util):
|
|
|
user = user_util.create_user()
|
|
|
data = {
|
|
|
'username': user.username,
|
|
|
'password': 'password',
|
|
|
'email': 'test@example.org',
|
|
|
'firstname': 'John',
|
|
|
'lastname': 'Smith',
|
|
|
'groups': [],
|
|
|
'active': True,
|
|
|
'admin': False,
|
|
|
'extern_name': 'test',
|
|
|
'extern_type': 'ldap',
|
|
|
'active_from_extern': True
|
|
|
}
|
|
|
return data
|
|
|
|
|
|
|
|
|
class TestRhodeCodeAuthPlugin(object):
|
|
|
def setup_method(self, method):
|
|
|
self.finalizers = []
|
|
|
self.user = mock.Mock()
|
|
|
self.user.username = 'test'
|
|
|
self.user.password = 'old-password'
|
|
|
self.fake_auth = {
|
|
|
'username': 'test',
|
|
|
'password': 'test',
|
|
|
'email': 'test@example.org',
|
|
|
'firstname': 'John',
|
|
|
'lastname': 'Smith',
|
|
|
'groups': [],
|
|
|
'active': True,
|
|
|
'admin': False,
|
|
|
'extern_name': 'test',
|
|
|
'extern_type': 'ldap',
|
|
|
'active_from_extern': True
|
|
|
}
|
|
|
|
|
|
def teardown_method(self, method):
|
|
|
if self.finalizers:
|
|
|
for finalizer in self.finalizers:
|
|
|
finalizer()
|
|
|
self.finalizers = []
|
|
|
|
|
|
def test_fake_password_is_created_for_the_new_user(self):
|
|
|
self._patch()
|
|
|
auth_plugin = RhodeCodeAuthPlugin('stub_id')
|
|
|
auth_plugin._authenticate(self.user, 'test', 'test', [])
|
|
|
self.password_generator_mock.assert_called_once_with(length=16)
|
|
|
create_user_kwargs = self.create_user_mock.call_args[1]
|
|
|
assert create_user_kwargs['password'] == 'new-password'
|
|
|
|
|
|
def test_fake_password_is_not_created_for_the_existing_user(self):
|
|
|
self._patch()
|
|
|
self.get_user_mock.return_value = self.user
|
|
|
auth_plugin = RhodeCodeAuthPlugin('stub_id')
|
|
|
auth_plugin._authenticate(self.user, 'test', 'test', [])
|
|
|
assert self.password_generator_mock.called is False
|
|
|
create_user_kwargs = self.create_user_mock.call_args[1]
|
|
|
assert create_user_kwargs['password'] == self.user.password
|
|
|
|
|
|
def _patch(self):
|
|
|
get_user_patch = mock.patch('rhodecode.model.db.User.get_by_username')
|
|
|
self.get_user_mock = get_user_patch.start()
|
|
|
self.get_user_mock.return_value = None
|
|
|
self.finalizers.append(get_user_patch.stop)
|
|
|
|
|
|
create_user_patch = mock.patch(
|
|
|
'rhodecode.model.user.UserModel.create_or_update')
|
|
|
self.create_user_mock = create_user_patch.start()
|
|
|
self.create_user_mock.return_value = None
|
|
|
self.finalizers.append(create_user_patch.stop)
|
|
|
|
|
|
auth_patch = mock.patch.object(RhodeCodeAuthPlugin, 'auth')
|
|
|
self.auth_mock = auth_patch.start()
|
|
|
self.auth_mock.return_value = self.fake_auth
|
|
|
self.finalizers.append(auth_patch.stop)
|
|
|
|
|
|
password_generator_patch = mock.patch(
|
|
|
'rhodecode.lib.auth.PasswordGenerator.gen_password')
|
|
|
self.password_generator_mock = password_generator_patch.start()
|
|
|
self.password_generator_mock.return_value = 'new-password'
|
|
|
self.finalizers.append(password_generator_patch.stop)
|
|
|
|
|
|
|
|
|
def test_missing_ldap():
|
|
|
from rhodecode.model.validators import Missing
|
|
|
|
|
|
try:
|
|
|
import ldap_not_existing
|
|
|
except ImportError:
|
|
|
# means that python-ldap is not installed
|
|
|
ldap_not_existing = Missing
|
|
|
|
|
|
# missing is singleton
|
|
|
assert ldap_not_existing == Missing
|
|
|
|
|
|
|
|
|
def test_import_ldap():
|
|
|
from rhodecode.model.validators import Missing
|
|
|
|
|
|
try:
|
|
|
import ldap
|
|
|
except ImportError:
|
|
|
# means that python-ldap is not installed
|
|
|
ldap = Missing
|
|
|
|
|
|
# missing is singleton
|
|
|
assert False is (ldap == Missing)
|
|
|
|