c2cgeoportal-commons 2.6.0__py3-none-any.whl → 2.8.1.180__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.
Potentially problematic release.
This version of c2cgeoportal-commons might be problematic. Click here for more details.
- c2cgeoportal_commons/__init__.py +2 -5
- c2cgeoportal_commons/alembic/env.py +40 -28
- c2cgeoportal_commons/alembic/main/028477929d13_add_technical_roles.py +10 -7
- c2cgeoportal_commons/alembic/main/04f05bfbb05e_remove_the_old_is_expanded_column.py +60 -0
- c2cgeoportal_commons/alembic/main/116b9b79fc4d_internal_and_external_layer_tables_.py +42 -43
- c2cgeoportal_commons/alembic/main/1418cb05921b_merge_1_6_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/164ac0819a61_add_image_format_to_wmts_layer.py +9 -6
- c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py +21 -24
- c2cgeoportal_commons/alembic/main/16e43f8c0330_remove_old_metadata_column.py +9 -6
- c2cgeoportal_commons/alembic/main/1d5d4abfebd1_add_restricted_theme.py +14 -8
- c2cgeoportal_commons/alembic/main/1de20166b274_remove_v1_artifacts.py +9 -6
- c2cgeoportal_commons/alembic/main/20137477bd02_update_icons_url.py +9 -6
- c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py +21 -20
- c2cgeoportal_commons/alembic/main/22e6dfb556de_add_description_tree_.py +9 -6
- c2cgeoportal_commons/alembic/main/29f2a32859ec_merge_1_6_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/2b8ed8c1df94_set_layergroup_treeitem_is_as_a_primary_.py +9 -6
- c2cgeoportal_commons/alembic/main/32527659d57b_move_exclude_properties_from_layerv1_to_.py +15 -12
- c2cgeoportal_commons/alembic/main/32b21aa1d0ed_merge_e004f76e951a_and_e004f76e951a_.py +7 -8
- c2cgeoportal_commons/alembic/main/338b57593823_remove_trigger_on_role_name_change.py +25 -24
- c2cgeoportal_commons/alembic/main/415746eb9f6_changes_for_v2.py +45 -43
- c2cgeoportal_commons/alembic/main/44c91d82d419_add_table_log.py +70 -0
- c2cgeoportal_commons/alembic/main/5109242131ce_add_column_time_widget.py +10 -9
- c2cgeoportal_commons/alembic/main/52916d8fde8b_add_sql_fields_to_vector_tiles.py +58 -0
- c2cgeoportal_commons/alembic/main/53ba1a68d5fe_add_theme_to_fulltextsearch.py +9 -6
- c2cgeoportal_commons/alembic/main/54645a535ad6_add_ordering_in_relation.py +17 -14
- c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py +10 -8
- c2cgeoportal_commons/alembic/main/596ba21e3833_separate_local_internal.py +13 -14
- c2cgeoportal_commons/alembic/main/678f88c7ad5e_add_vector_tiles_layers_table.py +9 -6
- c2cgeoportal_commons/alembic/main/6a412d9437b1_rename_serverogc_to_ogcserver.py +9 -6
- c2cgeoportal_commons/alembic/main/6d87fdad275a_convert_metadata_to_the_right_case.py +13 -20
- c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py +21 -20
- c2cgeoportal_commons/alembic/main/78fd093c8393_add_api_s_intrfaces.py +27 -48
- c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py +9 -6
- c2cgeoportal_commons/alembic/main/809650bd04c3_add_dimension_field.py +9 -6
- c2cgeoportal_commons/alembic/main/8117bb9bba16_use_dimension_on_all_the_layers.py +9 -6
- c2cgeoportal_commons/alembic/main/87f8330ed64e_add_missing_delete_cascades.py +9 -6
- c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py +11 -8
- c2cgeoportal_commons/alembic/main/94db7e7e5b21_merge_2_2_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/951ff84bd8ec_be_able_to_delete_a_wms_layer_in_sql.py +9 -6
- c2cgeoportal_commons/alembic/main/a00109812f89_add_field_layer_wms_valid.py +9 -6
- c2cgeoportal_commons/alembic/main/a4f1aac9bda_merge_1_6_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/b60f2a505f42_remame_uimetadata_to_metadata.py +9 -6
- c2cgeoportal_commons/alembic/main/c75124553bf3_remove_deprecated_columns.py +9 -6
- c2cgeoportal_commons/alembic/main/d48a63b348f1_change_mapserver_url_for_docker.py +13 -14
- c2cgeoportal_commons/alembic/main/d8ef99bc227e_be_able_to_delete_a_linked_functionality.py +15 -12
- c2cgeoportal_commons/alembic/main/daf738d5bae4_merge_2_0_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/dba87f2647f9_merge_2_2_on_2_3.py +7 -8
- c2cgeoportal_commons/alembic/main/e004f76e951a_add_missing_not_null.py +15 -32
- c2cgeoportal_commons/alembic/main/e7e03dedade3_put_the_default_wms_server_in_the_.py +13 -14
- c2cgeoportal_commons/alembic/main/e85afd327ab3_cascade_deletes_to_tsearch.py +9 -6
- c2cgeoportal_commons/alembic/main/ec82a8906649_add_missing_on_delete_cascade_on_layer_.py +13 -10
- c2cgeoportal_commons/alembic/main/ee25d267bf46_main_interface_desktop.py +11 -14
- c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/static/0c640a58a09a_add_opt_key_column.py +9 -6
- c2cgeoportal_commons/alembic/static/107b81f5b9fe_add_missing_delete_cascades.py +9 -6
- c2cgeoportal_commons/alembic/static/1857owc78a07_add_last_login_and_expiration_date.py +7 -6
- c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py +18 -19
- c2cgeoportal_commons/alembic/static/3f89a7d71a5e_alter_column_url_to_remove_limitation.py +8 -7
- c2cgeoportal_commons/alembic/static/44c91d82d419_add_table_log.py +70 -0
- c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py +9 -6
- c2cgeoportal_commons/alembic/static/5472fbc19f39_add_temp_password_column.py +9 -6
- c2cgeoportal_commons/alembic/static/76d72fb3fcb9_add_oauth2_pkce.py +68 -0
- c2cgeoportal_commons/alembic/static/7ef947f30f20_add_oauth2_tables.py +7 -6
- c2cgeoportal_commons/alembic/static/ae5e88f35669_add_table_user_role.py +15 -18
- c2cgeoportal_commons/alembic/static/bd029dbfc11a_fill_tech_data_column.py +10 -8
- c2cgeoportal_commons/lib/email_.py +11 -13
- c2cgeoportal_commons/lib/literal.py +44 -0
- c2cgeoportal_commons/lib/url.py +164 -57
- c2cgeoportal_commons/lib/validators.py +2 -4
- c2cgeoportal_commons/models/__init__.py +7 -10
- c2cgeoportal_commons/models/main.py +968 -127
- c2cgeoportal_commons/models/sqlalchemy.py +13 -18
- c2cgeoportal_commons/models/static.py +212 -34
- c2cgeoportal_commons/py.typed +0 -0
- c2cgeoportal_commons/testing/__init__.py +11 -10
- c2cgeoportal_commons/testing/initializedb.py +12 -12
- {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/METADATA +16 -11
- c2cgeoportal_commons-2.8.1.180.dist-info/RECORD +83 -0
- {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/WHEEL +1 -1
- tests/conftest.py +0 -2
- c2cgeoportal_commons-2.6.0.dist-info/RECORD +0 -76
- {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
# Copyright (c) 2012-2021, Camptocamp SA
|
|
1
|
+
# Copyright (c) 2012-2023, Camptocamp SA
|
|
4
2
|
# All rights reserved.
|
|
5
3
|
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -27,7 +25,7 @@
|
|
|
27
25
|
# of the authors and should not be interpreted as representing official policies,
|
|
28
26
|
# either expressed or implied, of the FreeBSD Project.
|
|
29
27
|
import json
|
|
30
|
-
from typing import Any, Optional, Type
|
|
28
|
+
from typing import Any, Dict, Optional, Type
|
|
31
29
|
|
|
32
30
|
from sqlalchemy.engine import Dialect
|
|
33
31
|
from sqlalchemy.types import VARCHAR, TypeDecorator, UserDefinedType
|
|
@@ -35,37 +33,34 @@ from sqlalchemy.types import VARCHAR, TypeDecorator, UserDefinedType
|
|
|
35
33
|
|
|
36
34
|
# get from https://docs.sqlalchemy.org/en/latest/orm/extensions/
|
|
37
35
|
# mutable.html#establishing-mutability-on-scalar-column-values
|
|
38
|
-
class JSONEncodedDict(TypeDecorator):
|
|
39
|
-
"""
|
|
40
|
-
Represents an immutable structure as a json-encoded string.
|
|
41
|
-
"""
|
|
36
|
+
class JSONEncodedDict(TypeDecorator): # type: ignore
|
|
37
|
+
"""Represent an immutable structure as a json-encoded string."""
|
|
42
38
|
|
|
43
39
|
impl = VARCHAR
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
def process_bind_param(value: Optional[dict], _: Dialect) -> Optional[str]:
|
|
41
|
+
def process_bind_param(self, value: Optional[Dict[str, Any]], _: Dialect) -> Optional[str]:
|
|
47
42
|
return json.dumps(value) if value is not None else None
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
def process_result_value(value: Optional[str], _: Dialect) -> Optional[dict]:
|
|
44
|
+
def process_result_value(self, value: Optional[str], _: Dialect) -> Optional[Dict[str, Any]]:
|
|
51
45
|
return json.loads(value) if value is not None else None
|
|
52
46
|
|
|
53
47
|
@property
|
|
54
|
-
def python_type(self) -> Type:
|
|
48
|
+
def python_type(self) -> Type[Any]:
|
|
55
49
|
return dict
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
def process_literal_param(value: str, dialect: Any) -> str:
|
|
51
|
+
def process_literal_param(self, value: str, dialect: Any) -> str:
|
|
59
52
|
del dialect
|
|
60
53
|
return json.dumps(value)
|
|
61
54
|
|
|
62
55
|
|
|
63
|
-
class TsVector(UserDefinedType):
|
|
56
|
+
class TsVector(UserDefinedType): # type: ignore
|
|
64
57
|
"""A custom type for PostgreSQL's tsvector type."""
|
|
65
58
|
|
|
66
|
-
|
|
59
|
+
cache_ok = True
|
|
60
|
+
|
|
61
|
+
def get_col_spec(self) -> str:
|
|
67
62
|
return "TSVECTOR"
|
|
68
63
|
|
|
69
64
|
@property
|
|
70
|
-
def python_type(self) -> Type:
|
|
65
|
+
def python_type(self) -> Type[Any]:
|
|
71
66
|
return dict
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
# Copyright (c) 2011-2021, Camptocamp SA
|
|
1
|
+
# Copyright (c) 2011-2023, Camptocamp SA
|
|
4
2
|
# All rights reserved.
|
|
5
3
|
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -33,7 +31,7 @@ import logging
|
|
|
33
31
|
from datetime import datetime
|
|
34
32
|
from hashlib import sha1
|
|
35
33
|
from hmac import compare_digest as compare_hash
|
|
36
|
-
from typing import Any, List
|
|
34
|
+
from typing import Any, List, Optional
|
|
37
35
|
|
|
38
36
|
import pytz
|
|
39
37
|
import sqlalchemy.schema
|
|
@@ -44,8 +42,9 @@ from sqlalchemy.ext.mutable import MutableDict
|
|
|
44
42
|
from sqlalchemy.orm import backref, relationship
|
|
45
43
|
from sqlalchemy.types import Boolean, DateTime, Integer, String, Unicode
|
|
46
44
|
|
|
45
|
+
from c2cgeoportal_commons.lib.literal import Literal
|
|
47
46
|
from c2cgeoportal_commons.models import Base, _
|
|
48
|
-
from c2cgeoportal_commons.models.main import Role
|
|
47
|
+
from c2cgeoportal_commons.models.main import AbstractLog, Role
|
|
49
48
|
|
|
50
49
|
try:
|
|
51
50
|
from c2cgeoform.ext.deform_ext import RelationSelect2Widget
|
|
@@ -55,6 +54,8 @@ except ModuleNotFoundError:
|
|
|
55
54
|
drop = None
|
|
56
55
|
|
|
57
56
|
class GenericClass:
|
|
57
|
+
"""Generic class."""
|
|
58
|
+
|
|
58
59
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
59
60
|
pass
|
|
60
61
|
|
|
@@ -79,10 +80,48 @@ user_role = Table(
|
|
|
79
80
|
)
|
|
80
81
|
|
|
81
82
|
|
|
82
|
-
class User(Base):
|
|
83
|
+
class User(Base): # type: ignore
|
|
84
|
+
"""The user table representation."""
|
|
85
|
+
|
|
83
86
|
__tablename__ = "user"
|
|
84
87
|
__table_args__ = {"schema": _schema}
|
|
85
|
-
__colanderalchemy_config__ = {
|
|
88
|
+
__colanderalchemy_config__ = {
|
|
89
|
+
"title": _("User"),
|
|
90
|
+
"plural": _("Users"),
|
|
91
|
+
"description": Literal(
|
|
92
|
+
_(
|
|
93
|
+
"""
|
|
94
|
+
<div class="help-block">
|
|
95
|
+
<p>Each user may have from 1 to n roles, but each user has a default role from
|
|
96
|
+
which are taken some settings. The default role (defined through the
|
|
97
|
+
"Settings from role" selection) has an influence on the role extent and on some
|
|
98
|
+
functionalities regarding their configuration.</p>
|
|
99
|
+
|
|
100
|
+
<p>Role extents for users can only be set in one role, because the application
|
|
101
|
+
is currently not able to check multiple extents for one user, thus it is the
|
|
102
|
+
default role which defines this unique extent.</p>
|
|
103
|
+
|
|
104
|
+
<p>Any functionality specified as <b>single</b> can be defined only once per user.
|
|
105
|
+
Hence, these functionalities have to be defined in the default role.</p>
|
|
106
|
+
|
|
107
|
+
<p>By default, functionalities are not specified as <b>single</b>. Currently, the
|
|
108
|
+
following functionalities are of <b>single</b> type:</p>
|
|
109
|
+
|
|
110
|
+
<ul>
|
|
111
|
+
<li><code>default_basemap</code></li>
|
|
112
|
+
<li><code>default_theme</code></li>
|
|
113
|
+
<li><code>preset_layer_filter</code></li>
|
|
114
|
+
<li><code>open_panel</code></li>
|
|
115
|
+
</ul>
|
|
116
|
+
|
|
117
|
+
<p>Any other functionality (with <b>single</b> not set or set to <code>false</code>) can
|
|
118
|
+
be defined in any role linked to the user.</p>
|
|
119
|
+
<hr>
|
|
120
|
+
</div>
|
|
121
|
+
"""
|
|
122
|
+
)
|
|
123
|
+
),
|
|
124
|
+
}
|
|
86
125
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
87
126
|
item_type = Column(
|
|
88
127
|
"type", String(10), nullable=False, info={"colanderalchemy": {"widget": HiddenWidget()}}
|
|
@@ -91,7 +130,15 @@ class User(Base):
|
|
|
91
130
|
|
|
92
131
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
93
132
|
username = Column(
|
|
94
|
-
Unicode,
|
|
133
|
+
Unicode,
|
|
134
|
+
unique=True,
|
|
135
|
+
nullable=False,
|
|
136
|
+
info={
|
|
137
|
+
"colanderalchemy": {
|
|
138
|
+
"title": _("Username"),
|
|
139
|
+
"description": _("Name used for authentication (must be unique)."),
|
|
140
|
+
}
|
|
141
|
+
},
|
|
95
142
|
)
|
|
96
143
|
_password = Column("password", Unicode, nullable=False, info={"colanderalchemy": {"exclude": True}})
|
|
97
144
|
temp_password = Column(
|
|
@@ -99,10 +146,27 @@ class User(Base):
|
|
|
99
146
|
)
|
|
100
147
|
tech_data = Column(MutableDict.as_mutable(HSTORE), info={"colanderalchemy": {"exclude": True}})
|
|
101
148
|
email = Column(
|
|
102
|
-
Unicode,
|
|
149
|
+
Unicode,
|
|
150
|
+
nullable=False,
|
|
151
|
+
info={
|
|
152
|
+
"colanderalchemy": {
|
|
153
|
+
"title": _("Email"),
|
|
154
|
+
"description": _(
|
|
155
|
+
"Used to send emails to the user, for example in case of password recovery."
|
|
156
|
+
),
|
|
157
|
+
"validator": Email(),
|
|
158
|
+
}
|
|
159
|
+
},
|
|
103
160
|
)
|
|
104
161
|
is_password_changed = Column(
|
|
105
|
-
Boolean,
|
|
162
|
+
Boolean,
|
|
163
|
+
default=False,
|
|
164
|
+
info={
|
|
165
|
+
"colanderalchemy": {
|
|
166
|
+
"title": _("The user changed his password"),
|
|
167
|
+
"description": _("Indicates if user has changed his password."),
|
|
168
|
+
}
|
|
169
|
+
},
|
|
106
170
|
)
|
|
107
171
|
|
|
108
172
|
settings_role_id = Column(
|
|
@@ -110,7 +174,7 @@ class User(Base):
|
|
|
110
174
|
info={
|
|
111
175
|
"colanderalchemy": {
|
|
112
176
|
"title": _("Settings from role"),
|
|
113
|
-
"description": "
|
|
177
|
+
"description": _("Used to get some settings for the user (not for permissions)."),
|
|
114
178
|
"widget": RelationSelect2Widget(
|
|
115
179
|
Role, "id", "name", order_by="name", default_value=("", _("- Select -"))
|
|
116
180
|
),
|
|
@@ -129,8 +193,24 @@ class User(Base):
|
|
|
129
193
|
Role,
|
|
130
194
|
secondary=user_role,
|
|
131
195
|
secondaryjoin=Role.id == user_role.c.role_id,
|
|
132
|
-
backref=backref(
|
|
133
|
-
|
|
196
|
+
backref=backref(
|
|
197
|
+
"users",
|
|
198
|
+
order_by="User.username",
|
|
199
|
+
info={
|
|
200
|
+
"colanderalchemy": {
|
|
201
|
+
"title": _("Users"),
|
|
202
|
+
"description": _("Users granted with this role."),
|
|
203
|
+
"exclude": True,
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
),
|
|
207
|
+
info={
|
|
208
|
+
"colanderalchemy": {
|
|
209
|
+
"title": _("Roles"),
|
|
210
|
+
"description": _("Roles granted to the user."),
|
|
211
|
+
"exclude": True,
|
|
212
|
+
}
|
|
213
|
+
},
|
|
134
214
|
)
|
|
135
215
|
|
|
136
216
|
last_login = Column(
|
|
@@ -138,25 +218,43 @@ class User(Base):
|
|
|
138
218
|
info={
|
|
139
219
|
"colanderalchemy": {
|
|
140
220
|
"title": _("Last login"),
|
|
221
|
+
"description": _("Date of the user's last login."),
|
|
141
222
|
"missing": drop,
|
|
142
223
|
"widget": DateTimeInputWidget(readonly=True),
|
|
143
224
|
}
|
|
144
225
|
},
|
|
145
226
|
)
|
|
146
227
|
|
|
147
|
-
expire_on = Column(
|
|
228
|
+
expire_on = Column(
|
|
229
|
+
DateTime(timezone=True),
|
|
230
|
+
info={
|
|
231
|
+
"colanderalchemy": {
|
|
232
|
+
"title": _("Expiration date"),
|
|
233
|
+
"description": _("After this date the user will not be able to login anymore."),
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
)
|
|
148
237
|
|
|
149
|
-
deactivated = Column(
|
|
238
|
+
deactivated = Column(
|
|
239
|
+
Boolean,
|
|
240
|
+
default=False,
|
|
241
|
+
info={
|
|
242
|
+
"colanderalchemy": {
|
|
243
|
+
"title": _("Deactivated"),
|
|
244
|
+
"description": _("Deactivate a user without removing it completely."),
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
)
|
|
150
248
|
|
|
151
|
-
def __init__(
|
|
249
|
+
def __init__( # nosec
|
|
152
250
|
self,
|
|
153
251
|
username: str = "",
|
|
154
252
|
password: str = "",
|
|
155
253
|
email: str = "",
|
|
156
254
|
is_password_changed: bool = False,
|
|
157
|
-
settings_role: Role = None,
|
|
158
|
-
roles: List[Role] = None,
|
|
159
|
-
expire_on: datetime = None,
|
|
255
|
+
settings_role: Optional[Role] = None,
|
|
256
|
+
roles: Optional[List[Role]] = None,
|
|
257
|
+
expire_on: Optional[datetime] = None,
|
|
160
258
|
deactivated: bool = False,
|
|
161
259
|
) -> None:
|
|
162
260
|
self.username = username
|
|
@@ -172,16 +270,16 @@ class User(Base):
|
|
|
172
270
|
|
|
173
271
|
@property
|
|
174
272
|
def password(self) -> str:
|
|
175
|
-
"""
|
|
176
|
-
return self._password #
|
|
273
|
+
"""Get the password."""
|
|
274
|
+
return self._password # type: ignore
|
|
177
275
|
|
|
178
276
|
@password.setter
|
|
179
277
|
def password(self, password: str) -> None:
|
|
180
|
-
"""
|
|
278
|
+
"""Encrypt password on the fly."""
|
|
181
279
|
self._password = self.__encrypt_password(password)
|
|
182
280
|
|
|
183
281
|
def set_temp_password(self, password: str) -> None:
|
|
184
|
-
"""
|
|
282
|
+
"""Encrypt password on the fly."""
|
|
185
283
|
self.temp_password = self.__encrypt_password(password)
|
|
186
284
|
|
|
187
285
|
@staticmethod
|
|
@@ -194,8 +292,8 @@ class User(Base):
|
|
|
194
292
|
return crypt.crypt(password, crypt.METHOD_SHA512)
|
|
195
293
|
|
|
196
294
|
def validate_password(self, passwd: str) -> bool:
|
|
197
|
-
"""
|
|
198
|
-
this method _MUST_ return a boolean.
|
|
295
|
+
"""
|
|
296
|
+
Check the password against existing credentials. this method _MUST_ return a boolean.
|
|
199
297
|
|
|
200
298
|
@param passwd: the password that was provided by the user to
|
|
201
299
|
try and authenticate. This is the clear text version that we will
|
|
@@ -214,7 +312,7 @@ class User(Base):
|
|
|
214
312
|
|
|
215
313
|
if (
|
|
216
314
|
self.temp_password is not None
|
|
217
|
-
and self.temp_password != ""
|
|
315
|
+
and self.temp_password != "" # nosec
|
|
218
316
|
and compare_hash(self.temp_password, crypt.crypt(passwd, self.temp_password))
|
|
219
317
|
):
|
|
220
318
|
self._password = self.temp_password
|
|
@@ -230,10 +328,12 @@ class User(Base):
|
|
|
230
328
|
self.last_login = datetime.now(pytz.utc)
|
|
231
329
|
|
|
232
330
|
def __str__(self) -> str:
|
|
233
|
-
return self.username or ""
|
|
331
|
+
return self.username or ""
|
|
234
332
|
|
|
235
333
|
|
|
236
|
-
class Shorturl(Base):
|
|
334
|
+
class Shorturl(Base): # type: ignore
|
|
335
|
+
"""The shorturl table representation."""
|
|
336
|
+
|
|
237
337
|
__tablename__ = "shorturl"
|
|
238
338
|
__table_args__ = {"schema": _schema}
|
|
239
339
|
id = Column(Integer, primary_key=True)
|
|
@@ -245,18 +345,79 @@ class Shorturl(Base):
|
|
|
245
345
|
nb_hits = Column(Integer)
|
|
246
346
|
|
|
247
347
|
|
|
248
|
-
class OAuth2Client(Base):
|
|
348
|
+
class OAuth2Client(Base): # type: ignore
|
|
349
|
+
"""The oauth2_client table representation."""
|
|
350
|
+
|
|
249
351
|
__tablename__ = "oauth2_client"
|
|
250
352
|
__table_args__ = {"schema": _schema}
|
|
251
353
|
__colanderalchemy_config__ = {"title": _("OAuth2 Client"), "plural": _("OAuth2 Clients")}
|
|
252
354
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
253
355
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
254
|
-
client_id = Column(
|
|
255
|
-
|
|
256
|
-
|
|
356
|
+
client_id = Column(
|
|
357
|
+
Unicode,
|
|
358
|
+
unique=True,
|
|
359
|
+
info={
|
|
360
|
+
"colanderalchemy": {
|
|
361
|
+
"title": _("Client ID"),
|
|
362
|
+
"description": _("The client identifier as e.-g. 'qgis'."),
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
)
|
|
366
|
+
secret = Column(
|
|
367
|
+
Unicode,
|
|
368
|
+
info={
|
|
369
|
+
"colanderalchemy": {
|
|
370
|
+
"title": _("Secret"),
|
|
371
|
+
"description": _("The secret."),
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
)
|
|
375
|
+
redirect_uri = Column(
|
|
376
|
+
Unicode,
|
|
377
|
+
info={
|
|
378
|
+
"colanderalchemy": {
|
|
379
|
+
"title": _("Redirect URI"),
|
|
380
|
+
"description": _(
|
|
381
|
+
"""
|
|
382
|
+
URI where user should be redirected after authentication
|
|
383
|
+
as e.-g. 'http://127.0.0.1:7070/' in case of QGIS desktop.
|
|
384
|
+
"""
|
|
385
|
+
),
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
)
|
|
389
|
+
state_required = Column(
|
|
390
|
+
Boolean,
|
|
391
|
+
default=False,
|
|
392
|
+
info={
|
|
393
|
+
"colanderalchemy": {
|
|
394
|
+
"title": _("State required"),
|
|
395
|
+
"description": _(
|
|
396
|
+
"The state is required for this client (see: "
|
|
397
|
+
"https://auth0.com/docs/secure/attack-protection/state-parameters)."
|
|
398
|
+
),
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
)
|
|
402
|
+
pkce_required = Column(
|
|
403
|
+
Boolean,
|
|
404
|
+
default=False,
|
|
405
|
+
info={
|
|
406
|
+
"colanderalchemy": {
|
|
407
|
+
"title": _("PKCE required"),
|
|
408
|
+
"description": _(
|
|
409
|
+
"PKCE is required for this client (see: "
|
|
410
|
+
"https://auth0.com/docs/get-started/authentication-and-authorization-flow/"
|
|
411
|
+
"authorization-code-flow-with-proof-key-for-code-exchange-pkce)."
|
|
412
|
+
),
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
)
|
|
257
416
|
|
|
258
417
|
|
|
259
|
-
class OAuth2BearerToken(Base):
|
|
418
|
+
class OAuth2BearerToken(Base): # type: ignore
|
|
419
|
+
"""The oauth2_bearertoken table representation."""
|
|
420
|
+
|
|
260
421
|
__tablename__ = "oauth2_bearertoken"
|
|
261
422
|
__table_args__ = (
|
|
262
423
|
sqlalchemy.schema.UniqueConstraint("client_id", "user_id"),
|
|
@@ -272,9 +433,12 @@ class OAuth2BearerToken(Base):
|
|
|
272
433
|
access_token = Column(Unicode(100), unique=True)
|
|
273
434
|
refresh_token = Column(Unicode(100), unique=True)
|
|
274
435
|
expire_at = Column(DateTime(timezone=True)) # in one hour
|
|
436
|
+
state = Column(String)
|
|
437
|
+
|
|
275
438
|
|
|
439
|
+
class OAuth2AuthorizationCode(Base): # type: ignore
|
|
440
|
+
"""The oauth2_authorizationcode table representation."""
|
|
276
441
|
|
|
277
|
-
class OAuth2AuthorizationCode(Base):
|
|
278
442
|
__tablename__ = "oauth2_authorizationcode"
|
|
279
443
|
__table_args__ = (
|
|
280
444
|
sqlalchemy.schema.UniqueConstraint("client_id", "user_id"),
|
|
@@ -289,4 +453,18 @@ class OAuth2AuthorizationCode(Base):
|
|
|
289
453
|
user = relationship(User)
|
|
290
454
|
redirect_uri = Column(Unicode)
|
|
291
455
|
code = Column(Unicode(100), unique=True)
|
|
456
|
+
state = Column(String)
|
|
457
|
+
challenge = Column(String(128))
|
|
458
|
+
challenge_method = Column(String(6))
|
|
292
459
|
expire_at = Column(DateTime(timezone=True)) # in 10 minutes
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class Log(AbstractLog):
|
|
463
|
+
"""The static log table representation."""
|
|
464
|
+
|
|
465
|
+
__tablename__ = "log"
|
|
466
|
+
__table_args__ = {"schema": _schema}
|
|
467
|
+
__mapper_args__ = {
|
|
468
|
+
"polymorphic_identity": "static",
|
|
469
|
+
"concrete": True,
|
|
470
|
+
}
|
|
File without changes
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
# Copyright (c) 2018-2020, Camptocamp SA
|
|
1
|
+
# Copyright (c) 2018-2023, Camptocamp SA
|
|
4
2
|
# All rights reserved.
|
|
5
3
|
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -28,6 +26,8 @@
|
|
|
28
26
|
# either expressed or implied, of the FreeBSD Project.
|
|
29
27
|
|
|
30
28
|
|
|
29
|
+
from typing import Any, Dict
|
|
30
|
+
|
|
31
31
|
import zope.sqlalchemy
|
|
32
32
|
from sqlalchemy import engine_from_config
|
|
33
33
|
from sqlalchemy.engine import Engine
|
|
@@ -35,11 +35,13 @@ from sqlalchemy.orm import Session, configure_mappers, sessionmaker
|
|
|
35
35
|
from transaction import TransactionManager
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def get_engine(settings:
|
|
38
|
+
def get_engine(settings: Dict[str, Any], prefix: str = "sqlalchemy.") -> Engine:
|
|
39
|
+
"""Get the engine."""
|
|
39
40
|
return engine_from_config(settings, prefix)
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
def get_session_factory(engine: Engine) -> sessionmaker:
|
|
44
|
+
"""Get the session factory."""
|
|
43
45
|
factory = sessionmaker()
|
|
44
46
|
factory.configure(bind=engine)
|
|
45
47
|
return factory
|
|
@@ -64,7 +66,6 @@ def get_tm_session(session_factory: sessionmaker, transaction_manager: Transacti
|
|
|
64
66
|
session_factory = get_session_factory(engine)
|
|
65
67
|
with transaction.manager:
|
|
66
68
|
dbsession = get_tm_session(session_factory, transaction.manager)
|
|
67
|
-
|
|
68
69
|
"""
|
|
69
70
|
dbsession = session_factory()
|
|
70
71
|
zope.sqlalchemy.register(dbsession, transaction_manager=transaction_manager)
|
|
@@ -72,13 +73,12 @@ def get_tm_session(session_factory: sessionmaker, transaction_manager: Transacti
|
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
def generate_mappers() -> None:
|
|
75
|
-
"""
|
|
76
|
-
Initialize the model for a Pyramid app.
|
|
77
|
-
"""
|
|
76
|
+
"""Initialize the model for a Pyramid app."""
|
|
78
77
|
|
|
79
78
|
# import or define all models here to ensure they are attached to the
|
|
80
79
|
# Base.metadata prior to any initialization routines
|
|
81
|
-
import c2cgeoportal_commons.models.main #
|
|
80
|
+
import c2cgeoportal_commons.models.main # pylint: disable=unused-import,import-outside-toplevel
|
|
81
|
+
import c2cgeoportal_commons.models.static # pylint: disable=import-outside-toplevel
|
|
82
82
|
|
|
83
83
|
# run configure_mappers after defining all of the models to ensure
|
|
84
84
|
# all relationships can be setup
|
|
@@ -86,8 +86,9 @@ def generate_mappers() -> None:
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
def get_session(
|
|
89
|
-
settings:
|
|
89
|
+
settings: Dict[str, Any], transaction_manager: TransactionManager, prefix: str = "sqlalchemy."
|
|
90
90
|
) -> Session:
|
|
91
|
+
"""Get the session."""
|
|
91
92
|
configure_mappers()
|
|
92
93
|
engine = get_engine(settings, prefix)
|
|
93
94
|
session_factory = get_session_factory(engine)
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
# Copyright (c) 2017-2020, Camptocamp SA
|
|
1
|
+
# Copyright (c) 2017-2021, Camptocamp SA
|
|
4
2
|
# All rights reserved.
|
|
5
3
|
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -30,7 +28,7 @@
|
|
|
30
28
|
|
|
31
29
|
import os
|
|
32
30
|
import sys
|
|
33
|
-
from typing import List
|
|
31
|
+
from typing import List, cast
|
|
34
32
|
|
|
35
33
|
from sqlalchemy.engine import Connection
|
|
36
34
|
from sqlalchemy.orm import Session
|
|
@@ -39,30 +37,32 @@ from c2cgeoportal_commons.models import Base
|
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
def usage(argv: List[str]) -> None:
|
|
40
|
+
"""Get the usage."""
|
|
42
41
|
cmd = os.path.basename(argv[0])
|
|
43
|
-
print("usage:
|
|
42
|
+
print(f"usage: {cmd} <config_uri> [var=value]\n" '(example: "{cmd} development.ini")')
|
|
44
43
|
sys.exit(1)
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
def schema_exists(connection: Connection, schema_name: str) -> bool:
|
|
48
|
-
|
|
47
|
+
"""Get if the schema exist."""
|
|
48
|
+
sql = f"""
|
|
49
49
|
SELECT count(*) AS count
|
|
50
50
|
FROM information_schema.schemata
|
|
51
|
-
WHERE schema_name = '{}';
|
|
52
|
-
"""
|
|
53
|
-
schema_name
|
|
54
|
-
)
|
|
51
|
+
WHERE schema_name = '{schema_name}';
|
|
52
|
+
"""
|
|
55
53
|
result = connection.execute(sql)
|
|
56
54
|
row = result.first()
|
|
57
|
-
return row[0] == 1
|
|
55
|
+
return cast(bool, row[0] == 1)
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
def truncate_tables(connection: Connection) -> None:
|
|
59
|
+
"""Truncate all the tables defined in the model."""
|
|
61
60
|
for t in Base.metadata.sorted_tables:
|
|
62
|
-
connection.execute("TRUNCATE TABLE {}.{} CASCADE;"
|
|
61
|
+
connection.execute(f"TRUNCATE TABLE {t.schema}.{t.name} CASCADE;")
|
|
63
62
|
|
|
64
63
|
|
|
65
64
|
def setup_test_data(dbsession: Session) -> None:
|
|
65
|
+
"""Initialize the testing data."""
|
|
66
66
|
from c2cgeoportal_commons.models.main import Role # pylint: disable=import-outside-toplevel
|
|
67
67
|
from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel
|
|
68
68
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: c2cgeoportal-commons
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.8.1.180
|
|
4
4
|
Summary: c2cgeoportal commons
|
|
5
5
|
Home-page: https://github.com/camptocamp/c2cgeoportal/
|
|
6
6
|
Author: Camptocamp
|
|
@@ -8,19 +8,24 @@ Author-email: info@camptocamp.com
|
|
|
8
8
|
License: UNKNOWN
|
|
9
9
|
Keywords: web gis geoportail c2cgeoportal geocommune pyramid
|
|
10
10
|
Platform: UNKNOWN
|
|
11
|
-
Classifier:
|
|
12
|
-
Classifier:
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
11
|
+
Classifier: Development Status :: 6 - Mature
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
14
13
|
Classifier: Framework :: Pyramid
|
|
15
|
-
Classifier:
|
|
16
|
-
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
14
|
+
Classifier: Intended Audience :: Other Audience
|
|
17
15
|
Classifier: License :: OSI Approved :: BSD License
|
|
18
|
-
Classifier:
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
21
|
+
Classifier: Typing :: Typed
|
|
19
22
|
Description-Content-Type: text/markdown
|
|
20
23
|
Requires-Dist: GeoAlchemy2
|
|
21
24
|
Requires-Dist: c2c.template
|
|
25
|
+
Requires-Dist: jinja2 (>=3.1.3)
|
|
22
26
|
Requires-Dist: papyrus
|
|
23
27
|
Requires-Dist: pyramid
|
|
28
|
+
Requires-Dist: setuptools (>=65.5.1)
|
|
24
29
|
Requires-Dist: sqlalchemy
|
|
25
30
|
Requires-Dist: transaction
|
|
26
31
|
Requires-Dist: zope.event
|
|
@@ -30,14 +35,16 @@ Provides-Extra: testing
|
|
|
30
35
|
Requires-Dist: transaction ; extra == 'testing'
|
|
31
36
|
Provides-Extra: upgrade
|
|
32
37
|
Requires-Dist: alembic ; extra == 'upgrade'
|
|
33
|
-
Requires-Dist: psycopg2
|
|
38
|
+
Requires-Dist: psycopg2 ; extra == 'upgrade'
|
|
34
39
|
|
|
35
40
|
# c2cgeoportal_common
|
|
36
41
|
|
|
37
42
|
## Checkout
|
|
38
43
|
|
|
44
|
+
```
|
|
39
45
|
git clone git@github.com:camptocamp/c2cgeoportal.git
|
|
40
46
|
cd common
|
|
47
|
+
```
|
|
41
48
|
|
|
42
49
|
## Create virtual environment
|
|
43
50
|
|
|
@@ -51,11 +58,9 @@ sudo -u postgres psql -c "CREATE USER \"www-data\" WITH PASSWORD 'www-data';"
|
|
|
51
58
|
DATABASE=geomapfish_tests
|
|
52
59
|
sudo -u postgres psql -c "CREATE DATABASE $DATABASE WITH OWNER \"www-data\";"
|
|
53
60
|
sudo -u postgres psql -d $DATABASE -c "CREATE EXTENSION postgis;"
|
|
54
|
-
|
|
55
61
|
```
|
|
56
|
-
use common/testing/initialized.py to create the database (development.ini at admin side)
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
use `common/testing/initialized.py` to create the database (`development.ini` at admin side)
|
|
59
64
|
|
|
60
65
|
## Run the tests
|
|
61
66
|
|