Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 1847844

Browse files
committed
Added HTTP basic authentication support
1 parent a41a960 commit 1847844

File tree

6 files changed

+203
-70
lines changed

6 files changed

+203
-70
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Unreleased
22
==========
3+
- [NEW] Added HTTP basic authentication support.
34
- [NEW] Added ``Result.all()`` convenience method.
45
- [NEW] Allow ``service_name`` to be specified when instantiating from a Bluemix VCAP_SERVICES environment variable.
56
- [IMPROVED] Updated ``posixpath.join`` references to use ``'/'.join`` when concatenating URL parts.

src/cloudant/client.py

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616
Top level API module that maps to a Cloudant or CouchDB client connection
1717
instance.
1818
"""
19-
import base64
2019
import json
2120

22-
from ._2to3 import bytes_, unicode_
21+
from .client_session import (
22+
BasicSession,
23+
ClientSession,
24+
CookieSession,
25+
IAMSession
26+
)
2327
from .database import CloudantDatabase, CouchDatabase
2428
from .feed import Feed, InfiniteFeed
2529
from .error import (
@@ -31,7 +35,6 @@
3135
append_response_error_content,
3236
CloudFoundryService,
3337
)
34-
from client_session import ClientSession, CookieSession, IAMSession
3538

3639

3740
class CouchDB(dict):
@@ -67,6 +70,8 @@ class CouchDB(dict):
6770
`Requests library timeout argument
6871
<http://docs.python-requests.org/en/master/user/quickstart/#timeouts>`_.
6972
but will apply to every request made using this client.
73+
:param bool use_basic_auth: Keyword argument, if set to True performs basic
74+
access authentication with server. Default is False.
7075
:param bool use_iam: Keyword argument, if set to True performs
7176
IAM authentication with server. Default is False.
7277
Use :func:`~cloudant.client.CouchDB.iam` to construct an IAM
@@ -86,7 +91,9 @@ def __init__(self, user, auth_token, admin_party=False, **kwargs):
8691
self._timeout = kwargs.get('timeout', None)
8792
self.r_session = None
8893
self._auto_renew = kwargs.get('auto_renew', False)
94+
self._use_basic_auth = kwargs.get('use_basic_auth', False)
8995
self._use_iam = kwargs.get('use_iam', False)
96+
9097
connect_to_couch = kwargs.get('connect', False)
9198
if connect_to_couch and self._DATABASE_CLASS == CouchDatabase:
9299
self.connect()
@@ -100,7 +107,16 @@ def connect(self):
100107
self.session_logout()
101108

102109
if self.admin_party:
103-
self.r_session = ClientSession(timeout=self._timeout)
110+
self.r_session = ClientSession(
111+
timeout=self._timeout
112+
)
113+
elif self._use_basic_auth:
114+
self.r_session = BasicSession(
115+
self._user,
116+
self._auth_token,
117+
self.server_url,
118+
timeout=self._timeout
119+
)
104120
elif self._use_iam:
105121
self.r_session = IAMSession(
106122
self._auth_token,
@@ -146,9 +162,6 @@ def session(self):
146162
147163
:returns: Dictionary of session info for the current session.
148164
"""
149-
if self.admin_party:
150-
return None
151-
152165
return self.r_session.info()
153166

