Revision 890c2065
b/docs/astakos-api-guide.rst | ||
---|---|---|
20 | 20 |
========================= ================================ |
21 | 21 |
Revision Description |
22 | 22 |
========================= ================================ |
23 |
0.13 (January 21, 2013) Extend api to export user presentation & quota information. |
|
23 | 24 |
0.6 (June 06, 2012) Split service and admin API. |
24 | 25 |
0.1 (Feb 10, 2012) Initial release. |
25 | 26 |
========================= ================================ |
26 | 27 |
|
27 |
Admin API Operations
|
|
28 |
--------------------
|
|
28 |
Get Services
|
|
29 |
^^^^^^^^^^^^
|
|
29 | 30 |
|
30 |
The operations described in this chapter allow users to authenticate themselves and priviledged users (ex. helpdesk) to access other user information.
|
|
31 |
Returns a json formatted list containing information about the supported cloud services.
|
|
31 | 32 |
|
32 |
Most of the operations require a valid token assigned to users having the necessary permissions. |
|
33 |
==================== ========= ================== |
|
34 |
Uri Method Description |
|
35 |
==================== ========= ================== |
|
36 |
``/im/get_services`` GET Get cloud services |
|
37 |
==================== ========= ================== |
|
33 | 38 |
|
34 |
.. _authenticate-api-label:
|
|
39 |
Example reply:
|
|
35 | 40 |
|
36 |
Authenticate |
|
37 |
^^^^^^^^^^^^ |
|
41 |
:: |
|
38 | 42 |
|
39 |
Authenticate API requests require a token. An application that wishes to connect to Astakos, but does not have a token, should redirect the user to ``/login``. (see :ref:`authentication-label`) |
|
43 |
[{"url": "/", "icon": "home-icon.png", "name": "grnet cloud", "id": "1"}, |
|
44 |
{"url": "/okeanos.html", "name": "~okeanos", "id": "2"}, |
|
45 |
{"url": "/ui/", "name": "pithos+", "id": "3"}] |
|
46 |
|
|
47 |
|
|
48 |
Get Menu |
|
49 |
^^^^^^^^ |
|
50 |
|
|
51 |
Returns a json formatted list containing the cloud bar links. |
|
40 | 52 |
|
41 | 53 |
==================== ========= ================== |
42 | 54 |
Uri Method Description |
43 | 55 |
==================== ========= ================== |
44 |
``/im/authenticate`` GET Authenticate user using token
|
|
56 |
``/im/get_menu`` GET Get cloud bar menu
|
|
45 | 57 |
==================== ========= ================== |
46 | 58 |
|
47 |
| |
|
48 |
|
|
49 |
==================== =========================== |
|
50 |
Request Header Name Value |
|
51 |
==================== =========================== |
|
52 |
X-Auth-Token Authentication token |
|
53 |
==================== =========================== |
|
59 |
Example reply if request user is not authenticated: |
|
54 | 60 |
|
55 |
Extended information on the user serialized in the json format will be returned:
|
|
61 |
::
|
|
56 | 62 |
|
57 |
=========================== ============================ |
|
58 |
Name Description |
|
59 |
=========================== ============================ |
|
60 |
username User uniq identifier |
|
61 |
uniq User email (uniq identifier used by Astakos) |
|
62 |
auth_token Authentication token |
|
63 |
auth_token_expires Token expiration date |
|
64 |
auth_token_created Token creation date |
|
65 |
has_credits Whether user has credits |
|
66 |
has_signed_terms Whether user has aggred on terms |
|
67 |
groups User groups |
|
68 |
=========================== ============================ |
|
63 |
[{"url": "/im/", "name": "Sign in"}] |
|
69 | 64 |
|
70 |
Example reply: |
|
65 |
Example reply if request user is authenticated:
|
|
71 | 66 |
|
72 | 67 |
:: |
73 | 68 |
|
74 |
{"username": "4ad9f34d6e7a4992b34502d40f40cb", |
|
75 |
"uniq": "user@example.com" |
|
76 |
"auth_token": "0000", |
|
77 |
"auth_token_expires": "Fri, 29 Jun 2012 10:03:37 GMT", |
|
78 |
"auth_token_created": "Wed, 30 May 2012 10:03:37 GMT", |
|
79 |
"has_credits": false, |
|
80 |
"has_signed_terms": true} |
|
69 |
[{"url": "/im/login", "name": "user@example.com"}, |
|
70 |
{"url": "/im/profile", "name": "My account"}, |
|
71 |
{"url": "/im/logout", "name": "Sign out"}] |
|
81 | 72 |
|
82 |
| |
|
73 |
Admin API Operations |
|
74 |
-------------------- |
|
83 | 75 |
|
84 |
=========================== ===================== |
|
85 |
Return Code Description |
|
86 |
=========================== ===================== |
|
87 |
204 (No Content) The request succeeded |
|
88 |
400 (Bad Request) Method not allowed or no user found |
|
89 |
401 (Unauthorized) Missing token or inactive user or penging approval terms |
|
90 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
|
91 |
=========================== ===================== |
|
76 |
The operations described in this chapter allow users to authenticate themselves and priviledged users (ex. helpdesk) to access other user information. |
|
92 | 77 |
|
93 |
Get User by email |
|
94 |
^^^^^^^^^^^^^^^^^ |
|
78 |
Most of the operations require a valid token assigned to users having the necessary permissions. |
|
95 | 79 |
|
96 |
Returns a json formatted dictionary containing information about a specific user |
|
80 |
.. _authenticate-api-label: |
|
81 |
|
|
82 |
Authenticate |
|
83 |
^^^^^^^^^^^^ |
|
97 | 84 |
|
98 |
============================== ========= ================== |
|
99 |
Uri Method Description |
|
100 |
============================== ========= ================== |
|
101 |
``/im/admin/api/v2.0/users/`` GET Get user information by email |
|
102 |
============================== ========= ================== |
|
85 |
Authenticate API requests require a token. An application that wishes to connect to Astakos, but does not have a token, should redirect the user to ``/login``. (see :ref:`authentication-label`) |
|
86 |
|
|
87 |
==================== ========= ================== |
|
88 |
Uri Method Description |
|
89 |
==================== ========= ================== |
|
90 |
``/im/authenticate`` GET Authenticate user using token |
|
91 |
==================== ========= ================== |
|
103 | 92 |
|
104 | 93 |
| |
105 | 94 |
|
106 | 95 |
==================== =========================== |
107 | 96 |
Request Header Name Value |
108 | 97 |
==================== =========================== |
109 |
X-Auth-Token Authentication token owned by |
|
110 |
a user having or inheriting ``im.can_access_userinfo`` permission |
|
98 |
X-Auth-Token User authentication token |
|
111 | 99 |
==================== =========================== |
112 | 100 |
|
113 | 101 |
| |
... | ... | |
115 | 103 |
====================== ========================= |
116 | 104 |
Request Parameter Name Value |
117 | 105 |
====================== ========================= |
118 |
name Email
|
|
106 |
usage (optional)
|
|
119 | 107 |
====================== ========================= |
120 | 108 |
|
109 |
Extended information on the user serialized in the json format will be returned: |
|
121 | 110 |
|
122 |
|
|
|
123 |
|
|
124 |
=========================== ===================== |
|
125 |
Return Code Description
|
|
126 |
=========================== =====================
|
|
127 |
200 (OK) The request succeeded
|
|
128 |
400 (Bad Request) Method not allowed
|
|
129 |
401 (Unauthorized) Missing or invalid token or unauthorized user
|
|
130 |
404 (Not Found) Missing email or inactive user
|
|
131 |
500 (Internal Server Error) The request cannot be completed because of an internal error
|
|
132 |
=========================== ===================== |
|
111 |
=========================== ============================
|
|
112 |
Name Description |
|
113 |
=========================== ============================
|
|
114 |
displayname User displayname
|
|
115 |
uuid User unique identifier
|
|
116 |
email List with user emails
|
|
117 |
name User full name
|
|
118 |
auth_token_created Token creation date
|
|
119 |
auth_token_expires Token expiration date
|
|
120 |
usage List of user resource usage (if usage request parameter is present)
|
|
121 |
=========================== ============================
|
|
133 | 122 |
|
134 |
Example reply: |
|
123 |
Example reply without `usage` request parameter:
|
|
135 | 124 |
|
136 | 125 |
:: |
137 | 126 |
|
138 |
{"username": "7e530044f90a4e7ba2cb94f3a26c40", |
|
139 |
"auth_token_created": "Wed, 30 May 2012 10:03:37 GMT", |
|
140 |
"name": "Firstname Surname", |
|
141 |
"groups": ["default"], |
|
142 |
"user_permissions": [], |
|
143 |
"has_credits": false, |
|
144 |
"auth_token_expires":"Fri, 29 Jun 2012 10:03:37 GMT", |
|
145 |
"enabled": true, |
|
146 |
"email": ["user@example.com"], |
|
147 |
"id": 4} |
|
127 |
{"id": "12", |
|
128 |
"displayname": "user@example.com", |
|
129 |
"uuid": "a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8", |
|
130 |
"email": "[user@example.com]", |
|
131 |
"name": "Firstname Lastname", |
|
132 |
"auth_token_created": "Wed, 30 May 2012 10:03:37 GMT", |
|
133 |
"auth_token_expires": "Fri, 29 Jun 2012 10:03:37 GMT"} |
|
148 | 134 |
|
149 |
Get User by username |
|
150 |
^^^^^^^^^^^^^^^^^^^^ |
|
135 |
Example reply with `usage` request parameter: |
|
151 | 136 |
|
152 |
Returns a json formatted dictionary containing information about a specific user |
|
153 |
|
|
154 |
======================================== ========= ================== |
|
155 |
Uri Method Description |
|
156 |
======================================== ========= ================== |
|
157 |
``/im/admin/api/v2.0/users/{username}`` GET Get user information by username |
|
158 |
======================================== ========= ================== |
|
137 |
:: |
|
159 | 138 |
|
160 |
| |
|
139 |
{"id": "12", |
|
140 |
"displayname": "user@example.com", |
|
141 |
"uuid": "a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8", |
|
142 |
"email": "[user@example.com]", |
|
143 |
"name": "Firstname Lastname", |
|
144 |
"auth_token_created": "Wed, 30 May 2012 10:03:37 GMT", |
|
145 |
"auth_token_expires": "Fri, 29 Jun 2012 10:03:37 GMT", |
|
146 |
"usage": [{"currValue": 4536392, |
|
147 |
"display_name": "Storage Space", |
|
148 |
"description": "Pithos account diskspace", |
|
149 |
"verbose_name": "Storage Space", |
|
150 |
"help_text_input_each": "This is the total amount of space on Pithos that will be granted to each user of this Project ", "maxValue": 5368710653, |
|
151 |
"pluralized_display_name": "Storage Space", |
|
152 |
"report_desc": "Storage Space", |
|
153 |
"help_text": "This is the space on Pithos for storing files and VM Images. ", |
|
154 |
"is_abbreviation": false, |
|
155 |
"placeholder": "eg. 10GB", |
|
156 |
"unit": "bytes", |
|
157 |
"name": "pithos+.diskspace"}, |
|
158 |
{"currValue": 0, |
|
159 |
"display_name": "System Disk", |
|
160 |
"description": "Virtual machine disk size", |
|
161 |
"verbose_name": "System Disk", |
|
162 |
"help_text_input_each": "This is the total amount of System Disk that will be granted to each user of this Project (this refers to the total System Disk of all VMs, not each VM's System Disk) ", |
|
163 |
"maxValue": 53687091200, |
|
164 |
"pluralized_display_name": "System Disk", |
|
165 |
"report_desc": "System Disk", |
|
166 |
"help_text": "This is the System Disk that the VMs have that run the OS ", |
|
167 |
"is_abbreviation": false, |
|
168 |
"placeholder": "eg. 5GB, 2GB etc", |
|
169 |
"unit": "bytes", |
|
170 |
"name": "cyclades.disk"}, |
|
171 |
{"currValue": 0, |
|
172 |
"display_name": "CPU", |
|
173 |
"description": "Number of virtual machine processors", |
|
174 |
"verbose_name": "cpu", |
|
175 |
"help_text_input_each": "This is the total number of CPUs that will be granted to each user of this Project (on all VMs) ", "maxValue": 6, "pluralized_display_name": "CPUs", |
|
176 |
"report_desc": "CPUs", |
|
177 |
"help_text": "CPUs used by VMs ", |
|
178 |
"is_abbreviation": true, |
|
179 |
"placeholder": "eg. 1", |
|
180 |
"unit": "", |
|
181 |
"name": "cyclades.cpu"}, |
|
182 |
{"currValue": 0, |
|
183 |
"display_name": "RAM", |
|
184 |
"description": "Virtual machines", |
|
185 |
"verbose_name": "ram", |
|
186 |
"help_text_input_each": "This is the total amount of RAM that will be granted to each user of this Project (on all VMs) ", "maxValue": 6442450944, |
|
187 |
"pluralized_display_name": "RAM", |
|
188 |
"report_desc": "RAM", |
|
189 |
"help_text": "RAM used by VMs ", |
|
190 |
"is_abbreviation": true, |
|
191 |
"placeholder": "eg. 4GB", |
|
192 |
"unit": "bytes", "name": "cyclades.ram"}, |
|
193 |
{"currValue": 0, "display_name": "VM", |
|
194 |
"description": "Number of virtual machines", |
|
195 |
"verbose_name": "vm", "help_text_input_each": "This is the total number of VMs that will be granted to each user of this Project ", "maxValue": 2, |
|
196 |
"pluralized_display_name": "VMs", |
|
197 |
"report_desc": "Virtual Machines", |
|
198 |
"help_text": "These are the VMs one can create on the Cyclades UI ", |
|
199 |
"is_abbreviation": true, "placeholder": "eg. 2", |
|
200 |
"unit": "", |
|
201 |
"name": "cyclades.vm"}, |
|
202 |
{"currValue": 0, |
|
203 |
"display_name": "private network", |
|
204 |
"description": "Private networks", |
|
205 |
"verbose_name": "private network", |
|
206 |
"help_text_input_each": "This is the total number of Private Networks that will be granted to each user of this Project ", |
|
207 |
"maxValue": 1, |
|
208 |
"pluralized_display_name": "private networks", |
|
209 |
"report_desc": "Private Networks", |
|
210 |
"help_text": "These are the Private Networks one can create on the Cyclades UI. ", |
|
211 |
"is_abbreviation": false, |
|
212 |
"placeholder": "eg. 1", |
|
213 |
"unit": "", |
|
214 |
"name": "cyclades.network.private"}]} |
|
161 | 215 |
|
162 |
==================== =========================== |
|
163 |
Request Header Name Value |
|
164 |
==================== =========================== |
|
165 |
X-Auth-Token Authentication token owned |
|
166 |
by a user having or inheriting ``im.can_access_userinfo`` permission |
|
167 |
==================== =========================== |
|
168 | 216 |
|
169 | 217 |
| |
170 | 218 |
|
171 | 219 |
=========================== ===================== |
172 | 220 |
Return Code Description |
173 | 221 |
=========================== ===================== |
174 |
200 (OK) The request succeeded |
|
175 |
400 (Bad Request) Method not allowed |
|
176 |
401 (Unauthorized) Missing or invalid token or unauthorized user |
|
177 |
404 (Not Found) Invalid username |
|
222 |
204 (No Content) The request succeeded |
|
223 |
400 (Bad Request) Method not allowed or no user found |
|
224 |
401 (Unauthorized) Missing token or inactive user or penging approval terms |
|
178 | 225 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
179 | 226 |
=========================== ===================== |
180 | 227 |
|
181 |
Example reply: |
|
182 |
|
|
183 |
:: |
|
184 |
|
|
185 |
{"username": "7e530044f90a4e7ba2cb94f3a26c40", |
|
186 |
"auth_token_created": "Wed, 30 May 2012 10:03:37 GMT", |
|
187 |
"name": "Firstname Surname", |
|
188 |
"groups": ["default"], |
|
189 |
"user_permissions": [], |
|
190 |
"has_credits": false, |
|
191 |
"auth_token_expires": |
|
192 |
"Fri, 29 Jun 2012 10:03:37 GMT", |
|
193 |
"enabled": true, |
|
194 |
"email": ["user@example.com"], |
|
195 |
"id": 4} |
|
196 |
|
|
197 |
Get Services |
|
198 |
^^^^^^^^^^^^ |
|
199 |
|
|
200 |
Returns a json formatted list containing information about the supported cloud services. |
|
201 |
|
|
202 |
==================== ========= ================== |
|
203 |
Uri Method Description |
|
204 |
==================== ========= ================== |
|
205 |
``/im/get_services`` GET Get cloud services |
|
206 |
==================== ========= ================== |
|
207 |
|
|
208 |
Example reply: |
|
209 |
|
|
210 |
:: |
|
211 |
|
|
212 |
[{"url": "/", "icon": "home-icon.png", "name": "grnet cloud", "id": "1"}, |
|
213 |
{"url": "/okeanos.html", "name": "~okeanos", "id": "2"}, |
|
214 |
{"url": "/ui/", "name": "pithos+", "id": "3"}] |
|
215 |
|
|
216 |
|
|
217 |
Get Menu |
|
218 |
^^^^^^^^ |
|
219 |
|
|
220 |
Returns a json formatted list containing the cloud bar links. |
|
221 |
|
|
222 |
==================== ========= ================== |
|
223 |
Uri Method Description |
|
224 |
==================== ========= ================== |
|
225 |
``/im/get_menu`` GET Get cloud bar menu |
|
226 |
==================== ========= ================== |
|
227 |
|
|
228 |
Example reply if request user is not authenticated: |
|
229 |
|
|
230 |
:: |
|
231 |
|
|
232 |
[{"url": "/im/", "name": "Sign in"}] |
|
233 |
|
|
234 |
Example reply if request user is authenticated: |
|
235 |
|
|
236 |
:: |
|
237 |
|
|
238 |
[{"url": "/im/login", "name": "user@example.com"}, |
|
239 |
{"url": "/im/profile", "name": "My account"}, |
|
240 |
{"url": "/im/logout", "name": "Sign out"}] |
|
241 |
|
|
242 |
Service API Operations |
|
243 |
---------------------- |
|
244 |
|
|
245 |
The operations described in this chapter allow services to access user information and perform specific tasks. |
|
246 |
|
|
247 |
The operations require a valid service token. |
|
248 | 228 |
|
249 | 229 |
Send feedback |
250 | 230 |
^^^^^^^^^^^^^ |
... | ... | |
254 | 234 |
========================= ========= ================== |
255 | 235 |
Uri Method Description |
256 | 236 |
========================= ========= ================== |
257 |
``/im/service/feedback`` POST Send feedback
|
|
237 |
``/feedback`` POST Send feedback
|
|
258 | 238 |
========================= ========= ================== |
259 | 239 |
|
260 | 240 |
| |
... | ... | |
262 | 242 |
==================== ============================ |
263 | 243 |
Request Header Name Value |
264 | 244 |
==================== ============================ |
265 |
X-Auth-Token Service Authentication token
|
|
245 |
X-Auth-Token User authentication token
|
|
266 | 246 |
==================== ============================ |
267 | 247 |
|
268 | 248 |
| |
... | ... | |
270 | 250 |
====================== ========================= |
271 | 251 |
Request Parameter Name Value |
272 | 252 |
====================== ========================= |
273 |
auth_token User token |
|
274 | 253 |
feedback_msg Feedback message |
275 | 254 |
feedback_data Additional information about service client status |
276 | 255 |
====================== ========================= |
... | ... | |
281 | 260 |
Return Code Description |
282 | 261 |
=========================== ===================== |
283 | 262 |
200 (OK) The request succeeded |
263 |
502 (Bad Gateway) Send feedback failure |
|
284 | 264 |
400 (Bad Request) Method not allowed or missing or invalid user token parameter or invalid message data |
285 | 265 |
401 (Unauthorized) Missing or expired service token |
286 | 266 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
287 | 267 |
=========================== ===================== |
288 | 268 |
|
289 |
Get User by email
|
|
290 |
^^^^^^^^^^^^^^^^^
|
|
269 |
Get User catalog
|
|
270 |
^^^^^^^^^^^^^^^^ |
|
291 | 271 |
|
292 | 272 |
Returns a json formatted dictionary containing information about a specific user |
293 | 273 |
|
294 | 274 |
================================ ========= ================== |
295 | 275 |
Uri Method Description |
296 | 276 |
================================ ========= ================== |
297 |
``/im/service/api/v2.0/users/`` GET Get user information by email
|
|
277 |
``/user_catalogs`` POST Get 2 catalogs containing uuid to displayname mapping and the opposite
|
|
298 | 278 |
================================ ========= ================== |
299 | 279 |
|
300 | 280 |
| |
... | ... | |
302 | 282 |
==================== ============================ |
303 | 283 |
Request Header Name Value |
304 | 284 |
==================== ============================ |
305 |
X-Auth-Token Service Authentication token
|
|
285 |
X-Auth-Token User authentication token
|
|
306 | 286 |
==================== ============================ |
307 | 287 |
|
308 | 288 |
| |
309 | 289 |
|
310 |
====================== ========================= |
|
311 |
Request Parameter Name Value |
|
312 |
====================== ========================= |
|
313 |
name Email |
|
314 |
====================== ========================= |
|
315 |
|
|
316 |
| |
|
317 |
|
|
318 |
=========================== ===================== |
|
319 |
Return Code Description |
|
320 |
=========================== ===================== |
|
321 |
200 (OK) The request succeeded |
|
322 |
400 (Bad Request) Method not allowed |
|
323 |
401 (Unauthorized) Missing or expired or invalid service token |
|
324 |
404 (Not Found) Missing email or inactive user |
|
325 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
|
326 |
=========================== ===================== |
|
290 |
The request body is a json formatted dictionary containing a list with uuids and another list of displaynames to translate. |
|
327 | 291 |
|
328 |
Example reply:
|
|
292 |
Example request content:
|
|
329 | 293 |
|
330 | 294 |
:: |
331 | 295 |
|
332 |
{"username": "7e530044f90a4e7ba2cb94f3a26c40", |
|
333 |
"auth_token_created": "Wed, 30 May 2012 10:03:37 GMT", |
|
334 |
"name": "Firstname Surname", |
|
335 |
"groups": ["default"], |
|
336 |
"user_permissions": [], |
|
337 |
"has_credits": false, |
|
338 |
"auth_token_expires":"Fri, 29 Jun 2012 10:03:37 GMT", |
|
339 |
"enabled": true, |
|
340 |
"email": ["user@example.com"], |
|
341 |
"id": 4} |
|
296 |
{"displaynames": ["user1@example.com", "user2@example.com"], |
|
297 |
"uuids":["ff53baa9-c025-4d56-a6e3-963db0438830", "a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8"]} |
|
342 | 298 |
|
343 |
Get User by username |
|
344 |
^^^^^^^^^^^^^^^^^^^^ |
|
345 |
|
|
346 |
Returns a json formatted dictionary containing information about a specific user |
|
299 |
Example reply: |
|
347 | 300 |
|
348 |
========================================== ========= ================== |
|
349 |
Uri Method Description |
|
350 |
========================================== ========= ================== |
|
351 |
``/im/service/api/v2.0/users/{username}`` GET Get user information by username |
|
352 |
========================================== ========= ================== |
|
301 |
:: |
|
353 | 302 |
|
354 |
| |
|
303 |
{"displayname_catalog": {"user1@example.com": "a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8", |
|
304 |
"user2@example.com": "816351c7-7405-4f26-a968-6380cf47ba1f"}, |
|
305 |
'uuid_catalog': {"a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8": "user1@example.com", |
|
306 |
"ff53baa9-c025-4d56-a6e3-963db0438830": "user2@example.com"}} |
|
355 | 307 |
|
356 |
==================== ============================ |
|
357 |
Request Header Name Value |
|
358 |
==================== ============================ |
|
359 |
X-Auth-Token Service Authentication token |
|
360 |
==================== ============================ |
|
361 | 308 |
|
362 | 309 |
| |
363 | 310 |
|
... | ... | |
365 | 312 |
Return Code Description |
366 | 313 |
=========================== ===================== |
367 | 314 |
200 (OK) The request succeeded |
368 |
400 (Bad Request) Method not allowed |
|
315 |
400 (Bad Request) Method not allowed or request body is not json formatted
|
|
369 | 316 |
401 (Unauthorized) Missing or expired or invalid service token |
370 |
404 (Not Found) Invalid username |
|
371 | 317 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
372 | 318 |
=========================== ===================== |
373 |
|
|
374 |
Example reply: |
|
375 |
|
|
376 |
:: |
|
377 |
|
|
378 |
{"username": "7e530044f90a4e7ba2cb94f3a26c40", |
|
379 |
"auth_token_created": "Wed, 30 May 2012 10:03:37 GMT", |
|
380 |
"name": "Firstname Surname", |
|
381 |
"groups": ["default"], |
|
382 |
"user_permissions": [], |
|
383 |
"has_credits": false, |
|
384 |
"auth_token_expires": |
|
385 |
"Fri, 29 Jun 2012 10:03:37 GMT", |
|
386 |
"enabled": true, |
|
387 |
"email": ["user@example.com"], |
|
388 |
"id": 4} |
b/docs/pithos-api-guide.rst | ||
---|---|---|
27 | 27 |
========================= ================================ |
28 | 28 |
Revision Description |
29 | 29 |
========================= ================================ |
30 |
0.13 (Jun 21, 2013) Proxy identity management services |
|
31 |
\ Uuid to displayname translation |
|
32 |
0.9 (Feb 17, 2012) Change permissions model. |
|
30 | 33 |
0.10 (Jul 18, 2012) Support for bulk COPY/MOVE/DELETE |
31 | 34 |
\ Optionally include public objects in listings. |
32 | 35 |
0.9 (Feb 17, 2012) Change permissions model. |
... | ... | |
72 | 75 |
\ Create object using hashmap. |
73 | 76 |
0.3 (June 14, 2011) Large object support with ``X-Object-Manifest``. |
74 | 77 |
\ Allow for publicly available objects via ``https://hostname/public``. |
75 |
\ Support time-variant account/container listings.
|
|
78 |
\ Support time-variant account/container listings. |
|
76 | 79 |
\ Add source version when duplicating with ``PUT``/``COPY``. |
77 | 80 |
\ Request version in object ``HEAD``/``GET`` requests (list versions with ``GET``). |
78 | 81 |
0.2 (May 31, 2011) Add object meta listing and filtering in containers. |
... | ... | |
106 | 109 |
|
107 | 110 |
A user management service that implements a login URI according to these conventions is Astakos (https://code.grnet.gr/projects/astakos), by GRNET. |
108 | 111 |
|
112 |
User feedback |
|
113 |
------------- |
|
114 |
|
|
115 |
Client software using Pithos, should forward to the ``/feedback`` URI. The Pithos service, depending on its configuration will delegate the request to the appropriate identity management URI. |
|
116 |
|
|
117 |
====================== ========================= |
|
118 |
Request Parameter Name Value |
|
119 |
====================== ========================= |
|
120 |
auth_token User token |
|
121 |
feedback_msg Feedback message |
|
122 |
feedback_data Additional information about service client status |
|
123 |
====================== ========================= |
|
124 |
|
|
125 |
| |
|
126 |
|
|
127 |
=========================== ===================== |
|
128 |
Return Code Description |
|
129 |
=========================== ===================== |
|
130 |
200 (OK) The request succeeded |
|
131 |
502 (Bad Gateway) Send feedback failure |
|
132 |
400 (Bad Request) Method not allowed or missing or invalid user token parameter or invalid message data |
|
133 |
401 (Unauthorized) Missing or expired service token |
|
134 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
|
135 |
=========================== ===================== |
|
136 |
|
|
137 |
User translation catalogs |
|
138 |
------------------------- |
|
139 |
|
|
140 |
Client software using Pithos, should forward to the ``/user_catalog`` URI to get uuid to displayname translations and vice versa. The Pithos service, depending on its configuration will delegate the request to the appropriate identity management URI. |
|
141 |
|
|
142 |
The request body is a json formatted dictionary containing a list with uuids and another list of displaynames to translate. |
|
143 |
|
|
144 |
Example request content: |
|
145 |
|
|
146 |
:: |
|
147 |
|
|
148 |
{"displaynames": ["user1@example.com", "user2@example.com"], |
|
149 |
"uuids":["ff53baa9-c025-4d56-a6e3-963db0438830", "a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8"]} |
|
150 |
|
|
151 |
Example reply: |
|
152 |
|
|
153 |
:: |
|
154 |
|
|
155 |
{"displayname_catalog": {"user1@example.com": "a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8", |
|
156 |
"user2@example.com": "816351c7-7405-4f26-a968-6380cf47ba1f"}, |
|
157 |
'uuid_catalog': {"a9dc21d2-bcb2-4104-9a9e-402b7c70d6d8": "user1@example.com", |
|
158 |
"ff53baa9-c025-4d56-a6e3-963db0438830": "user2@example.com"}} |
|
159 |
|
|
160 |
|
|
161 |
| |
|
162 |
|
|
163 |
=========================== ===================== |
|
164 |
Return Code Description |
|
165 |
=========================== ===================== |
|
166 |
200 (OK) The request succeeded |
|
167 |
400 (Bad Request) Method not allowed or request body is not json formatted |
|
168 |
401 (Unauthorized) Missing or expired or invalid service token |
|
169 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
|
170 |
=========================== ===================== |
|
171 |
|
|
109 | 172 |
The Pithos API |
110 | 173 |
-------------- |
111 | 174 |
|
b/snf-astakos-app/astakos/im/api/__init__.py | ||
---|---|---|
45 | 45 |
from astakos.im.api.faults import Fault, ItemNotFound, InternalServerError, BadRequest |
46 | 46 |
from astakos.im.settings import ( |
47 | 47 |
INVITATIONS_ENABLED, COOKIE_NAME, EMAILCHANGE_ENABLED, QUOTAHOLDER_URL) |
48 |
from astakos.im.forms import FeedbackForm |
|
49 |
from astakos.im.functions import send_feedback as send_feedback_func |
|
48 | 50 |
|
49 | 51 |
import logging |
50 | 52 |
logger = logging.getLogger(__name__) |
... | ... | |
203 | 205 |
super(MenuItem, self).__setattribute__(name, value) |
204 | 206 |
if name == 'current_path': |
205 | 207 |
self.__set_is_active__() |
208 |
|
|
209 |
def __get_uuid_displayname_catalogs(request): |
|
210 |
# Normal Response Codes: 200 |
|
211 |
# Error Response Codes: badRequest (400) |
|
212 |
|
|
213 |
try: |
|
214 |
input_data = json.loads(request.raw_post_data) |
|
215 |
except: |
|
216 |
raise BadRequest('Request body should be json formatted.') |
|
217 |
else: |
|
218 |
uuids = input_data.get('uuids', []) |
|
219 |
displaynames = input_data.get('displaynames', []) |
|
220 |
d = {'uuid_catalog':AstakosUser.objects.uuid_catalog(uuids), |
|
221 |
'displayname_catalog':AstakosUser.objects.displayname_catalog(displaynames)} |
|
222 |
|
|
223 |
response = HttpResponse() |
|
224 |
response.status = 200 |
|
225 |
response.content = json.dumps(d) |
|
226 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
|
227 |
response['Content-Length'] = len(response.content) |
|
228 |
return response |
|
229 |
|
|
230 |
def __send_feedback(request, email_template_name='im/feedback_mail.txt', user=None): |
|
231 |
if not user: |
|
232 |
auth_token = request.POST.get('auth', '') |
|
233 |
if not auth_token: |
|
234 |
raise BadRequest('Missing user authentication') |
|
235 |
|
|
236 |
try: |
|
237 |
user = AstakosUser.objects.get(auth_token=auth_token) |
|
238 |
except AstakosUser.DoesNotExist: |
|
239 |
raise BadRequest('Invalid user authentication') |
|
240 |
|
|
241 |
form = FeedbackForm(request.POST) |
|
242 |
if not form.is_valid(): |
|
243 |
raise BadRequest('Invalid data') |
|
244 |
|
|
245 |
msg = form.cleaned_data['feedback_msg'] |
|
246 |
data = form.cleaned_data['feedback_data'] |
|
247 |
try: |
|
248 |
send_feedback_func(msg, data, user, email_template_name) |
|
249 |
except: |
|
250 |
return HttpResponse(status=502) |
|
251 |
return HttpResponse(status=200) |
b/snf-astakos-app/astakos/im/api/service.py | ||
---|---|---|
40 | 40 |
from django.views.decorators.csrf import csrf_exempt |
41 | 41 |
from django.utils import simplejson as json |
42 | 42 |
|
43 |
from . import render_fault |
|
43 |
from . import render_fault, __get_uuid_displayname_catalog, __send_feedback
|
|
44 | 44 |
from .faults import ( |
45 | 45 |
Fault, Unauthorized, InternalServerError, BadRequest, ItemNotFound) |
46 |
from astakos.im.models import AstakosUser, Service |
|
47 |
from astakos.im.forms import FeedbackForm |
|
48 |
from astakos.im.functions import send_feedback as send_feedback_func |
|
46 |
from astakos.im.models import Service |
|
49 | 47 |
|
50 | 48 |
logger = logging.getLogger(__name__) |
51 | 49 |
|
... | ... | |
82 | 80 |
return wrapper |
83 | 81 |
return decorator |
84 | 82 |
|
85 |
|
|
86 |
@api_method(http_method='GET', token_required=True)
|
|
87 |
def get_user_info(request):
|
|
83 |
@csrf_exempt |
|
84 |
@api_method(http_method='POST', token_required=True)
|
|
85 |
def get_uuid_displayname_catalogs(request):
|
|
88 | 86 |
# Normal Response Codes: 200 |
89 | 87 |
# Error Response Codes: internalServerError (500) |
90 | 88 |
# badRequest (400) |
91 | 89 |
# unauthorised (401) |
92 |
# itemNotFound (404) |
|
93 |
username = request.META.get('HTTP_X_USER_USERNAME') |
|
94 |
uuid = request.META.get('HTTP_X_USER_UUID') |
|
95 |
if not username and not uuid: |
|
96 |
raise BadRequest('Either username or uuid is required.') |
|
97 |
|
|
98 |
query = AstakosUser.objects.all() |
|
99 |
user_info = None |
|
100 |
if username: |
|
101 |
try: |
|
102 |
user = query.get(username__iexact=username) |
|
103 |
except AstakosUser.DoesNotExist: |
|
104 |
raise ItemNotFound('Invalid username: %s' % username) |
|
105 |
else: |
|
106 |
user_info = {'uuid': user.uuid} |
|
107 |
else: |
|
108 |
try: |
|
109 |
user = query.get(uuid=uuid) |
|
110 |
except AstakosUser.DoesNotExist: |
|
111 |
raise ItemNotFound('Invalid uuid: %s' % uuid) |
|
112 |
else: |
|
113 |
user_info = {'username': user.username} |
|
114 |
|
|
115 |
response = HttpResponse() |
|
116 |
response.status = 200 |
|
117 |
response.content = json.dumps(user_info) |
|
118 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
|
119 |
response['Content-Length'] = len(response.content) |
|
120 |
return response |
|
121 | 90 |
|
91 |
return __get_uuid_displayname_catalog(request) |
|
122 | 92 |
|
123 | 93 |
@csrf_exempt |
124 | 94 |
@api_method(http_method='POST', token_required=True) |
... | ... | |
127 | 97 |
# Error Response Codes: internalServerError (500) |
128 | 98 |
# badRequest (400) |
129 | 99 |
# unauthorised (401) |
130 |
auth_token = request.POST.get('auth', '') |
|
131 |
if not auth_token: |
|
132 |
raise BadRequest('Missing user authentication') |
|
133 |
|
|
134 |
user = None |
|
135 |
try: |
|
136 |
user = AstakosUser.objects.get(auth_token=auth_token) |
|
137 |
except: |
|
138 |
pass |
|
139 |
|
|
140 |
if not user: |
|
141 |
raise BadRequest('Invalid user authentication') |
|
142 |
|
|
143 |
form = FeedbackForm(request.POST) |
|
144 |
if not form.is_valid(): |
|
145 |
raise BadRequest('Invalid data') |
|
146 | 100 |
|
147 |
msg = form.cleaned_data['feedback_msg'] |
|
148 |
data = form.cleaned_data['feedback_data'] |
|
149 |
send_feedback_func(msg, data, user, email_template_name) |
|
150 |
response = HttpResponse(status=200) |
|
151 |
response['Content-Length'] = len(response.content) |
|
152 |
return response |
|
101 |
return __send_feedback(request, email_template_name) |
b/snf-astakos-app/astakos/im/api/user.py | ||
---|---|---|
38 | 38 |
|
39 | 39 |
from django.http import HttpResponse |
40 | 40 |
from django.utils import simplejson as json |
41 |
from django.views.decorators.csrf import csrf_exempt |
|
41 | 42 |
|
42 | 43 |
from .faults import ( |
43 | 44 |
Fault, Unauthorized, InternalServerError, BadRequest, Forbidden) |
44 |
from . import render_fault |
|
45 |
from . import render_fault, __get_uuid_displayname_catalogs, __send_feedback |
|
46 |
|
|
45 | 47 |
from astakos.im.models import AstakosUser |
46 | 48 |
from astakos.im.util import epoch |
47 | 49 |
|
... | ... | |
115 | 117 |
'email': [user.email], |
116 | 118 |
'name': user.realname, |
117 | 119 |
'auth_token_created': epoch(user.auth_token_created), |
118 |
'auth_token_expires': epoch(user.auth_token_expires), |
|
119 |
'has_credits': user.has_credits} |
|
120 |
'auth_token_expires': epoch(user.auth_token_expires)} |
|
120 | 121 |
|
121 | 122 |
# append usage data if requested |
122 | 123 |
if request.REQUEST.get('usage', None): |
... | ... | |
132 | 133 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
133 | 134 |
response['Content-Length'] = len(response.content) |
134 | 135 |
return response |
136 |
|
|
137 |
@csrf_exempt |
|
138 |
@api_method(http_method='POST', token_required=True) |
|
139 |
def get_uuid_displayname_catalogs(request, user=None): |
|
140 |
# Normal Response Codes: 200 |
|
141 |
# Error Response Codes: internalServerError (500) |
|
142 |
# badRequest (400) |
|
143 |
# unauthorised (401) |
|
144 |
|
|
145 |
return __get_uuid_displayname_catalogs(request) |
|
146 |
|
|
147 |
@csrf_exempt |
|
148 |
@api_method(http_method='POST', token_required=True) |
|
149 |
def send_feedback(request, email_template_name='im/feedback_mail.txt', user=None): |
|
150 |
# Normal Response Codes: 200 |
|
151 |
# Error Response Codes: internalServerError (500) |
|
152 |
# badRequest (400) |
|
153 |
# unauthorised (401) |
|
154 |
|
|
155 |
return __send_feedback(request, email_template_name, user) |
b/snf-astakos-app/astakos/im/models.py | ||
---|---|---|
301 | 301 |
def verified(self): |
302 | 302 |
return self.filter(email_verified=True) |
303 | 303 |
|
304 |
def uuid_catalog(self, l=None): |
|
305 |
""" |
|
306 |
Returns a uuid to username mapping for the uuids appearing in l. |
|
307 |
If l is None returns the mapping for all existing users. |
|
308 |
""" |
|
309 |
q = self.filter(uuid__in=l) if l != None else self |
|
310 |
return dict(q.values_list('uuid', 'username')) |
|
311 |
|
|
312 |
def displayname_catalog(self, l=None): |
|
313 |
""" |
|
314 |
Returns a username to uuid mapping for the usernames appearing in l. |
|
315 |
If l is None returns the mapping for all existing users. |
|
316 |
""" |
|
317 |
q = self.filter(uuid__in=l) if l != None else self |
|
318 |
q = self.filter(username__in=l) if l != None else self |
|
319 |
return dict(q.values_list('username', 'uuid')) |
|
320 |
|
|
321 |
|
|
304 | 322 |
|
305 | 323 |
class AstakosUser(User): |
306 | 324 |
""" |
b/snf-astakos-app/astakos/im/urls.py | ||
---|---|---|
158 | 158 |
urlpatterns += patterns( |
159 | 159 |
'astakos.im.api.user', |
160 | 160 |
url(r'^authenticate/?$', 'authenticate')) |
161 |
|
|
162 |
urlpatterns += patterns( |
|
163 |
'astakos.im.api.service', |
|
164 |
url(r'^service/api/v2.0/feedback/?$', 'send_feedback'), |
|
165 |
url(r'^service/api/v2.0/users/?$', 'get_user_info')) |
b/snf-astakos-app/astakos/urls.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
from django.conf.urls.defaults import include, patterns |
35 | 35 |
|
36 |
|
|
37 | 36 |
urlpatterns = patterns('', |
38 |
(r'^im/', include('astakos.im.urls')) |
|
39 |
) |
|
37 |
(r'^im/', include('astakos.im.urls')), |
|
38 |
(r'^feedback/?$', 'astakos.im.api.user.send_feedback'), |
|
39 |
(r'^user_catalogs/?$', 'astakos.im.api.user.get_uuid_displayname_catalogs')) |
b/snf-common/synnefo/lib/astakos.py | ||
---|---|---|
66 | 66 |
return f |
67 | 67 |
return execute |
68 | 68 |
|
69 |
def call(token, url, headers={}): |
|
69 |
def call(token, url, headers={}, body=None, method='GET'):
|
|
70 | 70 |
p = urlparse(url) |
71 | 71 |
|
72 | 72 |
kwargs = {} |
73 | 73 |
kwargs['headers'] = headers |
74 | 74 |
kwargs['headers']['X-Auth-Token'] = token |
75 |
kwargs['headers']['Content-Length'] = 0 |
|
75 |
if body: |
|
76 |
kwargs['body'] = body |
|
77 |
kwargs['headers'].setdefault('content-type', 'application/octet-stream') |
|
78 |
kwargs['headers'].setdefault('content-length', len(body) if body else 0) |
|
79 |
|
|
76 | 80 |
|
77 | 81 |
conn = get_http_connection(p.netloc, p.scheme) |
78 | 82 |
try: |
79 |
conn.request('GET', p.path + '?' + p.query, **kwargs)
|
|
83 |
conn.request(method, p.path + '?' + p.query, **kwargs)
|
|
80 | 84 |
response = conn.getresponse() |
81 | 85 |
headers = response.getheaders() |
82 | 86 |
headers = dict((unquote(h), unquote(v)) for h,v in headers) |
... | ... | |
103 | 107 |
|
104 | 108 |
|
105 | 109 |
@retry(3) |
106 |
def get_username(
|
|
110 |
def get_displaynames(
|
|
107 | 111 |
token, |
108 |
uuid, |
|
109 |
url='http://127.0.0.1:8000/im/service/api/v2.0/users',
|
|
112 |
uuids,
|
|
113 |
url='http://127.0.0.1:8000/user_catalogs',
|
|
110 | 114 |
override_users={}): |
111 |
if override_users: |
|
112 |
return uuid |
|
113 | 115 |
|
114 |
headers = {} |
|
115 |
if uuid: |
|
116 |
headers['X-User-Uuid'] = uuid |
|
116 |
if override_users: |
|
117 |
return dict((u,u) for u in uuids) |
|
117 | 118 |
|
118 | 119 |
try: |
119 |
data = call(token, url, headers) |
|
120 |
data = call( |
|
121 |
token, url, headers={'content-type':'application/json'}, |
|
122 |
body=json.dumps({'uuids':uuids}), method='POST') |
|
120 | 123 |
except Exception, e: |
121 | 124 |
raise e |
122 | 125 |
else: |
123 |
return data.get('username')
|
|
126 |
return data.get('uuid_catalog')
|
|
124 | 127 |
|
125 | 128 |
|
126 | 129 |
@retry(3) |
127 |
def get_user_uuid(
|
|
130 |
def get_uuids(
|
|
128 | 131 |
token, |
129 |
username,
|
|
130 |
url='http://127.0.0.1:8000/im/service/api/v2.0/users',
|
|
132 |
displaynames,
|
|
133 |
url='http://127.0.0.1:8000/user_catalogs',
|
|
131 | 134 |
override_users={}): |
135 |
|
|
132 | 136 |
if override_users: |
133 |
return username
|
|
137 |
return dict((u,u) for u in displaynames)
|
|
134 | 138 |
|
135 |
headers = {} |
|
136 |
if username: |
|
137 |
headers['X-User-Username'] = username |
|
138 | 139 |
try: |
139 |
data = call(token, url, headers) |
|
140 |
data = call( |
|
141 |
token, url, headers={'content-type':'application/json'}, |
|
142 |
body=json.dumps({'displaynames':displaynames}), method='POST') |
|
140 | 143 |
except Exception, e: |
144 |
import traceback |
|
145 |
traceback.print_exc() |
|
141 | 146 |
raise e |
142 | 147 |
else: |
143 |
return data.get('uuid') |
|
148 |
return data.get('displayname_catalog') |
|
149 |
|
|
150 |
def get_user_uuid( |
|
151 |
token, |
|
152 |
displayname, |
|
153 |
url='http://127.0.0.1:8000/user_catalogs', |
|
154 |
override_users={}): |
|
155 |
|
|
156 |
if not displayname: |
|
157 |
return |
|
158 |
|
|
159 |
displayname_dict = get_uuids(token, [displayname], url, override_users) |
|
160 |
return displayname_dict.get(displayname) |
|
161 |
|
|
162 |
|
|
163 |
def get_displayname( |
|
164 |
token, |
|
165 |
uuid, |
|
166 |
url='http://127.0.0.1:8000/user_catalogs', |
|
167 |
override_users={}): |
|
168 |
|
|
169 |
if not uuid: |
|
170 |
return |
|
144 | 171 |
|
172 |
uuid_dict = get_displaynames(token, [uuid], url, override_users) |
|
173 |
return uuid_dict.get(uuid) |
|
145 | 174 |
|
146 | 175 |
def user_for_token(token, authentication_url, override_users, usage=False): |
147 | 176 |
if not token: |
... | ... | |
189 | 218 |
authentication_url) |
190 | 219 |
return None |
191 | 220 |
|
192 |
# use user uuid, instead of email, keep email/username reference to user_id
|
|
221 |
# use user uuid, instead of email, keep email/displayname reference to user_id
|
|
193 | 222 |
request.user_uniq = user['uuid'] |
194 | 223 |
request.user = user |
195 |
request.user_id = user.get('username')
|
|
224 |
request.user_id = user.get('displayname')
|
|
196 | 225 |
return user |
197 | 226 |
|
198 | 227 |
|
b/snf-cyclades-app/synnefo/api/management/commands/cyclades-astakos-migrate.py | ||
---|---|---|
52 | 52 |
def warn(*msgs): |
53 | 53 |
print "WARNING: %s" % ' '.join(msgs) |
54 | 54 |
|
55 |
get_username = functools.partial(astakos.get_username,
|
|
55 |
get_displayname = functools.partial(astakos.get_displayname,
|
|
56 | 56 |
settings.CYCLADES_ASTAKOS_SERVICE_TOKEN, |
57 | 57 |
url=settings.ASTAKOS_URL.replace('authenticate', |
58 | 58 |
'service/api/v2.0/users')) |
b/snf-pithos-app/README | ||
---|---|---|
27 | 27 |
|
28 | 28 |
Configure in ``settings.py`` or a ``.conf`` file in ``/etc/synnefo`` if using snf-webproject. |
29 | 29 |
|
30 |
=============================== ================================================== ============================================================
|
|
30 |
=============================== ==================================================== ============================================================
|
|
31 | 31 |
Name Default value Description |
32 |
=============================== ================================================== ============================================================ |
|
33 |
PITHOS_ASTAKOS_URL \http://127.0.0.1:8000/im/ Astakos API URL |
|
32 |
=============================== ==================================================== ============================================================ |
|
33 |
PROXY_USER_SERVICES True Whether to proxy user feedback and catalog services |
|
34 |
PITHOS_USER_CATALOG_URL \http://127.0.0.1:8000/im/service/api/v2.0/users/ Astakos User Catalog URL |
|
35 |
PITHOS_USER_FEEDBACK_URL \http://127.0.0.1:8000/im/service/api/v2.0/feedback/ Astakos User Feedback URL |
|
36 |
PITHOS_USER_LOGIN_URL \http://127.0.0.1:8000/login/ Astakos User Login URL |
|
34 | 37 |
PITHOS_AUTHENTICATION_URL \http://127.0.0.1:8000/im/authenticate/ Astakos Authentication URL |
35 |
PITHOS_USER_INFO_URL \http://127.0.0.1:8000/im/service/api/v2.0/users/ Astakos User Information URL |
|
36 | 38 |
PITHOS_AUTHENTICATION_USERS A dictionary of sample users (token to username) Set to empty or None to disable |
37 | 39 |
PITHOS_BACKEND_DB_MODULE pithos.backends.lib.sqlalchemy |
38 | 40 |
PITHOS_BACKEND_DB_CONNECTION sqlite:////tmp/pithos-backend.db SQLAlchemy database connection string |
... | ... | |
47 | 49 |
PITHOS_BACKEND_FREE_VERSIONING True Default versioning debit policy (default free) |
48 | 50 |
PITHOS_UPDATE_MD5 True Update object checksums when using hashmaps |
49 | 51 |
PITHOS_SERVICE_TOKEN '' Service token acquired by the identity provider (astakos) |
50 |
=============================== ================================================== ============================================================
|
|
52 |
=============================== ==================================================== ============================================================
|
|
51 | 53 |
|
52 | 54 |
To update checksums asynchronously, enable the queue, install snf-pithos-tools and use ``pithos-dispatcher``:: |
53 | 55 |
|
b/snf-pithos-app/conf/20-snf-pithos-app-settings.conf | ||
---|---|---|
1 |
#PITHOS_ASTAKOS_URL = 'http://127.0.0.1:8000/im/' |
|
1 |
#PROXY_USER_SERVICES = True |
|
2 |
#USER_CATALOG_URL = 'http://127.0.0.1:8000/im/service/api/v2.0/users/' |
|
3 |
#PITHOS_USER_FEEDBACK_URL = 'http://127.0.0.1:8000/im/service/api/v2.0/feedback/' |
|
4 |
#PITHOS_USER_LOGIN_URL = 'http://127.0.0.1:8000/login/' |
|
2 | 5 |
#PITHOS_AUTHENTICATION_URL = 'http://127.0.0.1:8000/im/authenticate/' |
3 |
#PITHOS_USER_INFO_URL = 'http://127.0.0.1:8000/im/service/api/v2.0/users/' |
|
4 | 6 |
|
5 | 7 |
# Set local users, or a remote host. To disable local users set them to None. |
6 | 8 |
#sample_users = { |
b/snf-pithos-app/pithos/api/delegate.py | ||
---|---|---|
44 | 44 |
from django.views.decorators.csrf import csrf_exempt |
45 | 45 |
|
46 | 46 |
from pithos.api.settings import ( |
47 |
AUTHENTICATION_URL, AUTHENTICATION_USERS, SERVICE_TOKEN, USER_INFO_URL) |
|
47 |
AUTHENTICATION_USERS, USER_LOGIN_URL, USER_FEEDBACK_URL, USER_CATALOG_URL, |
|
48 |
SERVICE_TOKEN) |
|
48 | 49 |
|
49 |
from synnefo.lib.astakos import get_username
|
|
50 |
from synnefo.lib.pool.http import get_http_connection
|
|
50 | 51 |
|
51 | 52 |
logger = logging.getLogger(__name__) |
52 | 53 |
|
53 | 54 |
|
54 | 55 |
def delegate_to_login_service(request): |
55 |
url = AUTHENTICATION_URL
|
|
56 |
url = USER_LOGIN_URL
|
|
56 | 57 |
users = AUTHENTICATION_USERS |
57 | 58 |
if users or not url: |
58 | 59 |
return HttpResponseNotFound() |
... | ... | |
63 | 64 |
else: |
64 | 65 |
proto = 'http://' |
65 | 66 |
params = dict([(k, v) for k, v in request.GET.items()]) |
66 |
uri = proto + p.netloc + '/login?' + urlencode(params)
|
|
67 |
uri = proto + p.netloc + p.path + '?' + urlencode(params)
|
|
67 | 68 |
return HttpResponseRedirect(uri) |
68 | 69 |
|
69 | 70 |
|
70 |
@csrf_exempt |
|
71 |
def delegate_to_feedback_service(request): |
|
72 |
url = AUTHENTICATION_URL |
|
73 |
users = AUTHENTICATION_USERS |
|
74 |
if users or not url: |
|
75 |
return HttpResponseNotFound() |
|
76 |
|
|
71 |
def proxy(request, url, headers={}, body=None): |
|
77 | 72 |
p = urlparse(url) |
78 |
if request.is_secure(): |
|
79 |
proto = 'https://' |
|
80 |
else: |
|
81 |
proto = 'http://' |
|
82 | 73 |
|
83 |
uri = proto + p.netloc + '/im/service/api/v2.0/feedback' |
|
84 |
headers = {'X-Auth-Token': SERVICE_TOKEN} |
|
85 |
values = dict([(k, unicode(v).encode('utf-8')) for k, v in request.POST.items()]) |
|
86 |
data = urllib.urlencode(values) |
|
87 |
req = urllib2.Request(uri, data, headers) |
|
74 |
kwargs = {} |
|
75 |
kwargs['headers'] = headers |
|
76 |
kwargs['headers'].update(request.META) |
|
77 |
kwargs['body'] = body |
|
78 |
kwargs['headers'].setdefault('content-type', 'application/json') |
|
79 |
kwargs['headers'].setdefault('content-length', len(body) if body else 0) |
|
80 |
|
|
81 |
conn = get_http_connection(p.netloc, p.scheme) |
|
88 | 82 |
try: |
89 |
urllib2.urlopen(req)
|
|
90 |
except urllib2.HTTPError, e:
|
|
91 |
logger.exception(e)
|
|
92 |
return HttpResponse(status=e.code)
|
|
93 |
except urllib2.URLError, e:
|
|
94 |
logger.exception(e)
|
|
95 |
return HttpResponse(status=e.reason)
|
|
96 |
return HttpResponse()
|
|
83 |
conn.request(request.method, p.path + '?' + p.query, **kwargs)
|
|
84 |
response = conn.getresponse()
|
|
85 |
length = response.getheader('content-length', None)
|
|
86 |
data = response.read(length)
|
|
87 |
status = int(response.status)
|
|
88 |
return HttpResponse(data, status=status)
|
|
89 |
finally:
|
|
90 |
conn.close()
|
|
97 | 91 |
|
92 |
@csrf_exempt |
|
93 |
def delegate_to_feedback_service(request): |
|
94 |
token = request.META.get('HTTP_X_AUTH_TOKEN') |
|
95 |
headers = {'X-Auth-Token': token} |
|
96 |
return proxy( |
|
97 |
request, USER_FEEDBACK_URL, headers=headers, body=request.raw_post_data) |
|
98 |
|
|
99 |
@csrf_exempt |
|
100 |
def delegate_to_user_catalogs_service(request): |
|
101 |
token = request.META.get('HTTP_X_AUTH_TOKEN') |
|
102 |
headers = {'X-Auth-Token': token, 'content-type': 'application/json'} |
|
103 |
return proxy( |
|
104 |
request, USER_CATALOG_URL, headers=headers, body=request.raw_post_data) |
b/snf-pithos-app/pithos/api/functions.py | ||
---|---|---|
58 | 58 |
copy_or_move_object, get_int_parameter, get_content_length, |
59 | 59 |
get_content_range, socket_read_iterator, SaveToBackendHandler, |
60 | 60 |
object_data_response, put_object_block, hashmap_md5, simple_list_response, |
61 |
api_method, retrieve_username, retrieve_uuid, |
|
62 |
put_account_translation_headers) |
|
61 |
api_method, retrieve_displayname, retrieve_uuid, retrieve_displaynames, |
|
62 |
retrieve_uuids) |
|
63 |
|
|
63 | 64 |
from pithos.api.settings import UPDATE_MD5 |
64 | 65 |
|
65 | 66 |
from pithos.backends.base import ( |
b/snf-pithos-app/pithos/api/settings.py | ||
---|---|---|
15 | 15 |
'0009': 'διογένης' |
16 | 16 |
} |
17 | 17 |
|
18 |
ASTAKOS_URL = getattr(settings, 'PITHOS_ASTAKOS_URL', |
|
19 |
'http://127.0.0.1:8000/im/') |
|
20 |
from urlparse import urljoin |
|
18 |
# Set to False if pithos running in the same machine with the identity management |
|
19 |
PROXY_USER_SERVICES = getattr(settings, 'PITHOS_PROXY_USER_SERVICES', True) |
|
20 |
|
|
21 |
USER_CATALOG_URL = getattr(settings, 'PITHOS_USER_CATALOG_URL', |
|
22 |
'http://127.0.0.1:8000/im/service/api/v2.0/users/') |
|
23 |
USER_FEEDBACK_URL = getattr(settings, 'PITHOS_USER_FEEDBACK_URL', |
|
24 |
'http://127.0.0.1:8000/im/service/api/v2.0/feedback/') |
|
25 |
USER_LOGIN_URL = getattr(settings, 'PITHOS_USER_LOGIN_URL', |
|
26 |
'http:127.0.0.1:8000/login/') |
|
21 | 27 |
AUTHENTICATION_URL = getattr(settings, 'PITHOS_AUTHENTICATION_URL', |
22 |
urljoin(ASTAKOS_URL, 'authenticate/')) |
|
23 |
USER_INFO_URL = getattr(settings, 'PITHOS_USER_INFO_URL', |
|
24 |
urljoin(ASTAKOS_URL, 'service/api/v2.0/users/')) |
|
28 |
'http://localhost:8000/im/authenticate/') |
|
25 | 29 |
AUTHENTICATION_USERS = getattr(settings, 'PITHOS_AUTHENTICATION_USERS', {}) |
26 | 30 |
|
27 | 31 |
COOKIE_NAME = getattr(settings, 'ASTAKOS_COOKIE_NAME', '_pithos2_a') |
b/snf-pithos-app/pithos/api/urls.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
from django.conf.urls.defaults import include, patterns |
35 | 35 |
|
36 |
import pithos.api.settings as settings |
|
37 |
|
|
36 | 38 |
# TODO: This only works when in this order. |
37 | 39 |
api_urlpatterns = patterns( |
38 | 40 |
'pithos.api.functions', |
... | ... | |
48 | 50 |
(r'^v1(?:$|/)', include(api_urlpatterns)), |
49 | 51 |
(r'^v1\.0(?:$|/)', include(api_urlpatterns)), |
50 | 52 |
(r'^public/(?P<v_public>.+?)/?$', 'pithos.api.public.public_demux'), |
51 |
(r'^login/?$', 'pithos.api.delegate.delegate_to_login_service'), |
|
52 |
(r'^feedback/?$', 'pithos.api.delegate.delegate_to_feedback_service')) |
|
53 |
(r'^login/?$', 'pithos.api.delegate.delegate_to_login_service')) |
|
54 |
|
|
55 |
if settings.PROXY_USER_SERVICES: |
|
56 |
urlpatterns += patterns( |
|
57 |
'', |
|
58 |
(r'^feedback/?$', 'pithos.api.delegate.delegate_to_feedback_service'), |
|
59 |
(r'^user_catalog/?$', 'pithos.api.delegate.delegate_to_user_catalogs_service')) |
b/snf-pithos-app/pithos/api/util.py | ||
---|---|---|
65 | 65 |
BACKEND_QUOTA, BACKEND_VERSIONING, |
66 | 66 |
BACKEND_FREE_VERSIONING, |
67 | 67 |
AUTHENTICATION_URL, AUTHENTICATION_USERS, |
68 |
SERVICE_TOKEN, COOKIE_NAME, USER_INFO_URL,
|
|
68 |
SERVICE_TOKEN, COOKIE_NAME, USER_CATALOG_URL,
|
|
69 | 69 |
RADOS_STORAGE, RADOS_POOL_BLOCKS, |
70 | 70 |
RADOS_POOL_MAPS) |
71 | 71 |
from pithos.backends import connect_backend |
72 | 72 |
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists, |
73 | 73 |
VersionNotExists) |
74 |
from synnefo.lib.astakos import get_user_uuid, get_username |
|
74 |
from synnefo.lib.astakos import (get_user_uuid, get_displayname, |
|
75 |
get_uuids, get_displaynames) |
|
75 | 76 |
|
76 | 77 |
import logging |
77 | 78 |
import re |
... | ... | |
240 | 241 |
if not restricted: |
241 | 242 |
response['X-Object-Hash'] = meta['hash'] |
242 | 243 |
response['X-Object-UUID'] = meta['uuid'] |
243 |
modified_by = retrieve_username(meta['modified_by'])
|
|
244 |
modified_by = retrieve_displayname(meta['modified_by'])
|
|
244 | 245 |
response['X-Object-Modified-By'] = smart_str( |
245 | 246 |
modified_by, strings_only=True) |
246 | 247 |
response['X-Object-Version'] = meta['version'] |
... | ... | |
294 | 295 |
else: |
295 | 296 |
return True |
296 | 297 |
|
297 |
def retrieve_username(uuid):
|
|
298 |
def retrieve_displayname(uuid):
|
|
298 | 299 |
try: |
299 |
return get_username(
|
|
300 |
SERVICE_TOKEN, uuid, USER_INFO_URL, AUTHENTICATION_USERS)
|
|
300 |
return get_displayname(
|
|
301 |
SERVICE_TOKEN, uuid, USER_CATALOG_URL, AUTHENTICATION_USERS)
|
|
301 | 302 |
except: |
302 |
# if it fails just leave the metadata intact
|
|
303 |
# if it fails just leave the input intact
|
|
303 | 304 |
return uuid |
304 | 305 |
|
305 |
def retrieve_uuid(username):
|
|
306 |
if is_uuid(username):
|
|
307 |
return username
|
|
306 |
def retrieve_displaynames(uuids):
|
|
307 |
return get_displaynames(
|
|
308 |
SERVICE_TOKEN, uuids, USER_CATALOG_URL, AUTHENTICATION_USERS)
|
|
308 | 309 |
|
309 |
try: |
|
310 |
return get_user_uuid( |
|
311 |
SERVICE_TOKEN, username, USER_INFO_URL, AUTHENTICATION_USERS) |
|
312 |
except Exception, e: |
|
313 |
if e.args: |
|
314 |
status = e.args[-1] |
|
315 |
if status == 404: |
|
316 |
raise ItemNotExists(username) |
|
317 |
raise |
|
318 |
|
|
319 |
def replace_permissions_username(holder): |
|
310 |
def retrieve_uuid(displayname): |
|
311 |
if is_uuid(displayname): |
|
312 |
return displayname |
|
313 |
|
|
314 |
uuid = get_user_uuid( |
|
315 |
SERVICE_TOKEN, displayname, USER_CATALOG_URL, AUTHENTICATION_USERS) |
|
316 |
if not uuid: |
|
317 |
raise ItemNotExists(displayname) |
|
318 |
return uuid |
|
319 |
|
|
320 |
def retrieve_uuids(displaynames): |
|
321 |
return get_uuids( |
|
322 |
SERVICE_TOKEN, displaynames, USER_CATALOG_URL, AUTHENTICATION_USERS) |
|
323 |
|
|
324 |
def replace_permissions_displayname(holder): |
|
320 | 325 |
try: |
321 | 326 |
# check first for a group permission |
322 | 327 |
account, group = holder.split(':') |
... | ... | |
330 | 335 |
# check first for a group permission |
331 | 336 |
account, group = holder.split(':') |
332 | 337 |
except ValueError: |
333 |
return retrieve_username(holder)
|
|
338 |
return retrieve_displayname(holder)
|
|
334 | 339 |
else: |
335 |
return ':'.join([retrieve_username(account), group])
|
|
340 |
return ':'.join([retrieve_displayname(account), group])
|
|
336 | 341 |
|
337 | 342 |
def update_sharing_meta(request, permissions, v_account, v_container, v_object, meta): |
338 | 343 |
if permissions is None: |
... | ... | |
595 | 600 |
raise BadRequest( |
596 | 601 |
'Bad X-Object-Sharing header value: missing prefix') |
597 | 602 |
|
598 |
# replace username with uuid
|
|
603 |
# replace displayname with uuid
|
|
599 | 604 |
try: |
600 | 605 |
ret['read'] = \ |
601 |
[replace_permissions_username(x) for x in ret.get('read', [])]
|
|
606 |
[replace_permissions_displayname(x) for x in ret.get('read', [])]
|
|
602 | 607 |
ret['write'] = \ |
603 |
[replace_permissions_username(x) for x in ret.get('write', [])]
|
|
608 |
[replace_permissions_displayname(x) for x in ret.get('write', [])]
|
|
604 | 609 |
except ItemNotExists, e: |
605 | 610 |
raise BadRequest( |
606 | 611 |
'Bad X-Object-Sharing header value: unknown account: %s' % e) |
b/snf-pithos-backend/pithos/backends/lib/sqlalchemy/alembic/versions/165ba3fbfe53_update_path_account.py | ||
---|---|---|
13 | 13 |
from alembic import op |
14 | 14 |
from sqlalchemy.sql import table, column, literal, and_ |
15 | 15 |
|
16 |
from synnefo.lib.astakos import get_user_uuid, get_username as get_user_username
|
|
16 |
from synnefo.lib.astakos import get_user_uuid, get_displayname as get_user_displayname
|
|
17 | 17 |
from pithos.api.settings import ( |
18 |
SERVICE_TOKEN, USER_INFO_URL, AUTHENTICATION_USERS)
|
|
18 |
SERVICE_TOKEN, USER_CATALOG_URL, AUTHENTICATION_USERS)
|
|
19 | 19 |
|
20 | 20 |
import sqlalchemy as sa |
21 | 21 |
|
... | ... | |
27 | 27 |
return uuid |
28 | 28 |
try: |
29 | 29 |
uuid = get_user_uuid( |
30 |
SERVICE_TOKEN, account, USER_INFO_URL, AUTHENTICATION_USERS)
|
|
30 |
SERVICE_TOKEN, account, USER_CATALOG_URL, AUTHENTICATION_USERS)
|
|
31 | 31 |
except Exception, e: |
32 | 32 |
print 'Unable to retrieve uuid for %s: %s' % (account, e) |
33 | 33 |
return |
... | ... | |
35 | 35 |
if uuid: |
36 | 36 |
catalog[account] = uuid |
37 | 37 |
return uuid |
38 |
|
|
38 |
|
|
39 | 39 |
inverse_catalog = {} |
40 |
def get_username(account):
|
|
40 |
def get_displayname(account):
|
|
41 | 41 |
global inverse_catalog |
42 |
username = inverse_catalog.get(account)
|
|
43 |
if username:
|
|
44 |
return username
|
|
42 |
displayname = inverse_catalog.get(account)
|
|
43 |
if displayname:
|
|
44 |
return displayname
|
|
45 | 45 |
try: |
46 |
username = get_user_username(
|
|
47 |
SERVICE_TOKEN, account, USER_INFO_URL, AUTHENTICATION_USERS)
|
|
46 |
displayname = get_user_displayname(
|
|
47 |
SERVICE_TOKEN, account, USER_CATALOG_URL, AUTHENTICATION_USERS)
|
|
48 | 48 |
except Exception, e: |
49 |
print 'Unable to retrieve username for %s: %s' % (account, e)
|
|
49 |
print 'Unable to retrieve displayname for %s: %s' % (account, e)
|
|
50 | 50 |
return |
51 | 51 |
else: |
52 |
if username:
|
|
53 |
catalog[account] = username
|
|
54 |
return username
|
|
52 |
if displayname:
|
|
53 |
catalog[account] = displayname
|
|
54 |
return displayname
|
|
55 | 55 |
|
56 | 56 |
n = table( |
57 | 57 |
'nodes', |
... | ... | |
159 | 159 |
|
160 | 160 |
def downgrade(): |
161 | 161 |
connection = op.get_bind() |
162 |
|
|
162 |
|
|
163 | 163 |
s = sa.select([n.c.node, n.c.path]) |
164 | 164 |
nodes = connection.execute(s).fetchall() |
165 | 165 |
for node, path in nodes: |
166 | 166 |
account, sep, rest = path.partition('/') |
167 |
username = get_username(account)
|
|
168 |
if not username:
|
|
167 |
displayname = get_displayname(account)
|
|
168 |
if not displayname:
|
|
169 | 169 |
continue |
170 |
path = sep.join([username, rest])
|
|
170 |
path = sep.join([displayname, rest])
|
|
171 | 171 |
u = n.update().where(n.c.node == node).values({'path':path}) |
172 | 172 |
connection.execute(u) |
173 |
|
|
173 |
|
|
174 | 174 |
s = sa.select([p.c.public_id, p.c.path]) |
175 | 175 |
public = connection.execute(s).fetchall() |
176 | 176 |
for id, path in public: |
177 | 177 |
account, sep, rest = path.partition('/') |
178 |
username = get_username(account)
|
|
179 |
if not username:
|
|
178 |
displayname = get_displayname(account)
|
|
179 |
if not displayname:
|
|
180 | 180 |
continue |
181 |
path = sep.join([username, rest])
|
|
181 |
path = sep.join([displayname, rest])
|
|
182 | 182 |
u = p.update().where(p.c.public_id == id).values({'path':path}) |
183 | 183 |
connection.execute(u) |
184 |
|
|
184 |
|
|
185 | 185 |
s = sa.select([x.c.feature_id, x.c.path]) |
186 | 186 |
xfeatures = connection.execute(s).fetchall() |
187 | 187 |
for id, path in xfeatures: |
188 | 188 |
account, sep, rest = path.partition('/') |
189 |
username = get_username(account)
|
|
190 |
if not username:
|
|
189 |
displayname = get_displayname(account)
|
|
190 |
if not displayname:
|
|
191 | 191 |
continue |
192 |
path = sep.join([username, rest])
|
|
192 |
path = sep.join([displayname, rest])
|
|
193 | 193 |
u = x.update().where(x.c.feature_id == id).values({'path':path}) |
194 | 194 |
connection.execute(u) |
195 | 195 |
|
... | ... | |
198 | 198 |
xfeaturevals = connection.execute(s).fetchall() |
199 | 199 |
for feature_id, key, value in xfeaturevals: |
200 | 200 |
account, sep, group = value.partition(':') |
201 |
username = get_username(account)
|
|
202 |
if not username:
|
|
201 |
displayname = get_displayname(account)
|
|
202 |
if not displayname:
|
|
203 | 203 |
continue |
204 |
new_value = sep.join([username, group])
|
|
204 |
new_value = sep.join([displayname, group])
|
|
205 | 205 |
u = xvals.update() |
206 | 206 |
u = u.where(and_( |
207 | 207 |
xvals.c.feature_id == feature_id, |
... | ... | |
213 | 213 |
s = sa.select([g.c.owner, g.c.name, g.c.member]) |
214 | 214 |
groups = connection.execute(s).fetchall() |
215 | 215 |
for owner, name, member in groups: |
216 |
owner_username = get_username(owner)
|
|
217 |
member_username = get_username(member)
|
|
218 |
if owner_username or member_username:
|
|
216 |
owner_displayname = get_displayname(owner)
|
|
217 |
member_displayname = get_displayname(member)
|
|
218 |
if owner_displayname or member_displayname:
|
|
219 | 219 |
u = g.update() |
220 | 220 |
u = u.where(and_( |
221 | 221 |
g.c.owner == owner, |
222 | 222 |
g.c.name == name, |
223 | 223 |
g.c.member == member)) |
224 | 224 |
values = {} |
225 |
if owner_username: |
|
226 |
values['owner'] = owner_username |
Also available in: Unified diff