Commit | Line | Data |
---|---|---|
a29d3526 | 1 | #!/usr/bin/env python |
e2c48b2f | 2 | # -*- coding: utf-8 -*- |
a29d3526 | 3 | |
36f274c0 MM |
4 | |
5 | """This module abstracts connection to pod. | |
6 | """ | |
7 | ||
8 | ||
b74513c5 | 9 | import json |
8993810c MM |
10 | import re |
11 | import requests | |
ff3d2ab4 | 12 | import warnings |
8993810c | 13 | |
2cf8467c MM |
14 | from diaspy import errors |
15 | ||
a29d3526 | 16 | |
36f274c0 | 17 | DEBUG = True |
178faa46 MM |
18 | |
19 | ||
a29d3526 | 20 | class Connection(): |
1cff2093 | 21 | """Object representing connection with the pod. |
a29d3526 | 22 | """ |
27a28aaf | 23 | _token_regex = re.compile(r'content="(.*?)"\s+name="csrf-token') |
39af9756 MM |
24 | _userinfo_regex = re.compile(r'window.current_user_attributes = ({.*})') |
25 | # this is for older version of D* | |
26 | _userinfo_regex_2 = re.compile(r'gon.user=({.*});gon.preloads') | |
27a28aaf | 27 | |
36f274c0 | 28 | def __init__(self, pod, username, password, schema='https'): |
8993810c MM |
29 | """ |
30 | :param pod: The complete url of the diaspora pod to use. | |
31 | :type pod: str | |
32 | :param username: The username used to log in. | |
33 | :type username: str | |
34 | :param password: The password used to log in. | |
35 | :type password: str | |
36 | """ | |
a29d3526 | 37 | self.pod = pod |
2cf8467c | 38 | self._session = requests.Session() |
36f274c0 | 39 | self._login_data = {'user[remember_me]': 1, 'utf8': '✓'} |
5de52803 | 40 | self._userdata = {} |
2cf8467c | 41 | self._token = '' |
5de52803 | 42 | self._diaspora_session = '' |
36f274c0 | 43 | self._cookies = self._fetchcookies() |
2cf8467c | 44 | try: |
36f274c0 MM |
45 | #self._setlogin(username, password) |
46 | self._login_data = {'user[username]': username, | |
47 | 'user[password]': password, | |
48 | 'authenticity_token': self._fetchtoken()} | |
49 | success = True | |
75a456f4 MM |
50 | except requests.exceptions.MissingSchema: |
51 | self.pod = '{0}://{1}'.format(schema, self.pod) | |
52 | warnings.warn('schema was missing') | |
36f274c0 | 53 | success = False |
2cf8467c MM |
54 | finally: |
55 | pass | |
56 | try: | |
36f274c0 MM |
57 | if not success: |
58 | self._login_data = {'user[username]': username, | |
59 | 'user[password]': password, | |
60 | 'authenticity_token': self._fetchtoken()} | |
2cf8467c MM |
61 | except Exception as e: |
62 | raise errors.LoginError('cannot create login data (caused by: {0})'.format(e)) | |
8993810c | 63 | |
36f274c0 MM |
64 | def _fetchcookies(self): |
65 | request = self.get('stream') | |
66 | return request.cookies | |
67 | ||
d4bd92cd MM |
68 | def __repr__(self): |
69 | """Returns token string. | |
70 | It will be easier to change backend if programs will just use: | |
71 | repr(connection) | |
72 | instead of calling a specified method. | |
73 | """ | |
cf6a800f | 74 | return self._fetchtoken() |
d4bd92cd | 75 | |
36f274c0 | 76 | def get(self, string, headers={}, params={}, direct=False, **kwargs): |
8993810c MM |
77 | """This method gets data from session. |
78 | Performs additional checks if needed. | |
79 | ||
80 | Example: | |
7a31b4aa | 81 | To obtain 'foo' from pod one should call `get('foo')`. |
8993810c MM |
82 | |
83 | :param string: URL to get without the pod's URL and slash eg. 'stream'. | |
84 | :type string: str | |
73a9e0d3 MM |
85 | :param direct: if passed as True it will not be expanded |
86 | :type direct: bool | |
8993810c | 87 | """ |
73a9e0d3 MM |
88 | if not direct: url = '{0}/{1}'.format(self.pod, string) |
89 | else: url = string | |
36f274c0 | 90 | return self._session.get(url, params=params, headers=headers, **kwargs) |
8993810c | 91 | |
36f274c0 | 92 | def post(self, string, data, headers={}, params={}, **kwargs): |
8993810c MM |
93 | """This method posts data to session. |
94 | Performs additional checks if needed. | |
95 | ||
96 | Example: | |
7a31b4aa | 97 | To post to 'foo' one should call `post('foo', data={})`. |
8993810c MM |
98 | |
99 | :param string: URL to post without the pod's URL and slash eg. 'status_messages'. | |
100 | :type string: str | |
101 | :param data: Data to post. | |
102 | :param headers: Headers (optional). | |
103 | :type headers: dict | |
104 | :param params: Parameters (optional). | |
105 | :type params: dict | |
106 | """ | |
107 | string = '{0}/{1}'.format(self.pod, string) | |
36f274c0 | 108 | request = self._session.post(string, data, headers=headers, params=params, **kwargs) |
8993810c MM |
109 | return request |
110 | ||
36f274c0 | 111 | def put(self, string, data=None, headers={}, params={}, **kwargs): |
178faa46 MM |
112 | """This method PUTs to session. |
113 | """ | |
114 | string = '{0}/{1}'.format(self.pod, string) | |
36f274c0 MM |
115 | if data is not None: request = self._session.put(string, data, headers=headers, params=params, **kwargs) |
116 | else: request = self._session.put(string, headers=headers, params=params, **kwargs) | |
178faa46 MM |
117 | return request |
118 | ||
36f274c0 | 119 | def delete(self, string, data, headers={}, **kwargs): |
8993810c MM |
120 | """This method lets you send delete request to session. |
121 | Performs additional checks if needed. | |
122 | ||
123 | :param string: URL to use. | |
124 | :type string: str | |
125 | :param data: Data to use. | |
126 | :param headers: Headers to use (optional). | |
127 | :type headers: dict | |
128 | """ | |
129 | string = '{0}/{1}'.format(self.pod, string) | |
36f274c0 | 130 | request = self._session.delete(string, data=data, headers=headers, **kwargs) |
8993810c MM |
131 | return request |
132 | ||
133 | def _setlogin(self, username, password): | |
134 | """This function is used to set data for login. | |
b0b4c46d | 135 | |
8993810c MM |
136 | .. note:: |
137 | It should be called before _login() function. | |
138 | """ | |
2cf8467c MM |
139 | self._login_data = {'user[username]': username, |
140 | 'user[password]': password, | |
141 | 'authenticity_token': self._fetchtoken()} | |
8993810c | 142 | |
385e7ebe MM |
143 | def _login(self): |
144 | """Handles actual login request. | |
145 | Raises LoginError if login failed. | |
146 | """ | |
147 | request = self.post('users/sign_in', | |
36f274c0 MM |
148 | data=self._login_data, |
149 | allow_redirects=False) | |
150 | if request.status_code != 302: | |
2cf8467c | 151 | raise errors.LoginError('{0}: login failed'.format(request.status_code)) |
385e7ebe | 152 | |
36f274c0 | 153 | def login(self, remember_me=1): |
385e7ebe MM |
154 | """This function is used to log in to a pod. |
155 | Will raise LoginError if password or username was not specified. | |
8993810c | 156 | """ |
2cf8467c MM |
157 | if not self._login_data['user[username]'] or not self._login_data['user[password]']: |
158 | raise errors.LoginError('username and/or password is not specified') | |
36f274c0 MM |
159 | self._login_data['user[remember_me]'] = remember_me |
160 | status = self._login() | |
2cf8467c | 161 | self._login_data = {} |
f3b7fd44 | 162 | return self |
385e7ebe | 163 | |
63cc182d MM |
164 | def logout(self): |
165 | """Logs out from a pod. | |
166 | When logged out you can't do anything. | |
167 | """ | |
168 | self.get('users/sign_out') | |
b0b4c46d | 169 | self.token = '' |
63cc182d | 170 | |
2cf8467c | 171 | def podswitch(self, pod, username, password): |
7a31b4aa | 172 | """Switches pod from current to another one. |
385e7ebe MM |
173 | """ |
174 | self.pod = pod | |
2cf8467c | 175 | self._setlogin(username, password) |
385e7ebe | 176 | self._login() |
8993810c | 177 | |
b0b4c46d MM |
178 | def _fetchtoken(self): |
179 | """This method tries to get token string needed for authentication on D*. | |
180 | ||
181 | :returns: token string | |
182 | """ | |
183 | request = self.get('stream') | |
184 | token = self._token_regex.search(request.text).group(1) | |
2cf8467c | 185 | self._token = token |
b0b4c46d | 186 | return token |
2ec93347 | 187 | |
2b6b487e | 188 | def get_token(self, fetch=True): |
385e7ebe | 189 | """This function returns a token needed for authentication in most cases. |
2cf8467c | 190 | **Notice:** using repr() is recommended method for getting token. |
a29d3526 | 191 | |
b0b4c46d MM |
192 | Each time it is run a _fetchtoken() is called and refreshed token is stored. |
193 | ||
194 | It is more safe to use than _fetchtoken(). | |
ff3d2ab4 | 195 | By setting new you can request new token or decide to get stored one. |
2b6b487e | 196 | If no token is stored new one will be fetched anyway. |
a29d3526 | 197 | |
8993810c MM |
198 | :returns: string -- token used to authenticate |
199 | """ | |
b0b4c46d | 200 | try: |
36f274c0 | 201 | if fetch or not self._token: self._fetchtoken() |
b0b4c46d | 202 | except requests.exceptions.ConnectionError as e: |
ff3d2ab4 | 203 | warnings.warn('{0} was cought: reusing old token'.format(e)) |
b0b4c46d | 204 | finally: |
2cf8467c MM |
205 | if not self._token: raise errors.TokenError('cannot obtain token and no previous token found for reuse') |
206 | return self._token | |
5de52803 MM |
207 | |
208 | def getSessionToken(self): | |
209 | """Returns session token string (_diaspora_session). | |
210 | """ | |
211 | return self._diaspora_session | |
39af9756 MM |
212 | |
213 | def getUserData(self): | |
214 | """Returns user data. | |
215 | """ | |
216 | request = self.get('bookmarklet') | |
217 | userdata = self._userinfo_regex.search(request.text) | |
218 | if userdata is None: userdata = self._userinfo_regex_2.search(request.text) | |
219 | if userdata is None: raise errors.DiaspyError('cannot find user data') | |
220 | userdata = userdata.group(1) | |
221 | return json.loads(userdata) |