Merge lp:~leonardr/launchpadlib/separate-login-for-cronjob into lp:launchpadlib
| Status: | Merged |
|---|---|
| Approved by: | Gavin Panella on 2010-12-23 |
| Approved revision: | 128 |
| Merged at revision: | 103 |
| Proposed branch: | lp:~leonardr/launchpadlib/separate-login-for-cronjob |
| Merge into: | lp:launchpadlib |
| Diff against target: |
1325 lines (+638/-234) 7 files modified
src/launchpadlib/NEWS.txt (+7/-0) src/launchpadlib/credentials.py (+139/-67) src/launchpadlib/launchpad.py (+200/-95) src/launchpadlib/testing/helpers.py (+53/-6) src/launchpadlib/tests/test_credential_store.py (+138/-0) src/launchpadlib/tests/test_http.py (+3/-2) src/launchpadlib/tests/test_launchpad.py (+98/-64) |
| To merge this branch: | bzr merge lp:~leonardr/launchpadlib/separate-login-for-cronjob |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Gavin Panella | 2010-12-23 | Approve on 2010-12-23 | |
|
Review via email:
|
|||
Description of the Change
This branch has three interrelated purposes:
1. Currently the "request token authorization engine" object includes two strategies: a strategy for authorizing an OAuth request token, and a strategy for storing the resulting launchpadlib credentials locally. This branch decouples those two strategies. The strategy for storing launchpadlib credentials is now kept in a separate object, the "credential store". The code from RequestTokenAut
2. Currently there is no way to store launchpadlib credentials unencrypted in a file on disk. We deliberately got rid of this feature for security reasons, but there is a legitimate case for this: see bug 686690. This branch introduces a new credential store, the UnencryptedFile
3. The difference between storing launchpadlib credentials unencrypted on disk vs. in the keyring is a big, big difference. We can't treat it as the difference between passing in a filename to login_with() and not passing in a filename. So I've split login_with() into two methods: login_securely() and login_insecurely(). You use login_insecurely() when you must: when your script must run without any user interaction whatsoever. You use login_securely() when you can.
I've taken this opportunity to deprecate most of the old login helpers: login_with(), login(), and get_token_
Since I'm defining new login methods, I've also taken this opportunity to reorder the arguments they accept. Over the past couple of years we've added lots of arguments to the end of the login_with() signature, and it got very disorganized.
4. Other notes:
I moved fake_keyring, FauxSocketModule, BadSaveKeyring, and InMemoryKeyring from test_launchpad.py to testing/helpers.py.
I will need to do a follow-up Launchpad branch to get it to use the new methods, and a follow-up launchpadlib branch to stop KnownTokens from using the deprecated login() method. (I put an XXX in this part of the code).
- 128. By Leonard Richardson on 2010-12-23
-
Merge from trunk.
| Robert Collins (lifeless) wrote : | # |
| Leonard Richardson (leonardr) wrote : | # |
I'm not attached to the current method names at all. That said, the difference is not quite "login-
Even though login_insecurely is "the one you should use for cron scripts", if there is no credential it will still cause a browser open and require user input. So we can't use my original ideas for names, "login_interactive" and "login_
My principles:
1. The difference between the methods is "look in the keyring" versus "look in an unencrypted (as far as launchpadlib knows) file".
2, I would like to guide people towards the keyring method. The unencrypted file method should be for people who have problems with the keyring method.
Related thoughts on encrypted filesystems:
I've got an encrypted home directory. I set up a cronjob to run a launchpadlib script. I use login_insecurely(), but I store my credential inside my home directory. Because I have an encrypted home directory, it's not really 'insecure'--it's about as secure as stuff in my keyring.
But the cronjob will only run when my home directory is unencrypted, ie. when I'm logged in. When I'm not logged in, the credential will be unreadable and the cronjob will fail. If I want the cronjob to run all the time, I need to store the credential in an unencrypted filesystem. I think that's the common cron scenario, and I'd say that is 'insecure', relatively speaking.
So, I don't think the existing names are terribly misleading. You can use login_insecurely() in a secure way, if you'll be logged in whenever the script runs. But if you're already logged in, login_securely() will work just as well. And the common case for login_securely() *is* more secure than the common case for login_insecurely().
| Gavin Panella (allenap) wrote : | # |
The new CredentialStore stuff is cool.
I've got lots of comments, but they're all fairly minor.
[1]
+ * Launchpad.
+ interaction.
+ * Launchpad.
+ user interaction.
These names worry me. I don't want to debate these names if they have
already been discussed. I just want to say: if I am the only person to
have seen them then I think they need some discussion, though not a
lot.
[2]
+
+
+ def do_load(self, unique_key):
Dangerous extra blank line.
[3]
Some lint:
credentials.py:29: 'base64' imported but unused
credentials.py:34: 'sys' imported but unused
credentials.py:35: 'textwrap' imported but unused
credentials.py:37: 'quote' imported but unused
launchpad.py:25: 'socket' imported but unused
launchpad.py:26: 'stat' imported but unused
launchpad.py:29: 'HostedFile' imported but unused
launchpad.py:29: 'ScalarValue' imported but unused
launchpad.py:36: 'SystemWideCons
launchpad.py:52: 'STAGING_
launchpad.py:52: 'EDGE_SERVICE_ROOT' imported but unused
testing/
testing/
testing/
tests/test_
tests/test_
tests/test_
tests/test_
tests/test_
tests/test_
tests/test_
tests/test_
[4]
+ def __init__(self, file, credential_
+ super(Unencrypt
+ credential_
+ self.file = file
Because file() is a builtin function/type consider using filename or
filepath instead, or something like that.
[5]
+ This method is deprecated as of launchpadlib version
+ 1.9.0. You should use Launchpad.
+ anonymous access, Launchpad.
+ that need to run without any user interaction, and
+ Launchpad.
+ specifying a custom authorization_
Perhaps it's worth putting some or all of this into a deprecation
warning, warnings.
[6]
+@contextmanager
+def fake_keyring(fake):
+ original_keyring = launchpadlib.
+ launchpadlib.
+ yield
+ launchpadlib.
This will break when a test fails because the yield can raise an
exception. Something like the following would work better I think:
@contextmanager
def fake_keyring(fake):
orig...
| Robert Collins (lifeless) wrote : | # |
So, these names then are essentially aliases to (pseudocode)
login(... store=keyringstore)
login(... store=filestore)
why not expose that instead.
login(... store=None)
"""
store: a store to request credentials from. If None, GnomeKeyringStore
will be used, as the most reliable and secure store we have available
today. Using a different store (see <...> for a list of stores) is
needed when running scripts outside of a desktop context such as from
cron.
"""
?
| Leonard Richardson (leonardr) wrote : | # |
> So, these names then are essentially aliases to (pseudocode)
> login(... store=keyringstore)
> login(... store=filestore)
>
> why not expose that instead.
> login(... store=None)
> """
> store: a store to request credentials from. If None, GnomeKeyringStore
> will be used, as the most reliable and secure store we have available
> today. Using a different store (see <...> for a list of stores) is
> needed when running scripts outside of a desktop context such as from
> cron.
> """
I'm fine with this. The main problem is that we have already staked out the namespace of generic names. login() and login_with() are existing methods that I hope to deprecate with this branch.
I think it wouldn't be _too_ bad to change the behavior of login() in 1.9.0, since everyone I know of uses login_with(). We could even call it 2.0.0. (But, I think a 3.0.0 would not be far behind--I was planning to make another big backward-
Without an alias for cron usage, the code to run a cron script would look like this:
store = UnencryptedFile
launchpad = Launchpad.login("my application" "production", credential_
Martin, are you OK with this?
- 129. By Leonard Richardson on 2011-01-03
-
Response to feedback: lint cleanup, (hackily) replace mktemp with mkstemp.
| Leonard Richardson (leonardr) wrote : | # |
I've addressed all of your comments except for [10]. I need to add
tests for the deprecation warning, which I will do with the
catch_warnings context manager. I also have some unknown test failures that
I don't remember having when I left for vacation last year, and I need
to look into those.
I got rid of most of the lint. launchpad.
launchpad.
client convenience, so I added comments to that effect.
Re: mkstemp. I deliberately avoided mkstemp because mkstemp creates an
empty file. lazr.restfulcli
if you give it an empty file. Should I do a lazr.restfulclient release
and make load_from_path() treat an empty file the same as a
nonexistant file?
In the meantime, I have put a hack in place to make mkstemp work on
the launchpadlib level.
See my previous comment re: the method names. I'm okay with one
generic name, but we took the most generic name ("login") in the first
version of launchpadlib, and then we took another generic name
("login_with") in a later revision.
| Gavin Panella (allenap) wrote : | # |
> Re: mkstemp. I deliberately avoided mkstemp because mkstemp creates
> an empty file. lazr.restfulcli
> crashes if you give it an empty file. Should I do a
> lazr.restfulclient release and make load_from_path() treat an empty
> file the same as a nonexistant file?
>
> In the meantime, I have put a hack in place to make mkstemp work on
> the launchpadlib level.
An alternate approach might be to safely create a temporary directory
with tempfile.mkdtemp() then use static names within.
[13]
+ warnings.warn(
+ ("The Launchpad.%s() method is deprecated. You should use "
+ "Launchpad.
+ "Launchpad.
+ "without any user interaction, and Launchpad.
+ "for all other purposes.") % name)
I haven't used warnings much before so I don't know if it works well
in practice, but you could pass DeprecationWarning as a second
argument to warn(). I think this makes it easier for users to filter
it out without missing other warnings.
- 130. By Leonard Richardson on 2011-01-03
-
Re-activated deprecation warning.
- 131. By Leonard Richardson on 2011-01-04
-
Fix test failures, which were caused by cached responses from actual usage being sent to the NoNetworkLaunchpad.
- 132. By Leonard Richardson on 2011-01-04
-
Finally got rid of all test failures and unxpected deprecation warnings.
- 133. By Leonard Richardson on 2011-01-04
-
Removed login_insecurely and login_securely. They are both now part of login_with(), which has been un-deprecated.
- 134. By Leonard Richardson on 2011-01-04
-
We don't need to test credential_
save_failed twice now that there's only one method. - 135. By Leonard Richardson on 2011-01-04
-
Updated the NEWS to reflect my most recent change.
| Leonard Richardson (leonardr) wrote : | # |
After evaluating my options I've decided to un-deprecate login_with(). This method acts like login_insecurely() when you specify a file to store the credential in, and acts like login_securely() when you don't. That's a perfect description of what I wanted login() to do.
Once we get rid of login(), we'll have space to come back to this problem and call the resulting method login(). At the very least, we need to rationalize the arguments to login_with(), which are out of control and presented in random order since we kept tacking arguments on to the end of the list.

Reading the cover, login_insecurely and login_securely seem like value
judgements rather than api changes.
Isn't the real difference can-prompt- for-credentials cached- credentials- only
login-and-
login-using-
?
After all, disk files in an encrypted per-account system are
approximately as secure as those in the gnome keyring (both need only
compromise the account to access the credentials).