mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-03 03:36:44 +00:00
Remove AccountURLChecker, the code that relies on it, associated tests, and docs.
This commit is contained in:
parent
19a3481e5f
commit
1b46f981c6
@ -78,33 +78,6 @@ start with "ssh-".
|
|||||||
Now add an ``accounts.file`` directive to your ``tahoe.cfg`` file, as described in
|
Now add an ``accounts.file`` directive to your ``tahoe.cfg`` file, as described in
|
||||||
the next sections.
|
the next sections.
|
||||||
|
|
||||||
Running An Account Server (accounts.url)
|
|
||||||
========================================
|
|
||||||
|
|
||||||
The accounts.url directive allows access requests to be controlled by an
|
|
||||||
HTTP-based login service, useful for centralized deployments. This was used
|
|
||||||
by AllMyData to provide web-based file access, where the service used a
|
|
||||||
simple PHP script and database lookups to map an account email address and
|
|
||||||
password to a Tahoe-LAFS directory cap. The service will receive a
|
|
||||||
multipart/form-data POST, just like one created with a <form> and <input>
|
|
||||||
fields, with three parameters:
|
|
||||||
|
|
||||||
• action: "authenticate" (this is a static string)
|
|
||||||
• email: USERNAME (Tahoe-LAFS has no notion of email addresses, but the
|
|
||||||
authentication service uses them as account names, so the interface
|
|
||||||
presents this argument as "email" rather than "username").
|
|
||||||
• passwd: PASSWORD
|
|
||||||
|
|
||||||
It should return a single string that either contains a Tahoe-LAFS directory
|
|
||||||
cap (URI:DIR2:...), or "0" to indicate a login failure.
|
|
||||||
|
|
||||||
Tahoe-LAFS recommends the service be secure, preferably localhost-only. This
|
|
||||||
makes it harder for attackers to brute force the password or use DNS
|
|
||||||
poisoning to cause the Tahoe-LAFS gateway to talk with the wrong server,
|
|
||||||
thereby revealing the usernames and passwords.
|
|
||||||
|
|
||||||
Public key authentication is not supported when an account server is used.
|
|
||||||
|
|
||||||
Configuring SFTP Access
|
Configuring SFTP Access
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
1
newsfragments/3652.removed
Normal file
1
newsfragments/3652.removed
Normal file
@ -0,0 +1 @@
|
|||||||
|
Removed support for the Account Server frontend authentication type.
|
@ -116,7 +116,6 @@ _client_config = configutil.ValidConfiguration(
|
|||||||
),
|
),
|
||||||
"sftpd": (
|
"sftpd": (
|
||||||
"accounts.file",
|
"accounts.file",
|
||||||
"accounts.url",
|
|
||||||
"enabled",
|
"enabled",
|
||||||
"host_privkey_file",
|
"host_privkey_file",
|
||||||
"host_pubkey_file",
|
"host_pubkey_file",
|
||||||
@ -1042,13 +1041,12 @@ class _Client(node.Node, pollmixin.PollMixin):
|
|||||||
accountfile = self.config.get_config("sftpd", "accounts.file", None)
|
accountfile = self.config.get_config("sftpd", "accounts.file", None)
|
||||||
if accountfile:
|
if accountfile:
|
||||||
accountfile = self.config.get_config_path(accountfile)
|
accountfile = self.config.get_config_path(accountfile)
|
||||||
accounturl = self.config.get_config("sftpd", "accounts.url", None)
|
|
||||||
sftp_portstr = self.config.get_config("sftpd", "port", "tcp:8022")
|
sftp_portstr = self.config.get_config("sftpd", "port", "tcp:8022")
|
||||||
pubkey_file = self.config.get_config("sftpd", "host_pubkey_file")
|
pubkey_file = self.config.get_config("sftpd", "host_pubkey_file")
|
||||||
privkey_file = self.config.get_config("sftpd", "host_privkey_file")
|
privkey_file = self.config.get_config("sftpd", "host_privkey_file")
|
||||||
|
|
||||||
from allmydata.frontends import sftpd
|
from allmydata.frontends import sftpd
|
||||||
s = sftpd.SFTPServer(self, accountfile, accounturl,
|
s = sftpd.SFTPServer(self, accountfile,
|
||||||
sftp_portstr, pubkey_file, privkey_file)
|
sftp_portstr, pubkey_file, privkey_file)
|
||||||
s.setServiceParent(self)
|
s.setServiceParent(self)
|
||||||
|
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from twisted.web.client import getPage
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.cred import error, checkers, credentials
|
from twisted.cred import error, checkers, credentials
|
||||||
from twisted.conch.ssh import keys
|
from twisted.conch.ssh import keys
|
||||||
from twisted.conch.checkers import SSHPublicKeyChecker, InMemorySSHKeyDB
|
from twisted.conch.checkers import SSHPublicKeyChecker, InMemorySSHKeyDB
|
||||||
|
|
||||||
from allmydata.util.dictutil import BytesKeyDict
|
from allmydata.util.dictutil import BytesKeyDict
|
||||||
from allmydata.util import base32
|
|
||||||
from allmydata.util.fileutil import abspath_expanduser_unicode
|
from allmydata.util.fileutil import abspath_expanduser_unicode
|
||||||
|
|
||||||
|
|
||||||
@ -86,54 +82,3 @@ class AccountFileChecker(object):
|
|||||||
d = defer.maybeDeferred(creds.checkPassword, correct)
|
d = defer.maybeDeferred(creds.checkPassword, correct)
|
||||||
d.addCallback(self._cbPasswordMatch, str(creds.username))
|
d.addCallback(self._cbPasswordMatch, str(creds.username))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
@implementer(checkers.ICredentialsChecker)
|
|
||||||
class AccountURLChecker(object):
|
|
||||||
credentialInterfaces = (credentials.IUsernamePassword,)
|
|
||||||
|
|
||||||
def __init__(self, client, auth_url):
|
|
||||||
self.client = client
|
|
||||||
self.auth_url = auth_url
|
|
||||||
|
|
||||||
def _cbPasswordMatch(self, rootcap, username):
|
|
||||||
return FTPAvatarID(username, rootcap)
|
|
||||||
|
|
||||||
def post_form(self, username, password):
|
|
||||||
sepbase = base32.b2a(os.urandom(4))
|
|
||||||
sep = "--" + sepbase
|
|
||||||
form = []
|
|
||||||
form.append(sep)
|
|
||||||
fields = {"action": "authenticate",
|
|
||||||
"email": username,
|
|
||||||
"passwd": password,
|
|
||||||
}
|
|
||||||
for name, value in fields.iteritems():
|
|
||||||
form.append('Content-Disposition: form-data; name="%s"' % name)
|
|
||||||
form.append('')
|
|
||||||
assert isinstance(value, str)
|
|
||||||
form.append(value)
|
|
||||||
form.append(sep)
|
|
||||||
form[-1] += "--"
|
|
||||||
body = "\r\n".join(form) + "\r\n"
|
|
||||||
headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
|
|
||||||
}
|
|
||||||
return getPage(self.auth_url, method="POST",
|
|
||||||
postdata=body, headers=headers,
|
|
||||||
followRedirect=True, timeout=30)
|
|
||||||
|
|
||||||
def _parse_response(self, res):
|
|
||||||
rootcap = res.strip()
|
|
||||||
if rootcap == "0":
|
|
||||||
raise error.UnauthorizedLogin
|
|
||||||
return rootcap
|
|
||||||
|
|
||||||
def requestAvatarId(self, credentials):
|
|
||||||
# construct a POST to the login form. While this could theoretically
|
|
||||||
# be done with something like the stdlib 'email' package, I can't
|
|
||||||
# figure out how, so we just slam together a form manually.
|
|
||||||
d = self.post_form(credentials.username, credentials.password)
|
|
||||||
d.addCallback(self._parse_response)
|
|
||||||
d.addCallback(self._cbPasswordMatch, str(credentials.username))
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
@ -1983,7 +1983,7 @@ class ShellSession(PrefixingLogMixin):
|
|||||||
components.registerAdapter(ShellSession, SFTPUserHandler, ISession)
|
components.registerAdapter(ShellSession, SFTPUserHandler, ISession)
|
||||||
|
|
||||||
|
|
||||||
from allmydata.frontends.auth import AccountURLChecker, AccountFileChecker, NeedRootcapLookupScheme
|
from allmydata.frontends.auth import AccountFileChecker, NeedRootcapLookupScheme
|
||||||
|
|
||||||
@implementer(portal.IRealm)
|
@implementer(portal.IRealm)
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
@ -2000,7 +2000,7 @@ class Dispatcher(object):
|
|||||||
class SFTPServer(service.MultiService):
|
class SFTPServer(service.MultiService):
|
||||||
name = "frontend:sftp"
|
name = "frontend:sftp"
|
||||||
|
|
||||||
def __init__(self, client, accountfile, accounturl,
|
def __init__(self, client, accountfile,
|
||||||
sftp_portstr, pubkey_file, privkey_file):
|
sftp_portstr, pubkey_file, privkey_file):
|
||||||
precondition(isinstance(accountfile, (str, type(None))), accountfile)
|
precondition(isinstance(accountfile, (str, type(None))), accountfile)
|
||||||
precondition(isinstance(pubkey_file, str), pubkey_file)
|
precondition(isinstance(pubkey_file, str), pubkey_file)
|
||||||
@ -2013,12 +2013,9 @@ class SFTPServer(service.MultiService):
|
|||||||
if accountfile:
|
if accountfile:
|
||||||
c = AccountFileChecker(self, accountfile)
|
c = AccountFileChecker(self, accountfile)
|
||||||
p.registerChecker(c)
|
p.registerChecker(c)
|
||||||
if accounturl:
|
if not accountfile:
|
||||||
c = AccountURLChecker(self, accounturl)
|
|
||||||
p.registerChecker(c)
|
|
||||||
if not accountfile and not accounturl:
|
|
||||||
# we could leave this anonymous, with just the /uri/CAP form
|
# we could leave this anonymous, with just the /uri/CAP form
|
||||||
raise NeedRootcapLookupScheme("must provide an account file or URL")
|
raise NeedRootcapLookupScheme("must provide an account file")
|
||||||
|
|
||||||
pubkey = keys.Key.fromFile(pubkey_file.encode(get_filesystem_encoding()))
|
pubkey = keys.Key.fromFile(pubkey_file.encode(get_filesystem_encoding()))
|
||||||
privkey = keys.Key.fromFile(privkey_file.encode(get_filesystem_encoding()))
|
privkey = keys.Key.fromFile(privkey_file.encode(get_filesystem_encoding()))
|
||||||
|
Loading…
Reference in New Issue
Block a user