api: move user related API to /api/user/ (#8283)
This commit is contained in:
parent
21cfacc69a
commit
6de84723ea
|
@ -24,18 +24,23 @@ de l'utilisateur, modification, etc.).
|
|||
<section id="pull">
|
||||
<title>Mode pull</title>
|
||||
|
||||
<p>
|
||||
Ces accès doivent se faire en passant les informations d'identification
|
||||
appropriées dans la <em>query string</em>.
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<title>Profil</title>
|
||||
<p>
|
||||
Les informations associées à un utilisateur sont accessibles à l'URL
|
||||
<code>/user</code>, elles reprennent son nom (<code>user_display_name</code>),
|
||||
<code>/api/user/</code>, elles reprennent son nom (<code>user_display_name</code>),
|
||||
son adresse électronique (<code>user_email</code>) ainsi que ses éventuelles
|
||||
autorisations d'accès au backoffice (<code>user_backoffice_access</code>) ou
|
||||
à l'interface d'administration (<code>user_admin_access</code>).
|
||||
</p>
|
||||
|
||||
<screen>
|
||||
<output style="prompt">$ </output><input>curl -H "Accept: application/json" https://www.example.net/user</input>
|
||||
<output style="prompt">$ </output><input>curl https://www.example.net/api/user/</input>
|
||||
<output>{
|
||||
"user_display_name": "Fred",
|
||||
"user_email": "fred@example.net",
|
||||
|
@ -44,6 +49,12 @@ autorisations d'accès au backoffice (<code>user_backoffice_access</code>) ou
|
|||
}
|
||||
</output></screen>
|
||||
|
||||
<note>
|
||||
<p>Note de compatibilité : cette information est également disponible à
|
||||
l'adresse <code>/user</code>.
|
||||
</p>
|
||||
</note>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="forms">
|
||||
|
@ -51,11 +62,11 @@ autorisations d'accès au backoffice (<code>user_backoffice_access</code>) ou
|
|||
|
||||
<p>
|
||||
La liste des formulaires transmis par un utilisateur est accessible à l'URL
|
||||
<code>/myspace/forms</code>, elle reprend un ensemble minimal
|
||||
<code>/api/user/forms</code>, elle reprend un ensemble minimal
|
||||
d'informations concernant chacun de ceux-ci.
|
||||
</p>
|
||||
<screen>
|
||||
<output style="prompt">$ </output><input>curl -H "Accept: application/json" https://www.example.net/myspace/forms</input>
|
||||
<output style="prompt">$ </output><input>curl https://www.example.net/api/user/forms</input>
|
||||
<output>[
|
||||
{
|
||||
"category_id": "1",
|
||||
|
@ -116,17 +127,23 @@ autorisations d'accès au backoffice (<code>user_backoffice_access</code>) ou
|
|||
}
|
||||
]</output></screen>
|
||||
|
||||
<note>
|
||||
<p>Note de compatibilité : cette information est également disponible à
|
||||
l'adresse <code>/myspace/forms</code>.
|
||||
</p>
|
||||
</note>
|
||||
|
||||
</section>
|
||||
<section>
|
||||
<title>Brouillons</title>
|
||||
|
||||
<p>
|
||||
La liste des brouillons de l'utilisateur est accessible à l'adresse
|
||||
<code>/myspace/drafts</code>.
|
||||
<code>/api/user/drafts</code>.
|
||||
</p>
|
||||
|
||||
<screen>
|
||||
<output style="prompt">$ </output><input>curl -H "Accept: application/json" https://www.example.net/myspace/drafts</input>
|
||||
<output style="prompt">$ </output><input>curl https://www.example.net/myspace/drafts</input>
|
||||
<output>[
|
||||
{
|
||||
"datetime": "2014-07-21 10:15:21",
|
||||
|
@ -136,6 +153,12 @@ autorisations d'accès au backoffice (<code>user_backoffice_access</code>) ou
|
|||
}
|
||||
]</output></screen>
|
||||
|
||||
<note>
|
||||
<p>Note de compatibilité : cette information est également disponible à
|
||||
l'adresse <code>/myspace/drafts</code>.
|
||||
</p>
|
||||
</note>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
|
|
@ -68,27 +68,27 @@ def test_user_page_redirect():
|
|||
assert output.headers.get('location') == 'http://example.net/myspace/'
|
||||
|
||||
def test_user_page_error_when_json_and_no_user():
|
||||
output = get_app(pub).get('/user?format=json', status=403)
|
||||
output = get_app(pub).get('/api/user/?format=json', status=403)
|
||||
assert output.json['err_desc'] == 'no user specified'
|
||||
|
||||
def test_get_user_from_api_query_string_error_missing_orig():
|
||||
output = get_app(pub).get('/user?format=json&signature=xxx', status=403)
|
||||
output = get_app(pub).get('/api/user/?format=json&signature=xxx', status=403)
|
||||
assert output.json['err_desc'] == 'missing/multiple orig field'
|
||||
|
||||
def test_get_user_from_api_query_string_error_invalid_orig():
|
||||
output = get_app(pub).get('/user?format=json&orig=coin&signature=xxx', status=403)
|
||||
output = get_app(pub).get('/api/user/?format=json&orig=coin&signature=xxx', status=403)
|
||||
assert output.json['err_desc'] == 'invalid orig'
|
||||
|
||||
def test_get_user_from_api_query_string_error_missing_algo():
|
||||
output = get_app(pub).get('/user?format=json&orig=coucou&signature=xxx', status=403)
|
||||
output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx', status=403)
|
||||
assert output.json['err_desc'] == 'missing/multiple algo field'
|
||||
|
||||
def test_get_user_from_api_query_string_error_invalid_algo():
|
||||
output = get_app(pub).get('/user?format=json&orig=coucou&signature=xxx&algo=coin', status=403)
|
||||
output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx&algo=coin', status=403)
|
||||
assert output.json['err_desc'] == 'invalid algo'
|
||||
|
||||
def test_get_user_from_api_query_string_error_invalid_signature():
|
||||
output = get_app(pub).get('/user?format=json&orig=coucou&signature=xxx&algo=sha1', status=403)
|
||||
output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx&algo=sha1', status=403)
|
||||
assert output.json['err_desc'] == 'invalid signature'
|
||||
|
||||
def test_get_user_from_api_query_string_error_missing_timestamp():
|
||||
|
@ -97,7 +97,7 @@ def test_get_user_from_api_query_string_error_missing_timestamp():
|
|||
hmac.new('1234',
|
||||
'format=json&orig=coucou&algo=sha1',
|
||||
hashlib.sha1).digest()))
|
||||
output = get_app(pub).get('/user?format=json&orig=coucou&algo=sha1&signature=%s' % signature, status=403)
|
||||
output = get_app(pub).get('/api/user/?format=json&orig=coucou&algo=sha1&signature=%s' % signature, status=403)
|
||||
assert output.json['err_desc'] == 'missing/multiple timestamp field'
|
||||
|
||||
def test_get_user_from_api_query_string_error_missing_email():
|
||||
|
@ -108,7 +108,7 @@ def test_get_user_from_api_query_string_error_missing_email():
|
|||
hmac.new('1234',
|
||||
query,
|
||||
hashlib.sha1).digest()))
|
||||
output = get_app(pub).get('/user?%s&signature=%s' % (query, signature), status=403)
|
||||
output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
|
||||
assert output.json['err_desc'] == 'no user specified'
|
||||
|
||||
def test_get_user_from_api_query_string_error_unknown_nameid():
|
||||
|
@ -119,7 +119,7 @@ def test_get_user_from_api_query_string_error_unknown_nameid():
|
|||
hmac.new('1234',
|
||||
query,
|
||||
hashlib.sha1).digest()))
|
||||
output = get_app(pub).get('/user?%s&signature=%s' % (query, signature), status=403)
|
||||
output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
|
||||
assert output.json['err_desc'] == 'unknown NameID'
|
||||
|
||||
def test_get_user_from_api_query_string_error_missing_email_valid_endpoint():
|
||||
|
@ -159,7 +159,7 @@ def test_get_user_from_api_query_string_error_success_sha1(local_user):
|
|||
hmac.new('1234',
|
||||
query,
|
||||
hashlib.sha1).digest()))
|
||||
output = get_app(pub).get('/user?%s&signature=%s' % (query, signature))
|
||||
output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature))
|
||||
assert output.json['user_display_name'] == u'Jean Darmette'
|
||||
|
||||
def test_get_user_from_api_query_string_error_invalid_signature_algo_mismatch(local_user):
|
||||
|
@ -170,7 +170,7 @@ def test_get_user_from_api_query_string_error_invalid_signature_algo_mismatch(lo
|
|||
hmac.new('1234',
|
||||
query,
|
||||
hashlib.sha1).digest()))
|
||||
output = get_app(pub).get('/user?%s&signature=%s' % (query, signature), status=403)
|
||||
output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
|
||||
assert output.json['err_desc'] == 'invalid signature'
|
||||
|
||||
def test_get_user_from_api_query_string_error_success_sha256(local_user):
|
||||
|
@ -181,12 +181,12 @@ def test_get_user_from_api_query_string_error_success_sha256(local_user):
|
|||
hmac.new('1234',
|
||||
query,
|
||||
hashlib.sha256).digest()))
|
||||
output = get_app(pub).get('/user?%s&signature=%s' % (query, signature))
|
||||
output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature))
|
||||
assert output.json['user_display_name'] == u'Jean Darmette'
|
||||
|
||||
def test_sign_url(local_user):
|
||||
signed_url = sign_url(
|
||||
'http://example.net/user?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
|
||||
'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
|
||||
'1234'
|
||||
)
|
||||
url = signed_url[len('http://example.net'):]
|
||||
|
@ -194,12 +194,21 @@ def test_sign_url(local_user):
|
|||
assert output.json['user_display_name'] == u'Jean Darmette'
|
||||
|
||||
signed_url = sign_url(
|
||||
'http://example.net/user?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
|
||||
'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
|
||||
'12345'
|
||||
)
|
||||
url = signed_url[len('http://example.net'):]
|
||||
output = get_app(pub).get(url, status=403)
|
||||
|
||||
def test_get_user_compat_endpoint(local_user):
|
||||
signed_url = sign_url(
|
||||
'http://example.net/user?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
|
||||
'1234'
|
||||
)
|
||||
url = signed_url[len('http://example.net'):]
|
||||
output = get_app(pub).get(url)
|
||||
assert output.json['user_display_name'] == u'Jean Darmette'
|
||||
|
||||
def test_formdef_list():
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
|
@ -311,7 +320,7 @@ def test_formdata(local_user):
|
|||
assert resp.json['fields']['file']['filename'] == 'test.txt'
|
||||
assert resp.json['fields']['file']['content_type'] == 'text/plain'
|
||||
|
||||
def test_myspace_forms(local_user):
|
||||
def test_user_forms(local_user):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
|
@ -320,7 +329,7 @@ def test_myspace_forms(local_user):
|
|||
fields.StringField(id='1', label='foobar2'),]
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/myspace/forms', user=local_user))
|
||||
resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
|
||||
assert len(resp.json) == 0
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
|
@ -330,11 +339,13 @@ def test_myspace_forms(local_user):
|
|||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/myspace/forms', user=local_user))
|
||||
resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
|
||||
resp2 = get_app(pub).get(sign_uri('/myspace/forms', user=local_user))
|
||||
assert len(resp.json) == 1
|
||||
assert resp.json[0]['form_status'] == 'New'
|
||||
assert resp.json == resp2.json
|
||||
|
||||
def test_myspace_drafts(local_user):
|
||||
def test_user_drafts(local_user):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
|
@ -343,7 +354,7 @@ def test_myspace_drafts(local_user):
|
|||
fields.StringField(id='1', label='foobar2'),]
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/myspace/drafts', user=local_user))
|
||||
resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
|
||||
assert len(resp.json) == 0
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
|
@ -354,8 +365,10 @@ def test_myspace_drafts(local_user):
|
|||
formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/myspace/drafts', user=local_user))
|
||||
resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
|
||||
resp2 = get_app(pub).get(sign_uri('/myspace/drafts', user=local_user))
|
||||
assert len(resp.json) == 1
|
||||
assert resp.json == resp2.json
|
||||
|
||||
def test_api_list_formdata(local_user):
|
||||
Role.wipe()
|
||||
|
|
91
wcs/api.py
91
wcs/api.py
|
@ -27,6 +27,7 @@ import sys
|
|||
|
||||
from quixote import get_request, get_publisher, get_response
|
||||
from quixote.directory import Directory
|
||||
from qommon import misc
|
||||
from qommon.errors import (AccessForbiddenError, QueryError, TraversalError,
|
||||
UnknownNameIdAccessForbiddenError)
|
||||
|
||||
|
@ -297,13 +298,101 @@ class ApiCategoriesDirectory(RootDirectory):
|
|||
return ApiCategoryDirectory(Category.get_by_urlname(component))
|
||||
|
||||
|
||||
class ApiUserDirectory(Directory):
|
||||
_q_exports = ['', 'forms', 'drafts']
|
||||
|
||||
def _q_index(self):
|
||||
get_response().set_content_type('application/json')
|
||||
user = get_user_from_api_query_string() or get_request().user
|
||||
if not user:
|
||||
raise AccessForbiddenError('no user specified')
|
||||
user_info = user.get_substitution_variables(prefix='')
|
||||
del user_info['user']
|
||||
user_info['user_roles'] = []
|
||||
for role_id in user.roles or []:
|
||||
user_info['user_roles'].append(Role.get(role_id).name)
|
||||
return json.dumps(user_info)
|
||||
|
||||
def get_user_forms(self, user):
|
||||
formdefs = FormDef.select(lambda x: not x.is_disabled())
|
||||
user_forms = []
|
||||
for formdef in formdefs:
|
||||
user_forms.extend(formdef.data_class().get_with_indexed_value(
|
||||
'user_id', user.id))
|
||||
try:
|
||||
user_forms.extend(formdef.data_class().get_with_indexed_value(
|
||||
'user_hash', user.hash))
|
||||
except AttributeError:
|
||||
pass
|
||||
user_forms.sort(lambda x, y: cmp(x.receipt_time, y.receipt_time))
|
||||
return user_forms
|
||||
|
||||
def drafts(self):
|
||||
get_response().set_content_type('application/json')
|
||||
user = get_user_from_api_query_string() or get_request().user
|
||||
if not user:
|
||||
raise AccessForbiddenError()
|
||||
drafts = []
|
||||
for form in self.get_user_forms(user):
|
||||
if not form.is_draft():
|
||||
continue
|
||||
title = '%(name)s, draft saved on %(datetime)s' % {
|
||||
'name': form.formdef.name,
|
||||
'datetime': misc.localstrftime(form.receipt_time)
|
||||
}
|
||||
# !!! no trailing slash in the special draft case
|
||||
url = form.get_url().rstrip('/')
|
||||
d = {'title': title,
|
||||
'name': form.formdef.name,
|
||||
'url': url,
|
||||
'datetime': misc.strftime.strftime('%Y-%m-%d %H:%M:%S', form.receipt_time),
|
||||
}
|
||||
drafts.append(d)
|
||||
|
||||
return json.dumps(drafts)
|
||||
|
||||
def forms(self):
|
||||
get_response().set_content_type('application/json')
|
||||
user = get_user_from_api_query_string() or get_request().user
|
||||
if not user:
|
||||
raise AccessForbiddenError()
|
||||
forms = []
|
||||
for form in self.get_user_forms(user):
|
||||
if form.is_draft():
|
||||
continue
|
||||
visible_status = form.get_visible_status(user=user)
|
||||
# skip hidden forms
|
||||
if not visible_status:
|
||||
continue
|
||||
name = form.formdef.name
|
||||
id = form.get_display_id()
|
||||
status = visible_status.name
|
||||
title = _('%(name)s #%(id)s (%(status)s)') % {
|
||||
'name': name,
|
||||
'id': id,
|
||||
'status': status
|
||||
}
|
||||
url = form.get_url()
|
||||
d = {'title': title,
|
||||
'name': form.formdef.name,
|
||||
'url': url,
|
||||
'datetime': misc.strftime.strftime('%Y-%m-%d %H:%M:%S', form.receipt_time),
|
||||
'status': status,
|
||||
}
|
||||
d.update(form.get_substitution_variables(minimal=True))
|
||||
forms.append(d)
|
||||
|
||||
return json.dumps(forms)
|
||||
|
||||
|
||||
class ApiDirectory(Directory):
|
||||
_q_exports = ['forms', 'roles', ('reverse-geocoding', 'reverse_geocoding'),
|
||||
'formdefs', 'categories']
|
||||
'formdefs', 'categories', 'user']
|
||||
|
||||
forms = ApiFormsDirectory()
|
||||
formdefs = ApiFormdefsDirectory()
|
||||
categories = ApiCategoriesDirectory()
|
||||
user = ApiUserDirectory()
|
||||
|
||||
def reverse_geocoding(self):
|
||||
try:
|
||||
|
|
|
@ -14,97 +14,20 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
|
||||
from quixote import get_request, get_response, redirect
|
||||
|
||||
from quixote import get_request, redirect
|
||||
import qommon.myspace
|
||||
from qommon import misc
|
||||
from qommon import errors
|
||||
|
||||
from api import get_user_from_api_query_string
|
||||
from formdef import FormDef
|
||||
|
||||
class MyspaceDirectory(qommon.myspace.MyspaceDirectory):
|
||||
_q_exports = ['', 'profile', 'new', 'password', 'remove', 'drafts', 'forms']
|
||||
|
||||
def get_user_forms(self, user):
|
||||
formdefs = FormDef.select(lambda x: not x.is_disabled())
|
||||
user_forms = []
|
||||
for formdef in formdefs:
|
||||
user_forms.extend(formdef.data_class().get_with_indexed_value(
|
||||
'user_id', user.id))
|
||||
try:
|
||||
user_forms.extend(formdef.data_class().get_with_indexed_value(
|
||||
'user_hash', user.hash))
|
||||
except AttributeError:
|
||||
pass
|
||||
user_forms.sort(lambda x, y: cmp(x.receipt_time, y.receipt_time))
|
||||
return user_forms
|
||||
|
||||
def drafts(self):
|
||||
if get_request().is_json():
|
||||
return self.drafts_json()
|
||||
from wcs.api import ApiUserDirectory
|
||||
return ApiUserDirectory().drafts()
|
||||
return redirect('.')
|
||||
|
||||
def drafts_json(self):
|
||||
get_response().set_content_type('application/json')
|
||||
user = get_user_from_api_query_string() or get_request().user
|
||||
if not user:
|
||||
return errors.AccessForbiddenError()
|
||||
drafts = []
|
||||
for form in self.get_user_forms(user):
|
||||
if not form.is_draft():
|
||||
continue
|
||||
title = '%(name)s, draft saved on %(datetime)s' % {
|
||||
'name': form.formdef.name,
|
||||
'datetime': misc.localstrftime(form.receipt_time)
|
||||
}
|
||||
# !!! no trailing slash in the special draft case
|
||||
url = form.get_url().rstrip('/')
|
||||
d = {'title': title,
|
||||
'name': form.formdef.name,
|
||||
'url': url,
|
||||
'datetime': misc.strftime.strftime('%Y-%m-%d %H:%M:%S', form.receipt_time),
|
||||
}
|
||||
drafts.append(d)
|
||||
|
||||
return json.dumps(drafts)
|
||||
|
||||
def forms(self):
|
||||
if get_request().is_json():
|
||||
return self.forms_json()
|
||||
from wcs.api import ApiUserDirectory
|
||||
return ApiUserDirectory().forms()
|
||||
return redirect('.')
|
||||
|
||||
def forms_json(self):
|
||||
get_response().set_content_type('application/json')
|
||||
user = get_user_from_api_query_string() or get_request().user
|
||||
if not user:
|
||||
return errors.AccessForbiddenError()
|
||||
forms = []
|
||||
for form in self.get_user_forms(user):
|
||||
if form.is_draft():
|
||||
continue
|
||||
visible_status = form.get_visible_status(user=user)
|
||||
# skip hidden forms
|
||||
if not visible_status:
|
||||
continue
|
||||
name = form.formdef.name
|
||||
id = form.get_display_id()
|
||||
status = visible_status.name
|
||||
title = _('%(name)s #%(id)s (%(status)s)') % {
|
||||
'name': name,
|
||||
'id': id,
|
||||
'status': status
|
||||
}
|
||||
url = form.get_url()
|
||||
d = {'title': title,
|
||||
'name': form.formdef.name,
|
||||
'url': url,
|
||||
'datetime': misc.strftime.strftime('%Y-%m-%d %H:%M:%S', form.receipt_time),
|
||||
'status': status,
|
||||
}
|
||||
d.update(form.get_substitution_variables(minimal=True))
|
||||
forms.append(d)
|
||||
|
||||
return json.dumps(forms)
|
||||
|
|
15
wcs/root.py
15
wcs/root.py
|
@ -233,22 +233,11 @@ class RootDirectory(Directory):
|
|||
return self.saml.slo_sp()
|
||||
|
||||
def user(self):
|
||||
# endpoint for backward compatibility, new code should call /api/user/
|
||||
if get_request().is_json():
|
||||
return self.user_json()
|
||||
return self.api.user._q_index()
|
||||
return redirect('myspace/')
|
||||
|
||||
def user_json(self):
|
||||
get_response().set_content_type('application/json')
|
||||
user = get_user_from_api_query_string() or get_request().user
|
||||
if not user:
|
||||
raise errors.AccessForbiddenError('no user specified')
|
||||
user_info = user.get_substitution_variables(prefix='')
|
||||
del user_info['user']
|
||||
user_info['user_roles'] = []
|
||||
for role_id in user.roles or []:
|
||||
user_info['user_roles'].append(Role.get(role_id).name)
|
||||
return json.dumps(user_info)
|
||||
|
||||
def roles(self):
|
||||
# endpoint for backward compatibility, new code should call /api/roles
|
||||
if not get_request().is_json():
|
||||
|
|
Loading…
Reference in New Issue