git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Add a credential-helper for KDE
@ 2011-08-27 19:54 Lukas Sandström
  2011-08-31  1:42 ` Jeff King
  0 siblings, 1 reply; 5+ messages in thread
From: Lukas Sandström @ 2011-08-27 19:54 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Lukas Sandström, Jeff King

This Python script plugs into the credentials API
of Git to ask the user for passwords with a nice
KDE password dialog.

The password is saved in the KWallet.

Signed-off-by: Lukas Sandström <luksan@gmail.com>
---

Here is a credentials-helper for KDE. You need to have PyKDE installed to use it.

See Documentation/gitcredentials.txt for more info.

 .../git-kde-credentials-helper.py                  |  122 ++++++++++++++++++++
 1 files changed, 122 insertions(+), 0 deletions(-)
 create mode 100755 contrib/kde-credetials-helper/git-kde-credentials-helper.py

diff --git a/contrib/kde-credetials-helper/git-kde-credentials-helper.py b/contrib/kde-credetials-helper/git-kde-credentials-helper.py
new file mode 100755
index 0000000..8d3be4d
--- /dev/null
+++ b/contrib/kde-credetials-helper/git-kde-credentials-helper.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# encoding=utf-8
+#
+# Copyright 2011, Lukas Sandström
+#
+# Licensed under the GPL version 2.
+
+import sys, commands
+from PyQt4.QtCore import QString
+from PyKDE4.kdecore import i18n, ki18n, KAboutData, KCmdLineArgs, KCmdLineOptions
+from PyKDE4.kdeui import KApplication, KWallet, KPasswordDialog
+
+appName     = "git-kde-credentials-helper"
+catalog     = ""
+programName = ki18n ("Git KDE credentials helper")
+version     = "0.1"
+description = ki18n ("Credentials storage helper for Git")
+license     = KAboutData.License_GPL_V2
+copyright   = ki18n ("(c) 2011 Lukas Sandström")
+text        = ki18n ("none")
+homePage    = "http://www.git-scm.com"
+bugEmail    = "luksan@gmail.com"
+
+aboutData   = KAboutData (appName, catalog, programName, version, description,
+                          license, copyright, text, homePage, bugEmail)
+
+class CredentialHelper(KApplication):
+    def __init__(self, token, username = None, desc = None, reject = False):
+        super(CredentialHelper, self).__init__()
+        self.password = None
+        self.username = username
+        self.save_password = False
+        self.token = token
+        self.desc = desc
+
+        if not self.token:
+            return
+
+        self.open_wallet()
+
+        if reject:
+            self.wallet.removeEntry(QString(token))
+            return
+
+        if not self.check_wallet():
+            self.ask_password_dialog()
+        
+        if self.save_password:
+            self.store_password()
+
+        self.output_credentials()
+
+    def output_credentials(self):
+        if self.username:
+            print "username=" + self.username
+        if self.password:
+            print "password=" + self.password
+
+    def store_password(self):
+        self.wallet.writeMap(QString(self.token),
+            {QString("username") : QString(self.username),
+             QString("password") : QString(self.password)})
+
+    def open_wallet(self):
+        self.wallet = KWallet.Wallet.openWallet(
+            KWallet.Wallet.LocalWallet(), 0, KWallet.Wallet.Synchronous)
+        if not self.wallet.isOpen():
+            return None
+        if not self.wallet.hasFolder("GitCredentials"):
+            self.wallet.createFolder("GitCredentials")
+        self.wallet.setFolder("GitCredentials")
+
+    def check_wallet(self):
+        (res, data) = self.wallet.readMap(self.token)
+        if res != 0:
+            return None
+        try:
+            self.username = data[QString("username")]
+            self.password = data[QString("password")]
+        except KeyError:
+            return None
+        return self.username and self.password
+
+    def ask_password_dialog(self):
+        dlg = KPasswordDialog(None,
+            KPasswordDialog.KPasswordDialogFlag(
+                KPasswordDialog.ShowKeepPassword |
+                KPasswordDialog.ShowUsernameLine))
+        if self.desc:
+            desc = self.desc
+        else:
+            desc = self.token
+        dlg.setPrompt(i18n("Please enter username and password for %s" % (desc)))
+        dlg.setUsername(self.username)
+        dlg.setKeepPassword(True)
+        if not dlg.exec_():
+            return
+        self.username = dlg.username()
+        self.password = dlg.password()
+        self.save_password = dlg.keepPassword()
+
+def main():    
+    KCmdLineArgs.init(sys.argv, aboutData)
+    
+    options = KCmdLineOptions()
+    options.add("unique <token>", ki18n("Unique token identifying the credential"))
+    options.add("description <desc>", ki18n("Human readable description of the credential"))
+    options.add("username <username>", ki18n("Requested username"))
+    options.add("reject", ki18n("Purge credential"))    
+    
+    KCmdLineArgs.addCmdLineOptions(options)
+    args = KCmdLineArgs.parsedArgs();
+
+    username = args.getOption("username")
+    token = args.getOption("unique")
+    desc = args.getOption("description")
+    reject = args.isSet("reject")
+
+    app = CredentialHelper(token, username, desc, reject)
+
+if __name__ == "__main__":
+    main()
-- 
1.7.6.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] Add a credential-helper for KDE
  2011-08-27 19:54 [PATCH] Add a credential-helper for KDE Lukas Sandström
@ 2011-08-31  1:42 ` Jeff King
  2011-09-18 14:52   ` [PATCH v2] " Lukas Sandström
  0 siblings, 1 reply; 5+ messages in thread
From: Jeff King @ 2011-08-31  1:42 UTC (permalink / raw)
  To: Lukas Sandström; +Cc: Git Mailing List

On Sat, Aug 27, 2011 at 09:54:02PM +0200, Lukas Sandström wrote:

> This Python script plugs into the credentials API
> of Git to ask the user for passwords with a nice
> KDE password dialog.

Thanks for working on this.

>  .../git-kde-credentials-helper.py                  |  122 ++++++++++++++++++++

Can we call it git-credential-kdewallet or similar? Then users can just
do:

  git config credential.helper kdewallet

(where "kdewallet" can be whatever you think is most appropriate; the
key is naming it git-credential-*).

>  1 files changed, 122 insertions(+), 0 deletions(-)
>  create mode 100755 contrib/kde-credetials-helper/git-kde-credentials-helper.py

Minor typo in directory name.

> +    def check_wallet(self):
> +        (res, data) = self.wallet.readMap(self.token)
> +        if res != 0:
> +            return None
> +        try:
> +            self.username = data[QString("username")]
> +            self.password = data[QString("password")]
> +        except KeyError:
> +            return None
> +        return self.username and self.password

If I am reading this correctly, you look up based purely on the context
token. Which means that if I do something like this:

  $ git push https://host.com/repo.git
  [enter username: user1, password: foo]
  $ git push https://user2@host.com/other-repo.git

We will invoke the helper as:

  git credential-kdewallet --unique=https:host.com --username=user2

but the helper will ignore the "user2" bit, and return "user1 / foo".

The "cache" helper I wrote handles this situation better, by indexing
both on the token and the username. I wonder if the username should
become part of the token. Or if the token should really just become a
canonicalized URL, minus the actual path. So the first one would get:

  --unique=https://host.com

and the second would get:

  --unique=https://user2@host.com

Then helpers wouldn't need to worry about doing anything special.

What do you think? Also, any comments in general on writing a helper?
You are the first one besides me to do so. Did you find anything in the
interface or the documentation confusing? Suggestions are very welcome,
as nothing has been released yet and we're free to tweak as much as we
want.

-Peff

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2] Add a credential-helper for KDE
  2011-08-31  1:42 ` Jeff King
