# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.conch.tap}.
"""
from typing import Any, Tuple, Union
from twisted.application.internet import StreamServerEndpointService
from twisted.cred import error
from twisted.cred.checkers import FilePasswordDB, ICredentialsChecker
from twisted.cred.credentials import ISSHPrivateKey, IUsernamePassword, UsernamePassword
from twisted.internet.defer import Deferred
from twisted.python.reflect import requireModule
from twisted.trial.unittest import TestCase
cryptography = requireModule("cryptography")
unix = requireModule("twisted.conch.unix")
if cryptography and unix:
from twisted.conch import tap
from twisted.conch.openssh_compat.factory import OpenSSHFactory
class MakeServiceTests(TestCase):
"""
Tests for L{tap.makeService}.
"""
if not cryptography:
skip = "can't run without cryptography"
if not unix:
skip = "can't run on non-posix computers"
usernamePassword = (b"iamuser", b"thisispassword")
def setUp(self) -> None:
"""
Create a file with two users.
"""
self.filename = self.mktemp()
with open(self.filename, "wb+") as f:
f.write(b":".join(self.usernamePassword))
self.options = tap.Options()
def test_basic(self) -> None:
"""
L{tap.makeService} returns a L{StreamServerEndpointService} instance
running on TCP port 22, and the linked protocol factory is an instance
of L{OpenSSHFactory}.
"""
config = tap.Options()
service = tap.makeService(config)
self.assertIsInstance(service, StreamServerEndpointService)
self.assertEqual(service.endpoint._port, 22)
self.assertIsInstance(service.factory, OpenSSHFactory)
def test_defaultAuths(self) -> None:
"""
Make sure that if the C{--auth} command-line option is not passed,
the default checkers are (for backwards compatibility): SSH and UNIX
"""
numCheckers = 2
self.assertIn(
ISSHPrivateKey,
self.options["credInterfaces"],
"SSH should be one of the default checkers",
)
self.assertIn(
IUsernamePassword,
self.options["credInterfaces"],
"UNIX should be one of the default checkers",
)
self.assertEqual(
numCheckers,
len(self.options["credCheckers"]),
"There should be %d checkers by default" % (numCheckers,),
)
def test_authAdded(self) -> None:
"""
The C{--auth} command-line option will add a checker to the list of
checkers, and it should be the only auth checker
"""
self.options.parseOptions(["--auth", "file:" + self.filename])
self.assertEqual(len(self.options["credCheckers"]), 1)
def test_multipleAuthAdded(self) -> None:
"""
Multiple C{--auth} command-line options will add all checkers specified
to the list ofcheckers, and there should only be the specified auth
checkers (no default checkers).
"""
self.options.parseOptions(
[
"--auth",
"file:" + self.filename,
"--auth",
"memory:testuser:testpassword",
]
)
self.assertEqual(len(self.options["credCheckers"]), 2)
def test_authFailure(self) -> Any:
"""
The checker created by the C{--auth} command-line option returns a
L{Deferred} that fails with L{UnauthorizedLogin} when
presented with credentials that are unknown to that checker.
"""
self.options.parseOptions(["--auth", "file:" + self.filename])
checker: FilePasswordDB = self.options["credCheckers"][-1]
self.assertIsInstance(checker, FilePasswordDB)
invalid = UsernamePassword(self.usernamePassword[0], b"fake")
# Wrong password should raise error
return self.assertFailure(
checker.requestAvatarId(invalid), error.UnauthorizedLogin
)
def test_authSuccess(self) -> Deferred[None]:
"""
The checker created by the C{--auth} command-line option returns a
L{Deferred} that returns the avatar id when presented with credentials
that are known to that checker.
"""
self.options.parseOptions(["--auth", "file:" + self.filename])
checker: ICredentialsChecker = self.options["credCheckers"][-1]
correct = UsernamePassword(*self.usernamePassword)
d = checker.requestAvatarId(correct)
def checkSuccess(username: Union[bytes, Tuple[()]]) -> None:
self.assertEqual(username, correct.username)
return d.addCallback(checkSuccess)
def test_checkers(self) -> None:
"""
The L{OpenSSHFactory} built by L{tap.makeService} has a portal with
L{ISSHPrivateKey} and L{IUsernamePassword} interfaces registered as
checkers.
"""
config = tap.Options()
service = tap.makeService(config)
portal = service.factory.portal
self.assertEqual(
set(portal.checkers.keys()), {ISSHPrivateKey, IUsernamePassword}
)
|