~thisfred/+junk/ubuntuone-couch-0.1.0

« back to all changes in this revision

Viewing changes to bin/u1couch-query

  • Committer: eric.casteleijn at canonical
  • Date: 2011-02-17 20:16:43 UTC
  • mfrom: (1.1.13 add-tests)
  • Revision ID: eric.casteleijn@canonical.com-20110217201643-8cyxzg6jl85pvtbg
mergedĀ lp:~thisfred/ubuntu-one-couch/add-tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
 
2
 
 
3
# Copyright 2011 Canonical Ltd.
 
4
#
 
5
# Ubuntu One Couch is free software: you can redistribute it and/or
 
6
# modify it under the terms of the GNU Lesser General Public License
 
7
# version 3 as published by the Free Software Foundation.
 
8
#
 
9
# Ubuntu One Couch is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
12
# Lesser General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU Lesser General Public
 
15
# License along with desktopcouch. If not, see
 
16
# <http://www.gnu.org/licenses/>.
 
17
 
2
18
"""ubuntuone-couchdb-query: Command line query tool for Ubuntu One CouchDBs"""
3
19
 
4
 
# sil@kryogenix.org, 2010-02-13
5
20
 
6
21
from optparse import OptionParser
7
 
from oauth import oauth
8
 
import gnomekeyring, gobject, httplib2, simplejson, urlparse, cgi, urllib
9
 
 
10
 
try:
11
 
    from ubuntu_sso.main import SSOCredentials
12
 
except ImportError:
13
 
    SSOCredentials = None
14
22
 
15
23
import socket
 
24
from u1couch import query
 
25
 
16
26
socket.setdefaulttimeout(5)
17
27
 
18
 
def get_prod_oauth_token():
19
 
    """Get the token from the keyring"""
20
 
    gobject.set_application_name("Ubuntu One Web API Tool")
21
 
 
22
 
    consumer = oauth.OAuthConsumer("ubuntuone", "hammertime")
23
 
    items = []
24
 
    items = gnomekeyring.find_items_sync(
25
 
        gnomekeyring.ITEM_GENERIC_SECRET,
26
 
        {'ubuntuone-realm': "https://ubuntuone.com",
27
 
         'oauth-consumer-key': consumer.key})
28
 
    return oauth.OAuthToken.from_string(items[0].secret)
29
 
 
30
 
def get_prod_oauth_token(explicit_token_store=None):
31
 
    """Get the token from the keyring"""
32
 
    gobject.set_application_name("Ubuntu One Web API Tool")
33
 
    if (SSOCredentials is not None) and (explicit_token_store in ["sso", None]):
34
 
        creds = SSOCredentials('Ubuntu One')
35
 
        info = creds.find_credentials('Ubuntu One')
36
 
        consumer = oauth.OAuthConsumer(info['consumer_key'],
37
 
                                    info['consumer_secret'])
38
 
        secret='oauth_token=%s&oauth_token_secret=%s' % (info['token'],
39
 
                                                        info['token_secret'])
40
 
    elif explicit_token_store in ["hammertime", None]:
41
 
        consumer = oauth.OAuthConsumer("ubuntuone", "hammertime")
42
 
        items = []
43
 
        items = gnomekeyring.find_items_sync(
44
 
            gnomekeyring.ITEM_GENERIC_SECRET,
45
 
            {'ubuntuone-realm': "https://ubuntuone.com",
46
 
            'oauth-consumer-key': consumer.key})
47
 
        secret = items[0].secret
48
 
    else:
49
 
        raise Exception("Wasn't able to get a token")
50
 
 
51
 
    return (oauth.OAuthToken.from_string(secret), consumer)
52
 
 
53
 
def get_oauth_request_header(consumer, access_token, http_url, signature_method):
54
 
    """Get an oauth request header given the token and the url"""
55
 
    assert http_url.startswith("https")
56
 
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
57
 
        http_url=http_url,
58
 
        http_method="GET",
59
 
        oauth_consumer=consumer,
60
 
        token=access_token)
61
 
    oauth_request.sign_request(signature_method, consumer, access_token)
