geovisio 2.5.0__py3-none-any.whl → 2.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geovisio/__init__.py +38 -8
- geovisio/admin_cli/__init__.py +2 -2
- geovisio/admin_cli/db.py +8 -0
- geovisio/config_app.py +64 -0
- geovisio/db_migrations.py +24 -3
- geovisio/templates/main.html +14 -14
- geovisio/templates/viewer.html +3 -3
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +667 -0
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +730 -0
- geovisio/translations/es/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/es/LC_MESSAGES/messages.po +778 -0
- geovisio/translations/fi/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fi/LC_MESSAGES/messages.po +589 -0
- geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fr/LC_MESSAGES/messages.po +814 -0
- geovisio/translations/ko/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ko/LC_MESSAGES/messages.po +685 -0
- geovisio/translations/messages.pot +686 -0
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +594 -0
- geovisio/utils/__init__.py +1 -1
- geovisio/utils/auth.py +50 -11
- geovisio/utils/db.py +65 -0
- geovisio/utils/excluded_areas.py +83 -0
- geovisio/utils/extent.py +30 -0
- geovisio/utils/fields.py +1 -1
- geovisio/utils/filesystems.py +0 -1
- geovisio/utils/link.py +14 -0
- geovisio/utils/params.py +20 -0
- geovisio/utils/pictures.py +94 -69
- geovisio/utils/reports.py +171 -0
- geovisio/utils/sequences.py +288 -126
- geovisio/utils/tokens.py +37 -42
- geovisio/utils/upload_set.py +654 -0
- geovisio/web/auth.py +50 -37
- geovisio/web/collections.py +305 -319
- geovisio/web/configuration.py +14 -0
- geovisio/web/docs.py +288 -12
- geovisio/web/excluded_areas.py +377 -0
- geovisio/web/items.py +203 -151
- geovisio/web/map.py +322 -106
- geovisio/web/params.py +69 -26
- geovisio/web/pictures.py +14 -31
- geovisio/web/reports.py +399 -0
- geovisio/web/rss.py +13 -7
- geovisio/web/stac.py +129 -121
- geovisio/web/tokens.py +105 -112
- geovisio/web/upload_set.py +768 -0
- geovisio/web/users.py +100 -73
- geovisio/web/utils.py +38 -9
- geovisio/workers/runner_pictures.py +278 -183
- geovisio-2.7.0.dist-info/METADATA +95 -0
- geovisio-2.7.0.dist-info/RECORD +66 -0
- geovisio-2.5.0.dist-info/METADATA +0 -115
- geovisio-2.5.0.dist-info/RECORD +0 -41
- {geovisio-2.5.0.dist-info → geovisio-2.7.0.dist-info}/LICENSE +0 -0
- {geovisio-2.5.0.dist-info → geovisio-2.7.0.dist-info}/WHEEL +0 -0
geovisio/web/stac.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import psycopg
|
|
2
1
|
from psycopg.sql import SQL
|
|
3
2
|
from flask import Blueprint, current_app, request, url_for
|
|
3
|
+
from flask_babel import gettext as _
|
|
4
4
|
from geovisio import errors
|
|
5
|
-
from geovisio.utils import auth
|
|
5
|
+
from geovisio.utils import auth, db
|
|
6
6
|
from psycopg.rows import dict_row
|
|
7
7
|
from geovisio.utils.fields import SortBy, SQLDirection, Bounds, SortByField
|
|
8
8
|
from geovisio.web.utils import (
|
|
@@ -14,6 +14,7 @@ from geovisio.web.utils import (
|
|
|
14
14
|
get_root_link,
|
|
15
15
|
removeNoneInDict,
|
|
16
16
|
user_dependant_response,
|
|
17
|
+
get_api_version,
|
|
17
18
|
)
|
|
18
19
|
from geovisio.utils.sequences import (
|
|
19
20
|
get_collections,
|
|
@@ -57,127 +58,134 @@ def getLanding():
|
|
|
57
58
|
$ref: '#/components/schemas/GeoVisioLanding'
|
|
58
59
|
"""
|
|
59
60
|
|
|
60
|
-
with
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"""
|
|
72
|
-
).fetchone()
|
|
73
|
-
|
|
74
|
-
extent = (
|
|
75
|
-
cleanNoneInDict(
|
|
76
|
-
{
|
|
77
|
-
"spatial": (
|
|
78
|
-
{"bbox": [[spatial_xmin, spatial_ymin, spatial_xmax, spatial_ymax]]} if spatial_xmin is not None else None
|
|
79
|
-
),
|
|
80
|
-
"temporal": (
|
|
81
|
-
{"interval": [[dbTsToStac(temporal_min), dbTsToStac(temporal_max)]]} if temporal_min is not None else None
|
|
82
|
-
),
|
|
83
|
-
}
|
|
84
|
-
)
|
|
85
|
-
if spatial_xmin is not None or temporal_min is not None
|
|
86
|
-
else None
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
sequences = [
|
|
90
|
-
{"rel": "child", "title": f'User "{s[1]}" sequences', "href": url_for("stac.getUserCatalog", userId=s[0], _external=True)}
|
|
91
|
-
for s in cursor.execute(
|
|
92
|
-
"""
|
|
93
|
-
SELECT DISTINCT s.account_id, a.name
|
|
94
|
-
FROM sequences s
|
|
95
|
-
JOIN accounts a ON s.account_id = a.id
|
|
96
|
-
"""
|
|
97
|
-
).fetchall()
|
|
98
|
-
]
|
|
61
|
+
with db.cursor(current_app) as cursor:
|
|
62
|
+
spatial_xmin, spatial_ymin, spatial_xmax, spatial_ymax, temporal_min, temporal_max = cursor.execute(
|
|
63
|
+
"""SELECT
|
|
64
|
+
GREATEST(-180, ST_XMin(ST_EstimatedExtent('pictures', 'geom'))),
|
|
65
|
+
GREATEST(-90, ST_YMin(ST_EstimatedExtent('pictures', 'geom'))),
|
|
66
|
+
LEAST(180, ST_XMax(ST_EstimatedExtent('pictures', 'geom'))),
|
|
67
|
+
LEAST(90, ST_YMax(ST_EstimatedExtent('pictures', 'geom'))),
|
|
68
|
+
MIN(ts), MAX(ts)
|
|
69
|
+
FROM pictures
|
|
70
|
+
"""
|
|
71
|
+
).fetchone()
|
|
99
72
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
73
|
+
extent = (
|
|
74
|
+
cleanNoneInDict(
|
|
75
|
+
{
|
|
76
|
+
"spatial": ({"bbox": [[spatial_xmin, spatial_ymin, spatial_xmax, spatial_ymax]]} if spatial_xmin is not None else None),
|
|
77
|
+
"temporal": (
|
|
78
|
+
{"interval": [[dbTsToStac(temporal_min), dbTsToStac(temporal_max)]]} if temporal_min is not None else None
|
|
79
|
+
),
|
|
80
|
+
}
|
|
107
81
|
)
|
|
82
|
+
if spatial_xmin is not None or temporal_min is not None
|
|
83
|
+
else None
|
|
84
|
+
)
|
|
108
85
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.replace("111", "{x}")
|
|
118
|
-
.replace("222", "{y}")
|
|
119
|
-
.replace("333", "{z}")
|
|
120
|
-
.replace("bob", "{userId}")
|
|
121
|
-
)
|
|
86
|
+
catalog = dbSequencesToStacCatalog(
|
|
87
|
+
id="geovisio",
|
|
88
|
+
title=current_app.config["API_SUMMARY"].name.get("en"),
|
|
89
|
+
description=current_app.config["API_SUMMARY"].description.get("en"),
|
|
90
|
+
sequences=[],
|
|
91
|
+
request=request,
|
|
92
|
+
extent=extent,
|
|
93
|
+
)
|
|
122
94
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
95
|
+
catalog["geovisio_version"] = get_api_version()
|
|
96
|
+
|
|
97
|
+
mapUrl = (
|
|
98
|
+
url_for("map.getTile", x="11111", y="22222", z="33333", format="mvt", _external=True)
|
|
99
|
+
.replace("11111", "{x}")
|
|
100
|
+
.replace("22222", "{y}")
|
|
101
|
+
.replace("33333", "{z}")
|
|
102
|
+
)
|
|
103
|
+
userMapUrl = (
|
|
104
|
+
url_for("map.getUserTile", userId="bob", x="11111", y="22222", z="33333", format="mvt", _external=True)
|
|
105
|
+
.replace("11111", "{x}")
|
|
106
|
+
.replace("22222", "{y}")
|
|
107
|
+
.replace("33333", "{z}")
|
|
108
|
+
.replace("bob", "{userId}")
|
|
109
|
+
)
|
|
110
|
+
userStyleUrl = url_for("map.getUserStyle", userId="bob", _external=True).replace("bob", "{userId}")
|
|
111
|
+
|
|
112
|
+
if "stac_extensions" not in catalog:
|
|
113
|
+
catalog["stac_extensions"] = []
|
|
114
|
+
|
|
115
|
+
catalog["stac_extensions"] += ["https://stac-extensions.github.io/web-map-links/v1.0.0/schema.json"]
|
|
116
|
+
|
|
117
|
+
catalog["links"] += cleanNoneInList(
|
|
118
|
+
[
|
|
119
|
+
{"rel": "service-desc", "type": "application/json", "href": url_for("flasgger.swagger", _external=True)},
|
|
120
|
+
{"rel": "service-doc", "type": "text/html", "href": url_for("flasgger.apidocs", _external=True)},
|
|
121
|
+
{"rel": "conformance", "type": "application/json", "href": url_for("stac.getConformance", _external=True)},
|
|
122
|
+
{"rel": "data", "type": "application/json", "href": url_for("stac_collections.getAllCollections", _external=True)},
|
|
123
|
+
{"rel": "child", "type": "application/json", "href": url_for("user.listUsers", _external=True)},
|
|
124
|
+
{
|
|
125
|
+
"rel": "data",
|
|
126
|
+
"type": "application/rss+xml",
|
|
127
|
+
"href": url_for("stac_collections.getAllCollections", _external=True, format="rss"),
|
|
128
|
+
},
|
|
129
|
+
{"rel": "search", "type": "application/geo+json", "href": url_for("stac_items.searchItems", _external=True)},
|
|
130
|
+
{
|
|
131
|
+
"rel": "xyz",
|
|
132
|
+
"type": "application/vnd.mapbox-vector-tile",
|
|
133
|
+
"href": mapUrl,
|
|
134
|
+
"title": "Pictures and sequences vector tiles",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"rel": "xyz-style",
|
|
138
|
+
"type": "application/json",
|
|
139
|
+
"href": url_for("map.getStyle", _external=True),
|
|
140
|
+
"title": "MapLibre Style JSON",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"rel": "user-xyz",
|
|
144
|
+
"type": "application/vnd.mapbox-vector-tile",
|
|
145
|
+
"href": userMapUrl,
|
|
146
|
+
"title": "Pictures and sequences vector tiles for a given user",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"rel": "user-xyz-style",
|
|
150
|
+
"type": "application/json",
|
|
151
|
+
"href": userStyleUrl,
|
|
152
|
+
"title": "MapLibre Style JSON",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"rel": "collection-preview",
|
|
156
|
+
"type": "image/jpeg",
|
|
157
|
+
"href": url_for("stac_collections.getCollectionThumbnail", collectionId="{id}", _external=True),
|
|
158
|
+
"title": "Thumbnail URL for a given sequence",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"rel": "item-preview",
|
|
162
|
+
"type": "image/jpeg",
|
|
163
|
+
"href": url_for("pictures.getPictureThumb", pictureId="{id}", format="jpg", _external=True),
|
|
164
|
+
"title": "Thumbnail URL for a given picture",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"rel": "users",
|
|
168
|
+
"type": "application/json",
|
|
169
|
+
"href": url_for("user.listUsers", _external=True),
|
|
170
|
+
"title": "List of users",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"rel": "user-search",
|
|
174
|
+
"type": "application/json",
|
|
175
|
+
"href": url_for("user.searchUser", _external=True),
|
|
176
|
+
"title": "Search users",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"rel": "report",
|
|
180
|
+
"type": "application/json",
|
|
181
|
+
"href": url_for("reports.postReport", _external=True),
|
|
182
|
+
"title": "Post feedback/report about picture or sequence",
|
|
183
|
+
},
|
|
184
|
+
get_license_link(),
|
|
185
|
+
]
|
|
186
|
+
)
|
|
179
187
|
|
|
180
|
-
|
|
188
|
+
return catalog, 200, {"Content-Type": "application/json"}
|
|
181
189
|
|
|
182
190
|
|
|
183
191
|
@bp.route("/conformance")
|
|
@@ -322,11 +330,11 @@ def getUserCatalog(userId, userIdMatchesAccount=False):
|
|
|
322
330
|
|
|
323
331
|
userName = None
|
|
324
332
|
meta_collection = None
|
|
325
|
-
with
|
|
333
|
+
with db.cursor(current_app, row_factory=dict_row) as cursor:
|
|
326
334
|
userName = cursor.execute("SELECT name FROM accounts WHERE id = %s", [userId]).fetchone()
|
|
327
335
|
|
|
328
336
|
if not userName:
|
|
329
|
-
raise errors.InvalidAPIUsage(
|
|
337
|
+
raise errors.InvalidAPIUsage(_("Impossible to find user %(u)s", u=userId))
|
|
330
338
|
userName = userName["name"]
|
|
331
339
|
|
|
332
340
|
meta_collection = cursor.execute(
|
|
@@ -336,7 +344,7 @@ def getUserCatalog(userId, userIdMatchesAccount=False):
|
|
|
336
344
|
|
|
337
345
|
if not meta_collection or meta_collection["min_order"] is None:
|
|
338
346
|
# No data found, trying to give the most meaningfull error message
|
|
339
|
-
raise errors.InvalidAPIUsage(
|
|
347
|
+
raise errors.InvalidAPIUsage(_("No data loaded for user %(u)s", u=userId), 404)
|
|
340
348
|
|
|
341
349
|
db_collections = get_collections(collection_request)
|
|
342
350
|
|
geovisio/web/tokens.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import flask
|
|
2
2
|
from flask import current_app, url_for, request
|
|
3
|
+
from flask_babel import gettext as _
|
|
3
4
|
from dateutil import tz
|
|
4
|
-
import psycopg
|
|
5
5
|
from psycopg.rows import dict_row
|
|
6
6
|
from authlib.jose import jwt
|
|
7
7
|
from authlib.jose.errors import DecodeError
|
|
8
8
|
import logging
|
|
9
9
|
import uuid
|
|
10
|
-
from geovisio.utils import auth
|
|
10
|
+
from geovisio.utils import auth, db
|
|
11
11
|
from geovisio import errors, utils
|
|
12
12
|
|
|
13
13
|
|
|
@@ -19,44 +19,46 @@ bp = flask.Blueprint("tokens", __name__, url_prefix="/api")
|
|
|
19
19
|
def list_tokens(account):
|
|
20
20
|
"""
|
|
21
21
|
List the tokens of a authenticated user
|
|
22
|
+
|
|
23
|
+
The list of tokens will not contain their JWT counterpart (the JWT is the real token used in authentication).
|
|
24
|
+
|
|
25
|
+
The JWT counterpart can be retreived by providing the token's id to the endpoint [/users/me/tokens/{token_id}](#/Auth/get_api_users_me_tokens__token_id_).
|
|
22
26
|
---
|
|
23
27
|
tags:
|
|
24
28
|
- Auth
|
|
25
29
|
- Users
|
|
26
30
|
responses:
|
|
27
31
|
200:
|
|
28
|
-
description:
|
|
32
|
+
description: The list of tokens, without their JWT counterpart.
|
|
29
33
|
content:
|
|
30
34
|
application/json:
|
|
31
35
|
schema:
|
|
32
36
|
$ref: '#/components/schemas/GeoVisioTokens'
|
|
33
37
|
"""
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
records = db.fetchall(
|
|
40
|
+
current_app,
|
|
41
|
+
"SELECT id, description, generated_at FROM tokens WHERE account_id = %(account)s",
|
|
42
|
+
{"account": account.id},
|
|
43
|
+
row_factory=dict_row,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
tokens = [
|
|
47
|
+
{
|
|
48
|
+
"id": r["id"],
|
|
49
|
+
"description": r["description"],
|
|
50
|
+
"generated_at": r["generated_at"].astimezone(tz.gettz("UTC")).isoformat(),
|
|
51
|
+
"links": [
|
|
45
52
|
{
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"links": [
|
|
50
|
-
{
|
|
51
|
-
"rel": "self",
|
|
52
|
-
"type": "application/json",
|
|
53
|
-
"href": url_for("tokens.get_jwt_token", token_id=r["id"], _external=True),
|
|
54
|
-
}
|
|
55
|
-
],
|
|
53
|
+
"rel": "self",
|
|
54
|
+
"type": "application/json",
|
|
55
|
+
"href": url_for("tokens.get_jwt_token", token_id=r["id"], _external=True),
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
for r in records
|
|
60
|
+
]
|
|
61
|
+
return flask.jsonify(tokens)
|
|
60
62
|
|
|
61
63
|
|
|
62
64
|
@bp.route("/users/me/tokens/<uuid:token_id>", methods=["GET"])
|
|
@@ -65,7 +67,7 @@ def get_jwt_token(token_id: uuid.UUID, account: auth.Account):
|
|
|
65
67
|
"""
|
|
66
68
|
Get the JWT token corresponding to a token id.
|
|
67
69
|
|
|
68
|
-
This token will be needed to authenticate others api calls
|
|
70
|
+
This JWT token will be needed to authenticate others api calls
|
|
69
71
|
---
|
|
70
72
|
tags:
|
|
71
73
|
- Auth
|
|
@@ -83,31 +85,29 @@ def get_jwt_token(token_id: uuid.UUID, account: auth.Account):
|
|
|
83
85
|
content:
|
|
84
86
|
application/json:
|
|
85
87
|
schema:
|
|
86
|
-
$ref: '#/components/schemas/
|
|
88
|
+
$ref: '#/components/schemas/GeoVisioEncodedToken'
|
|
87
89
|
"""
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
)
|
|
91
|
+
token = db.fetchone(
|
|
92
|
+
current_app,
|
|
93
|
+
"SELECT id, description, generated_at FROM tokens WHERE account_id = %(account)s AND id = %(token)s",
|
|
94
|
+
{"account": account.id, "token": token_id},
|
|
95
|
+
row_factory=dict_row,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# check token existence
|
|
99
|
+
if not token:
|
|
100
|
+
raise errors.InvalidAPIUsage(_("Impossible to find token"), status_code=404)
|
|
101
|
+
|
|
102
|
+
jwt_token = _generate_jwt_token(token["id"])
|
|
103
|
+
return flask.jsonify(
|
|
104
|
+
{
|
|
105
|
+
"jwt_token": jwt_token,
|
|
106
|
+
"id": token["id"],
|
|
107
|
+
"description": token["description"],
|
|
108
|
+
"generated_at": token["generated_at"].astimezone(tz.gettz("UTC")).isoformat(),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
@bp.route("/users/me/tokens/<uuid:token_id>", methods=["DELETE"])
|
|
@@ -132,22 +132,16 @@ def revoke_token(token_id: uuid.UUID, account: auth.Account):
|
|
|
132
132
|
description: The token has been correctly deleted
|
|
133
133
|
"""
|
|
134
134
|
|
|
135
|
-
with
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
""",
|
|
142
|
-
{"account": account.id, "token": token_id},
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
token_deleted = res.rowcount
|
|
135
|
+
with db.execute(
|
|
136
|
+
current_app,
|
|
137
|
+
"DELETE FROM tokens WHERE account_id = %(account)s AND id = %(token)s",
|
|
138
|
+
{"account": account.id, "token": token_id},
|
|
139
|
+
) as res:
|
|
140
|
+
token_deleted = res.rowcount
|
|
146
141
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return flask.jsonify({"message": "token revoked"}), 200
|
|
142
|
+
if not token_deleted:
|
|
143
|
+
raise errors.InvalidAPIUsage(_("Impossible to find token"), status_code=404)
|
|
144
|
+
return flask.jsonify({"message": "token revoked"}), 200
|
|
151
145
|
|
|
152
146
|
|
|
153
147
|
@bp.route("/auth/tokens/generate", methods=["POST"])
|
|
@@ -176,30 +170,31 @@ def generate_non_associated_token():
|
|
|
176
170
|
$ref: '#/components/schemas/JWTokenClaimable'
|
|
177
171
|
"""
|
|
178
172
|
description = request.args.get("description", "")
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
],
|
|
173
|
+
|
|
174
|
+
token = db.fetchone(
|
|
175
|
+
current_app,
|
|
176
|
+
"INSERT INTO tokens (description) VALUES (%(description)s) RETURNING *",
|
|
177
|
+
{"description": description},
|
|
178
|
+
row_factory=dict_row,
|
|
179
|
+
)
|
|
180
|
+
if not token:
|
|
181
|
+
raise errors.InternalError(_("Impossible to generate a new token"))
|
|
182
|
+
|
|
183
|
+
jwt_token = _generate_jwt_token(token["id"])
|
|
184
|
+
token = {
|
|
185
|
+
"id": token["id"],
|
|
186
|
+
"jwt_token": jwt_token,
|
|
187
|
+
"description": token["description"],
|
|
188
|
+
"generated_at": token["generated_at"].astimezone(tz.gettz("UTC")).isoformat(),
|
|
189
|
+
"links": [
|
|
190
|
+
{
|
|
191
|
+
"rel": "claim",
|
|
192
|
+
"type": "application/json",
|
|
193
|
+
"href": url_for("tokens.claim_non_associated_token", token_id=token["id"], _external=True),
|
|
201
194
|
}
|
|
202
|
-
|
|
195
|
+
],
|
|
196
|
+
}
|
|
197
|
+
return flask.jsonify(token)
|
|
203
198
|
|
|
204
199
|
|
|
205
200
|
@bp.route("/auth/tokens/<uuid:token_id>/claim", methods=["GET"])
|
|
@@ -225,30 +220,28 @@ def claim_non_associated_token(token_id, account):
|
|
|
225
220
|
200:
|
|
226
221
|
description: The token has been correctly associated to the account
|
|
227
222
|
"""
|
|
228
|
-
with
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if associated_account:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
conn.commit()
|
|
251
|
-
return "You are now logged in the CLI, you can upload your pictures", 200
|
|
223
|
+
with db.cursor(current_app, row_factory=dict_row) as cursor:
|
|
224
|
+
token = cursor.execute(
|
|
225
|
+
"""
|
|
226
|
+
SELECT account_id FROM tokens WHERE id = %(token)s
|
|
227
|
+
""",
|
|
228
|
+
{"token": token_id},
|
|
229
|
+
).fetchone()
|
|
230
|
+
if not token:
|
|
231
|
+
raise errors.InvalidAPIUsage(_("Impossible to find token"), status_code=404)
|
|
232
|
+
|
|
233
|
+
associated_account = token["account_id"]
|
|
234
|
+
if associated_account:
|
|
235
|
+
if associated_account != account.id:
|
|
236
|
+
raise errors.InvalidAPIUsage(_("Token already claimed by another account"), status_code=403)
|
|
237
|
+
else:
|
|
238
|
+
return flask.jsonify({"message": "token already associated to account"}), 200
|
|
239
|
+
|
|
240
|
+
cursor.execute(
|
|
241
|
+
"UPDATE tokens SET account_id = %(account)s WHERE id = %(token)s",
|
|
242
|
+
{"account": account.id, "token": token_id},
|
|
243
|
+
)
|
|
244
|
+
return "You are now logged in the CLI, you can upload your pictures", 200
|
|
252
245
|
|
|
253
246
|
|
|
254
247
|
def _generate_jwt_token(token_id: uuid.UUID) -> str:
|