Show More
@@ -1,88 +1,99 b'' | |||||
1 |
|
1 | |||
2 |
|
2 | |||
3 | # Copyright (C) 2016-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import unicodedata |
|
21 | from rhodecode.lib.str_utils import convert_special_chars, safe_int, safe_bytes | |
|
22 | ||||
|
23 | ||||
|
24 | def to_bytes_preparer(value): | |||
|
25 | ||||
|
26 | if value: | |||
|
27 | value = safe_bytes(value) | |||
|
28 | return value | |||
22 |
|
29 | |||
23 |
|
30 | |||
24 | def strip_preparer(value): |
|
31 | def strip_preparer(value): | |
25 | """ |
|
32 | """ | |
26 | strips given values using .strip() function |
|
33 | strips given values using .strip() function | |
27 | """ |
|
34 | """ | |
28 |
|
35 | |||
29 | if value: |
|
36 | if value: | |
30 | value = value.strip() |
|
37 | value = value.strip() | |
31 | return value |
|
38 | return value | |
32 |
|
39 | |||
33 |
|
40 | |||
34 | def slugify_preparer(value, keep_case=True): |
|
41 | def slugify_preparer(value, keep_case=True): | |
35 | """ |
|
42 | """ | |
36 | Slugify given value to a safe representation for url/id |
|
43 | Slugify given value to a safe representation for url/id | |
37 | """ |
|
44 | """ | |
38 | from rhodecode.lib.utils import repo_name_slug |
|
45 | from rhodecode.lib.utils import repo_name_slug | |
39 | if value: |
|
46 | if value: | |
40 | value = repo_name_slug(value if keep_case else value.lower()) |
|
47 | value = repo_name_slug(value if keep_case else value.lower()) | |
41 | return value |
|
48 | return value | |
42 |
|
49 | |||
43 |
|
50 | |||
44 | def non_ascii_strip_preparer(value): |
|
51 | def non_ascii_strip_preparer(value): | |
45 | """ |
|
52 | """ | |
46 | trie to replace non-ascii letters to their ascii representation |
|
53 | trie to replace non-ascii letters to their ascii representation | |
47 | eg:: |
|
54 | eg:: | |
48 |
|
55 | |||
49 | `ΕΌoΕw` converts into `zolw` |
|
56 | `ΕΌoΕw` converts into `zolw` | |
50 | """ |
|
57 | """ | |
51 | if value: |
|
58 | if value: | |
52 | value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') |
|
59 | value = convert_special_chars(value) | |
53 | return value |
|
60 | return value | |
54 |
|
61 | |||
55 |
|
62 | |||
56 | def unique_list_preparer(value): |
|
63 | def unique_list_preparer(value): | |
57 | """ |
|
64 | """ | |
58 | Converts an list to a list with only unique values |
|
65 | Converts an list to a list with only unique values | |
59 | """ |
|
66 | """ | |
60 |
|
67 | |||
61 | def make_unique(value): |
|
68 | def make_unique(value): | |
62 | seen = [] |
|
69 | seen = [] | |
63 | return [c for c in value if |
|
70 | return [c for c in value if | |
64 | not (c in seen or seen.append(c))] |
|
71 | not (c in seen or seen.append(c))] | |
65 |
|
72 | |||
66 | if isinstance(value, list): |
|
73 | if isinstance(value, list): | |
67 | ret_val = make_unique(value) |
|
74 | ret_val = make_unique(value) | |
68 | elif isinstance(value, set): |
|
75 | elif isinstance(value, set): | |
69 | ret_val = list(value) |
|
76 | ret_val = list(value) | |
70 | elif isinstance(value, tuple): |
|
77 | elif isinstance(value, tuple): | |
71 | ret_val = make_unique(value) |
|
78 | ret_val = make_unique(value) | |
72 | elif value is None: |
|
79 | elif value is None: | |
73 | ret_val = [] |
|
80 | ret_val = [] | |
74 | else: |
|
81 | else: | |
75 | ret_val = [value] |
|
82 | ret_val = [value] | |
76 |
|
83 | |||
77 | return ret_val |
|
84 | return ret_val | |
78 |
|
85 | |||
79 |
|
86 | |||
80 | def unique_list_from_str_preparer(value): |
|
87 | def unique_list_from_str_preparer(value): | |
81 | """ |
|
88 | """ | |
82 | Converts an list to a list with only unique values |
|
89 | Converts an list to a list with only unique values | |
83 | """ |
|
90 | """ | |
84 | from rhodecode.lib.utils2 import aslist |
|
91 | from rhodecode.lib.utils2 import aslist | |
85 |
|
92 | |||
86 | if isinstance(value, str): |
|
93 | if isinstance(value, str): | |
87 | value = aslist(value, ',') |
|
94 | value = aslist(value, ',') | |
88 | return unique_list_preparer(value) No newline at end of file |
|
95 | return unique_list_preparer(value) | |
|
96 | ||||
|
97 | ||||
|
98 | def ensure_value_is_int(value): | |||
|
99 | return safe_int(value) |
@@ -1,183 +1,194 b'' | |||||
1 |
|
1 | |||
2 |
|
2 | |||
3 | # Copyright (C) 2016-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import os |
|
21 | import os | |
22 |
|
22 | |||
23 | import colander |
|
23 | import colander | |
24 |
|
24 | |||
|
25 | from rhodecode.lib.str_utils import safe_str | |||
25 | from rhodecode.translation import _ |
|
26 | from rhodecode.translation import _ | |
26 | from rhodecode.model.validation_schema import preparers |
|
27 | from rhodecode.model.validation_schema import preparers | |
27 |
|
28 | |||
28 |
|
29 | |||
29 | def nodes_to_sequence(nodes, colander_node=None): |
|
30 | def nodes_to_sequence(nodes, colander_node=None): | |
30 | """ |
|
31 | """ | |
31 | Converts old style dict nodes to new list of dicts |
|
32 | Converts old style dict nodes to new list of dicts | |
32 |
|
33 | |||
33 | :param nodes: dict with key beeing name of the file |
|
34 | :param nodes: dict with key beeing name of the file | |
34 |
|
35 | |||
35 | """ |
|
36 | """ | |
36 | if not isinstance(nodes, dict): |
|
37 | if not isinstance(nodes, dict): | |
37 |
msg = 'Nodes needs to be a dict, got { |
|
38 | msg = f'Nodes needs to be a dict, got {type(nodes)}' | |
38 | raise colander.Invalid(colander_node, msg) |
|
39 | raise colander.Invalid(colander_node, msg) | |
39 | out = [] |
|
40 | out = [] | |
40 |
|
41 | |||
41 | for key, val in nodes.items(): |
|
42 | for key, val in nodes.items(): | |
42 | val = (isinstance(val, dict) and val) or {} |
|
43 | val = (isinstance(val, dict) and val) or {} | |
43 | out.append(dict( |
|
44 | out.append(dict( | |
44 | filename=key, |
|
45 | filename=key, | |
45 | content=val.get('content'), |
|
46 | content=val.get('content'), | |
46 | mimetype=val.get('mimetype') |
|
47 | mimetype=val.get('mimetype') | |
47 | )) |
|
48 | )) | |
48 |
|
49 | |||
49 | out = Nodes().deserialize(out) |
|
50 | out = Nodes().deserialize(out) | |
50 | return out |
|
51 | return out | |
51 |
|
52 | |||
52 |
|
53 | |||
53 | def sequence_to_nodes(nodes, colander_node=None): |
|
54 | def sequence_to_nodes(nodes, colander_node=None): | |
|
55 | ||||
54 | if not isinstance(nodes, list): |
|
56 | if not isinstance(nodes, list): | |
55 | msg = 'Nodes needs to be a list, got {}'.format(type(nodes)) |
|
57 | msg = 'Nodes needs to be a list, got {}'.format(type(nodes)) | |
56 | raise colander.Invalid(colander_node, msg) |
|
58 | raise colander.Invalid(colander_node, msg) | |
57 | nodes = Nodes().deserialize(nodes) |
|
59 | nodes = Nodes().deserialize(nodes) | |
58 |
|
60 | |||
59 | out = {} |
|
61 | out = {} | |
60 | try: |
|
62 | try: | |
61 | for file_data in nodes: |
|
63 | for file_data in nodes: | |
62 | file_data_skip = file_data.copy() |
|
64 | file_data_skip = file_data.copy() | |
63 | # if we got filename_org we use it as a key so we keep old |
|
65 | # if we got filename_org we use it as a key so we keep old | |
64 | # name as input and rename is-reflected inside the values as |
|
66 | # name as input and rename is-reflected inside the values as | |
65 | # filename and filename_org differences. |
|
67 | # filename and filename_org differences. | |
66 | filename_org = file_data.get('filename_org') |
|
68 | filename_org = file_data.get('filename_org') | |
67 | filename = filename_org or file_data['filename'] |
|
69 | filename = filename_org or file_data['filename'] | |
68 | out[filename] = {} |
|
70 | out[filename] = {} | |
69 | out[filename].update(file_data_skip) |
|
71 | out[filename].update(file_data_skip) | |
70 |
|
72 | |||
71 | except Exception as e: |
|
73 | except Exception as e: | |
72 | msg = 'Invalid data format org_exc:`{}`'.format(repr(e)) |
|
74 | msg = 'Invalid data format org_exc:`{}`'.format(repr(e)) | |
73 | raise colander.Invalid(colander_node, msg) |
|
75 | raise colander.Invalid(colander_node, msg) | |
74 | return out |
|
76 | return out | |
75 |
|
77 | |||
76 |
|
78 | |||
77 | @colander.deferred |
|
79 | @colander.deferred | |
78 | def deferred_lifetime_validator(node, kw): |
|
80 | def deferred_lifetime_validator(node, kw): | |
79 | options = kw.get('lifetime_options', []) |
|
81 | options = kw.get('lifetime_options', []) | |
80 | return colander.All( |
|
82 | return colander.All( | |
81 | colander.Range(min=-1, max=60 * 24 * 30 * 12), |
|
83 | colander.Range(min=-1, max=60 * 24 * 30 * 12), | |
82 | colander.OneOf([x for x in options])) |
|
84 | colander.OneOf([x for x in options])) | |
83 |
|
85 | |||
84 |
|
86 | |||
85 | def unique_gist_validator(node, value): |
|
87 | def unique_gist_validator(node, value): | |
86 | from rhodecode.model.db import Gist |
|
88 | from rhodecode.model.db import Gist | |
87 | existing = Gist.get_by_access_id(value) |
|
89 | existing = Gist.get_by_access_id(value) | |
88 | if existing: |
|
90 | if existing: | |
89 |
msg = _( |
|
91 | msg = _('Gist with name {} already exists').format(value) | |
90 | raise colander.Invalid(node, msg) |
|
92 | raise colander.Invalid(node, msg) | |
91 |
|
93 | |||
92 |
|
94 | |||
93 | def filename_validator(node, value): |
|
95 | def filename_validator(node, value): | |
94 | if value != os.path.basename(value): |
|
96 | if value != os.path.basename(value): | |
95 |
msg = _( |
|
97 | msg = _('Filename {} cannot be inside a directory').format(safe_str(value)) | |
96 | raise colander.Invalid(node, msg) |
|
98 | raise colander.Invalid(node, msg) | |
97 |
|
99 | |||
98 |
|
100 | |||
99 | class NodeSchema(colander.MappingSchema): |
|
101 | class NodeSchema(colander.MappingSchema): | |
100 | # if we perform rename this will be org filename |
|
102 | # if we perform rename this will be org filename | |
101 | filename_org = colander.SchemaNode( |
|
103 | filename_org = colander.SchemaNode( | |
102 | colander.String(), |
|
104 | colander.String(encoding='utf-8'), | |
103 | preparer=[preparers.strip_preparer, |
|
105 | preparer=[ | |
104 |
|
|
106 | preparers.strip_preparer, | |
|
107 | preparers.non_ascii_strip_preparer, | |||
|
108 | preparers.to_bytes_preparer, | |||
|
109 | ], | |||
105 | validator=filename_validator, |
|
110 | validator=filename_validator, | |
106 | missing=None) |
|
111 | missing=None) | |
107 |
|
112 | |||
108 | filename = colander.SchemaNode( |
|
113 | filename = colander.SchemaNode( | |
109 | colander.String(), |
|
114 | colander.String(encoding='utf-8'), | |
110 | preparer=[preparers.strip_preparer, |
|
115 | preparer=[ | |
111 |
|
|
116 | preparers.strip_preparer, | |
|
117 | preparers.non_ascii_strip_preparer, | |||
|
118 | preparers.to_bytes_preparer, | |||
|
119 | ], | |||
112 | validator=filename_validator) |
|
120 | validator=filename_validator) | |
113 |
|
121 | |||
114 | content = colander.SchemaNode( |
|
122 | content = colander.SchemaNode( | |
115 |
colander.String() |
|
123 | colander.String(encoding='utf-8'), | |
|
124 | preparer=[preparers.to_bytes_preparer]) | |||
|
125 | ||||
116 | mimetype = colander.SchemaNode( |
|
126 | mimetype = colander.SchemaNode( | |
117 | colander.String(), |
|
127 | colander.String(), | |
118 | missing=None) |
|
128 | missing=None) | |
119 |
|
129 | |||
120 |
|
130 | |||
121 | class Nodes(colander.SequenceSchema): |
|
131 | class Nodes(colander.SequenceSchema): | |
122 | filenames = NodeSchema() |
|
132 | filenames = NodeSchema() | |
123 |
|
133 | |||
124 | def validator(self, node, cstruct): |
|
134 | def validator(self, node, cstruct): | |
125 | if not isinstance(cstruct, list): |
|
135 | if not isinstance(cstruct, list): | |
126 | return |
|
136 | return | |
127 |
|
137 | |||
128 | found_filenames = [] |
|
138 | found_filenames = [] | |
129 | for data in cstruct: |
|
139 | for data in cstruct: | |
130 | filename = data['filename'] |
|
140 | filename = data['filename'] | |
131 | if filename in found_filenames: |
|
141 | if filename in found_filenames: | |
132 | msg = _('Duplicated value for filename found: `{}`').format( |
|
142 | msg = _('Duplicated value for filename found: `{}`').format( | |
133 | filename) |
|
143 | filename) | |
134 | raise colander.Invalid(node, msg) |
|
144 | raise colander.Invalid(node, msg) | |
135 | found_filenames.append(filename) |
|
145 | found_filenames.append(filename) | |
136 |
|
146 | |||
137 |
|
147 | |||
138 | class GistSchema(colander.MappingSchema): |
|
148 | class GistSchema(colander.MappingSchema): | |
139 | """ |
|
149 | """ | |
140 | schema = GistSchema() |
|
150 | schema = GistSchema() | |
141 | schema.bind( |
|
151 | schema.bind( | |
142 | lifetime_options = [1,2,3] |
|
152 | lifetime_options = [1,2,3] | |
143 | ) |
|
153 | ) | |
144 | out = schema.deserialize(dict( |
|
154 | out = schema.deserialize(dict( | |
145 | nodes=[ |
|
155 | nodes=[ | |
146 | {'filename': 'x', 'content': 'xxx', }, |
|
156 | {'filename': 'x', 'content': 'xxx', }, | |
147 | {'filename': 'docs/Z', 'content': 'xxx', 'mimetype': 'x'}, |
|
157 | {'filename': 'docs/Z', 'content': 'xxx', 'mimetype': 'x'}, | |
148 | ] |
|
158 | ] | |
149 | )) |
|
159 | )) | |
150 | """ |
|
160 | """ | |
151 |
|
161 | |||
152 | from rhodecode.model.db import Gist |
|
162 | from rhodecode.model.db import Gist | |
153 |
|
163 | |||
154 | gistid = colander.SchemaNode( |
|
164 | gistid = colander.SchemaNode( | |
155 | colander.String(), |
|
165 | colander.String(), | |
156 | missing=None, |
|
166 | missing=None, | |
157 | preparer=[preparers.strip_preparer, |
|
167 | preparer=[preparers.strip_preparer, | |
158 | preparers.non_ascii_strip_preparer, |
|
168 | preparers.non_ascii_strip_preparer, | |
159 | preparers.slugify_preparer], |
|
169 | preparers.slugify_preparer], | |
160 | validator=colander.All( |
|
170 | validator=colander.All( | |
161 | colander.Length(min=3), |
|
171 | colander.Length(min=3), | |
162 | unique_gist_validator |
|
172 | unique_gist_validator | |
163 | )) |
|
173 | )) | |
164 |
|
174 | |||
165 | description = colander.SchemaNode( |
|
175 | description = colander.SchemaNode( | |
166 | colander.String(), |
|
176 | colander.String(), | |
167 |
missing= |
|
177 | missing='') | |
168 |
|
178 | |||
169 | lifetime = colander.SchemaNode( |
|
179 | lifetime = colander.SchemaNode( | |
170 | colander.Integer(), |
|
180 | colander.Integer(), | |
171 | validator=deferred_lifetime_validator) |
|
181 | validator=deferred_lifetime_validator) | |
172 |
|
182 | |||
173 | gist_acl_level = colander.SchemaNode( |
|
183 | gist_acl_level = colander.SchemaNode( | |
174 | colander.String(), |
|
184 | colander.String(), | |
|
185 | missing=Gist.ACL_LEVEL_PRIVATE, | |||
175 | validator=colander.OneOf([Gist.ACL_LEVEL_PUBLIC, |
|
186 | validator=colander.OneOf([Gist.ACL_LEVEL_PUBLIC, | |
176 | Gist.ACL_LEVEL_PRIVATE])) |
|
187 | Gist.ACL_LEVEL_PRIVATE])) | |
177 |
|
188 | |||
178 | gist_type = colander.SchemaNode( |
|
189 | gist_type = colander.SchemaNode( | |
179 | colander.String(), |
|
190 | colander.String(), | |
180 |
missing=Gist.GIST_P |
|
191 | missing=Gist.GIST_PRIVATE, | |
181 | validator=colander.OneOf([Gist.GIST_PRIVATE, Gist.GIST_PUBLIC])) |
|
192 | validator=colander.OneOf([Gist.GIST_PRIVATE, Gist.GIST_PUBLIC])) | |
182 |
|
193 | |||
183 | nodes = Nodes() |
|
194 | nodes = Nodes() |
@@ -1,450 +1,460 b'' | |||||
1 |
|
1 | |||
2 |
|
2 | |||
3 | # Copyright (C) 2016-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import colander |
|
21 | import colander | |
22 | import deform.widget |
|
22 | import deform.widget | |
23 |
|
23 | |||
24 | from rhodecode.translation import _ |
|
24 | from rhodecode.translation import _ | |
25 | from rhodecode.model.validation_schema.utils import convert_to_optgroup, username_converter |
|
25 | from rhodecode.model.validation_schema.utils import convert_to_optgroup, username_converter | |
26 | from rhodecode.model.validation_schema import validators, preparers, types |
|
26 | from rhodecode.model.validation_schema import validators, preparers, types | |
27 |
|
27 | |||
28 | DEFAULT_LANDING_REF = 'rev:tip' |
|
28 | DEFAULT_LANDING_REF = 'rev:tip' | |
29 |
|
29 | |||
30 |
|
30 | |||
31 | def get_group_and_repo(repo_name): |
|
31 | def get_group_and_repo(repo_name): | |
32 | from rhodecode.model.repo_group import RepoGroupModel |
|
32 | from rhodecode.model.repo_group import RepoGroupModel | |
33 | return RepoGroupModel()._get_group_name_and_parent( |
|
33 | return RepoGroupModel()._get_group_name_and_parent( | |
34 | repo_name, get_object=True) |
|
34 | repo_name, get_object=True) | |
35 |
|
35 | |||
36 |
|
36 | |||
37 | def get_repo_group(repo_group_id): |
|
37 | def get_repo_group(repo_group_id): | |
38 | from rhodecode.model.repo_group import RepoGroup |
|
38 | from rhodecode.model.repo_group import RepoGroup | |
39 | return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR |
|
39 | return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | @colander.deferred |
|
42 | @colander.deferred | |
43 | def deferred_repo_type_validator(node, kw): |
|
43 | def deferred_repo_type_validator(node, kw): | |
44 | options = kw.get('repo_type_options', []) |
|
44 | options = kw.get('repo_type_options', []) | |
45 | return colander.OneOf([x for x in options]) |
|
45 | return colander.OneOf([x for x in options]) | |
46 |
|
46 | |||
47 |
|
47 | |||
48 | @colander.deferred |
|
48 | @colander.deferred | |
49 | def deferred_repo_owner_validator(node, kw): |
|
49 | def deferred_repo_owner_validator(node, kw): | |
50 |
|
50 | |||
51 | def repo_owner_validator(node, value): |
|
51 | def repo_owner_validator(node, value): | |
52 | from rhodecode.model.db import User |
|
52 | from rhodecode.model.db import User | |
53 | value = username_converter(value) |
|
53 | value = username_converter(value) | |
54 | existing = User.get_by_username(value) |
|
54 | existing = User.get_by_username(value) | |
55 | if not existing: |
|
55 | if not existing: | |
56 | msg = _(u'Repo owner with id `{}` does not exists').format(value) |
|
56 | msg = _(u'Repo owner with id `{}` does not exists').format(value) | |
57 | raise colander.Invalid(node, msg) |
|
57 | raise colander.Invalid(node, msg) | |
58 |
|
58 | |||
59 | return repo_owner_validator |
|
59 | return repo_owner_validator | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | @colander.deferred |
|
62 | @colander.deferred | |
63 | def deferred_landing_ref_validator(node, kw): |
|
63 | def deferred_landing_ref_validator(node, kw): | |
64 | options = kw.get( |
|
64 | options = kw.get( | |
65 | 'repo_ref_options', [DEFAULT_LANDING_REF]) |
|
65 | 'repo_ref_options', [DEFAULT_LANDING_REF]) | |
66 | return colander.OneOf([x for x in options]) |
|
66 | return colander.OneOf([x for x in options]) | |
67 |
|
67 | |||
68 |
|
68 | |||
69 | @colander.deferred |
|
69 | @colander.deferred | |
70 | def deferred_sync_uri_validator(node, kw): |
|
70 | def deferred_sync_uri_validator(node, kw): | |
71 | repo_type = kw.get('repo_type') |
|
71 | repo_type = kw.get('repo_type') | |
72 | validator = validators.CloneUriValidator(repo_type) |
|
72 | validator = validators.CloneUriValidator(repo_type) | |
73 | return validator |
|
73 | return validator | |
74 |
|
74 | |||
75 |
|
75 | |||
76 | @colander.deferred |
|
76 | @colander.deferred | |
77 | def deferred_landing_ref_widget(node, kw): |
|
77 | def deferred_landing_ref_widget(node, kw): | |
78 | from rhodecode.model.scm import ScmModel |
|
78 | from rhodecode.model.scm import ScmModel | |
79 |
|
79 | |||
80 | repo_type = kw.get('repo_type') |
|
80 | repo_type = kw.get('repo_type') | |
81 | default_opts = [] |
|
81 | default_opts = [] | |
82 | if repo_type: |
|
82 | if repo_type: | |
83 | default_landing_ref, _lbl = ScmModel.backend_landing_ref(repo_type) |
|
83 | default_landing_ref, _lbl = ScmModel.backend_landing_ref(repo_type) | |
84 | default_opts.append((default_landing_ref, default_landing_ref)) |
|
84 | default_opts.append((default_landing_ref, default_landing_ref)) | |
85 |
|
85 | |||
86 | items = kw.get('repo_ref_items', default_opts) |
|
86 | items = kw.get('repo_ref_items', default_opts) | |
87 | items = convert_to_optgroup(items) |
|
87 | items = convert_to_optgroup(items) | |
88 | return deform.widget.Select2Widget(values=items) |
|
88 | return deform.widget.Select2Widget(values=items) | |
89 |
|
89 | |||
90 |
|
90 | |||
91 | @colander.deferred |
|
91 | @colander.deferred | |
92 | def deferred_fork_of_validator(node, kw): |
|
92 | def deferred_fork_of_validator(node, kw): | |
93 | old_values = kw.get('old_values') or {} |
|
93 | old_values = kw.get('old_values') or {} | |
94 |
|
94 | |||
95 | def fork_of_validator(node, value): |
|
95 | def fork_of_validator(node, value): | |
96 | from rhodecode.model.db import Repository, RepoGroup |
|
96 | from rhodecode.model.db import Repository, RepoGroup | |
97 | existing = Repository.get_by_repo_name(value) |
|
97 | existing = Repository.get_by_repo_name(value) | |
98 | if not existing: |
|
98 | if not existing: | |
99 | msg = _(u'Fork with id `{}` does not exists').format(value) |
|
99 | msg = _(u'Fork with id `{}` does not exists').format(value) | |
100 | raise colander.Invalid(node, msg) |
|
100 | raise colander.Invalid(node, msg) | |
101 | elif old_values['repo_name'] == existing.repo_name: |
|
101 | elif old_values['repo_name'] == existing.repo_name: | |
102 | msg = _(u'Cannot set fork of ' |
|
102 | msg = _(u'Cannot set fork of ' | |
103 | u'parameter of this repository to itself').format(value) |
|
103 | u'parameter of this repository to itself').format(value) | |
104 | raise colander.Invalid(node, msg) |
|
104 | raise colander.Invalid(node, msg) | |
105 |
|
105 | |||
106 | return fork_of_validator |
|
106 | return fork_of_validator | |
107 |
|
107 | |||
108 |
|
108 | |||
109 | @colander.deferred |
|
109 | @colander.deferred | |
110 | def deferred_can_write_to_group_validator(node, kw): |
|
110 | def deferred_can_write_to_group_validator(node, kw): | |
111 | request_user = kw.get('user') |
|
111 | request_user = kw.get('user') | |
112 | old_values = kw.get('old_values') or {} |
|
112 | old_values = kw.get('old_values') or {} | |
113 |
|
113 | |||
114 | def can_write_to_group_validator(node, value): |
|
114 | def can_write_to_group_validator(node, value): | |
115 | """ |
|
115 | """ | |
116 | Checks if given repo path is writable by user. This includes checks if |
|
116 | Checks if given repo path is writable by user. This includes checks if | |
117 | user is allowed to create repositories under root path or under |
|
117 | user is allowed to create repositories under root path or under | |
118 | repo group paths |
|
118 | repo group paths | |
119 | """ |
|
119 | """ | |
120 |
|
120 | |||
121 | from rhodecode.lib.auth import ( |
|
121 | from rhodecode.lib.auth import ( | |
122 | HasPermissionAny, HasRepoGroupPermissionAny) |
|
122 | HasPermissionAny, HasRepoGroupPermissionAny) | |
123 | from rhodecode.model.repo_group import RepoGroupModel |
|
123 | from rhodecode.model.repo_group import RepoGroupModel | |
124 |
|
124 | |||
125 | messages = { |
|
125 | messages = { | |
126 | 'invalid_repo_group': |
|
126 | 'invalid_repo_group': | |
127 | _(u"Repository group `{}` does not exist"), |
|
127 | _(u"Repository group `{}` does not exist"), | |
128 | # permissions denied we expose as not existing, to prevent |
|
128 | # permissions denied we expose as not existing, to prevent | |
129 | # resource discovery |
|
129 | # resource discovery | |
130 | 'permission_denied': |
|
130 | 'permission_denied': | |
131 | _(u"Repository group `{}` does not exist"), |
|
131 | _(u"Repository group `{}` does not exist"), | |
132 | 'permission_denied_root': |
|
132 | 'permission_denied_root': | |
133 | _(u"You do not have the permission to store " |
|
133 | _(u"You do not have the permission to store " | |
134 | u"repositories in the root location.") |
|
134 | u"repositories in the root location.") | |
135 | } |
|
135 | } | |
136 |
|
136 | |||
137 | value = value['repo_group_name'] |
|
137 | value = value['repo_group_name'] | |
138 |
|
138 | |||
139 | is_root_location = value is types.RootLocation |
|
139 | is_root_location = value is types.RootLocation | |
140 | # NOT initialized validators, we must call them |
|
140 | # NOT initialized validators, we must call them | |
141 | can_create_repos_at_root = HasPermissionAny('hg.admin', 'hg.create.repository') |
|
141 | can_create_repos_at_root = HasPermissionAny('hg.admin', 'hg.create.repository') | |
142 |
|
142 | |||
143 | # if values is root location, we simply need to check if we can write |
|
143 | # if values is root location, we simply need to check if we can write | |
144 | # to root location ! |
|
144 | # to root location ! | |
145 | if is_root_location: |
|
145 | if is_root_location: | |
146 |
|
146 | |||
147 | if can_create_repos_at_root(user=request_user): |
|
147 | if can_create_repos_at_root(user=request_user): | |
148 | # we can create repo group inside tool-level. No more checks |
|
148 | # we can create repo group inside tool-level. No more checks | |
149 | # are required |
|
149 | # are required | |
150 | return |
|
150 | return | |
151 | else: |
|
151 | else: | |
152 | old_name = old_values.get('repo_name') |
|
152 | old_name = old_values.get('repo_name') | |
153 | if old_name and old_name == old_values.get('submitted_repo_name'): |
|
153 | if old_name and old_name == old_values.get('submitted_repo_name'): | |
154 | # since we didn't change the name, we can skip validation and |
|
154 | # since we didn't change the name, we can skip validation and | |
155 | # allow current users without store-in-root permissions to update |
|
155 | # allow current users without store-in-root permissions to update | |
156 | return |
|
156 | return | |
157 |
|
157 | |||
158 | # "fake" node name as repo_name, otherwise we oddly report |
|
158 | # "fake" node name as repo_name, otherwise we oddly report | |
159 | # the error as if it was coming form repo_group |
|
159 | # the error as if it was coming form repo_group | |
160 | # however repo_group is empty when using root location. |
|
160 | # however repo_group is empty when using root location. | |
161 | node.name = 'repo_name' |
|
161 | node.name = 'repo_name' | |
162 | raise colander.Invalid(node, messages['permission_denied_root']) |
|
162 | raise colander.Invalid(node, messages['permission_denied_root']) | |
163 |
|
163 | |||
164 | # parent group not exists ? throw an error |
|
164 | # parent group not exists ? throw an error | |
165 | repo_group = RepoGroupModel().get_by_group_name(value) |
|
165 | repo_group = RepoGroupModel().get_by_group_name(value) | |
166 | if value and not repo_group: |
|
166 | if value and not repo_group: | |
167 | raise colander.Invalid( |
|
167 | raise colander.Invalid( | |
168 | node, messages['invalid_repo_group'].format(value)) |
|
168 | node, messages['invalid_repo_group'].format(value)) | |
169 |
|
169 | |||
170 | gr_name = repo_group.group_name |
|
170 | gr_name = repo_group.group_name | |
171 |
|
171 | |||
172 | # create repositories with write permission on group is set to true |
|
172 | # create repositories with write permission on group is set to true | |
173 | create_on_write = HasPermissionAny( |
|
173 | create_on_write = HasPermissionAny( | |
174 | 'hg.create.write_on_repogroup.true')(user=request_user) |
|
174 | 'hg.create.write_on_repogroup.true')(user=request_user) | |
175 |
|
175 | |||
176 | group_admin = HasRepoGroupPermissionAny('group.admin')( |
|
176 | group_admin = HasRepoGroupPermissionAny('group.admin')( | |
177 | gr_name, 'can write into group validator', user=request_user) |
|
177 | gr_name, 'can write into group validator', user=request_user) | |
178 | group_write = HasRepoGroupPermissionAny('group.write')( |
|
178 | group_write = HasRepoGroupPermissionAny('group.write')( | |
179 | gr_name, 'can write into group validator', user=request_user) |
|
179 | gr_name, 'can write into group validator', user=request_user) | |
180 |
|
180 | |||
181 | forbidden = not (group_admin or (group_write and create_on_write)) |
|
181 | forbidden = not (group_admin or (group_write and create_on_write)) | |
182 |
|
182 | |||
183 | # TODO: handling of old values, and detecting no-change in path |
|
183 | # TODO: handling of old values, and detecting no-change in path | |
184 | # to skip permission checks in such cases. This only needs to be |
|
184 | # to skip permission checks in such cases. This only needs to be | |
185 | # implemented if we use this schema in forms as well |
|
185 | # implemented if we use this schema in forms as well | |
186 |
|
186 | |||
187 | # gid = (old_data['repo_group'].get('group_id') |
|
187 | # gid = (old_data['repo_group'].get('group_id') | |
188 | # if (old_data and 'repo_group' in old_data) else None) |
|
188 | # if (old_data and 'repo_group' in old_data) else None) | |
189 | # value_changed = gid != safe_int(value) |
|
189 | # value_changed = gid != safe_int(value) | |
190 | # new = not old_data |
|
190 | # new = not old_data | |
191 |
|
191 | |||
192 | # do check if we changed the value, there's a case that someone got |
|
192 | # do check if we changed the value, there's a case that someone got | |
193 | # revoked write permissions to a repository, he still created, we |
|
193 | # revoked write permissions to a repository, he still created, we | |
194 | # don't need to check permission if he didn't change the value of |
|
194 | # don't need to check permission if he didn't change the value of | |
195 | # groups in form box |
|
195 | # groups in form box | |
196 | # if value_changed or new: |
|
196 | # if value_changed or new: | |
197 | # # parent group need to be existing |
|
197 | # # parent group need to be existing | |
198 | # TODO: ENDS HERE |
|
198 | # TODO: ENDS HERE | |
199 |
|
199 | |||
200 | if repo_group and forbidden: |
|
200 | if repo_group and forbidden: | |
201 | msg = messages['permission_denied'].format(value) |
|
201 | msg = messages['permission_denied'].format(value) | |
202 | raise colander.Invalid(node, msg) |
|
202 | raise colander.Invalid(node, msg) | |
203 |
|
203 | |||
204 | return can_write_to_group_validator |
|
204 | return can_write_to_group_validator | |
205 |
|
205 | |||
206 |
|
206 | |||
207 | @colander.deferred |
|
207 | @colander.deferred | |
208 | def deferred_unique_name_validator(node, kw): |
|
208 | def deferred_unique_name_validator(node, kw): | |
209 | request_user = kw.get('user') |
|
209 | request_user = kw.get('user') | |
210 | old_values = kw.get('old_values') or {} |
|
210 | old_values = kw.get('old_values') or {} | |
211 |
|
211 | |||
212 | def unique_name_validator(node, value): |
|
212 | def unique_name_validator(node, value): | |
213 | from rhodecode.model.db import Repository, RepoGroup |
|
213 | from rhodecode.model.db import Repository, RepoGroup | |
214 | name_changed = value != old_values.get('repo_name') |
|
214 | name_changed = value != old_values.get('repo_name') | |
215 |
|
215 | |||
216 | existing = Repository.get_by_repo_name(value) |
|
216 | existing = Repository.get_by_repo_name(value) | |
217 | if name_changed and existing: |
|
217 | if name_changed and existing: | |
218 | msg = _(u'Repository with name `{}` already exists').format(value) |
|
218 | msg = _(u'Repository with name `{}` already exists').format(value) | |
219 | raise colander.Invalid(node, msg) |
|
219 | raise colander.Invalid(node, msg) | |
220 |
|
220 | |||
221 | existing_group = RepoGroup.get_by_group_name(value) |
|
221 | existing_group = RepoGroup.get_by_group_name(value) | |
222 | if name_changed and existing_group: |
|
222 | if name_changed and existing_group: | |
223 | msg = _(u'Repository group with name `{}` already exists').format( |
|
223 | msg = _(u'Repository group with name `{}` already exists').format( | |
224 | value) |
|
224 | value) | |
225 | raise colander.Invalid(node, msg) |
|
225 | raise colander.Invalid(node, msg) | |
226 | return unique_name_validator |
|
226 | return unique_name_validator | |
227 |
|
227 | |||
228 |
|
228 | |||
229 | @colander.deferred |
|
229 | @colander.deferred | |
230 | def deferred_repo_name_validator(node, kw): |
|
230 | def deferred_repo_name_validator(node, kw): | |
231 | def no_git_suffix_validator(node, value): |
|
231 | def no_git_suffix_validator(node, value): | |
232 | if value.endswith('.git'): |
|
232 | if value.endswith('.git'): | |
233 | msg = _('Repository name cannot end with .git') |
|
233 | msg = _('Repository name cannot end with .git') | |
234 | raise colander.Invalid(node, msg) |
|
234 | raise colander.Invalid(node, msg) | |
235 | return colander.All( |
|
235 | return colander.All( | |
236 | no_git_suffix_validator, validators.valid_name_validator) |
|
236 | no_git_suffix_validator, validators.valid_name_validator) | |
237 |
|
237 | |||
238 |
|
238 | |||
239 | @colander.deferred |
|
239 | @colander.deferred | |
240 | def deferred_repo_group_validator(node, kw): |
|
240 | def deferred_repo_group_validator(node, kw): | |
241 | options = kw.get( |
|
241 | options = kw.get('repo_repo_group_options') | |
242 | 'repo_repo_group_options') |
|
242 | ||
243 | return colander.OneOf([x for x in options]) |
|
243 | def repo_group_validator(node, value): | |
|
244 | choices = [x for x in options] | |||
|
245 | err = _('Group ID: `${val}` is not one of allowed ${choices}') | |||
|
246 | ||||
|
247 | if preparers.ensure_value_is_int(value) not in choices: | |||
|
248 | choices = ', '.join(['%s' % x for x in sorted(choices)]) | |||
|
249 | err = _(err, mapping={'val': value, 'choices': choices}) | |||
|
250 | raise colander.Invalid(node, err) | |||
|
251 | ||||
|
252 | return repo_group_validator | |||
244 |
|
253 | |||
245 |
|
254 | |||
246 | @colander.deferred |
|
255 | @colander.deferred | |
247 | def deferred_repo_group_widget(node, kw): |
|
256 | def deferred_repo_group_widget(node, kw): | |
248 | items = kw.get('repo_repo_group_items') |
|
257 | items = kw.get('repo_repo_group_items') | |
249 | return deform.widget.Select2Widget(values=items) |
|
258 | return deform.widget.Select2Widget(values=items) | |
250 |
|
259 | |||
251 |
|
260 | |||
252 | class GroupType(colander.Mapping): |
|
261 | class GroupType(colander.Mapping): | |
253 | def _validate(self, node, value): |
|
262 | def _validate(self, node, value): | |
254 | try: |
|
263 | try: | |
255 | return dict(repo_group_name=value) |
|
264 | return dict(repo_group_name=value) | |
256 | except Exception as e: |
|
265 | except Exception as e: | |
257 | raise colander.Invalid( |
|
266 | raise colander.Invalid( | |
258 | node, '"${val}" is not a mapping type: ${err}'.format( |
|
267 | node, '"${val}" is not a mapping type: ${err}'.format( | |
259 | val=value, err=e)) |
|
268 | val=value, err=e)) | |
260 |
|
269 | |||
261 | def deserialize(self, node, cstruct): |
|
270 | def deserialize(self, node, cstruct): | |
262 | if cstruct is colander.null: |
|
271 | if cstruct is colander.null: | |
263 | return cstruct |
|
272 | return cstruct | |
264 |
|
273 | |||
265 | appstruct = super(GroupType, self).deserialize(node, cstruct) |
|
274 | appstruct = super(GroupType, self).deserialize(node, cstruct) | |
266 | validated_name = appstruct['repo_group_name'] |
|
275 | validated_name = appstruct['repo_group_name'] | |
267 |
|
276 | |||
268 | # inject group based on once deserialized data |
|
277 | # inject group based on once deserialized data | |
269 | (repo_name_without_group, |
|
278 | (repo_name_without_group, | |
270 | parent_group_name, |
|
279 | parent_group_name, | |
271 | parent_group) = get_group_and_repo(validated_name) |
|
280 | parent_group) = get_group_and_repo(validated_name) | |
272 |
|
281 | |||
273 | appstruct['repo_name_with_group'] = validated_name |
|
282 | appstruct['repo_name_with_group'] = validated_name | |
274 | appstruct['repo_name_without_group'] = repo_name_without_group |
|
283 | appstruct['repo_name_without_group'] = repo_name_without_group | |
275 | appstruct['repo_group_name'] = parent_group_name or types.RootLocation |
|
284 | appstruct['repo_group_name'] = parent_group_name or types.RootLocation | |
276 |
|
285 | |||
277 | if parent_group: |
|
286 | if parent_group: | |
278 | appstruct['repo_group_id'] = parent_group.group_id |
|
287 | appstruct['repo_group_id'] = parent_group.group_id | |
279 |
|
288 | |||
280 | return appstruct |
|
289 | return appstruct | |
281 |
|
290 | |||
282 |
|
291 | |||
283 | class GroupSchema(colander.SchemaNode): |
|
292 | class GroupSchema(colander.SchemaNode): | |
284 | schema_type = GroupType |
|
293 | schema_type = GroupType | |
285 | validator = deferred_can_write_to_group_validator |
|
294 | validator = deferred_can_write_to_group_validator | |
286 | missing = colander.null |
|
295 | missing = colander.null | |
287 |
|
296 | |||
288 |
|
297 | |||
289 | class RepoGroup(GroupSchema): |
|
298 | class RepoGroup(GroupSchema): | |
290 | repo_group_name = colander.SchemaNode( |
|
299 | repo_group_name = colander.SchemaNode( | |
291 | types.GroupNameType()) |
|
300 | types.GroupNameType()) | |
292 | repo_group_id = colander.SchemaNode( |
|
301 | repo_group_id = colander.SchemaNode( | |
293 | colander.String(), missing=None) |
|
302 | colander.String(), missing=None) | |
294 | repo_name_without_group = colander.SchemaNode( |
|
303 | repo_name_without_group = colander.SchemaNode( | |
295 | colander.String(), missing=None) |
|
304 | colander.String(), missing=None) | |
296 |
|
305 | |||
297 |
|
306 | |||
298 | class RepoGroupAccessSchema(colander.MappingSchema): |
|
307 | class RepoGroupAccessSchema(colander.MappingSchema): | |
299 | repo_group = RepoGroup() |
|
308 | repo_group = RepoGroup() | |
300 |
|
309 | |||
301 |
|
310 | |||
302 | class RepoNameUniqueSchema(colander.MappingSchema): |
|
311 | class RepoNameUniqueSchema(colander.MappingSchema): | |
303 | unique_repo_name = colander.SchemaNode( |
|
312 | unique_repo_name = colander.SchemaNode( | |
304 | colander.String(), |
|
313 | colander.String(), | |
305 | validator=deferred_unique_name_validator) |
|
314 | validator=deferred_unique_name_validator) | |
306 |
|
315 | |||
307 |
|
316 | |||
308 | class RepoSchema(colander.MappingSchema): |
|
317 | class RepoSchema(colander.MappingSchema): | |
309 |
|
318 | |||
310 | repo_name = colander.SchemaNode( |
|
319 | repo_name = colander.SchemaNode( | |
311 | types.RepoNameType(), |
|
320 | types.RepoNameType(), | |
312 | validator=deferred_repo_name_validator) |
|
321 | validator=deferred_repo_name_validator) | |
313 |
|
322 | |||
314 | repo_type = colander.SchemaNode( |
|
323 | repo_type = colander.SchemaNode( | |
315 | colander.String(), |
|
324 | colander.String(), | |
316 | validator=deferred_repo_type_validator) |
|
325 | validator=deferred_repo_type_validator) | |
317 |
|
326 | |||
318 | repo_owner = colander.SchemaNode( |
|
327 | repo_owner = colander.SchemaNode( | |
319 | colander.String(), |
|
328 | colander.String(), | |
320 | validator=deferred_repo_owner_validator, |
|
329 | validator=deferred_repo_owner_validator, | |
321 | widget=deform.widget.TextInputWidget()) |
|
330 | widget=deform.widget.TextInputWidget()) | |
322 |
|
331 | |||
323 | repo_description = colander.SchemaNode( |
|
332 | repo_description = colander.SchemaNode( | |
324 | colander.String(), missing='', |
|
333 | colander.String(), missing='', | |
325 | widget=deform.widget.TextAreaWidget()) |
|
334 | widget=deform.widget.TextAreaWidget()) | |
326 |
|
335 | |||
327 | repo_landing_commit_ref = colander.SchemaNode( |
|
336 | repo_landing_commit_ref = colander.SchemaNode( | |
328 | colander.String(), |
|
337 | colander.String(), | |
329 | validator=deferred_landing_ref_validator, |
|
338 | validator=deferred_landing_ref_validator, | |
330 | preparers=[preparers.strip_preparer], |
|
339 | preparers=[preparers.strip_preparer], | |
331 | missing=DEFAULT_LANDING_REF, |
|
340 | missing=DEFAULT_LANDING_REF, | |
332 | widget=deferred_landing_ref_widget) |
|
341 | widget=deferred_landing_ref_widget) | |
333 |
|
342 | |||
334 | repo_clone_uri = colander.SchemaNode( |
|
343 | repo_clone_uri = colander.SchemaNode( | |
335 | colander.String(), |
|
344 | colander.String(), | |
336 | validator=deferred_sync_uri_validator, |
|
345 | validator=deferred_sync_uri_validator, | |
337 | preparers=[preparers.strip_preparer], |
|
346 | preparers=[preparers.strip_preparer], | |
338 | missing='') |
|
347 | missing='') | |
339 |
|
348 | |||
340 | repo_push_uri = colander.SchemaNode( |
|
349 | repo_push_uri = colander.SchemaNode( | |
341 | colander.String(), |
|
350 | colander.String(), | |
342 | validator=deferred_sync_uri_validator, |
|
351 | validator=deferred_sync_uri_validator, | |
343 | preparers=[preparers.strip_preparer], |
|
352 | preparers=[preparers.strip_preparer], | |
344 | missing='') |
|
353 | missing='') | |
345 |
|
354 | |||
346 | repo_fork_of = colander.SchemaNode( |
|
355 | repo_fork_of = colander.SchemaNode( | |
347 | colander.String(), |
|
356 | colander.String(), | |
348 | validator=deferred_fork_of_validator, |
|
357 | validator=deferred_fork_of_validator, | |
349 | missing=None) |
|
358 | missing=None) | |
350 |
|
359 | |||
351 | repo_private = colander.SchemaNode( |
|
360 | repo_private = colander.SchemaNode( | |
352 | types.StringBooleanType(), |
|
361 | types.StringBooleanType(), | |
353 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
362 | missing=False, widget=deform.widget.CheckboxWidget()) | |
354 | repo_copy_permissions = colander.SchemaNode( |
|
363 | repo_copy_permissions = colander.SchemaNode( | |
355 | types.StringBooleanType(), |
|
364 | types.StringBooleanType(), | |
356 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
365 | missing=False, widget=deform.widget.CheckboxWidget()) | |
357 | repo_enable_statistics = colander.SchemaNode( |
|
366 | repo_enable_statistics = colander.SchemaNode( | |
358 | types.StringBooleanType(), |
|
367 | types.StringBooleanType(), | |
359 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
368 | missing=False, widget=deform.widget.CheckboxWidget()) | |
360 | repo_enable_downloads = colander.SchemaNode( |
|
369 | repo_enable_downloads = colander.SchemaNode( | |
361 | types.StringBooleanType(), |
|
370 | types.StringBooleanType(), | |
362 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
371 | missing=False, widget=deform.widget.CheckboxWidget()) | |
363 | repo_enable_locking = colander.SchemaNode( |
|
372 | repo_enable_locking = colander.SchemaNode( | |
364 | types.StringBooleanType(), |
|
373 | types.StringBooleanType(), | |
365 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
374 | missing=False, widget=deform.widget.CheckboxWidget()) | |
366 |
|
375 | |||
367 | def deserialize(self, cstruct): |
|
376 | def deserialize(self, cstruct): | |
368 | """ |
|
377 | """ | |
369 | Custom deserialize that allows to chain validation, and verify |
|
378 | Custom deserialize that allows to chain validation, and verify | |
370 | permissions, and as last step uniqueness |
|
379 | permissions, and as last step uniqueness | |
371 | """ |
|
380 | """ | |
372 |
|
381 | |||
373 | # first pass, to validate given data |
|
382 | # first pass, to validate given data | |
374 | appstruct = super(RepoSchema, self).deserialize(cstruct) |
|
383 | appstruct = super(RepoSchema, self).deserialize(cstruct) | |
375 | validated_name = appstruct['repo_name'] |
|
384 | validated_name = appstruct['repo_name'] | |
376 |
|
385 | |||
377 | # second pass to validate permissions to repo_group |
|
386 | # second pass to validate permissions to repo_group | |
378 | if 'old_values' in self.bindings: |
|
387 | if 'old_values' in self.bindings: | |
379 | # save current repo name for name change checks |
|
388 | # save current repo name for name change checks | |
380 | self.bindings['old_values']['submitted_repo_name'] = validated_name |
|
389 | self.bindings['old_values']['submitted_repo_name'] = validated_name | |
381 | second = RepoGroupAccessSchema().bind(**self.bindings) |
|
390 | second = RepoGroupAccessSchema().bind(**self.bindings) | |
382 | appstruct_second = second.deserialize({'repo_group': validated_name}) |
|
391 | appstruct_second = second.deserialize({'repo_group': validated_name}) | |
383 | # save result |
|
392 | # save result | |
384 | appstruct['repo_group'] = appstruct_second['repo_group'] |
|
393 | appstruct['repo_group'] = appstruct_second['repo_group'] | |
385 |
|
394 | |||
386 | # thirds to validate uniqueness |
|
395 | # thirds to validate uniqueness | |
387 | third = RepoNameUniqueSchema().bind(**self.bindings) |
|
396 | third = RepoNameUniqueSchema().bind(**self.bindings) | |
388 | third.deserialize({'unique_repo_name': validated_name}) |
|
397 | third.deserialize({'unique_repo_name': validated_name}) | |
389 |
|
398 | |||
390 | return appstruct |
|
399 | return appstruct | |
391 |
|
400 | |||
392 |
|
401 | |||
393 | class RepoSettingsSchema(RepoSchema): |
|
402 | class RepoSettingsSchema(RepoSchema): | |
394 | repo_group = colander.SchemaNode( |
|
403 | repo_group = colander.SchemaNode( | |
395 | colander.Integer(), |
|
404 | colander.Integer(), | |
396 | validator=deferred_repo_group_validator, |
|
405 | validator=deferred_repo_group_validator, | |
397 | widget=deferred_repo_group_widget, |
|
406 | widget=deferred_repo_group_widget, | |
|
407 | preparers=[preparers.ensure_value_is_int], | |||
398 | missing='') |
|
408 | missing='') | |
399 |
|
409 | |||
400 | repo_clone_uri_change = colander.SchemaNode( |
|
410 | repo_clone_uri_change = colander.SchemaNode( | |
401 | colander.String(), |
|
411 | colander.String(), | |
402 | missing='NEW') |
|
412 | missing='NEW') | |
403 |
|
413 | |||
404 | repo_clone_uri = colander.SchemaNode( |
|
414 | repo_clone_uri = colander.SchemaNode( | |
405 | colander.String(), |
|
415 | colander.String(), | |
406 | preparers=[preparers.strip_preparer], |
|
416 | preparers=[preparers.strip_preparer], | |
407 | validator=deferred_sync_uri_validator, |
|
417 | validator=deferred_sync_uri_validator, | |
408 | missing='') |
|
418 | missing='') | |
409 |
|
419 | |||
410 | repo_push_uri_change = colander.SchemaNode( |
|
420 | repo_push_uri_change = colander.SchemaNode( | |
411 | colander.String(), |
|
421 | colander.String(), | |
412 | missing='NEW') |
|
422 | missing='NEW') | |
413 |
|
423 | |||
414 | repo_push_uri = colander.SchemaNode( |
|
424 | repo_push_uri = colander.SchemaNode( | |
415 | colander.String(), |
|
425 | colander.String(), | |
416 | preparers=[preparers.strip_preparer], |
|
426 | preparers=[preparers.strip_preparer], | |
417 | validator=deferred_sync_uri_validator, |
|
427 | validator=deferred_sync_uri_validator, | |
418 | missing='') |
|
428 | missing='') | |
419 |
|
429 | |||
420 | def deserialize(self, cstruct): |
|
430 | def deserialize(self, cstruct): | |
421 | """ |
|
431 | """ | |
422 | Custom deserialize that allows to chain validation, and verify |
|
432 | Custom deserialize that allows to chain validation, and verify | |
423 | permissions, and as last step uniqueness |
|
433 | permissions, and as last step uniqueness | |
424 | """ |
|
434 | """ | |
425 |
|
435 | |||
426 | # first pass, to validate given data |
|
436 | # first pass, to validate given data | |
427 | appstruct = super(RepoSchema, self).deserialize(cstruct) |
|
437 | appstruct = super(RepoSchema, self).deserialize(cstruct) | |
428 | validated_name = appstruct['repo_name'] |
|
438 | validated_name = appstruct['repo_name'] | |
429 | # because of repoSchema adds repo-group as an ID, we inject it as |
|
439 | # because of repoSchema adds repo-group as an ID, we inject it as | |
430 | # full name here because validators require it, it's unwrapped later |
|
440 | # full name here because validators require it, it's unwrapped later | |
431 | # so it's safe to use and final name is going to be without group anyway |
|
441 | # so it's safe to use and final name is going to be without group anyway | |
432 |
|
442 | |||
433 | group, separator = get_repo_group(appstruct['repo_group']) |
|
443 | group, separator = get_repo_group(appstruct['repo_group']) | |
434 | if group: |
|
444 | if group: | |
435 | validated_name = separator.join([group.group_name, validated_name]) |
|
445 | validated_name = separator.join([group.group_name, validated_name]) | |
436 |
|
446 | |||
437 | # second pass to validate permissions to repo_group |
|
447 | # second pass to validate permissions to repo_group | |
438 | if 'old_values' in self.bindings: |
|
448 | if 'old_values' in self.bindings: | |
439 | # save current repo name for name change checks |
|
449 | # save current repo name for name change checks | |
440 | self.bindings['old_values']['submitted_repo_name'] = validated_name |
|
450 | self.bindings['old_values']['submitted_repo_name'] = validated_name | |
441 | second = RepoGroupAccessSchema().bind(**self.bindings) |
|
451 | second = RepoGroupAccessSchema().bind(**self.bindings) | |
442 | appstruct_second = second.deserialize({'repo_group': validated_name}) |
|
452 | appstruct_second = second.deserialize({'repo_group': validated_name}) | |
443 | # save result |
|
453 | # save result | |
444 | appstruct['repo_group'] = appstruct_second['repo_group'] |
|
454 | appstruct['repo_group'] = appstruct_second['repo_group'] | |
445 |
|
455 | |||
446 | # thirds to validate uniqueness |
|
456 | # thirds to validate uniqueness | |
447 | third = RepoNameUniqueSchema().bind(**self.bindings) |
|
457 | third = RepoNameUniqueSchema().bind(**self.bindings) | |
448 | third.deserialize({'unique_repo_name': validated_name}) |
|
458 | third.deserialize({'unique_repo_name': validated_name}) | |
449 |
|
459 | |||
450 | return appstruct |
|
460 | return appstruct |
@@ -1,160 +1,161 b'' | |||||
1 |
|
1 | |||
2 |
|
2 | |||
3 | # Copyright (C) 2011-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2011-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import os |
|
|||
22 | import re |
|
21 | import re | |
23 | import logging |
|
22 | import logging | |
24 |
|
23 | |||
25 |
|
||||
26 | import ipaddress |
|
24 | import ipaddress | |
27 | import colander |
|
25 | import colander | |
28 |
|
26 | |||
29 | from rhodecode.translation import _ |
|
27 | from rhodecode.translation import _ | |
30 |
from rhodecode.lib.utils2 import glob2re |
|
28 | from rhodecode.lib.utils2 import glob2re | |
|
29 | from rhodecode.lib.str_utils import safe_str | |||
31 | from rhodecode.lib.ext_json import json |
|
30 | from rhodecode.lib.ext_json import json | |
32 |
|
31 | |||
33 | log = logging.getLogger(__name__) |
|
32 | log = logging.getLogger(__name__) | |
34 |
|
33 | |||
35 |
|
34 | |||
36 | def ip_addr_validator(node, value): |
|
35 | def ip_addr_validator(node, value): | |
37 | try: |
|
36 | try: | |
38 | # this raises an ValueError if address is not IpV4 or IpV6 |
|
37 | # this raises an ValueError if address is not IpV4 or IpV6 | |
39 |
ipaddress.ip_network(safe_ |
|
38 | ipaddress.ip_network(safe_str(value), strict=False) | |
40 | except ValueError: |
|
39 | except ValueError: | |
41 |
msg = _( |
|
40 | msg = _('Please enter a valid IPv4 or IpV6 address') | |
42 | raise colander.Invalid(node, msg) |
|
41 | raise colander.Invalid(node, msg) | |
43 |
|
42 | |||
44 |
|
43 | |||
45 | class IpAddrValidator(object): |
|
44 | class IpAddrValidator(object): | |
46 | def __init__(self, strict=True): |
|
45 | def __init__(self, strict=True): | |
47 | self.strict = strict |
|
46 | self.strict = strict | |
48 |
|
47 | |||
49 | def __call__(self, node, value): |
|
48 | def __call__(self, node, value): | |
50 | try: |
|
49 | try: | |
51 | # this raises an ValueError if address is not IpV4 or IpV6 |
|
50 | # this raises an ValueError if address is not IpV4 or IpV6 | |
52 |
ipaddress.ip_network(safe_ |
|
51 | ipaddress.ip_network(safe_str(value), strict=self.strict) | |
53 | except ValueError: |
|
52 | except ValueError: | |
54 |
msg = _( |
|
53 | msg = _('Please enter a valid IPv4 or IpV6 address') | |
55 | raise colander.Invalid(node, msg) |
|
54 | raise colander.Invalid(node, msg) | |
56 |
|
55 | |||
57 |
|
56 | |||
58 | def glob_validator(node, value): |
|
57 | def glob_validator(node, value): | |
59 | try: |
|
58 | try: | |
60 | re.compile('^' + glob2re(value) + '$') |
|
59 | re.compile('^' + glob2re(value) + '$') | |
61 | except Exception: |
|
60 | except Exception: | |
62 |
msg = _( |
|
61 | msg = _('Invalid glob pattern') | |
63 | raise colander.Invalid(node, msg) |
|
62 | raise colander.Invalid(node, msg) | |
64 |
|
63 | |||
65 |
|
64 | |||
66 | def valid_name_validator(node, value): |
|
65 | def valid_name_validator(node, value): | |
67 | from rhodecode.model.validation_schema import types |
|
66 | from rhodecode.model.validation_schema import types | |
68 | if value is types.RootLocation: |
|
67 | if value is types.RootLocation: | |
69 | return |
|
68 | return | |
70 |
|
69 | |||
71 | msg = _('Name must start with a letter or number. Got `{}`').format(value) |
|
70 | msg = _('Name must start with a letter or number. Got `{}`').format(value) | |
72 | if not re.match(r'^[a-zA-z0-9]{1,}', value): |
|
71 | if not re.match(r'^[a-zA-z0-9]{1,}', value): | |
73 | raise colander.Invalid(node, msg) |
|
72 | raise colander.Invalid(node, msg) | |
74 |
|
73 | |||
75 |
|
74 | |||
76 | class InvalidCloneUrl(Exception): |
|
75 | class InvalidCloneUrl(Exception): | |
77 | allowed_prefixes = () |
|
76 | allowed_prefixes = () | |
78 |
|
77 | |||
79 |
|
78 | |||
80 | def url_validator(url, repo_type, config): |
|
79 | def url_validator(url, repo_type, config): | |
81 | from rhodecode.lib.vcs.backends.hg import MercurialRepository |
|
80 | from rhodecode.lib.vcs.backends.hg import MercurialRepository | |
82 | from rhodecode.lib.vcs.backends.git import GitRepository |
|
81 | from rhodecode.lib.vcs.backends.git import GitRepository | |
83 | from rhodecode.lib.vcs.backends.svn import SubversionRepository |
|
82 | from rhodecode.lib.vcs.backends.svn import SubversionRepository | |
84 |
|
83 | |||
85 | if repo_type == 'hg': |
|
84 | if repo_type == 'hg': | |
86 | allowed_prefixes = ('http', 'svn+http', 'git+http') |
|
85 | allowed_prefixes = ('http', 'svn+http', 'git+http') | |
87 |
|
86 | |||
88 | if 'http' in url[:4]: |
|
87 | if 'http' in url[:4]: | |
89 | # initially check if it's at least the proper URL |
|
88 | # initially check if it's at least the proper URL | |
90 | # or does it pass basic auth |
|
89 | # or does it pass basic auth | |
91 |
|
90 | |||
92 | return MercurialRepository.check_url(url, config) |
|
91 | return MercurialRepository.check_url(url, config) | |
93 | elif 'svn+http' in url[:8]: # svn->hg import |
|
92 | elif 'svn+http' in url[:8]: # svn->hg import | |
94 | SubversionRepository.check_url(url, config) |
|
93 | SubversionRepository.check_url(url, config) | |
95 | elif 'git+http' in url[:8]: # git->hg import |
|
94 | elif 'git+http' in url[:8]: # git->hg import | |
96 | raise NotImplementedError() |
|
95 | raise NotImplementedError() | |
97 | else: |
|
96 | else: | |
98 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' |
|
97 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' | |
99 | 'Allowed url must start with one of %s' |
|
98 | 'Allowed url must start with one of %s' | |
100 | % (url, ','.join(allowed_prefixes))) |
|
99 | % (url, ','.join(allowed_prefixes))) | |
101 | exc.allowed_prefixes = allowed_prefixes |
|
100 | exc.allowed_prefixes = allowed_prefixes | |
102 | raise exc |
|
101 | raise exc | |
103 |
|
102 | |||
104 | elif repo_type == 'git': |
|
103 | elif repo_type == 'git': | |
105 | allowed_prefixes = ('http', 'svn+http', 'hg+http') |
|
104 | allowed_prefixes = ('http', 'svn+http', 'hg+http') | |
106 | if 'http' in url[:4]: |
|
105 | if 'http' in url[:4]: | |
107 | # initially check if it's at least the proper URL |
|
106 | # initially check if it's at least the proper URL | |
108 | # or does it pass basic auth |
|
107 | # or does it pass basic auth | |
109 | return GitRepository.check_url(url, config) |
|
108 | return GitRepository.check_url(url, config) | |
110 | elif 'svn+http' in url[:8]: # svn->git import |
|
109 | elif 'svn+http' in url[:8]: # svn->git import | |
111 | raise NotImplementedError() |
|
110 | raise NotImplementedError() | |
112 | elif 'hg+http' in url[:8]: # hg->git import |
|
111 | elif 'hg+http' in url[:8]: # hg->git import | |
113 | raise NotImplementedError() |
|
112 | raise NotImplementedError() | |
114 | else: |
|
113 | else: | |
115 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' |
|
114 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' | |
116 | 'Allowed url must start with one of %s' |
|
115 | 'Allowed url must start with one of %s' | |
117 | % (url, ','.join(allowed_prefixes))) |
|
116 | % (url, ','.join(allowed_prefixes))) | |
118 | exc.allowed_prefixes = allowed_prefixes |
|
117 | exc.allowed_prefixes = allowed_prefixes | |
119 | raise exc |
|
118 | raise exc | |
120 | elif repo_type == 'svn': |
|
119 | elif repo_type == 'svn': | |
121 | # no validation for SVN yet |
|
120 | # no validation for SVN yet | |
122 | return |
|
121 | return | |
123 |
|
122 | |||
124 | raise InvalidCloneUrl('Invalid repo type specified: `{}`'.format(repo_type)) |
|
123 | raise InvalidCloneUrl('Invalid repo type specified: `{}`'.format(repo_type)) | |
125 |
|
124 | |||
126 |
|
125 | |||
127 | class CloneUriValidator(object): |
|
126 | class CloneUriValidator(object): | |
128 | def __init__(self, repo_type): |
|
127 | def __init__(self, repo_type): | |
129 | self.repo_type = repo_type |
|
128 | self.repo_type = repo_type | |
130 |
|
129 | |||
131 | def __call__(self, node, value): |
|
130 | def __call__(self, node, value): | |
132 |
|
131 | |||
133 | from rhodecode.lib.utils import make_db_config |
|
132 | from rhodecode.lib.utils import make_db_config | |
134 | try: |
|
133 | try: | |
135 | config = make_db_config(clear_session=False) |
|
134 | config = make_db_config(clear_session=False) | |
136 | url_validator(value, self.repo_type, config) |
|
135 | url_validator(value, self.repo_type, config) | |
137 | except InvalidCloneUrl as e: |
|
136 | except InvalidCloneUrl as e: | |
138 | log.warning(e) |
|
137 | log.warning(e) | |
139 |
raise colander.Invalid(node, e |
|
138 | raise colander.Invalid(node, str(e)) | |
140 | except Exception: |
|
139 | except Exception as e: | |
141 | log.exception('Url validation failed') |
|
140 | log.exception('Url validation failed') | |
142 | msg = _(u'invalid clone url or credentials for {repo_type} repository').format( |
|
141 | reason = repr(e) | |
143 | repo_type=self.repo_type) |
|
142 | reason = reason.replace('<', '<').replace('>', '>') | |
|
143 | msg = _('invalid clone url or credentials for {repo_type} repository. Reason: {reason}')\ | |||
|
144 | .format(reason=reason, repo_type=self.repo_type) | |||
144 | raise colander.Invalid(node, msg) |
|
145 | raise colander.Invalid(node, msg) | |
145 |
|
146 | |||
146 |
|
147 | |||
147 | def json_validator(node, value): |
|
148 | def json_validator(node, value): | |
148 | try: |
|
149 | try: | |
149 | json.loads(value) |
|
150 | json.loads(value) | |
150 |
except (Exception,) |
|
151 | except (Exception,): | |
151 |
msg = _( |
|
152 | msg = _('Please enter a valid json object') | |
152 | raise colander.Invalid(node, msg) |
|
153 | raise colander.Invalid(node, msg) | |
153 |
|
154 | |||
154 |
|
155 | |||
155 | def json_validator_with_exc(node, value): |
|
156 | def json_validator_with_exc(node, value): | |
156 | try: |
|
157 | try: | |
157 | json.loads(value) |
|
158 | json.loads(value) | |
158 | except (Exception,) as e: |
|
159 | except (Exception,) as e: | |
159 |
msg = _( |
|
160 | msg = _('Please enter a valid json object: `{}`'.format(e)) | |
160 | raise colander.Invalid(node, msg) |
|
161 | raise colander.Invalid(node, msg) |
General Comments 0
You need to be logged in to leave comments.
Login now