62
 
    return oauth_request.to_header()
63
 
 
64
 
def request(urlpath, sigmeth, http_method, request_body, show_tokens, 
65
 
        server_override=None, explicit_token_store=None):
66
 
    """Make a request to couchdb.one.ubuntu.com for the user's data.
67
 
    
68
 
       The user supplies a urlpath (for example, dbname). We need to actually
69
 
       request https://couchdb.one.ubuntu.com/PREFIX/dbname, and sign it with
70
 
       the user's OAuth token, which must be in the keyring.
71
 
       
72
 
       We find the prefix by querying https://one.ubuntu.com/api/account/
73
 
       (see desktopcouch.replication_services.ubuntuone, which does this).
74
 
    """
75
 
    
76
 
    # First check that there's a token in the keyring.
77
 
    try:
78
 
        (access_token, consumer) = get_prod_oauth_token(explicit_token_store)
79
 
    except: # bare except, norty
80
 
        raise Exception("Unable to retrieve your Ubuntu One access details "
81
 
            "from the keyring. Try connecting your machine to Ubuntu One.")
82
 
    
83
 
    # Set the signature method. This should be HMAC unless you have a jolly
84
 
    # good reason for it to not be.
85
 
    if sigmeth == "PLAINTEXT":
86
 
        signature_method = oauth.OAuthSignatureMethod_PLAINTEXT()
87
 
    elif sigmeth == "HMAC_SHA1":
88
 
        signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
89
 
    else:
90
 
        signature_method = oauth.OAuthSignatureMethod_PLAINTEXT()
91
 
    
92
 
    if show_tokens:
93
 
        print "Using OAuth data:"
94
 
        print "consumer: %s : %s\ntoken: %s" % (
95
 
            consumer.key, consumer.secret, access_token)
96
 
    
97
 
    # Look up the user's prefix
98
 
    infourl = "https://one.ubuntu.com/api/account/"
99
 
    oauth_header = get_oauth_request_header(consumer, access_token, infourl, signature_method)
100
 
    client = httplib2.Http()
101
 
    resp, content = client.request(infourl, "GET", headers=oauth_header)
102
 
    if resp['status'] == "200":
103
 
        try:
104
 
            document = simplejson.loads(content)
105
 
        except ValueError:
106
 
            raise Exception("Got unexpected content:\n%s" % content)
107
 
        if "couchdb_root" not in document:
108
 
            raise ValueError("couchdb_root not found in %s" % (document,))
109
 
        if "id" not in document:
110
 
            raise ValueError("id not found in %s" % (document,))
111
 
        COUCH_ROOT = document["couchdb_root"]
112
 
        USERID = document["id"]
113
 
    else:
114
 
        raise ValueError("Error retrieving user data (%s)" % (resp['status'], 
115
 
            url))
116
 
 
117
 
    # COUCH_ROOT must have all internal slashes escaped
118
 
    schema, netloc, path, params, query, fragment = urlparse.urlparse(COUCH_ROOT)
119
 
    if server_override:
120
 
        netloc = server_override
121
 
    path = "/" + urllib.quote(path[1:], safe="") # don't escape the first /
122
 
    COUCH_ROOT = urlparse.urlunparse((schema, netloc, path, params, query, fragment))
123
 
    
124
 
    # Now use COUCH_ROOT and the specified user urlpath to get data
125
 
    if urlpath == "_all_dbs":
126
 
        couch_url = "%s%s" % (COUCH_ROOT, urlpath)
127
 
        couch_url = urlparse.urlunparse((schema, netloc, "_all_dbs", None, "user_id=%s" % USERID, None))
128
 
    else:
129
 
        couch_url = "%s%%2F%s" % (COUCH_ROOT, urlpath)
130
 
    schema, netloc, path, params, query, fragment = urlparse.urlparse(couch_url)
131
 
    querystr_as_dict = dict(cgi.parse_qsl(query))
132
 
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
133
 
        http_url=couch_url,
134
 
        http_method=http_method,
135
 
        oauth_consumer=consumer,