154167
def session_cookie(self):
@@ -157,29 +170,33 @@ def session_cookie(self):
157170
158171
:returns: Session cookie for the current session
159172
"""
160-
if self.admin_party:
161-
return None
162173
return self.r_session.cookies.get('AuthSession')
163174

164175
def session_login(self, user=None, passwd=None):
165176
"""
166177
Performs a session login by posting the auth information
167178
to the _session endpoint.
179+
180+
:param str user: Username used to connect to CouchDB.
181+
:param str auth_token: Authentication token used to connect to CouchDB.
168182
"""
169-
if self.admin_party:
170-
return
183+
self.change_credentials(user=user, auth_token=passwd)
184+
185+
def change_credentials(self, user=None, auth_token=None):
186+
"""
187+
Change login credentials.
171188
172-
self.r_session.set_credentials(user, passwd)
189+
:param str user: Username used to connect to CouchDB.
190+
:param str auth_token: Authentication token used to connect to CouchDB.
191+
"""
192+
self.r_session.set_credentials(user, auth_token)
173193
self.r_session.login()
174194

175195
def session_logout(self):
176196
"""
177197
Performs a session logout and clears the current session by
178198
sending a delete request to the _session endpoint.
179199
"""
180-
if self.admin_party:
181-
return
182-
183200
self.r_session.logout()
184201

185202
def basic_auth_str(self):
@@ -189,13 +206,7 @@ def basic_auth_str(self):
189206
190207
:returns: Basic http authentication string
191208
"""
192-
if self.admin_party:
193-
return None
194-
hash_ = base64.urlsafe_b64encode(bytes_("{username}:{password}".format(
195-
username=self._user,
196-
password=self._auth_token
197-
)))
198-
return "Basic {0}".format(unicode_(hash_))
209+
return self.r_session.base64_user_pass()
199210

200211
def all_dbs(self):
201212
"""

src/cloudant/client_session.py

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
"""
1616
Module containing client session classes.
1717
"""
18-
18+
import base64
1919
import json
2020
import os
2121

2222
from requests import RequestException, Session
2323

24-
from ._2to3 import url_join
24+
from ._2to3 import bytes_, unicode_, url_join
2525
from .error import CloudantException
2626

2727

@@ -30,11 +30,34 @@ class ClientSession(Session):
3030
This class extends Session and provides a default timeout.
3131
"""
3232

33-
def __init__(self, **kwargs):
33+
def __init__(self, username=None, password=None, session_url=None, **kwargs):
3434
super(ClientSession, self).__init__()
35+
36+
self._username = username
37+
self._password = password
38+
self._session_url = session_url
39+
40+
self._auto_renew = kwargs.get('auto_renew', False)
3541
self._timeout = kwargs.get('timeout', None)
3642

37-
def request(self, method, url, **kwargs): # pylint: disable=W0221
43+
def base64_user_pass(self):
44+
"""
45+
Composes a basic http auth string, suitable for use with the
46+
_replicator database, and other places that need it.
47+
48+
:returns: Basic http authentication string
49+
"""
50+
if self._username is None or self._password is None:
51+
return None
52+
53+
hash_ = base64.urlsafe_b64encode(bytes_("{username}:{password}".format(
54+
username=self._username,
55+
password=self._password
56+
)))
57+
return "Basic {0}".format(unicode_(hash_))
58+
59+
# pylint: disable=arguments-differ
60+
def request(self, method, url, **kwargs):
3861
"""
3962
Overrides ``requests.Session.request`` to set the timeout.
4063
"""
@@ -43,27 +66,75 @@ def request(self, method, url, **kwargs): # pylint: disable=W0221
4366

4467
return resp
4568

69+
def info(self):
70+
"""
71+
Get session information.
72+
"""
73+
if self._session_url is None:
74+
return None
4675

47-
class CookieSession(ClientSession):
76+
resp = self.get(self._session_url)
77+
resp.raise_for_status()
78+
return resp.json()
79+
80+
def set_credentials(self, username, password):
81+
"""
82+
Set a new username and password.
83+
84+
:param str username: New username.
85+
:param str password: New password.
86+
"""
87+
if username is not None:
88+
self._username = username
89+
90+
if password is not None:
91+
self._password = password
92+
93+
def login(self):
94+
"""
95+
No-op method - not implemented here.
96+
"""
97+
pass
98+
99+
def logout(self):
100+
"""
101+
No-op method - not implemented here.
102+
"""
103+
pass
104+
105+
106+
class BasicSession(ClientSession):
48107
"""
49-
This class extends ClientSession and provides cookie authentication.
108+
This class extends ClientSession to provide basic access authentication.
50109
"""
51110

52111
def __init__(self, username, password, server_url, **kwargs):
53-
super(CookieSession, self).__init__(**kwargs)
54-
self._username = username
55-
self._password = password
56-
self._auto_renew = kwargs.get('auto_renew', False)
57-
self._session_url = url_join(server_url, '_session')
112+
super(BasicSession, self).__init__(
113+
username=username,
114+
password=password,
115+
session_url=url_join(server_url, '_session'),
116+
**kwargs)
58117

