From cd387b9bd512760496153019f91cd17ac8636635 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 14:41:07 +0000 Subject: Update README.md. --- README.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 52d6d16..5b4ccf3 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ leap_mx ======= **Note:** Currently in development. Feel free to test, and please [report - bugs](mailto:isis@leap.se). + bugs on our tracker](https://we.riseup.net/leap/mx) or [by email](mailto:isis@leap.se). An asynchronous, transparently-encrypting remailer for the LEAP platform, using BigCouch/CouchDB and PGP/GnuPG, written in Twisted Python. ## [install](#install) ## -========================= -[tl;dr](#tl;dr) ### [virtualenv](#virtualenv) ### ================================= +Impatient? Don't like virtualenvs? [tl;dr](#tl;dr) + Virtualenv is somewhat equivalent to fakeroot for python packages, and -- due to being packaged with copies of pip and python -- can be used to bootstrap its own install process, allowing pip and python to be used with sudo. -#### [installing without sudo] #### +#### installing without sudo #### To install without using sudo, a bootstrap script to handle the setup process is provided. It does the following: @@ -38,7 +38,7 @@ $ ./bootstrap $ workon leap_mx ~~~ -#### [installing in a regular virtualenv] ### +#### installing in a regular virtualenv ### To install python, virtualenv, and get started, do: ~~~ @@ -62,8 +62,20 @@ Although, **it is advised** to install inside a python virtualenv. ## [running](#running) ## ========================= -To get running, clone this repo, and (assuming you've already set up your virtualenv and obtained all the requirements) do: +To get running, clone this repo, and (assuming you've already set up your +virtualenv and obtained all the requirements) do: ~~~ $ ./start_mx.py --help -~~~ \ No newline at end of file +~~~ + +## [hacking](#hacking) ## +========================= +Please see the HACKING and DESIGN docs. + +Our bugtracker is [here](https://leap.se/code/projects/eip_server/issue/new). + +Please use that for bug reports and feature requests instead of github's +tracker. We're using github for code commenting and review between +collaborators. + -- cgit v1.2.3 From 5b4625c529d969eec76c41644e0000be42ce0c28 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 14:53:27 +0000 Subject: Add original DESIGN documentation from https://we.riseup.net/leap/mx. --- DESIGN.md | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..27a17d8 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,186 @@ +# design # + +## overview # +---------------------- +This page pertains to the incoming mail exchange servers of the provider. + +General overview of how incoming email will work: + + 1. Incoming message is received by provider's MX servers. + 2. The MTA (postfix in our case) does a ton of checks on the message before we + even check to see if the recipient is valid (this comes from experience + running the riseup mail infrastructure, where the vast majority of messages + can be rejected early in the SMTP reception and thus save a ton of processing + time on the server). + 3. Postfix then queries the database to check if the recipient is valid, if + they are over quota, if their account is enabled, and to resolve any aliases + for the account. + 4. The message is then delivered to an on-disk message spool. + 5. A daemon watches for new files in this spool. Each message is encrypted to + the user's public key, and stored in the user's incoming message queue (stored + in couchdb), and removed from disk. + 6. When the user next logs in with their client, the user's message queue is + emptied by the client. + 7. Each message is decrypted by the client, and then stored in the user's + "inbox" as an unread message. + 8. This local inbox uses soledad for storage + 9. Soledad, in the background, will then re-encrypt this email (now a soledad + document), and sync to the cloud. + +## postfix pipeline ## +--------------------------- +incoming mx servers will run postfix, configured in a particular way: + + 1. postscreen: before accepting an incoming message, checks RBLs, checks RFC + validity, checks for spam pipelining. + (pass) proceed to next step. + (fail) return SMTP error, which bounces email. + 2. more SMTP checks: valid hostnames, etc. + (pass) accepted, proceed to next step. + (fail) return SMTP error, which bounces email. + + 3. check_recipient_access -- look up each recipient and ensure they are + allowed to receive messages. + (pass) empty result, proceed to next step. + (fail) return SMTP error code and error comment, bounce message. + 4. milter processessing (spamassassin & clamav) + (pass) continue + (fail) bounce message, flag as spam, or silently kill. + 5. virtual_alias_maps -- map user defined aliases and forwards + (local address) continue if new address is for this mx + (remote address) continue. normally, postfix would relay to the remote domain, but we don't want that. + 6. deliver message to spool + (write) save the message to disk on the mx. + 7. postfix's job is done, mail_receiver picks up email from spool directory + +Questions: + + * what is the best way to have postfix write a message to a spool directory? + There is a built-in facility for saving to a maildir, so we could just + specify a common maildir for everyone. alternately, we could pipe to a + simple command that was responsible for safely saving the file to disk. a + third possibility would be to have a local long running daemon that spoke + lmtp that postfix forward the message on to for delivery. + + * if virtual_alias_maps comes after check_recipient_access, then a user with + aliases set but who is over quota will not be able to forward email. i think + this is fine. + + * if we are going to support forwarding, we should ensure that the message + gets encrypted before getting forwarded. so, postfix should not do any + forwarding. instead, this should be the job of mail_receiver. + +Considerations: + + 1. high load should fill queue, not crash pipeline: It is important that the + pipeline be able to handle massive bursts of email, as often happens with + email. This means map lookups need to be very fast, and when there is a high + load of email postfix should not be waiting on the mail receiver but must be + able to pass the message off quickly and have the slower mail receiver churn + through the backlog as best it can. + + 2. don't lose messages: It is important to not lose any messages when there is + a problem. So, generally, a copy of an email should always exist in some spool + somewhere, and that copy should not be deleted until there is confirmation + that the next stage has succeeded. + +## alias_resolver ## +------------------------------ +The alias_resolver will be a daemon running on MX servers that handles lookups +in the user database of email aliases, forwards, quota, and account status. + +Communication with: + + 1. postfix:: alias_resolver will be bound to localhost and speak postfix's + very simple [tcp map protocol -> http://www.postfix.org/tcp_table.5.html]. + + 2. couchdb:: alias_resolver will make couchdb queries to a local http load + balancer that connects to a couchdb/bigcouch + cluster. [directly accessing the couch->https://we.riseup.net/leap+platform/querying-the-couchdb] + might help getting started. + +Discussion: + + * we want the lookups to be fast. using views in couchdb, these should be very + fast. when using bigcouch, we can make it faster by specifying a read quorum + of 1 (instead of the default 2). this will make it so that only a single + couchdb needs to be queried to find the result. i don't know if this would + cause problems, but aliases don't change very often. + +alias_resolver will be responsible for two map lookups in postfix: + +### check_recipient ### + +postfix config: + +@check_recipient_access tcp:localhost:1000@ + +postfix will send "get username@domain.org" and alias_resolver should return an +empty result ("200 \n", i think) if postfix should deliver email to the +user. otherwise, it should return an error. here is example response, verbatim, +that can be used to bounce over quota users: + +``` +200 DEFER_IF_PERMIT Sorry, your message cannot be delivered because the +recipient's mailbox is full. If you can contact them another way, you may wish +to tell them of this problem. +``` + +"DEFER_IF_PERMIT" will let the other MX known that this error is temporary and +that they should try again soon. Typically, an MX will try repeatedly, at +longer and longer intervals, for four days before giving up. + +### virtual alias map ### + +postfix config: + +@virtual_alias_map tcp:localhost:1001@ + +postfix will send "get alias-address@domain.org" and alias_resolver should +return "200 id_123456\n", where 123456 is the unique id of the user that has +alias-address@domain.org. + +couchdb should have a view that will let us query on an (alias) address and +return the user id. + +note: if the result of the alias map (e.g. id_123456) does not have a domain +suffix, i think postfix will use the 'default transport'. if we want it to use +the virtual transport instead, we should append the domain (eg +id_123456@example.org). see +http://www.postfix.org/ADDRESS_REWRITING_README.html#resolve + +## mail_receiver ## + +the mail_receiver is a daemon that runs on incoming MX servers and is +responsible for encrypting incoming email to the user's public key and saving +the email to an incoming queue database for that user. + +communicates with: + + * message spool directory:: mail_reciever sits and waits for new email to be + written to the spool directory (maybe using this + https://github.com/seb-m/pyinotify, i think it is better than FAM). when a + new file is dumped into the spool, mail_receiver reads the file, encrypts + the entire thing using the public key of the recipient, and saves to + couchdb. + * couchdb get:: mail_receiver does a query on user id to get back user's + public openpgp key. read quorum of 1 is probably ok. + * couchdb put:: mail_receiver communicates with couchdb for storing encrypted + email for each user (eventually, mail_receiver will communicate with a local + http proxy, that communicates with a bigcouch cluster, but the api is + identical) + +discussion: + * i am not sure if postfix adds a header to indicate to whom a message was + actually delivered. if not, this is a problem, because then how do we know + what db to put it in or what public key to use? this is perhaps a good + reason to not let postfix handle writing the message to disk, but instead + pipe it to another command (because postfix sets env variables for stuff + like recipient). + + * should the incoming message queue be a separate database or should it be + just documents in the user's main database with special flags? + + * whenever possible, we should refer to the user by a fixed id, not their + username, because we want to support the ability to change usernames. so, + for example, database names should not be based on usernames. -- cgit v1.2.3 From 9af27054358c27a5bc8c784e3b586278f58a0f05 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 18:45:31 +0000 Subject: Update DESIGN docs with curent status. --- DESIGN.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 27a17d8..2d9fe82 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -38,7 +38,6 @@ incoming mx servers will run postfix, configured in a particular way: 2. more SMTP checks: valid hostnames, etc. (pass) accepted, proceed to next step. (fail) return SMTP error, which bounces email. - 3. check_recipient_access -- look up each recipient and ensure they are allowed to receive messages. (pass) empty result, proceed to next step. @@ -61,11 +60,9 @@ Questions: simple command that was responsible for safely saving the file to disk. a third possibility would be to have a local long running daemon that spoke lmtp that postfix forward the message on to for delivery. - * if virtual_alias_maps comes after check_recipient_access, then a user with aliases set but who is over quota will not be able to forward email. i think this is fine. - * if we are going to support forwarding, we should ensure that the message gets encrypted before getting forwarded. so, postfix should not do any forwarding. instead, this should be the job of mail_receiver. @@ -78,12 +75,11 @@ Considerations: load of email postfix should not be waiting on the mail receiver but must be able to pass the message off quickly and have the slower mail receiver churn through the backlog as best it can. - 2. don't lose messages: It is important to not lose any messages when there is a problem. So, generally, a copy of an email should always exist in some spool somewhere, and that copy should not be deleted until there is confirmation that the next stage has succeeded. - + ## alias_resolver ## ------------------------------ The alias_resolver will be a daemon running on MX servers that handles lookups @@ -99,18 +95,18 @@ Communication with: cluster. [directly accessing the couch->https://we.riseup.net/leap+platform/querying-the-couchdb] might help getting started. -Discussion: +### Discussion: ### - * we want the lookups to be fast. using views in couchdb, these should be very - fast. when using bigcouch, we can make it faster by specifying a read quorum - of 1 (instead of the default 2). this will make it so that only a single - couchdb needs to be queried to find the result. i don't know if this would - cause problems, but aliases don't change very often. + 1. we want the lookups to be fast. using views in couchdb, these should be + very fast. when using bigcouch, we can make it faster by specifying a read + quorum of 1 (instead of the default 2). this will make it so that only a + single couchdb needs to be queried to find the result. i don't know if this + would cause problems, but aliases don't change very often. alias_resolver will be responsible for two map lookups in postfix: -### check_recipient ### - +#### check_recipient #### +------------------------- postfix config: @check_recipient_access tcp:localhost:1000@ @@ -126,12 +122,12 @@ recipient's mailbox is full. If you can contact them another way, you may wish to tell them of this problem. ``` -"DEFER_IF_PERMIT" will let the other MX known that this error is temporary and +"DEFER_IF_PERMIT" will let the other MX know that this error is temporary and that they should try again soon. Typically, an MX will try repeatedly, at longer and longer intervals, for four days before giving up. -### virtual alias map ### - +#### virtual alias map #### +--------------------------- postfix config: @virtual_alias_map tcp:localhost:1001@ @@ -149,6 +145,56 @@ the virtual transport instead, we should append the domain (eg id_123456@example.org). see http://www.postfix.org/ADDRESS_REWRITING_README.html#resolve + +### Current status: ### +The current implementation of alias_resolver is in +leap-mx/src/leap/mx/alias_resolver.py. + +The class ```alias_resolver.StatusCodes``` deals with creating SMTP-like +response messages for Postfix, speaking Postfix's TCP Map protocol (from item +#1). + +As for Discussion item #1: + +It might be possible to use +[python-memcached](https://pypi.python.org/pypi/python-memcached/) as an +interface to a [memcached](http://memcached.org/) instance to speed up database +lookups, by keeping an in memory mapping of recent request/response +pairs. Also, Twisted now (I think as of 12.0.0) ships with a protocol for +handling Memcached servers, this is in ```twisted.protocols.memcache```. This +should be prioritised for later, if it is decided that querying the CouchDB is +too expensive or time-consuming. + +Thus far, to speed up alias lookup, an in-memory mapping of alias<->resolution +pairs is created by ```alias_resolver.AliasResolverFactory()```, which can be +optionally seeded with a dictionary of ```{ 'alias': 'resolution' }``` pairs +by doing: +~~~~~~ +>>> from leap.mx import alias_resolver +>>> aliasResolverFactory = alias_resolver.AliasResolverFactory( +... addr='1.2.3.4', port=4242, data={'isis': 'isis@leap.se', +... 'drebs': 'drebs@leap.se'}) +>>> aliasResolver = aliasResolverFactory.buildProtocol() +>>> aliasResolver.check_recipient_access('isis') +200 OK Others might say 'HELLA AWESOME'...but we're not convinced. +~~~~~~ + +TODO: + 1. The AliasResolverFactory needs to be connected to the CouchDB. The + classmethod in which this should occur is ```AliasResolverFactory.get()```. + + 2. I am not sure where to get the user's UUID from (Soledad?). Wherever we get + it from, it will need to be returned in + ```AliasResolver.virtual_alias_map()```, and if we want Postfix to hear about + it, then that response will need to be fed into ```AliasResolver.sendCode```. + + 3. Other than those two things, I think everything is done. The only potential + other thing I can think of is that the codes in + ```alias_resolver.StatusCodes``` might need to be urlencoded for Postfix to + accept them, but this is like two lines of code from urllib. + + + ## mail_receiver ## the mail_receiver is a daemon that runs on incoming MX servers and is @@ -184,3 +230,9 @@ discussion: * whenever possible, we should refer to the user by a fixed id, not their username, because we want to support the ability to change usernames. so, for example, database names should not be based on usernames. + +### Current Status: ### +None of this is done, although having it be a separate daemon sound weird. + +You would probably want to use ```twisted.mail.mail.FileMonitoringService``` to +watch the mailbox (is the mailbox virtual or a maildir or mbox or?) -- cgit v1.2.3 From bfb8701fdeeffddee090306b0fca36733605cdc2 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:29:49 +0000 Subject: Update module docstring for alias_resolver.py. --- src/leap/mx/alias_resolver.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index e079b80..f9f0ca1 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -5,22 +5,15 @@ alias_resolver.py ================= Classes for resolving postfix aliases. -@authors: Isis Agora Lovecruft -@version: 0.0.1-beta -@license: see included LICENSE file -@copyright: copyright 2013 Isis Agora Lovecruft +:authors: Isis Agora Lovecruft +:version: 0.0.1-beta +:license: see included LICENSE file +:copyright: (c) 2013 Isis Agora Lovecruft TODO: o Look into using twisted.protocols.postfix.policies classes for controlling concurrent connections and throttling resource consumption. - - o alias.ProcessAlias() - -## have uuid -> get gpg keyid - -alias.ProcessAlias('/path/to/mail_reciever', *args) - ''' import os -- cgit v1.2.3 From b86827df8d3db5ca87eb064f65886660426fa60f Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:30:23 +0000 Subject: Import our couchdb module into alias_resolver.py. --- src/leap/mx/alias_resolver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index f9f0ca1..8ab84e5 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -27,6 +27,7 @@ except ImportError: print "This software requires Twisted. Please see the README file" print "for instructions on getting required dependencies." +from leap.mx import couchdb from leap.mx.util import net, log, config, exceptions -- cgit v1.2.3 From db49089b2f13d45a1f0fca5ae5cc52dfcc4eac2e Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:31:01 +0000 Subject: Update docstring for function alias_resolver.createUUID(). --- src/leap/mx/alias_resolver.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 8ab84e5..d8d4646 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -32,14 +32,16 @@ from leap.mx.util import net, log, config, exceptions def createUUID(alias): - """ - Creates Universal Unique ID by taking the SHA-1 HASH of an email alias: + """Creates Universal Unique ID by taking the SHA1 HASH of an email alias: + + >>> uuid.uuid5(uuid.NAMESPACE_URL, "isis@leap.se") + UUID('7194878e-4aea-563f-85a4-4f58519f3c4f') - >>> uuid.uuid5(uuid.NAMESPACE_URL, "isis@leap.se") - UUID('7194878e-4aea-563f-85a4-4f58519f3c4f') + TODO: Is there a commonly accepted way to check that an email address + is valid? - @param alias: An email address alias. - @returns: A :class:`uuid.UUID` containing attributes specifying the UUID. + :param str alias: An email address alias. + :returns: A :class:`uuid.UUID` containing attributes specifying the UUID. """ return uuid.uuid5(uuid.NAMESPACE_URL, str(alias)) -- cgit v1.2.3 From 23bfd56fc80fac38ece4273d8c0ebe678e458be3 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:31:34 +0000 Subject: Add DatabaseNotConnected exception to alias_resolver.py. --- src/leap/mx/alias_resolver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index d8d4646..30d8ee3 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -45,6 +45,10 @@ def createUUID(alias): """ return uuid.uuid5(uuid.NAMESPACE_URL, str(alias)) + +class DatabaseNotConnected(Exception): + """Raised when not currently connected to a database.""" + class StatusCodes(object): """ The Postfix manual states: -- cgit v1.2.3 From 4b13ac66d10b98a57c9161ed9b43a26b22585e96 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:32:44 +0000 Subject: Update class docstring for alias_resolver.StatusCodes. --- src/leap/mx/alias_resolver.py | 46 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 30d8ee3..310830b 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -50,15 +50,51 @@ class DatabaseNotConnected(Exception): """Raised when not currently connected to a database.""" class StatusCodes(object): - """ - The Postfix manual states: + """The Postfix manual states: + + The request completion status is one of OK, RETRY, NOKEY (lookup failed + because the key was not found), BAD (malformed request) or DENY (the + table is not approved for proxy read or update access). + + In brief, Postfix will send ``get SPACE key NEWLINE``, or + ``put SPACE key NEWLINE`` where ``key`` is an alias or email address. + It expects non-printable ascii characters to be url-encoded, i.e. a + get-request would look like: + + ``get%20isis@leap.se%0A`` + + and in response, Postfix expects an SMTP-like status code and a string + describing the nature of or reason for the response, no longer than + 4096 "characters" (which, due to UTF-8 ubiquity, we'll err on the safe + side and assume that means 4096 bytes.) - The request completion status is one of OK, RETRY, NOKEY (lookup - failed because the key was not found), BAD (malformed request) or DENY - (the table is not approved for proxy read or update access). + From the Postfix manual on its TCP map protocol + (http://www.postfix.org/tcp_table.5.html): + + 500 SPACE text NEWLINE + In case of a lookup request, the requested data + does not exist. In case of an update request, the + request was rejected. The text describes the + nature of the problem. + + 400 SPACE text NEWLINE + This indicates an error condition. The text + describes the nature of the problem. The client + should retry the request later. + + 200 SPACE text NEWLINE + The request was successful. In the case of a lookup + request, the text contains an encoded version of + the requested data. Other SMTP codes: http://www.greenend.org.uk/rjk/tech/smtpreplies.html + + >>> statcodes = StatusCodes() + >>> if : + >>> response_message = statcodes(200) + >>> aliasresolver.tellMTA() """ + OK = "OK Others might say 'HELLA AWESOME'...but we're not convinced." RETRY = "RETRY Server is busy plotting revolution; requests might take a while." BAD = "BAD bad Leroy Brown, baddest man in the whole...er. Malformed request." -- cgit v1.2.3 From df6f7a038f3738f76cf7f5bb4a85e17a9a27ec56 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:34:02 +0000 Subject: Update mappings of Postfix TCP map protocol codes to message strings. --- src/leap/mx/alias_resolver.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 310830b..38311d8 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -101,15 +101,23 @@ class StatusCodes(object): NOKEY = "NOKEY Couldn't find your keys, sorry. Did you check in the sofa?" DEFER = "DEFER_IF_LOCAL xxx fill me in" DENY = "DENY no gurlz aloud in teh tree house." - FAIL = "FAIL xxx fill me in" - - fakeSMTPCodes = { '250': OK, - '300': RETRY, - '500': BAD, - '550': NOKEY, - '552': DEFER, - '553': DENY, - '554': FAIL, } + FAIL = "FAIL this belongs on the failblog" + + SMTPCodes = { '200': OK, + '400': RETRY, + '500': BAD, + '550': NOKEY, + '552': DEFER, + '553': DENY, + '554': FAIL, } + + SMTPStrings = { 'OK' 200, + 'RETRY': 400, + 'BAD': 500, + 'NOKEY': 550, + 'DEFER': 552, + 'DENY': 553, + 'FAIL': 554, } def __init__(self, status_code=None): """xxx fill me in""" -- cgit v1.2.3 From e17f2beb230b3276473d77957e4d6a3f4da2c814 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:34:57 +0000 Subject: Create method-level docstrings for alias_resolver.StatusCodes. --- src/leap/mx/alias_resolver.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 38311d8..4fc20f5 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -120,12 +120,30 @@ class StatusCodes(object): 'FAIL': 554, } def __init__(self, status_code=None): - """xxx fill me in""" + """Construct an SMTP status code generator. + + :type status_code: str or int + :param status_code: (optional) see :func:`StatusCode.get`. + """ if status_code: self.get(status_code) def get(self, status_code=None) - """xxx fill me in""" + """Takes an SMTP-like status code and returns an SMTP-like message. + + :type status_code: str or int + :param status_code: The string or integer for the response we want + to give back to the MTA, after looking up an + email address in the local user database. + Can be one of: + * ``OK`` or ``200`` + * ``RETRY`` or ``400`` + * ``BAD`` or ``500`` + * ``NOKEY`` or ``550`` + * ``DEFER`` or ``552`` + * ``DENY`` or ``553`` + * ``FAIL`` or ``554`` + """ if status_code: if isinstance(status_code, str): return status_code, getattr(self, status_code.upper(), None) -- cgit v1.2.3 From 8646cf310a34828920f9cbf36c1f4c3da3cb6f9d Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:35:47 +0000 Subject: Fix logic for return of Postfix TCP map responses in alias_resolver.StatusCodes --- src/leap/mx/alias_resolver.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 4fc20f5..48e7a85 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -146,11 +146,15 @@ class StatusCodes(object): """ if status_code: if isinstance(status_code, str): - return status_code, getattr(self, status_code.upper(), None) + if status_code.upper() in self.SMTPStrings.keys(): + return self.SMTPStrings[status_code], getattr( + self, status_code.upper(), '') + else: + return 500, self.FAIL elif isinstance(status_code, int): - for k, v in self.fake_smtp_codes.items(): - ## we want to return None if it's 550 - if k == str(status_code) and k != '550': + for k, v in self.SMTPCodes.items(): + ## we want to return None if it's 500 + if k == str(status_code) and k != '500': return status_code, v log.debug("%s" % self.NOKEY) return None, '' -- cgit v1.2.3 From 0d6a18c52fa2632e9915bf4acdf01a43c2487e8d Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:38:18 +0000 Subject: Update docstrings in alias_resolver.AliasResolver. --- src/leap/mx/alias_resolver.py | 45 ++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 48e7a85..2a244d0 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -161,12 +161,11 @@ class StatusCodes(object): class AliasResolver(postfix.PostfixTCPMapServer): - """ - Resolve postfix aliases, similarly to using "$ postmap -q ". + """Resolve postfix aliases, similarly to using "$ postmap -q ". This class starts a simple LineReceiver server which listens for a string - specifying an alias to look up, :param:`key`, and which will be used to - query the local Postfix server. You can test it with: + specifying an alias ``key`` to look up, which will be used to query the + local user database. You can test it with: $ ./alias_resolver.py & $ /usr/bin/postmap -q tcp:localhost:1347 @@ -176,7 +175,12 @@ class AliasResolver(postfix.PostfixTCPMapServer): https://www.iana.org/assignments/smtp-enhanced-status-codes/ """ def __init__(self, *args, **kwargs): - """Create a server which listens for Postfix aliases to resolve.""" + """Create a server which listens for Postfix aliases to resolve. + + :param int timeout: Number of seconds to wait for a response. + :param str delimiter: The delimiter to use for the EOL on responses. + (Default: '\n') + """ super(postfix.PostfixTCPMapServer, self).__init__(*args, **kwargs) self.status_codes = StatusCodes() @@ -198,7 +202,11 @@ class AliasResolver(postfix.PostfixTCPMapServer): @defer.inlineCallbacks def do_put(self, keyAndValue): - """Add a key and value to the database, provided it does not exist.""" + """Add a key and value to the database, provided it does not exist. + + :param str keyAndValue: An alias and email address, separated by a + space, i.e. ``"isis isis@leap.se"``. + """ if keyAndValue is None: self.sendCode(500) log.warn("Command 'put' takes two parameters.") @@ -219,20 +227,26 @@ class AliasResolver(postfix.PostfixTCPMapServer): @defer.inlineCallbacks def do_delete(self, key): - """ - Delete an alias from the mapping database. + """Delete an alias from the CouchDB. + + xxx I'm not sure if implementing this would be a good idea... - xxx not sure if this is a good idea... + :param str key: An email address to delete from the CouchDB. """ raise NotImplemented def check_recipient_access(self, key): - """Make a query to resolve an alias.""" - self.do_get(self, key) + """Make a query to the CouchDB to resolve an alias. - def virtual_alias_map(self, key): + If the ``key`` is an email address which the CouchDB has information + for that account, we should respond to Postfix with an '200%20\n". + + :param str key: An email address to look up in the CouchDB. """ - Get the Universal Unique ID for the alias address. If + self.do_get(key) + + def virtual_alias_map(self, key): + """Get the Universal Unique ID for the alias address. If virtual_transport is True, then suffix the UUID with a domain. xxx I don't think we actually need couchdb for this, the UUID is an @@ -241,6 +255,11 @@ class AliasResolver(postfix.PostfixTCPMapServer): querying a database, I would presume), it seems silly to do this. Instead, we should query CouchDB with the UUID to get the GPG keyid. + + xxx Or are we supposed to query Soledad for this? + + :param str key: An email address to look up in the CouchDB. + :returns: The UUID of the user. """ ## xxx need email address parser client_id = createUUID(key) -- cgit v1.2.3 From 80e9dc5548b36d1c4f99446a4c1412fcb2c93122 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:40:05 +0000 Subject: Add support for Postfix virtual transports to alias_resolver.AliasResolver. --- src/leap/mx/alias_resolver.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 2a244d0..c080123 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -174,6 +174,10 @@ class AliasResolver(postfix.PostfixTCPMapServer): http://www.postfix.org/proxymap.8.html https://www.iana.org/assignments/smtp-enhanced-status-codes/ """ + + virtual_transport = '@example.com' + use_virtual_transport = False + def __init__(self, *args, **kwargs): """Create a server which listens for Postfix aliases to resolve. @@ -262,31 +266,13 @@ class AliasResolver(postfix.PostfixTCPMapServer): :returns: The UUID of the user. """ ## xxx need email address parser - client_id = createUUID(key) + userid = createUUID(key) - if self.virtual_transport: - return client.get_urn() + '@example.com' - else: - return client.get_urn() - - def _cbGot(self, value): - """Callback for self.get()""" - if value is None: - self.sendCode(550) + if self.use_virtual_transport \ + and isinstance(self.virtual_transport, str): + return userid.get_urn() + self.virtual_transport else: - self.sendCode(250, quote(value)) - - def _cbNot(self, fail): - """Errback for self.get()""" - self.sendCode(554, fail.getErrorMessage()) - - def _cbPut(self, value): - """xxx fill me in""" - pass - - def _cbPout(self, fail): - """xxx fill me in""" - pass + return userid.get_urn() class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): @@ -314,6 +300,8 @@ class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): super(postfix.PostfixTCPMapDeferringDictServerFactory, self).__init__(data=data) self.timeout = timeout + self.virtual_transport = virtual_transport + self.use_virtual_transport = use_virtual_transport self.noisy = True if config.advanced.noisy else False try: @@ -339,6 +327,8 @@ class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): """ proto = self.protocol() proto.timeout = self.timeout + proto.virtual_transport = self.virtual_transport + proto.use_virtual_transport = self.use_virtual_transport proto.factory = self return proto -- cgit v1.2.3 From 2c3083e0631f59faeb60197bfa06235b36b86a99 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:41:15 +0000 Subject: Change the logic on sending Postfix TCP map status codes and messages. --- src/leap/mx/alias_resolver.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index c080123..1a7392d 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -189,9 +189,21 @@ class AliasResolver(postfix.PostfixTCPMapServer): self.status_codes = StatusCodes() def sendCode(self, code, message=None): - """Send an SMTP-like code with a message.""" - if not message: - message = self.status_codes.get(code) + """Send an SMTP-like code with a message. + + :type code: str or int + :param code: The status code to send, see + ``alias_resolver.StatusCodes``. + """ + try: + assert isinstance(code, int), "status code must be type int" + except AssertionError as ae: + log.err(ae.message) + self.sendLine('500 internal server error: %s' % ae.message) + + msg = self.status_codes.get(code) + if message is not None and isinstance(message, str): + msg += (" " + message) self.sendLine('%3.3d %s' % (code, message or '')) def do_get(self, key): -- cgit v1.2.3 From 6ff05c3bf6d66f9b9efcfccf4a61b11d0434cf43 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:46:31 +0000 Subject: Pass the deferred response from CouchDB along to sendLine in AliasResolver. --- src/leap/mx/alias_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 1a7392d..8a1537a 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -259,7 +259,7 @@ class AliasResolver(postfix.PostfixTCPMapServer): :param str key: An email address to look up in the CouchDB. """ - self.do_get(key) + return self.do_get(key) def virtual_alias_map(self, key): """Get the Universal Unique ID for the alias address. If -- cgit v1.2.3 From 784b0f531edee13f9ff66b73f90aad7176ae4a24 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:47:43 +0000 Subject: Add CouchDB instance as an attribute of AliasResolverFactory and update docs. --- src/leap/mx/alias_resolver.py | 58 +++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 8a1537a..a489e4c 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -288,26 +288,48 @@ class AliasResolver(postfix.PostfixTCPMapServer): class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): - """ - A Factory for creating :class:`AliasResolver` servers, which handles inputs - and outputs, and keeps an in-memory mapping of Postfix aliases in the form - of a dictionary. - - xxx fill me in + """A Factory for creating :class:`AliasResolver` servers, which handles + inputs and outputs, and keeps an in-memory mapping of Postfix aliases in + the form of a dictionary. + + >>> from leap.mx import alias_resolver + >>> aliasResolverFactory = alias_resolver.AliasResolver( + ... data={'isis': 'isis@leap.se', + ... 'drebs': 'drebs@leap.se', + ... 'elijah': 'elijah@leap.se',}) + >>> aliasResolver = aliasResolverFactory.buildProtocol() + >>> aliasResolver.check_recipient_access('isis') """ protocol = AliasResolver - - def __init__(self, addr='127.0.0.1', port=4242, timeout=120, data=None): - """ - Create a Factory which returns :class:`AliasResolver` servers. - - @param addr: A string giving the IP address of this server. - Default: '127.0.0.1' - @param port: An integer that specifies the port number to listen - on. Default: 4242 - @param timeout: An integer specifying the number of seconds to wait - until we should time out. Default: 120 - @param data: A dict to use to initialise or update the alias mapping. + database = couchdb.ConnectedCouchDB + + def __init__(self, addr='127.0.0.1', port=4242, timeout=120, + data=None, virtual_transport=None, use_virtual_transport=False, + couch_host=None, couch_port=None, couch_dbname='users', + couch_username=None, couch_password=None): + """Create a Factory which returns :class:`AliasResolver` servers. + + :param str addr: A string giving the IP address of this server, for + talking to postfix. Default: '127.0.0.1' + :param int port: An integer that specifies the port number that this + server should listen and respond on, for talking to + Postfix. on. Default: 4242 + :param int timeout: An integer specifying the number of seconds to wait + until we should time out. Default: 120 + :param dict data: A dict to use to initialise or update the alias + mapping. + :param str virtual_transport: The domain portion of an email address + to suffix the UUID responses of + ``AliasResolver.virtual_alias_map`` with. + :param bool use_virtual_transport: If True, suffix UUIDs with the + ``virtual_transport`` string. + + :param str couch_host: The IP address of the CouchDB server to query. + :param int couch_port: The port of the CouchDB server to query. + :param str couch_dbname: The database in the CouchDB to bind to. + :param str couch_username: The username for authenticating to the + CouchDB. + :param str couch_password: The password for authentication. """ super(postfix.PostfixTCPMapDeferringDictServerFactory, self).__init__(data=data) -- cgit v1.2.3 From dd0920e81e15539560f1d29033658132ba7044c1 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:48:59 +0000 Subject: Add connection to CouchDB instance to AliasResolverFactory. --- src/leap/mx/alias_resolver.py | 47 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index a489e4c..0747882 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -338,6 +338,16 @@ class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): self.use_virtual_transport = use_virtual_transport self.noisy = True if config.advanced.noisy else False + if couch_port is None: + couch_port = 5984 + if couch_dbname is None: + couch_dbname = 'users' + self.database_connected = False + if couch_host is not None: + self.couch = self.connectDatabase(couch_host, couch_port, + couch_dbname, couch_username, + couch_password) + try: assert isinstance(port, int), "Port number must be an integer" assert isinstance(timeout, int), "Timeout must be an integer" @@ -366,17 +376,36 @@ class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): proto.factory = self return proto - def get(self, *args, **kwargs): - """ - xxx connect me to the couchdb - """ - pass + def _cb_connectDatabase(self): + self.database_connected = True - def put(self, *args, **kwargs): - """ - xxx connect me to the couchdb + def connectDatabase(self, couch_host, couch_port=None, couch_dbname=None, + couch_username=None, couch_password=None): + """Connect to the CouchDB instance.""" + if not self.database_connected: + d = self.database(couch_host, couch_port, dbName=couch_dbname, + username=couch_username, password=couch_password) + d.addCallback(self._cb_connectDatabase) + d.addErrback(log.err) + return d + else: + return self.couch ## xxx are we sure we only want one connection? + + def get(self, key, **kwargs): + """Query the CouchDB for a user's info. + + :param str key: The alias to look up. Should be either an email address + or a username. (xxx do we want to also support lookups + by UUID?) """ - pass + if self.database_connected: + return self.couch.queryByEmailOrAlias(key) + else: + raise DatabaseNotConnected("Must be connected to a database.") + + def put(self, key, **kwargs): + """Add an alias to the CouchDB database.""" + raise NotImplemented if __name__ == "__main__": -- cgit v1.2.3 From cc56ea05d09dd2304350ed531d6220e9b2adc29d Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:49:38 +0000 Subject: Update AliasResolver.buildProtocol() docstring. --- src/leap/mx/alias_resolver.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 0747882..f71c4d8 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -366,9 +366,7 @@ class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): % (addr, port)) def buildProtocol(self): - """ - Create an instance of the :class:`AliasResolver` server. - """ + """Create an instance of the :class:`AliasResolver` server.""" proto = self.protocol() proto.timeout = self.timeout proto.virtual_transport = self.virtual_transport -- cgit v1.2.3 From f69a085cfe9d904404cb421e802515b716cfbf9c Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:50:23 +0000 Subject: Update couchdb.ConnectedCouchDB class docstring. --- src/leap/mx/couchdb.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index b5d4127..c020295 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -26,11 +26,10 @@ from leap.mx.util import log class ConnectedCouchDB(client.CouchDB): - """ - Connect to a CouchDB instance. + """Connect to a CouchDB instance. - ## xxx will we need to open CouchDB documents and views? - ## yes, these are in a _design document + CouchDB document for testing is '_design', and the view is simply + a preconfigured set of mapped responses. """ def __init__(self, host, port, dbName=None, username=None, password=None, *args, **kwargs): -- cgit v1.2.3 From 07488a8d4c4aea1f7c86cd13219ee37ec8b46850 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:50:50 +0000 Subject: Add default CouchDB port to couchdb.ConnectCouchDB parameters. --- src/leap/mx/couchdb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index c020295..f63d2f0 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -31,7 +31,7 @@ class ConnectedCouchDB(client.CouchDB): CouchDB document for testing is '_design', and the view is simply a preconfigured set of mapped responses. """ - def __init__(self, host, port, dbName=None, username=None, + def __init__(self, host, port=5984, dbName=None, username=None, password=None, *args, **kwargs): """ Connect to a CouchDB instance. @@ -44,7 +44,8 @@ class ConnectedCouchDB(client.CouchDB): @returns: A :class:`twisted.internet.defer.Deferred` representing the the client connection to the CouchDB instance. """ - super(client.CouchDB, self).__init__(host, port, + super(client.CouchDB, self).__init__(host, + port=port, dbName=dbName, username=username, password=password, -- cgit v1.2.3 From 368f03896aaf88249805b5ae2484c67d8fe73d74 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:52:29 +0000 Subject: Update ConnectedCouchDB.__init__() docstring. --- src/leap/mx/couchdb.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index f63d2f0..e7aee8b 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -36,12 +36,12 @@ class ConnectedCouchDB(client.CouchDB): """ Connect to a CouchDB instance. - @param host: A hostname string for the CouchDB server. - @param port: The port of the CouchDB server, as an integer. - @param dbName: (optional) The default database to connect to. - @param username: (optional) The username for authorization. - @param password: (optional) The password for authorization. - @returns: A :class:`twisted.internet.defer.Deferred` representing the + :param str host: A hostname string for the CouchDB server. + :param int port: The port of the CouchDB server. + :param str dbName: (optional) The default database to bind queries to. + :param str username: (optional) The username for authorization. + :param str password: (optional) The password for authorization. + :returns: A :class:`twisted.internet.defer.Deferred` representing the the client connection to the CouchDB instance. """ super(client.CouchDB, self).__init__(host, -- cgit v1.2.3 From 9a166f7cd1247e022ca2f0b5685e927f3d0edfca Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:53:04 +0000 Subject: Remove duplicate call to bind database, it's in the super() call. --- src/leap/mx/couchdb.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index e7aee8b..826e83e 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -50,9 +50,7 @@ class ConnectedCouchDB(client.CouchDB): username=username, password=password, *args, **kwargs) - if dbName: - self.bindToDB(dbName) - else: + if dbName is None: databases = self.listDB() log.msg("Available databases: %s" % databases) -- cgit v1.2.3 From 6e97e1f509025569979153542aa2362bb7c2f413 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:53:39 +0000 Subject: Override parent CouchDB methods for database creation and deletion. --- src/leap/mx/couchdb.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index 826e83e..9b69091 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -54,6 +54,14 @@ class ConnectedCouchDB(client.CouchDB): databases = self.listDB() log.msg("Available databases: %s" % databases) + def createDB(self, dbName): + """Overrides ``paisley.client.CouchDB.createDB``.""" + pass + + def deleteDB(self, dbName): + """Overrides ``paisley.client.CouchDB.deleteDB``.""" + pass + def queryByEmailOrAlias(self, alias, dbDoc="User", view="by_email_or_alias"): """ -- cgit v1.2.3 From 5048af81ffea65f9abc8d9aae451a58f97e015fd Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:54:14 +0000 Subject: Update ConnectedCouchDB.queryByEmailOrAlias() docstring. --- src/leap/mx/couchdb.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index 9b69091..fb4d8f6 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -64,12 +64,11 @@ class ConnectedCouchDB(client.CouchDB): def queryByEmailOrAlias(self, alias, dbDoc="User", view="by_email_or_alias"): - """ - Check to see if a particular email or alias exists. + """Check to see if a particular email or alias exists. - @param alias: A string representing the email or alias to check. - @param dbDoc: The CouchDB document to open. - @param view: The view of the CouchDB document to use. + :param str alias: A string representing the email or alias to check. + :param str dbDoc: The CouchDB document to open. + :param str view: The view of the CouchDB document to use. """ assert isinstance(alias, str), "Email or alias queries must be string" -- cgit v1.2.3 From a0cdd89a1e74f61c263bd7ee4b214d823b8e3a7c Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:54:54 +0000 Subject: Update ConnectedCouchDB.query() docstring. --- src/leap/mx/couchdb.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index fb4d8f6..fe46ac6 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -89,8 +89,10 @@ class ConnectedCouchDB(client.CouchDB): return d def query(self, uri): - """ - Query a CouchDB instance that we are connected to. + """Query a CouchDB instance that we are connected to. + + :param str uri: A particular URI in the CouchDB, i.e. + "/users/_design/User/_view/by_email_or_alias". """ try: self.checkURI(uri) ## xxx write checkURI() -- cgit v1.2.3 From 471c584b4fac9de68200f3b252292f1735f3d1a4 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 15 Apr 2013 19:55:19 +0000 Subject: Update ConnectedCouchDB.listUsersAndEmails() docstring. --- src/leap/mx/couchdb.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py index fe46ac6..04cfc4d 100644 --- a/src/leap/mx/couchdb.py +++ b/src/leap/mx/couchdb.py @@ -109,12 +109,14 @@ class ConnectedCouchDB(client.CouchDB): @defer.inlineCallbacks def listUsersAndEmails(self, limit=1000, reverse=False): - """ - List all users and email addresses, up to the given limit. + """List all users and email addresses, up to the given limit. + + :param int limit: The number of results to limit the response to. + :param bool reverse: Start at the end of the database mapping. """ query = "/users/_design/User/_view/by_email_or_alias/?reduce=false" answer = yield self.query(query, limit=limit, reverse=reverse) - + if answer: parsed = yield self.parseResult(answer) if parsed: -- cgit v1.2.3