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