@ 2011-09-18 14:52   ` Lukas Sandström
  2011-09-18 18:49     ` Jeff King
  2011-09-30 10:21     ` Jeff King
  0 siblings, 2 replies; 5+ messages in thread
From: Lukas Sandström @ 2011-09-18 14:52 UTC (permalink / raw)
  To: Jeff King; +Cc: Lukas Sandström, Git Mailing List

This Python script plugs into the credentials API
of Git to ask the user for passwords with a nice
KDE password dialog.

The password is saved in the KWallet.

Signed-off-by: Lukas Sandström <luksan@gmail.com>
---

On 2011-08-31 03:42, Jeff King wrote:
> Can we call it git-credential-kdewallet or similar? Then users can just
> do:
> 
>   git config credential.helper kdewallet
> 
> (where "kdewallet" can be whatever you think is most appropriate; the
> key is naming it git-credential-*).

Done.

[...]

> If I am reading this correctly, you look up based purely on the context
> token. Which means that if I do something like this:
> 
>   $ git push https://host.com/repo.git
>   [enter username: user1, password: foo]
>   $ git push https://user2@host.com/other-repo.git
> 
> We will invoke the helper as:
> 
>   git credential-kdewallet --unique=https:host.com --username=user2
> 
> but the helper will ignore the "user2" bit, and return "user1 / foo".
> 
> The "cache" helper I wrote handles this situation better, by indexing
> both on the token and the username. I wonder if the username should
> become part of the token. Or if the token should really just become a
> canonicalized URL, minus the actual path. So the first one would get:
> 
>   --unique=https://host.com
> 
> and the second would get:
> 
>   --unique=https://user2@host.com
> 
> Then helpers wouldn't need to worry about doing anything special.
> 
> What do you think? Also, any comments in general on writing a helper?
> You are the first one besides me to do so. Did you find anything in the
> interface or the documentation confusing? Suggestions are very welcome,
> as nothing has been released yet and we're free to tweak as much as we
> want.
> 
> -Peff

Right. Multiple usernames per "unique" context is supported in this version.
I looked at the git-credential-storage helper when I wrote the first patch,
which didn't have obvious support for multiple usernames per unique context.

Keeping the username outside the token is probably a good thing, but perhaps it
should be clarified in the api-docs that multiple usernames has to be supported.

Also; what about rejecting credentials. This code currently deletes just a 
username/password pair if a username is specified, and all credentials associated
with the token if only --unique and --reject is specified. Is this correct/expected
behavior?

When I first wrote the helper I tried to immediately ask for a new password if a
credential was rejected, but this didn't work with the HTTP auth code, since it
doesn't retry the auth with the new credentials after a reject. I think it would
be better if we asked for a new password instead of just saying "auth failed" and 
having the user retry the fetch/pull when the stored credentials are incorrect.

/Lkas

 .../git-credential-kdewallet.py                    |  137 ++++++++++++++++++++
 1 files changed, 137 insertions(+), 0 deletions(-)
 create mode 100755 contrib/git-credential-kdewallet/git-credential-kdewallet.py

diff --git a/contrib/git-credential-kdewallet/git-credential-kdewallet.py b/contrib/git-credential-kdewallet/git-credential-kdewallet.py
new file mode 100755
index 0000000..29c4ae1
--- /dev/null
+++ b/contrib/git-credential-kdewallet/git-credential-kdewallet.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# encoding=utf-8
+#
+# Copyright 2011, Lukas Sandström
+#
+# Licensed under the GPL version 2.
+
+import sys
+from PyQt4.QtCore import QString
+from PyKDE4.kdecore import i18n, ki18n, KAboutData, KCmdLineArgs, KCmdLineOptions
+from PyKDE4.kdeui import KApplication, KWallet, KPasswordDialog
+
+appName     = "git-credential-kdewallet"
+catalog     = ""
+programName = ki18n ("Git KDE credentials helper")
+version     = "0.1"
+description = ki18n ("Credentials storage helper for Git")
+license     = KAboutData.License_GPL_V2
+copyright   = ki18n ("(c) 2011 Lukas Sandström")
+text        = ki18n ("none")
+homePage    = "http://www.git-scm.com"
+bugEmail    = "luksan@gmail.com"
+
+aboutData   = KAboutData (appName, catalog, programName, version, description,
+                          license, copyright, text, homePage, bugEmail)
+
+class CredentialHelper(KApplication):
+    def __init__(self, token, username = None, desc = None, reject = False):
+        super(CredentialHelper, self).__init__()
+        self.password = None
+        self.username = username
+        self.save_password = False
+        self.token = token
+        self.desc = desc
+
+        if not self.token:
+            return
+
+        self.open_wallet()
+
+        if reject:
+            self.reject_credential()
+            return
+
+        if not self.check_wallet():
+            self.ask_password_dialog()
+
+        if self.save_password:
+            self.store_password()
+
+        self.output_credentials()
+
+    def output_credentials(self):
+        if self.username:
+            print "username=" + self.username
+        if self.password:
+            print "password=" + self.password
+
+    def reject_credential(self):
+        (res, data) = self.wallet.readMap(self.token)
+        if self.username:
+            try:
+                del data[QString(self.username)]
+            except KeyError:
+                pass
+            self.wallet.writeMap(self.token, data)
+        else:
+            self.wallet.removeEntry(self.token)
+
+    def store_password(self):
+        (res, data) = self.wallet.readMap(self.token)
+        data[QString(self.username)] = QString(self.password)
+        self.wallet.writeMap(QString(self.token), data)
+
+    def open_wallet(self):
+        self.wallet = KWallet.Wallet.openWallet(
+            KWallet.Wallet.LocalWallet(), 0, KWallet.Wallet.Synchronous)
+        if not self.wallet.isOpen():
+            return None
+        if not self.wallet.hasFolder("GitCredentials"):
+            self.wallet.createFolder("GitCredentials")
+        self.wallet.setFolder("GitCredentials")
+
+    def check_wallet(self):
+        (res, data) = self.wallet.readMap(self.token)
+        if res != 0:
+            return None
+        for u, p in data.iteritems():
+            # Pick the first complete credential if no username is specified
+            if not self.username and u and p:
+                self.username = u
+                self.password = p
+                return True
+            if self.username == u:
+                self.password = p
+                return True
+        return None
+
+    def ask_password_dialog(self):
+        dlg = KPasswordDialog(None,
+            KPasswordDialog.KPasswordDialogFlag(
+                KPasswordDialog.ShowKeepPassword |
+                KPasswordDialog.ShowUsernameLine))
+        if self.desc:
+            desc = self.desc
+        else:
+            desc = self.token
+        dlg.setPrompt(i18n("Please enter username and password for %s" % (desc)))
+        dlg.setUsername(self.username)
+        dlg.setKeepPassword(True)
+        if not dlg.exec_():
+            return
+        self.username = dlg.username()
+        self.password = dlg.password()
+        self.save_password = dlg.keepPassword()
+
+def main():
+    KCmdLineArgs.init(sys.argv, aboutData)
+
+    options = KCmdLineOptions()
+    options.add("unique <token>", ki18n("Unique token identifying the credential"))
+    options.add("description <desc>", ki18n("Human readable description of the credential"))
+    options.add("username <username>", ki18n("Requested username"))
+    options.add("reject", ki18n("Purge credential"))
+
+    KCmdLineArgs.addCmdLineOptions(options)
+    args = KCmdLineArgs.parsedArgs();
+
+    username = args.getOption("username")
+    token = args.getOption("unique")
+    desc = args.getOption("description")
+    reject = args.isSet("reject")
+
+    app = CredentialHelper(token, username, desc, reject)
+
+if __name__ == "__main__":
+    main()
-- 
1.7.6.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v2] Add a credential-helper for KDE
  2011-09-18 14:52   ` [PATCH v2] " Lukas Sandström