59-
def info(self):
118+
def request(self, method, url, **kwargs):
60119
"""
61-
Get cookie based login user information.
120+
Overrides ``requests.Session.request`` to provide basic access
121+
authentication.
62122
"""
63-
resp = self.get(self._session_url)
64-
resp.raise_for_status()
123+
return super(BasicSession, self).request(
124+
method, url, auth=(self._username, self._password), **kwargs)
65125

66-
return resp.json()
126+
127+
class CookieSession(ClientSession):
128+
"""
129+
This class extends ClientSession and provides cookie authentication.
130+
"""
131+
132+
def __init__(self, username, password, server_url, **kwargs):
133+
super(CookieSession, self).__init__(
134+
username=username,
135+
password=password,
136+
session_url=url_join(server_url, '_session'),
137+
**kwargs)
67138

68139
def login(self):
69140
"""
@@ -83,7 +154,7 @@ def logout(self):
83154
resp = super(CookieSession, self).request('DELETE', self._session_url)
84155
resp.raise_for_status()
85156

86-
def request(self, method, url, **kwargs): # pylint: disable=W0221
157+
def request(self, method, url, **kwargs):
87158
"""
88159
Overrides ``requests.Session.request`` to renew the cookie and then
89160
retry the original request (if required).
@@ -105,42 +176,21 @@ def request(self, method, url, **kwargs): # pylint: disable=W0221
105176

106177
return resp
107178

108-
def set_credentials(self, username, password):
109-
"""
110-
Set a new username and password.
111-
112-
:param str username: New username.
113-
:param str password: New password.
114-
"""
115-
if username is not None:
116-
self._username = username
117-
118-
if password is not None:
119-
self._password = password
120-
121179

122180
class IAMSession(ClientSession):
123181
"""
124182
This class extends ClientSession and provides IAM authentication.
125183
"""
126184

127185
def __init__(self, api_key, server_url, **kwargs):
128-
super(IAMSession, self).__init__(**kwargs)
186+
super(IAMSession, self).__init__(
187+
session_url=url_join(server_url, '_iam_session'),
188+
**kwargs)
189+
129190
self._api_key = api_key
130-
self._auto_renew = kwargs.get('auto_renew', False)
131-
self._session_url = url_join(server_url, '_iam_session')
132191
self._token_url = os.environ.get(
133192
'IAM_TOKEN_URL', 'https://iam.bluemix.net/oidc/token')
134193

135-
def info(self):
136-
"""
137-
Get IAM cookie based login user information.
138-
"""
139-
resp = self.get(self._session_url)
140-
resp.raise_for_status()
141-
142-
return resp.json()
143-
144194
def login(self):
145195
"""
146196
Perform IAM cookie based user login.
@@ -164,7 +214,7 @@ def logout(self):
164214
"""
165215
self.cookies.clear()
166216

167-
def request(self, method, url, **kwargs): # pylint: disable=W0221
217+
def request(self, method, url, **kwargs):
168218
"""
169219
Overrides ``requests.Session.request`` to renew the IAM cookie
170220
and then retry the original request (if required).
@@ -191,7 +241,7 @@ def request(self, method, url, **kwargs): # pylint: disable=W0221
191241

192242
return resp
193243

194-
# pylint: disable=arguments-differ
244+
# pylint: disable=arguments-differ, unused-argument
195245
def set_credentials(self, username, api_key):
196246
"""
197247
Set a new IAM API key.

src/cloudant/database.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,13 @@ def creds(self):
9494
9595
:returns: Dictionary containing authentication information
9696
"""
97-
if self.admin_party:
97+
session = self.client.session()
98+
if session is None:
9899
return None
100+
99101
return {
100102
"basic_auth": self.client.basic_auth_str(),
101-
"user_ctx": self.client.session()['userCtx']
103+
"user_ctx": session.get('userCtx')
102104
}
103105

104106
def exists(self):

0 commit comments

Comments
 (0)