##// END OF EJS Templates
validators/schemas: python3 fixes str vs unicode and few test breaking fixes
super-admin -
r5066:ccd88b7c default
parent child Browse files
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,74 +1,74 b''
1
1
2
2
3 # Copyright (C) 2017-2020 RhodeCode GmbH
3 # Copyright (C) 2017-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.translation import _
25 from rhodecode.translation import _
26 from rhodecode.model.validation_schema import preparers
26 from rhodecode.model.validation_schema import preparers
27 from rhodecode.model.validation_schema import types
27 from rhodecode.model.validation_schema import types
28
28
29
29
30 @colander.deferred
30 @colander.deferred
31 def deferred_lifetime_validator(node, kw):
31 def deferred_lifetime_validator(node, kw):
32 options = kw.get('lifetime_options', [])
32 options = kw.get('lifetime_options', [])
33 return colander.All(
33 return colander.All(
34 colander.Range(min=-1, max=60 * 24 * 30 * 12),
34 colander.Range(min=-1, max=60 * 24 * 30 * 12),
35 colander.OneOf([x for x in options]))
35 colander.OneOf([x for x in options]))
36
36
37
37
38 def unique_gist_validator(node, value):
38 def unique_gist_validator(node, value):
39 from rhodecode.model.db import Gist
39 from rhodecode.model.db import Gist
40 existing = Gist.get_by_access_id(value)
40 existing = Gist.get_by_access_id(value)
41 if existing:
41 if existing:
42 msg = _(u'Gist with name {} already exists').format(value)
42 msg = _(u'Gist with name {} already exists').format(value)
43 raise colander.Invalid(node, msg)
43 raise colander.Invalid(node, msg)
44
44
45
45
46 def filename_validator(node, value):
46 def filename_validator(node, value):
47 if value != os.path.basename(value):
47 if value != os.path.basename(value):
48 msg = _(u'Filename {} cannot be inside a directory').format(value)
48 msg = _(u'Filename {} cannot be inside a directory').format(value)
49 raise colander.Invalid(node, msg)
49 raise colander.Invalid(node, msg)
50
50
51
51
52 comment_types = ['note', 'todo']
52 comment_types = ['note', 'todo']
53
53
54
54
55 class CommentSchema(colander.MappingSchema):
55 class CommentSchema(colander.MappingSchema):
56 from rhodecode.model.db import ChangesetComment, ChangesetStatus
56 from rhodecode.model.db import ChangesetComment, ChangesetStatus
57
57
58 comment_body = colander.SchemaNode(colander.String())
58 comment_body = colander.SchemaNode(colander.String())
59 comment_type = colander.SchemaNode(
59 comment_type = colander.SchemaNode(
60 colander.String(),
60 colander.String(),
61 validator=colander.OneOf(ChangesetComment.COMMENT_TYPES),
61 validator=colander.OneOf(ChangesetComment.COMMENT_TYPES),
62 missing=ChangesetComment.COMMENT_TYPE_NOTE)
62 missing=ChangesetComment.COMMENT_TYPE_NOTE)
63 is_draft = colander.SchemaNode(colander.Boolean(),missing=False)
63 is_draft = colander.SchemaNode(colander.Boolean(), missing=False)
64 comment_file = colander.SchemaNode(colander.String(), missing=None)
64 comment_file = colander.SchemaNode(colander.String(), missing=None)
65 comment_line = colander.SchemaNode(colander.String(), missing=None)
65 comment_line = colander.SchemaNode(colander.String(), missing=None)
66 status_change = colander.SchemaNode(
66 status_change = colander.SchemaNode(
67 colander.String(), missing=None,
67 colander.String(), missing=None,
68 validator=colander.OneOf([x[0] for x in ChangesetStatus.STATUSES]))
68 validator=colander.OneOf([x[0] for x in ChangesetStatus.STATUSES]))
69 renderer_type = colander.SchemaNode(colander.String())
69 renderer_type = colander.SchemaNode(colander.String())
70
70
71 resolves_comment_id = colander.SchemaNode(colander.Integer(), missing=None)
71 resolves_comment_id = colander.SchemaNode(colander.Integer(), missing=None)
72
72
73 user = colander.SchemaNode(types.StrOrIntType())
73 user = colander.SchemaNode(types.StrOrIntType())
74 repo = colander.SchemaNode(types.StrOrIntType())
74 repo = colander.SchemaNode(types.StrOrIntType())
@@ -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 {}'.format(type(nodes))
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 = _(u'Gist with name {} already exists').format(value)
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 = _(u'Filename {} cannot be inside a directory').format(value)
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 preparers.non_ascii_strip_preparer],
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 preparers.non_ascii_strip_preparer],
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=u'')
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_PUBLIC,
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, safe_unicode
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_unicode(value), strict=False)
38 ipaddress.ip_network(safe_str(value), strict=False)
40 except ValueError:
39 except ValueError:
41 msg = _(u'Please enter a valid IPv4 or IpV6 address')
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_unicode(value), strict=self.strict)
51 ipaddress.ip_network(safe_str(value), strict=self.strict)
53 except ValueError:
52 except ValueError:
54 msg = _(u'Please enter a valid IPv4 or IpV6 address')
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 = _(u'Invalid glob pattern')
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.message)
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('<', '&lt;').replace('>', '&gt;')
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,) as e:
151 except (Exception,):
151 msg = _(u'Please enter a valid json object')
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 = _(u'Please enter a valid json object: `{}`'.format(e))
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