@ 2011-09-18 18:49     ` Jeff King
  2011-09-30 10:21     ` Jeff King
  1 sibling, 0 replies; 5+ messages in thread
From: Jeff King @ 2011-09-18 18:49 UTC (permalink / raw)
  To: Lukas Sandström; +Cc: Git Mailing List

On Sun, Sep 18, 2011 at 04:52:58PM +0200, Lukas Sandström wrote:

> Right. Multiple usernames per "unique" context is supported in this version.
> I looked at the git-credential-storage helper when I wrote the first patch,
> which didn't have obvious support for multiple usernames per unique context.

Yeah, sorry about that. The -cache helper is much more fully fleshed out
(though I have improved the -store helper in the past few days to handle
multiple usernames better).

> Keeping the username outside the token is probably a good thing, but perhaps it
> should be clarified in the api-docs that multiple usernames has to be supported.

OK, I'll try to write up a clarification.

> Also; what about rejecting credentials. This code currently deletes just a 
> username/password pair if a username is specified, and all credentials associated
> with the token if only --unique and --reject is specified. Is this correct/expected
> behavior?

Yes, that's what I think should happen, and what both of my helpers do.
In practice, I don't think it will be called that way by git, which
will always be rejecting a username we just tried. But I wanted to leave
things flexible in case a user wants to manually remove a credential
from a store.