136
 
        token=access_token,
137
 
        parameters=querystr_as_dict)
138
 
    oauth_request.sign_request(signature_method, consumer, access_token)
139
 
    failed = 0
140
 
    #print "Connecting to effective Couch URL", oauth_request.to_url()
141
 
    #print "Connecting to actual Couch URL (with OAuth header)", couch_url
142
 
    #print oauth_request.to_header(), querystr_as_dict
143
 
    while 1:
144
 
        try:
145
 
            resp, content = client.request(couch_url, http_method, 
146
 
                headers=oauth_request.to_header(), body=request_body)
147
 
            break
148
 
        except IOError:
149
 
            failed += 1
150
 
    if failed > 0:
151
 
        if failed == 1:
152
 
            s = ""
153
 
        else:
154
 
            s = "s"
155
 
        print "(request failed %s time%s)" % (failed, s)
156
 
    if resp['status'] == "200":
157
 
        try:
158
 
            return simplejson.loads(content)
159
 
        except:
160
 
            print "(data returned from CouchDB was invalid JSON)"
161
 
            return content
162
 
    elif resp['status'] == "400":
163
 
        print "The server could not parse the oauth token:\n%s" % content
164
 
    elif resp['status'] == "401":
165
 
        print "Access Denied"
166
 
        print "Content:"
167
 
        return content
168
 
    else:
169
 
        return (
170
 
            "There was a problem processing the request:\nstatus:%s, response:"
171
 
            " %r" % (resp['status'], content))
172
 
 
173
28
 
174
29
if __name__ == "__main__":
175
 
    parser = OptionParser(usage="prog [options] urlpath")
176
 
    parser.add_option("--oauth-signature-method", dest="sigmeth",
 
30
    PARSER = OptionParser(usage="prog [options] urlpath")
 
31
    PARSER.add_option("--oauth-signature-method", dest="sigmeth",
177
32
                      default="HMAC_SHA1",
178
33
                      help="OAuth signature method to use (PLAINTEXT or "
179
34
                      "HMAC_SHA1)")
180
 
    parser.add_option("--http-method", dest="http_method",
 
35
    PARSER.add_option("--http-method", dest="http_method",
181
36
                      default="GET",
182
37
                      help="HTTP method to use")
183
 
    parser.add_option("--body", dest="body",
 
38
    PARSER.add_option("--body", dest="body",
184
39
                      default=None,
185
40
                      help="HTTP request body")
186
 
    parser.add_option("--show-tokens", dest="show_tokens",
 
41
    PARSER.add_option("--show-tokens", dest="show_tokens",
187
42
                      default=False,
188
43
                      help="Show the OAuth tokens we're using")
189
 
    parser.add_option("--server-override", dest="server_override",
 
44
    PARSER.add_option("--server-override", dest="server_override",
190
45
                      default=None,
191
46
                      help="Use a different server")
192
 
    parser.add_option("--explicit-token-store", dest="explicit_token_store",
193
 
                      default=None,
194
 
                      help="Explicitly choose a token store (sso or hammertime)")
195
47
 
196
 
    (options, args) = parser.parse_args()
197
 
    if len(args) != 1:
198
 
        parser.error("You must specify a urlpath (e.g., a dbname)")
199
 
    print request(
200
 
        urlpath=args[0], sigmeth=options.sigmeth,
201
 
        http_method=options.http_method, request_body=options.body,
202
 
        show_tokens=options.show_tokens, 
203
 
        server_override=options.server_override,
204
 
        explicit_token_store=options.explicit_token_store)
 
48
    (OPTIONS, ARGS) = PARSER.parse_args()
 
49
    if len(ARGS) != 1:
 
50
        PARSER.error("You must specify a urlpath (e.g., a dbname)")
 
51
    print query.request(
 
52
        urlpath=ARGS[0], sig_meth=OPTIONS.sigmeth,
 
53
        http_method=OPTIONS.http_method, request_body=OPTIONS.body,
 
54
        show_tokens=OPTIONS.show_tokens,
 
55
        server_override=OPTIONS.server_override)