> When I first wrote the helper I tried to immediately ask for a new password if a
> credential was rejected, but this didn't work with the HTTP auth code, since it
> doesn't retry the auth with the new credentials after a reject. I think it would
> be better if we asked for a new password instead of just saying "auth failed" and 
> having the user retry the fetch/pull when the stored credentials are incorrect.

Yeah, I had a patch early on to retry authentication a few times before
exiting, but I wondered how helpful it was. It's usually pretty easy to
retry your command again via shell history, and sometimes looping on
asking for authentication can be annoying (because things like askpass
will actually grab the keyboard focus).

So I dunno what is best. I don't consider it a big deal, but maybe
others do.

Even if we did do the retry from git, the helper shouldn't ask
immediately for the new credential inside a --reject. It should wait to
be invoked again asking for the password. I know this may be an extra
fork/exec/startup cycle, but it keeps the interface to the helper simple
and flexible.

-Peff

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v2] Add a credential-helper for KDE
  2011-09-18 14:52   ` [PATCH v2] " Lukas Sandström
  2011-09-18 18:49     ` Jeff King
@ 2011-09-30 10:21     ` Jeff King
  1 sibling, 0 replies; 5+ messages in thread
From: Jeff King @ 2011-09-30 10:21 UTC (permalink / raw)
  To: Lukas Sandström; +Cc: Git Mailing List

On Sun, Sep 18, 2011 at 04:52:58PM +0200, Lukas Sandström wrote:

> This Python script plugs into the credentials API
> of Git to ask the user for passwords with a nice
> KDE password dialog.
> 
> The password is saved in the KWallet.

So I managed to play with this a bit tonight. Overall, it seems pretty
nice.

Initially, it seemed somewhat clumsy. It asked me to open the wallet
(using a password) each time git ran. Which is about as annoying as just
typing my git password each time. :)

The magic trick was to configure kwallet to "keep the wallet open for 10
minutes after the last use" instead of "close when no applications have
the wallet open". Since git runs as many small programs, kwallet has no
real idea of how long a git session is.

This is totally not a kwallet thing, and nothing to do with your helper.
But since the helper is so annoyingly useless without that config, it
might be worth mentioning it in a README.

> Right. Multiple usernames per "unique" context is supported in this version.
> I looked at the git-credential-storage helper when I wrote the first patch,
> which didn't have obvious support for multiple usernames per unique context.

This part passed my tests just fine. Very nice.

> +class CredentialHelper(KApplication):
> +    def __init__(self, token, username = None, desc = None, reject = False):
> +        super(CredentialHelper, self).__init__()
> +        self.password = None
> +        self.username = username
> +        self.save_password = False
> +        self.token = token
> +        self.desc = desc
> +
> +        if not self.token:
> +            return

My tests complained about doing nothing when there is no token. As I've
mentioned elsewhere, this doesn't matter now (as git never invokes the
helper that way), but it would be nice to future-proof the helper by
just ignoring the wallet, but still doing the nice password dialog.

> +    def open_wallet(self):
> +        self.wallet = KWallet.Wallet.openWallet(
> +            KWallet.Wallet.LocalWallet(), 0, KWallet.Wallet.Synchronous)
> +        if not self.wallet.isOpen():
> +            return None
> +        if not self.wallet.hasFolder("GitCredentials"):
> +            self.wallet.createFolder("GitCredentials")
> +        self.wallet.setFolder("GitCredentials")

I peeked around the KWallet manager. There's a "passwords" folder in the
wallet, and I was surprised that the passwords didn't go there. But when
I tried using konqueror to store a password, I found that it also made
its own folder, and then stored a map within it for each URL.

So I'm not really sure if you're following kwallet best practices or
not, as I'm clearly confused about what the "passwords" folder is for.
;)

> +    def check_wallet(self):
> +        (res, data) = self.wallet.readMap(self.token)

So you're just using the token as a big blob. Which is how I
anticipated, but is the complete opposite of what OS X Keychain wants.
Which is leading me to think we should really just hand helpers both
forms: the information broken down by item (e.g., --host=github.com),
and a full URL (e.g., --url=https://github.com/). And then the helpers
can use whatever they like (where you would use "url" instead of the
current "unique").

-Peff

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2011-09-30 10:21 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-08-27 19:54 [PATCH] Add a credential-helper for KDE Lukas Sandström
2011-08-31  1:42 ` Jeff King
2011-09-18 14:52   ` [PATCH v2] " Lukas Sandström
2011-09-18 18:49     ` Jeff King
2011-09-30 10:21     ` Jeff King

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).