##// END OF EJS Templates
views: fixed some view names for better usage in view whitelist access
marcink -
r1944:5ee1b12e default
parent child Browse files
Show More
@@ -1,412 +1,412 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 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 time
21 import time
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 import peppercorn
25 import peppercorn
26
26
27 from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPFound
27 from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPFound
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
34 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.utils2 import time_to_datetime
35 from rhodecode.lib.utils2 import time_to_datetime
36 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
37 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
38 from rhodecode.model.gist import GistModel
38 from rhodecode.model.gist import GistModel
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.db import Gist, User, or_
40 from rhodecode.model.db import Gist, User, or_
41 from rhodecode.model import validation_schema
41 from rhodecode.model import validation_schema
42 from rhodecode.model.validation_schema.schemas import gist_schema
42 from rhodecode.model.validation_schema.schemas import gist_schema
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class GistView(BaseAppView):
48 class GistView(BaseAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 _ = self.request.translate
51 _ = self.request.translate
52 c = self._get_local_tmpl_context()
52 c = self._get_local_tmpl_context()
53 c.user = c.auth_user.get_instance()
53 c.user = c.auth_user.get_instance()
54
54
55 c.lifetime_values = [
55 c.lifetime_values = [
56 (-1, _('forever')),
56 (-1, _('forever')),
57 (5, _('5 minutes')),
57 (5, _('5 minutes')),
58 (60, _('1 hour')),
58 (60, _('1 hour')),
59 (60 * 24, _('1 day')),
59 (60 * 24, _('1 day')),
60 (60 * 24 * 30, _('1 month')),
60 (60 * 24 * 30, _('1 month')),
61 ]
61 ]
62
62
63 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
63 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
64 c.acl_options = [
64 c.acl_options = [
65 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
65 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
66 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
66 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
67 ]
67 ]
68
68
69 self._register_global_c(c)
69 self._register_global_c(c)
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @view_config(
73 @view_config(
74 route_name='gists_show', request_method='GET',
74 route_name='gists_show', request_method='GET',
75 renderer='rhodecode:templates/admin/gists/index.mako')
75 renderer='rhodecode:templates/admin/gists/index.mako')
76 def gist_show_all(self):
76 def gist_show_all(self):
77 c = self.load_default_context()
77 c = self.load_default_context()
78
78
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
80 c.show_private = self.request.GET.get('private') and not_default_user
80 c.show_private = self.request.GET.get('private') and not_default_user
81 c.show_public = self.request.GET.get('public') and not_default_user
81 c.show_public = self.request.GET.get('public') and not_default_user
82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
83
83
84 gists = _gists = Gist().query()\
84 gists = _gists = Gist().query()\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 .order_by(Gist.created_on.desc())
86 .order_by(Gist.created_on.desc())
87
87
88 c.active = 'public'
88 c.active = 'public'
89 # MY private
89 # MY private
90 if c.show_private and not c.show_public:
90 if c.show_private and not c.show_public:
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
93 c.active = 'my_private'
93 c.active = 'my_private'
94 # MY public
94 # MY public
95 elif c.show_public and not c.show_private:
95 elif c.show_public and not c.show_private:
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
98 c.active = 'my_public'
98 c.active = 'my_public'
99 # MY public+private
99 # MY public+private
100 elif c.show_private and c.show_public:
100 elif c.show_private and c.show_public:
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 Gist.gist_type == Gist.GIST_PRIVATE))\
102 Gist.gist_type == Gist.GIST_PRIVATE))\
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
104 c.active = 'my_all'
104 c.active = 'my_all'
105 # Show all by super-admin
105 # Show all by super-admin
106 elif c.show_all:
106 elif c.show_all:
107 c.active = 'all'
107 c.active = 'all'
108 gists = _gists
108 gists = _gists
109
109
110 # default show ALL public gists
110 # default show ALL public gists
111 if not c.show_public and not c.show_private and not c.show_all:
111 if not c.show_public and not c.show_private and not c.show_all:
112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 c.active = 'public'
113 c.active = 'public'
114
114
115 _render = self.request.get_partial_renderer(
115 _render = self.request.get_partial_renderer(
116 'data_table/_dt_elements.mako')
116 'data_table/_dt_elements.mako')
117
117
118 data = []
118 data = []
119
119
120 for gist in gists:
120 for gist in gists:
121 data.append({
121 data.append({
122 'created_on': _render('gist_created', gist.created_on),
122 'created_on': _render('gist_created', gist.created_on),
123 'created_on_raw': gist.created_on,
123 'created_on_raw': gist.created_on,
124 'type': _render('gist_type', gist.gist_type),
124 'type': _render('gist_type', gist.gist_type),
125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 'author_raw': h.escape(gist.owner.full_contact),
127 'author_raw': h.escape(gist.owner.full_contact),
128 'expires': _render('gist_expires', gist.gist_expires),
128 'expires': _render('gist_expires', gist.gist_expires),
129 'description': _render('gist_description', gist.gist_description)
129 'description': _render('gist_description', gist.gist_description)
130 })
130 })
131 c.data = json.dumps(data)
131 c.data = json.dumps(data)
132
132
133 return self._get_template_context(c)
133 return self._get_template_context(c)
134
134
135 @LoginRequired()
135 @LoginRequired()
136 @NotAnonymous()
136 @NotAnonymous()
137 @view_config(
137 @view_config(
138 route_name='gists_new', request_method='GET',
138 route_name='gists_new', request_method='GET',
139 renderer='rhodecode:templates/admin/gists/new.mako')
139 renderer='rhodecode:templates/admin/gists/new.mako')
140 def gist_new(self):
140 def gist_new(self):
141 c = self.load_default_context()
141 c = self.load_default_context()
142 return self._get_template_context(c)
142 return self._get_template_context(c)
143
143
144 @LoginRequired()
144 @LoginRequired()
145 @NotAnonymous()
145 @NotAnonymous()
146 @CSRFRequired()
146 @CSRFRequired()
147 @view_config(
147 @view_config(
148 route_name='gists_create', request_method='POST',
148 route_name='gists_create', request_method='POST',
149 renderer='rhodecode:templates/admin/gists/new.mako')
149 renderer='rhodecode:templates/admin/gists/new.mako')
150 def gist_create(self):
150 def gist_create(self):
151 _ = self.request.translate
151 _ = self.request.translate
152 c = self.load_default_context()
152 c = self.load_default_context()
153
153
154 data = dict(self.request.POST)
154 data = dict(self.request.POST)
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
156 data['nodes'] = [{
156 data['nodes'] = [{
157 'filename': data['filename'],
157 'filename': data['filename'],
158 'content': data.get('content'),
158 'content': data.get('content'),
159 'mimetype': data.get('mimetype') # None is autodetect
159 'mimetype': data.get('mimetype') # None is autodetect
160 }]
160 }]
161
161
162 data['gist_type'] = (
162 data['gist_type'] = (
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
164 data['gist_acl_level'] = (
164 data['gist_acl_level'] = (
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
166
166
167 schema = gist_schema.GistSchema().bind(
167 schema = gist_schema.GistSchema().bind(
168 lifetime_options=[x[0] for x in c.lifetime_values])
168 lifetime_options=[x[0] for x in c.lifetime_values])
169
169
170 try:
170 try:
171
171
172 schema_data = schema.deserialize(data)
172 schema_data = schema.deserialize(data)
173 # convert to safer format with just KEYs so we sure no duplicates
173 # convert to safer format with just KEYs so we sure no duplicates
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
175 schema_data['nodes'])
175 schema_data['nodes'])
176
176
177 gist = GistModel().create(
177 gist = GistModel().create(
178 gist_id=schema_data['gistid'], # custom access id not real ID
178 gist_id=schema_data['gistid'], # custom access id not real ID
179 description=schema_data['description'],
179 description=schema_data['description'],
180 owner=self._rhodecode_user.user_id,
180 owner=self._rhodecode_user.user_id,
181 gist_mapping=schema_data['nodes'],
181 gist_mapping=schema_data['nodes'],
182 gist_type=schema_data['gist_type'],
182 gist_type=schema_data['gist_type'],
183 lifetime=schema_data['lifetime'],
183 lifetime=schema_data['lifetime'],
184 gist_acl_level=schema_data['gist_acl_level']
184 gist_acl_level=schema_data['gist_acl_level']
185 )
185 )
186 Session().commit()
186 Session().commit()
187 new_gist_id = gist.gist_access_id
187 new_gist_id = gist.gist_access_id
188 except validation_schema.Invalid as errors:
188 except validation_schema.Invalid as errors:
189 defaults = data
189 defaults = data
190 errors = errors.asdict()
190 errors = errors.asdict()
191
191
192 if 'nodes.0.content' in errors:
192 if 'nodes.0.content' in errors:
193 errors['content'] = errors['nodes.0.content']
193 errors['content'] = errors['nodes.0.content']
194 del errors['nodes.0.content']
194 del errors['nodes.0.content']
195 if 'nodes.0.filename' in errors:
195 if 'nodes.0.filename' in errors:
196 errors['filename'] = errors['nodes.0.filename']
196 errors['filename'] = errors['nodes.0.filename']
197 del errors['nodes.0.filename']
197 del errors['nodes.0.filename']
198
198
199 data = render('rhodecode:templates/admin/gists/new.mako',
199 data = render('rhodecode:templates/admin/gists/new.mako',
200 self._get_template_context(c), self.request)
200 self._get_template_context(c), self.request)
201 html = formencode.htmlfill.render(
201 html = formencode.htmlfill.render(
202 data,
202 data,
203 defaults=defaults,
203 defaults=defaults,
204 errors=errors,
204 errors=errors,
205 prefix_error=False,
205 prefix_error=False,
206 encoding="UTF-8",
206 encoding="UTF-8",
207 force_defaults=False
207 force_defaults=False
208 )
208 )
209 return Response(html)
209 return Response(html)
210
210
211 except Exception:
211 except Exception:
212 log.exception("Exception while trying to create a gist")
212 log.exception("Exception while trying to create a gist")
213 h.flash(_('Error occurred during gist creation'), category='error')
213 h.flash(_('Error occurred during gist creation'), category='error')
214 raise HTTPFound(h.route_url('gists_new'))
214 raise HTTPFound(h.route_url('gists_new'))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
216
216
217 @LoginRequired()
217 @LoginRequired()
218 @NotAnonymous()
218 @NotAnonymous()
219 @CSRFRequired()
219 @CSRFRequired()
220 @view_config(
220 @view_config(
221 route_name='gist_delete', request_method='POST')
221 route_name='gist_delete', request_method='POST')
222 def gist_delete(self):
222 def gist_delete(self):
223 _ = self.request.translate
223 _ = self.request.translate
224 gist_id = self.request.matchdict['gist_id']
224 gist_id = self.request.matchdict['gist_id']
225
225
226 c = self.load_default_context()
226 c = self.load_default_context()
227 c.gist = Gist.get_or_404(gist_id)
227 c.gist = Gist.get_or_404(gist_id)
228
228
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
230 if not (h.HasPermissionAny('hg.admin')() or owner):
230 if not (h.HasPermissionAny('hg.admin')() or owner):
231 log.warning('Deletion of Gist was forbidden '
231 log.warning('Deletion of Gist was forbidden '
232 'by unauthorized user: `%s`', self._rhodecode_user)
232 'by unauthorized user: `%s`', self._rhodecode_user)
233 raise HTTPNotFound()
233 raise HTTPNotFound()
234
234
235 GistModel().delete(c.gist)
235 GistModel().delete(c.gist)
236 Session().commit()
236 Session().commit()
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
238
238
239 raise HTTPFound(h.route_url('gists_show'))
239 raise HTTPFound(h.route_url('gists_show'))
240
240
241 def _get_gist(self, gist_id):
241 def _get_gist(self, gist_id):
242
242
243 gist = Gist.get_or_404(gist_id)
243 gist = Gist.get_or_404(gist_id)
244
244
245 # Check if this gist is expired
245 # Check if this gist is expired
246 if gist.gist_expires != -1:
246 if gist.gist_expires != -1:
247 if time.time() > gist.gist_expires:
247 if time.time() > gist.gist_expires:
248 log.error(
248 log.error(
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
250 raise HTTPNotFound()
250 raise HTTPNotFound()
251
251
252 # check if this gist requires a login
252 # check if this gist requires a login
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
255 log.error("Anonymous user %s tried to access protected gist `%s`",
255 log.error("Anonymous user %s tried to access protected gist `%s`",
256 self._rhodecode_user, gist_id)
256 self._rhodecode_user, gist_id)
257 raise HTTPNotFound()
257 raise HTTPNotFound()
258 return gist
258 return gist
259
259
260 @LoginRequired()
260 @LoginRequired()
261 @view_config(
261 @view_config(
262 route_name='gist_show', request_method='GET',
262 route_name='gist_show', request_method='GET',
263 renderer='rhodecode:templates/admin/gists/show.mako')
263 renderer='rhodecode:templates/admin/gists/show.mako')
264 @view_config(
264 @view_config(
265 route_name='gist_show_rev', request_method='GET',
265 route_name='gist_show_rev', request_method='GET',
266 renderer='rhodecode:templates/admin/gists/show.mako')
266 renderer='rhodecode:templates/admin/gists/show.mako')
267 @view_config(
267 @view_config(
268 route_name='gist_show_formatted', request_method='GET',
268 route_name='gist_show_formatted', request_method='GET',
269 renderer=None)
269 renderer=None)
270 @view_config(
270 @view_config(
271 route_name='gist_show_formatted_path', request_method='GET',
271 route_name='gist_show_formatted_path', request_method='GET',
272 renderer=None)
272 renderer=None)
273 def show(self):
273 def gist_show(self):
274 gist_id = self.request.matchdict['gist_id']
274 gist_id = self.request.matchdict['gist_id']
275
275
276 # TODO(marcink): expose those via matching dict
276 # TODO(marcink): expose those via matching dict
277 revision = self.request.matchdict.get('revision', 'tip')
277 revision = self.request.matchdict.get('revision', 'tip')
278 f_path = self.request.matchdict.get('f_path', None)
278 f_path = self.request.matchdict.get('f_path', None)
279 return_format = self.request.matchdict.get('format')
279 return_format = self.request.matchdict.get('format')
280
280
281 c = self.load_default_context()
281 c = self.load_default_context()
282 c.gist = self._get_gist(gist_id)
282 c.gist = self._get_gist(gist_id)
283 c.render = not self.request.GET.get('no-render', False)
283 c.render = not self.request.GET.get('no-render', False)
284
284
285 try:
285 try:
286 c.file_last_commit, c.files = GistModel().get_gist_files(
286 c.file_last_commit, c.files = GistModel().get_gist_files(
287 gist_id, revision=revision)
287 gist_id, revision=revision)
288 except VCSError:
288 except VCSError:
289 log.exception("Exception in gist show")
289 log.exception("Exception in gist show")
290 raise HTTPNotFound()
290 raise HTTPNotFound()
291
291
292 if return_format == 'raw':
292 if return_format == 'raw':
293 content = '\n\n'.join([f.content for f in c.files
293 content = '\n\n'.join([f.content for f in c.files
294 if (f_path is None or f.path == f_path)])
294 if (f_path is None or f.path == f_path)])
295 response = Response(content)
295 response = Response(content)
296 response.content_type = 'text/plain'
296 response.content_type = 'text/plain'
297 return response
297 return response
298
298
299 return self._get_template_context(c)
299 return self._get_template_context(c)
300
300
301 @LoginRequired()
301 @LoginRequired()
302 @NotAnonymous()
302 @NotAnonymous()
303 @view_config(
303 @view_config(
304 route_name='gist_edit', request_method='GET',
304 route_name='gist_edit', request_method='GET',
305 renderer='rhodecode:templates/admin/gists/edit.mako')
305 renderer='rhodecode:templates/admin/gists/edit.mako')
306 def gist_edit(self):
306 def gist_edit(self):
307 _ = self.request.translate
307 _ = self.request.translate
308 gist_id = self.request.matchdict['gist_id']
308 gist_id = self.request.matchdict['gist_id']
309 c = self.load_default_context()
309 c = self.load_default_context()
310 c.gist = self._get_gist(gist_id)
310 c.gist = self._get_gist(gist_id)
311
311
312 owner = c.gist.gist_owner == self._rhodecode_user.user_id
312 owner = c.gist.gist_owner == self._rhodecode_user.user_id
313 if not (h.HasPermissionAny('hg.admin')() or owner):
313 if not (h.HasPermissionAny('hg.admin')() or owner):
314 raise HTTPNotFound()
314 raise HTTPNotFound()
315
315
316 try:
316 try:
317 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
317 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
318 except VCSError:
318 except VCSError:
319 log.exception("Exception in gist edit")
319 log.exception("Exception in gist edit")
320 raise HTTPNotFound()
320 raise HTTPNotFound()
321
321
322 if c.gist.gist_expires == -1:
322 if c.gist.gist_expires == -1:
323 expiry = _('never')
323 expiry = _('never')
324 else:
324 else:
325 # this cannot use timeago, since it's used in select2 as a value
325 # this cannot use timeago, since it's used in select2 as a value
326 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
326 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
327
327
328 c.lifetime_values.append(
328 c.lifetime_values.append(
329 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
329 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
330 )
330 )
331
331
332 return self._get_template_context(c)
332 return self._get_template_context(c)
333
333
334 @LoginRequired()
334 @LoginRequired()
335 @NotAnonymous()
335 @NotAnonymous()
336 @CSRFRequired()
336 @CSRFRequired()
337 @view_config(
337 @view_config(
338 route_name='gist_update', request_method='POST',
338 route_name='gist_update', request_method='POST',
339 renderer='rhodecode:templates/admin/gists/edit.mako')
339 renderer='rhodecode:templates/admin/gists/edit.mako')
340 def gist_update(self):
340 def gist_update(self):
341 _ = self.request.translate
341 _ = self.request.translate
342 gist_id = self.request.matchdict['gist_id']
342 gist_id = self.request.matchdict['gist_id']
343 c = self.load_default_context()
343 c = self.load_default_context()
344 c.gist = self._get_gist(gist_id)
344 c.gist = self._get_gist(gist_id)
345
345
346 owner = c.gist.gist_owner == self._rhodecode_user.user_id
346 owner = c.gist.gist_owner == self._rhodecode_user.user_id
347 if not (h.HasPermissionAny('hg.admin')() or owner):
347 if not (h.HasPermissionAny('hg.admin')() or owner):
348 raise HTTPNotFound()
348 raise HTTPNotFound()
349
349
350 data = peppercorn.parse(self.request.POST.items())
350 data = peppercorn.parse(self.request.POST.items())
351
351
352 schema = gist_schema.GistSchema()
352 schema = gist_schema.GistSchema()
353 schema = schema.bind(
353 schema = schema.bind(
354 # '0' is special value to leave lifetime untouched
354 # '0' is special value to leave lifetime untouched
355 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
355 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
356 )
356 )
357
357
358 try:
358 try:
359 schema_data = schema.deserialize(data)
359 schema_data = schema.deserialize(data)
360 # convert to safer format with just KEYs so we sure no duplicates
360 # convert to safer format with just KEYs so we sure no duplicates
361 schema_data['nodes'] = gist_schema.sequence_to_nodes(
361 schema_data['nodes'] = gist_schema.sequence_to_nodes(
362 schema_data['nodes'])
362 schema_data['nodes'])
363
363
364 GistModel().update(
364 GistModel().update(
365 gist=c.gist,
365 gist=c.gist,
366 description=schema_data['description'],
366 description=schema_data['description'],
367 owner=c.gist.owner,
367 owner=c.gist.owner,
368 gist_mapping=schema_data['nodes'],
368 gist_mapping=schema_data['nodes'],
369 lifetime=schema_data['lifetime'],
369 lifetime=schema_data['lifetime'],
370 gist_acl_level=schema_data['gist_acl_level']
370 gist_acl_level=schema_data['gist_acl_level']
371 )
371 )
372
372
373 Session().commit()
373 Session().commit()
374 h.flash(_('Successfully updated gist content'), category='success')
374 h.flash(_('Successfully updated gist content'), category='success')
375 except NodeNotChangedError:
375 except NodeNotChangedError:
376 # raised if nothing was changed in repo itself. We anyway then
376 # raised if nothing was changed in repo itself. We anyway then
377 # store only DB stuff for gist
377 # store only DB stuff for gist
378 Session().commit()
378 Session().commit()
379 h.flash(_('Successfully updated gist data'), category='success')
379 h.flash(_('Successfully updated gist data'), category='success')
380 except validation_schema.Invalid as errors:
380 except validation_schema.Invalid as errors:
381 errors = errors.asdict()
381 errors = errors.asdict()
382 h.flash(_('Error occurred during update of gist {}: {}').format(
382 h.flash(_('Error occurred during update of gist {}: {}').format(
383 gist_id, errors), category='error')
383 gist_id, errors), category='error')
384 except Exception:
384 except Exception:
385 log.exception("Exception in gist edit")
385 log.exception("Exception in gist edit")
386 h.flash(_('Error occurred during update of gist %s') % gist_id,
386 h.flash(_('Error occurred during update of gist %s') % gist_id,
387 category='error')
387 category='error')
388
388
389 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
389 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
390
390
391 @LoginRequired()
391 @LoginRequired()
392 @NotAnonymous()
392 @NotAnonymous()
393 @view_config(
393 @view_config(
394 route_name='gist_edit_check_revision', request_method='GET',
394 route_name='gist_edit_check_revision', request_method='GET',
395 renderer='json_ext')
395 renderer='json_ext')
396 def gist_edit_check_revision(self):
396 def gist_edit_check_revision(self):
397 _ = self.request.translate
397 _ = self.request.translate
398 gist_id = self.request.matchdict['gist_id']
398 gist_id = self.request.matchdict['gist_id']
399 c = self.load_default_context()
399 c = self.load_default_context()
400 c.gist = self._get_gist(gist_id)
400 c.gist = self._get_gist(gist_id)
401
401
402 last_rev = c.gist.scm_instance().get_commit()
402 last_rev = c.gist.scm_instance().get_commit()
403 success = True
403 success = True
404 revision = self.request.GET.get('revision')
404 revision = self.request.GET.get('revision')
405
405
406 if revision != last_rev.raw_id:
406 if revision != last_rev.raw_id:
407 log.error('Last revision %s is different then submitted %s'
407 log.error('Last revision %s is different then submitted %s'
408 % (revision, last_rev))
408 % (revision, last_rev))
409 # our gist has newer version than we
409 # our gist has newer version than we
410 success = False
410 success = False
411
411
412 return {'success': success}
412 return {'success': success}
@@ -1,126 +1,126 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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
21
22 from rhodecode.apps._base import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23
23
24
24
25 def includeme(config):
25 def includeme(config):
26
26
27 config.add_route(
27 config.add_route(
28 name='my_account_profile',
28 name='my_account_profile',
29 pattern=ADMIN_PREFIX + '/my_account/profile')
29 pattern=ADMIN_PREFIX + '/my_account/profile')
30
30
31 # my account edit details
31 # my account edit details
32 config.add_route(
32 config.add_route(
33 name='my_account_edit',
33 name='my_account_edit',
34 pattern=ADMIN_PREFIX + '/my_account/edit')
34 pattern=ADMIN_PREFIX + '/my_account/edit')
35 config.add_route(
35 config.add_route(
36 name='my_account_update',
36 name='my_account_update',
37 pattern=ADMIN_PREFIX + '/my_account/update')
37 pattern=ADMIN_PREFIX + '/my_account/update')
38
38
39 # my account password
39 # my account password
40 config.add_route(
40 config.add_route(
41 name='my_account_password',
41 name='my_account_password',
42 pattern=ADMIN_PREFIX + '/my_account/password')
42 pattern=ADMIN_PREFIX + '/my_account/password')
43
43
44 config.add_route(
44 config.add_route(
45 name='my_account_password_update',
45 name='my_account_password_update',
46 pattern=ADMIN_PREFIX + '/my_account/password')
46 pattern=ADMIN_PREFIX + '/my_account/password/update')
47
47
48 # my account tokens
48 # my account tokens
49 config.add_route(
49 config.add_route(
50 name='my_account_auth_tokens',
50 name='my_account_auth_tokens',
51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
52 config.add_route(
52 config.add_route(
53 name='my_account_auth_tokens_add',
53 name='my_account_auth_tokens_add',
54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
55 config.add_route(
55 config.add_route(
56 name='my_account_auth_tokens_delete',
56 name='my_account_auth_tokens_delete',
57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
58
58
59 # my account emails
59 # my account emails
60 config.add_route(
60 config.add_route(
61 name='my_account_emails',
61 name='my_account_emails',
62 pattern=ADMIN_PREFIX + '/my_account/emails')
62 pattern=ADMIN_PREFIX + '/my_account/emails')
63 config.add_route(
63 config.add_route(
64 name='my_account_emails_add',
64 name='my_account_emails_add',
65 pattern=ADMIN_PREFIX + '/my_account/emails/new')
65 pattern=ADMIN_PREFIX + '/my_account/emails/new')
66 config.add_route(
66 config.add_route(
67 name='my_account_emails_delete',
67 name='my_account_emails_delete',
68 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
68 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
69
69
70 config.add_route(
70 config.add_route(
71 name='my_account_repos',
71 name='my_account_repos',
72 pattern=ADMIN_PREFIX + '/my_account/repos')
72 pattern=ADMIN_PREFIX + '/my_account/repos')
73
73
74 config.add_route(
74 config.add_route(
75 name='my_account_watched',
75 name='my_account_watched',
76 pattern=ADMIN_PREFIX + '/my_account/watched')
76 pattern=ADMIN_PREFIX + '/my_account/watched')
77
77
78 config.add_route(
78 config.add_route(
79 name='my_account_perms',
79 name='my_account_perms',
80 pattern=ADMIN_PREFIX + '/my_account/perms')
80 pattern=ADMIN_PREFIX + '/my_account/perms')
81
81
82 config.add_route(
82 config.add_route(
83 name='my_account_notifications',
83 name='my_account_notifications',
84 pattern=ADMIN_PREFIX + '/my_account/notifications')
84 pattern=ADMIN_PREFIX + '/my_account/notifications')
85
85
86 config.add_route(
86 config.add_route(
87 name='my_account_notifications_toggle_visibility',
87 name='my_account_notifications_toggle_visibility',
88 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
88 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
89
89
90 # my account pull requests
90 # my account pull requests
91 config.add_route(
91 config.add_route(
92 name='my_account_pullrequests',
92 name='my_account_pullrequests',
93 pattern=ADMIN_PREFIX + '/my_account/pull_requests')
93 pattern=ADMIN_PREFIX + '/my_account/pull_requests')
94 config.add_route(
94 config.add_route(
95 name='my_account_pullrequests_data',
95 name='my_account_pullrequests_data',
96 pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
96 pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
97
97
98 # notifications
98 # notifications
99 config.add_route(
99 config.add_route(
100 name='notifications_show_all',
100 name='notifications_show_all',
101 pattern=ADMIN_PREFIX + '/notifications')
101 pattern=ADMIN_PREFIX + '/notifications')
102
102
103 # notifications
103 # notifications
104 config.add_route(
104 config.add_route(
105 name='notifications_mark_all_read',
105 name='notifications_mark_all_read',
106 pattern=ADMIN_PREFIX + '/notifications/mark_all_read')
106 pattern=ADMIN_PREFIX + '/notifications/mark_all_read')
107
107
108 config.add_route(
108 config.add_route(
109 name='notifications_show',
109 name='notifications_show',
110 pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
110 pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
111
111
112 config.add_route(
112 config.add_route(
113 name='notifications_update',
113 name='notifications_update',
114 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
114 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
115
115
116 config.add_route(
116 config.add_route(
117 name='notifications_delete',
117 name='notifications_delete',
118 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
118 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
119
119
120 # channelstream test
120 # channelstream test
121 config.add_route(
121 config.add_route(
122 name='my_account_notifications_test_channelstream',
122 name='my_account_notifications_test_channelstream',
123 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
123 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
124
124
125 # Scan module for configuration decorators.
125 # Scan module for configuration decorators.
126 config.scan()
126 config.scan()
@@ -1,138 +1,144 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 pytest
21 import pytest
22 import mock
22 import mock
23
23
24 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.lib.auth import check_password
26 from rhodecode.lib.auth import check_password
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.model.user import UserModel
28 from rhodecode.model.user import UserModel
29 from rhodecode.tests import assert_session_flash
29 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests.fixture import Fixture, TestController, error_function
30 from rhodecode.tests.fixture import Fixture, TestController, error_function
31
31
32 fixture = Fixture()
32 fixture = Fixture()
33
33
34
34
35 def route_path(name, **kwargs):
35 def route_path(name, **kwargs):
36 return {
36 return {
37 'home': '/',
37 'home': '/',
38 'my_account_password':
38 'my_account_password':
39 ADMIN_PREFIX + '/my_account/password',
39 ADMIN_PREFIX + '/my_account/password',
40 'my_account_password_update':
41 ADMIN_PREFIX + '/my_account/password/update',
40 }[name].format(**kwargs)
42 }[name].format(**kwargs)
41
43
42
44
43 test_user_1 = 'testme'
45 test_user_1 = 'testme'
44 test_user_1_password = '0jd83nHNS/d23n'
46 test_user_1_password = '0jd83nHNS/d23n'
45
47
46
48
47 class TestMyAccountPassword(TestController):
49 class TestMyAccountPassword(TestController):
48 def test_valid_change_password(self, user_util):
50 def test_valid_change_password(self, user_util):
49 new_password = 'my_new_valid_password'
51 new_password = 'my_new_valid_password'
50 user = user_util.create_user(password=test_user_1_password)
52 user = user_util.create_user(password=test_user_1_password)
51 self.log_user(user.username, test_user_1_password)
53 self.log_user(user.username, test_user_1_password)
52
54
53 form_data = [
55 form_data = [
54 ('current_password', test_user_1_password),
56 ('current_password', test_user_1_password),
55 ('__start__', 'new_password:mapping'),
57 ('__start__', 'new_password:mapping'),
56 ('new_password', new_password),
58 ('new_password', new_password),
57 ('new_password-confirm', new_password),
59 ('new_password-confirm', new_password),
58 ('__end__', 'new_password:mapping'),
60 ('__end__', 'new_password:mapping'),
59 ('csrf_token', self.csrf_token),
61 ('csrf_token', self.csrf_token),
60 ]
62 ]
61 response = self.app.post(route_path('my_account_password'), form_data).follow()
63 response = self.app.post(
64 route_path('my_account_password_update'), form_data).follow()
62 assert 'Successfully updated password' in response
65 assert 'Successfully updated password' in response
63
66
64 # check_password depends on user being in session
67 # check_password depends on user being in session
65 Session().add(user)
68 Session().add(user)
66 try:
69 try:
67 assert check_password(new_password, user.password)
70 assert check_password(new_password, user.password)
68 finally:
71 finally:
69 Session().expunge(user)
72 Session().expunge(user)
70
73
71 @pytest.mark.parametrize('current_pw, new_pw, confirm_pw', [
74 @pytest.mark.parametrize('current_pw, new_pw, confirm_pw', [
72 ('', 'abcdef123', 'abcdef123'),
75 ('', 'abcdef123', 'abcdef123'),
73 ('wrong_pw', 'abcdef123', 'abcdef123'),
76 ('wrong_pw', 'abcdef123', 'abcdef123'),
74 (test_user_1_password, test_user_1_password, test_user_1_password),
77 (test_user_1_password, test_user_1_password, test_user_1_password),
75 (test_user_1_password, '', ''),
78 (test_user_1_password, '', ''),
76 (test_user_1_password, 'abcdef123', ''),
79 (test_user_1_password, 'abcdef123', ''),
77 (test_user_1_password, '', 'abcdef123'),
80 (test_user_1_password, '', 'abcdef123'),
78 (test_user_1_password, 'not_the', 'same_pw'),
81 (test_user_1_password, 'not_the', 'same_pw'),
79 (test_user_1_password, 'short', 'short'),
82 (test_user_1_password, 'short', 'short'),
80 ])
83 ])
81 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
84 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
82 user_util):
85 user_util):
83 user = user_util.create_user(password=test_user_1_password)
86 user = user_util.create_user(password=test_user_1_password)
84 self.log_user(user.username, test_user_1_password)
87 self.log_user(user.username, test_user_1_password)
85
88
86 form_data = [
89 form_data = [
87 ('current_password', current_pw),
90 ('current_password', current_pw),
88 ('__start__', 'new_password:mapping'),
91 ('__start__', 'new_password:mapping'),
89 ('new_password', new_pw),
92 ('new_password', new_pw),
90 ('new_password-confirm', confirm_pw),
93 ('new_password-confirm', confirm_pw),
91 ('__end__', 'new_password:mapping'),
94 ('__end__', 'new_password:mapping'),
92 ('csrf_token', self.csrf_token),
95 ('csrf_token', self.csrf_token),
93 ]
96 ]
94 response = self.app.post(route_path('my_account_password'), form_data)
97 response = self.app.post(
98 route_path('my_account_password_update'), form_data)
95
99
96 assert_response = response.assert_response()
100 assert_response = response.assert_response()
97 assert assert_response.get_elements('.error-block')
101 assert assert_response.get_elements('.error-block')
98
102
99 @mock.patch.object(UserModel, 'update_user', error_function)
103 @mock.patch.object(UserModel, 'update_user', error_function)
100 def test_invalid_change_password_exception(self, user_util):
104 def test_invalid_change_password_exception(self, user_util):
101 user = user_util.create_user(password=test_user_1_password)
105 user = user_util.create_user(password=test_user_1_password)
102 self.log_user(user.username, test_user_1_password)
106 self.log_user(user.username, test_user_1_password)
103
107
104 form_data = [
108 form_data = [
105 ('current_password', test_user_1_password),
109 ('current_password', test_user_1_password),
106 ('__start__', 'new_password:mapping'),
110 ('__start__', 'new_password:mapping'),
107 ('new_password', '123456'),
111 ('new_password', '123456'),
108 ('new_password-confirm', '123456'),
112 ('new_password-confirm', '123456'),
109 ('__end__', 'new_password:mapping'),
113 ('__end__', 'new_password:mapping'),
110 ('csrf_token', self.csrf_token),
114 ('csrf_token', self.csrf_token),
111 ]
115 ]
112 response = self.app.post(route_path('my_account_password'), form_data)
116 response = self.app.post(
117 route_path('my_account_password_update'), form_data)
113 assert_session_flash(
118 assert_session_flash(
114 response, 'Error occurred during update of user password')
119 response, 'Error occurred during update of user password')
115
120
116 def test_password_is_updated_in_session_on_password_change(self, user_util):
121 def test_password_is_updated_in_session_on_password_change(self, user_util):
117 old_password = 'abcdef123'
122 old_password = 'abcdef123'
118 new_password = 'abcdef124'
123 new_password = 'abcdef124'
119
124
120 user = user_util.create_user(password=old_password)
125 user = user_util.create_user(password=old_password)
121 session = self.log_user(user.username, old_password)
126 session = self.log_user(user.username, old_password)
122 old_password_hash = session['password']
127 old_password_hash = session['password']
123
128
124 form_data = [
129 form_data = [
125 ('current_password', old_password),
130 ('current_password', old_password),
126 ('__start__', 'new_password:mapping'),
131 ('__start__', 'new_password:mapping'),
127 ('new_password', new_password),
132 ('new_password', new_password),
128 ('new_password-confirm', new_password),
133 ('new_password-confirm', new_password),
129 ('__end__', 'new_password:mapping'),
134 ('__end__', 'new_password:mapping'),
130 ('csrf_token', self.csrf_token),
135 ('csrf_token', self.csrf_token),
131 ]
136 ]
132 self.app.post(route_path('my_account_password'), form_data)
137 self.app.post(
138 route_path('my_account_password_update'), form_data)
133
139
134 response = self.app.get(route_path('home'))
140 response = self.app.get(route_path('home'))
135 session = response.get_session_from_response()
141 session = response.get_session_from_response()
136 new_password_hash = session['rhodecode_user']['password']
142 new_password_hash = session['rhodecode_user']['password']
137
143
138 assert old_password_hash != new_password_hash No newline at end of file
144 assert old_password_hash != new_password_hash
@@ -1,584 +1,586 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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 logging
21 import logging
22 import datetime
22 import datetime
23
23
24 import formencode
24 import formencode
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode import forms
31 from rhodecode import forms
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import audit_logger
33 from rhodecode.lib import audit_logger
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.channelstream import channelstream_request, \
36 from rhodecode.lib.channelstream import channelstream_request, \
37 ChannelstreamException
37 ChannelstreamException
38 from rhodecode.lib.utils2 import safe_int, md5, str2bool
38 from rhodecode.lib.utils2 import safe_int, md5, str2bool
39 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.comment import CommentsModel
40 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
42 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
43 PullRequest)
43 PullRequest)
44 from rhodecode.model.forms import UserForm
44 from rhodecode.model.forms import UserForm
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.pull_request import PullRequestModel
46 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.scm import RepoList
47 from rhodecode.model.scm import RepoList
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.validation_schema.schemas import user_schema
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class MyAccountView(BaseAppView, DataGridAppView):
55 class MyAccountView(BaseAppView, DataGridAppView):
56 ALLOW_SCOPED_TOKENS = False
56 ALLOW_SCOPED_TOKENS = False
57 """
57 """
58 This view has alternative version inside EE, if modified please take a look
58 This view has alternative version inside EE, if modified please take a look
59 in there as well.
59 in there as well.
60 """
60 """
61
61
62 def load_default_context(self):
62 def load_default_context(self):
63 c = self._get_local_tmpl_context()
63 c = self._get_local_tmpl_context()
64 c.user = c.auth_user.get_instance()
64 c.user = c.auth_user.get_instance()
65 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
65 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
66 self._register_global_c(c)
66 self._register_global_c(c)
67 return c
67 return c
68
68
69 @LoginRequired()
69 @LoginRequired()
70 @NotAnonymous()
70 @NotAnonymous()
71 @view_config(
71 @view_config(
72 route_name='my_account_profile', request_method='GET',
72 route_name='my_account_profile', request_method='GET',
73 renderer='rhodecode:templates/admin/my_account/my_account.mako')
73 renderer='rhodecode:templates/admin/my_account/my_account.mako')
74 def my_account_profile(self):
74 def my_account_profile(self):
75 c = self.load_default_context()
75 c = self.load_default_context()
76 c.active = 'profile'
76 c.active = 'profile'
77 return self._get_template_context(c)
77 return self._get_template_context(c)
78
78
79 @LoginRequired()
79 @LoginRequired()
80 @NotAnonymous()
80 @NotAnonymous()
81 @view_config(
81 @view_config(
82 route_name='my_account_password', request_method='GET',
82 route_name='my_account_password', request_method='GET',
83 renderer='rhodecode:templates/admin/my_account/my_account.mako')
83 renderer='rhodecode:templates/admin/my_account/my_account.mako')
84 def my_account_password(self):
84 def my_account_password(self):
85 c = self.load_default_context()
85 c = self.load_default_context()
86 c.active = 'password'
86 c.active = 'password'
87 c.extern_type = c.user.extern_type
87 c.extern_type = c.user.extern_type
88
88
89 schema = user_schema.ChangePasswordSchema().bind(
89 schema = user_schema.ChangePasswordSchema().bind(
90 username=c.user.username)
90 username=c.user.username)
91
91
92 form = forms.Form(
92 form = forms.Form(
93 schema, buttons=(forms.buttons.save, forms.buttons.reset))
93 schema,
94 action=h.route_path('my_account_password_update'),
95 buttons=(forms.buttons.save, forms.buttons.reset))
94
96
95 c.form = form
97 c.form = form
96 return self._get_template_context(c)
98 return self._get_template_context(c)
97
99
98 @LoginRequired()
100 @LoginRequired()
99 @NotAnonymous()
101 @NotAnonymous()
100 @CSRFRequired()
102 @CSRFRequired()
101 @view_config(
103 @view_config(
102 route_name='my_account_password', request_method='POST',
104 route_name='my_account_password_update', request_method='POST',
103 renderer='rhodecode:templates/admin/my_account/my_account.mako')
105 renderer='rhodecode:templates/admin/my_account/my_account.mako')
104 def my_account_password_update(self):
106 def my_account_password_update(self):
105 _ = self.request.translate
107 _ = self.request.translate
106 c = self.load_default_context()
108 c = self.load_default_context()
107 c.active = 'password'
109 c.active = 'password'
108 c.extern_type = c.user.extern_type
110 c.extern_type = c.user.extern_type
109
111
110 schema = user_schema.ChangePasswordSchema().bind(
112 schema = user_schema.ChangePasswordSchema().bind(
111 username=c.user.username)
113 username=c.user.username)
112
114
113 form = forms.Form(
115 form = forms.Form(
114 schema, buttons=(forms.buttons.save, forms.buttons.reset))
116 schema, buttons=(forms.buttons.save, forms.buttons.reset))
115
117
116 if c.extern_type != 'rhodecode':
118 if c.extern_type != 'rhodecode':
117 raise HTTPFound(self.request.route_path('my_account_password'))
119 raise HTTPFound(self.request.route_path('my_account_password'))
118
120
119 controls = self.request.POST.items()
121 controls = self.request.POST.items()
120 try:
122 try:
121 valid_data = form.validate(controls)
123 valid_data = form.validate(controls)
122 UserModel().update_user(c.user.user_id, **valid_data)
124 UserModel().update_user(c.user.user_id, **valid_data)
123 c.user.update_userdata(force_password_change=False)
125 c.user.update_userdata(force_password_change=False)
124 Session().commit()
126 Session().commit()
125 except forms.ValidationFailure as e:
127 except forms.ValidationFailure as e:
126 c.form = e
128 c.form = e
127 return self._get_template_context(c)
129 return self._get_template_context(c)
128
130
129 except Exception:
131 except Exception:
130 log.exception("Exception updating password")
132 log.exception("Exception updating password")
131 h.flash(_('Error occurred during update of user password'),
133 h.flash(_('Error occurred during update of user password'),
132 category='error')
134 category='error')
133 else:
135 else:
134 instance = c.auth_user.get_instance()
136 instance = c.auth_user.get_instance()
135 self.session.setdefault('rhodecode_user', {}).update(
137 self.session.setdefault('rhodecode_user', {}).update(
136 {'password': md5(instance.password)})
138 {'password': md5(instance.password)})
137 self.session.save()
139 self.session.save()
138 h.flash(_("Successfully updated password"), category='success')
140 h.flash(_("Successfully updated password"), category='success')
139
141
140 raise HTTPFound(self.request.route_path('my_account_password'))
142 raise HTTPFound(self.request.route_path('my_account_password'))
141
143
142 @LoginRequired()
144 @LoginRequired()
143 @NotAnonymous()
145 @NotAnonymous()
144 @view_config(
146 @view_config(
145 route_name='my_account_auth_tokens', request_method='GET',
147 route_name='my_account_auth_tokens', request_method='GET',
146 renderer='rhodecode:templates/admin/my_account/my_account.mako')
148 renderer='rhodecode:templates/admin/my_account/my_account.mako')
147 def my_account_auth_tokens(self):
149 def my_account_auth_tokens(self):
148 _ = self.request.translate
150 _ = self.request.translate
149
151
150 c = self.load_default_context()
152 c = self.load_default_context()
151 c.active = 'auth_tokens'
153 c.active = 'auth_tokens'
152
154
153 c.lifetime_values = [
155 c.lifetime_values = [
154 (str(-1), _('forever')),
156 (str(-1), _('forever')),
155 (str(5), _('5 minutes')),
157 (str(5), _('5 minutes')),
156 (str(60), _('1 hour')),
158 (str(60), _('1 hour')),
157 (str(60 * 24), _('1 day')),
159 (str(60 * 24), _('1 day')),
158 (str(60 * 24 * 30), _('1 month')),
160 (str(60 * 24 * 30), _('1 month')),
159 ]
161 ]
160 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
162 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
161 c.role_values = [
163 c.role_values = [
162 (x, AuthTokenModel.cls._get_role_name(x))
164 (x, AuthTokenModel.cls._get_role_name(x))
163 for x in AuthTokenModel.cls.ROLES]
165 for x in AuthTokenModel.cls.ROLES]
164 c.role_options = [(c.role_values, _("Role"))]
166 c.role_options = [(c.role_values, _("Role"))]
165 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
167 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
166 c.user.user_id, show_expired=True)
168 c.user.user_id, show_expired=True)
167 return self._get_template_context(c)
169 return self._get_template_context(c)
168
170
169 def maybe_attach_token_scope(self, token):
171 def maybe_attach_token_scope(self, token):
170 # implemented in EE edition
172 # implemented in EE edition
171 pass
173 pass
172
174
173 @LoginRequired()
175 @LoginRequired()
174 @NotAnonymous()
176 @NotAnonymous()
175 @CSRFRequired()
177 @CSRFRequired()
176 @view_config(
178 @view_config(
177 route_name='my_account_auth_tokens_add', request_method='POST',)
179 route_name='my_account_auth_tokens_add', request_method='POST',)
178 def my_account_auth_tokens_add(self):
180 def my_account_auth_tokens_add(self):
179 _ = self.request.translate
181 _ = self.request.translate
180 c = self.load_default_context()
182 c = self.load_default_context()
181
183
182 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
184 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
183 description = self.request.POST.get('description')
185 description = self.request.POST.get('description')
184 role = self.request.POST.get('role')
186 role = self.request.POST.get('role')
185
187
186 token = AuthTokenModel().create(
188 token = AuthTokenModel().create(
187 c.user.user_id, description, lifetime, role)
189 c.user.user_id, description, lifetime, role)
188 token_data = token.get_api_data()
190 token_data = token.get_api_data()
189
191
190 self.maybe_attach_token_scope(token)
192 self.maybe_attach_token_scope(token)
191 audit_logger.store_web(
193 audit_logger.store_web(
192 'user.edit.token.add', action_data={
194 'user.edit.token.add', action_data={
193 'data': {'token': token_data, 'user': 'self'}},
195 'data': {'token': token_data, 'user': 'self'}},
194 user=self._rhodecode_user, )
196 user=self._rhodecode_user, )
195 Session().commit()
197 Session().commit()
196
198
197 h.flash(_("Auth token successfully created"), category='success')
199 h.flash(_("Auth token successfully created"), category='success')
198 return HTTPFound(h.route_path('my_account_auth_tokens'))
200 return HTTPFound(h.route_path('my_account_auth_tokens'))
199
201
200 @LoginRequired()
202 @LoginRequired()
201 @NotAnonymous()
203 @NotAnonymous()
202 @CSRFRequired()
204 @CSRFRequired()
203 @view_config(
205 @view_config(
204 route_name='my_account_auth_tokens_delete', request_method='POST')
206 route_name='my_account_auth_tokens_delete', request_method='POST')
205 def my_account_auth_tokens_delete(self):
207 def my_account_auth_tokens_delete(self):
206 _ = self.request.translate
208 _ = self.request.translate
207 c = self.load_default_context()
209 c = self.load_default_context()
208
210
209 del_auth_token = self.request.POST.get('del_auth_token')
211 del_auth_token = self.request.POST.get('del_auth_token')
210
212
211 if del_auth_token:
213 if del_auth_token:
212 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
214 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
213 token_data = token.get_api_data()
215 token_data = token.get_api_data()
214
216
215 AuthTokenModel().delete(del_auth_token, c.user.user_id)
217 AuthTokenModel().delete(del_auth_token, c.user.user_id)
216 audit_logger.store_web(
218 audit_logger.store_web(
217 'user.edit.token.delete', action_data={
219 'user.edit.token.delete', action_data={
218 'data': {'token': token_data, 'user': 'self'}},
220 'data': {'token': token_data, 'user': 'self'}},
219 user=self._rhodecode_user,)
221 user=self._rhodecode_user,)
220 Session().commit()
222 Session().commit()
221 h.flash(_("Auth token successfully deleted"), category='success')
223 h.flash(_("Auth token successfully deleted"), category='success')
222
224
223 return HTTPFound(h.route_path('my_account_auth_tokens'))
225 return HTTPFound(h.route_path('my_account_auth_tokens'))
224
226
225 @LoginRequired()
227 @LoginRequired()
226 @NotAnonymous()
228 @NotAnonymous()
227 @view_config(
229 @view_config(
228 route_name='my_account_emails', request_method='GET',
230 route_name='my_account_emails', request_method='GET',
229 renderer='rhodecode:templates/admin/my_account/my_account.mako')
231 renderer='rhodecode:templates/admin/my_account/my_account.mako')
230 def my_account_emails(self):
232 def my_account_emails(self):
231 _ = self.request.translate
233 _ = self.request.translate
232
234
233 c = self.load_default_context()
235 c = self.load_default_context()
234 c.active = 'emails'
236 c.active = 'emails'
235
237
236 c.user_email_map = UserEmailMap.query()\
238 c.user_email_map = UserEmailMap.query()\
237 .filter(UserEmailMap.user == c.user).all()
239 .filter(UserEmailMap.user == c.user).all()
238 return self._get_template_context(c)
240 return self._get_template_context(c)
239
241
240 @LoginRequired()
242 @LoginRequired()
241 @NotAnonymous()
243 @NotAnonymous()
242 @CSRFRequired()
244 @CSRFRequired()
243 @view_config(
245 @view_config(
244 route_name='my_account_emails_add', request_method='POST')
246 route_name='my_account_emails_add', request_method='POST')
245 def my_account_emails_add(self):
247 def my_account_emails_add(self):
246 _ = self.request.translate
248 _ = self.request.translate
247 c = self.load_default_context()
249 c = self.load_default_context()
248
250
249 email = self.request.POST.get('new_email')
251 email = self.request.POST.get('new_email')
250
252
251 try:
253 try:
252 UserModel().add_extra_email(c.user.user_id, email)
254 UserModel().add_extra_email(c.user.user_id, email)
253 audit_logger.store_web(
255 audit_logger.store_web(
254 'user.edit.email.add', action_data={
256 'user.edit.email.add', action_data={
255 'data': {'email': email, 'user': 'self'}},
257 'data': {'email': email, 'user': 'self'}},
256 user=self._rhodecode_user,)
258 user=self._rhodecode_user,)
257
259
258 Session().commit()
260 Session().commit()
259 h.flash(_("Added new email address `%s` for user account") % email,
261 h.flash(_("Added new email address `%s` for user account") % email,
260 category='success')
262 category='success')
261 except formencode.Invalid as error:
263 except formencode.Invalid as error:
262 h.flash(h.escape(error.error_dict['email']), category='error')
264 h.flash(h.escape(error.error_dict['email']), category='error')
263 except Exception:
265 except Exception:
264 log.exception("Exception in my_account_emails")
266 log.exception("Exception in my_account_emails")
265 h.flash(_('An error occurred during email saving'),
267 h.flash(_('An error occurred during email saving'),
266 category='error')
268 category='error')
267 return HTTPFound(h.route_path('my_account_emails'))
269 return HTTPFound(h.route_path('my_account_emails'))
268
270
269 @LoginRequired()
271 @LoginRequired()
270 @NotAnonymous()
272 @NotAnonymous()
271 @CSRFRequired()
273 @CSRFRequired()
272 @view_config(
274 @view_config(
273 route_name='my_account_emails_delete', request_method='POST')
275 route_name='my_account_emails_delete', request_method='POST')
274 def my_account_emails_delete(self):
276 def my_account_emails_delete(self):
275 _ = self.request.translate
277 _ = self.request.translate
276 c = self.load_default_context()
278 c = self.load_default_context()
277
279
278 del_email_id = self.request.POST.get('del_email_id')
280 del_email_id = self.request.POST.get('del_email_id')
279 if del_email_id:
281 if del_email_id:
280 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
282 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
281 UserModel().delete_extra_email(c.user.user_id, del_email_id)
283 UserModel().delete_extra_email(c.user.user_id, del_email_id)
282 audit_logger.store_web(
284 audit_logger.store_web(
283 'user.edit.email.delete', action_data={
285 'user.edit.email.delete', action_data={
284 'data': {'email': email, 'user': 'self'}},
286 'data': {'email': email, 'user': 'self'}},
285 user=self._rhodecode_user,)
287 user=self._rhodecode_user,)
286 Session().commit()
288 Session().commit()
287 h.flash(_("Email successfully deleted"),
289 h.flash(_("Email successfully deleted"),
288 category='success')
290 category='success')
289 return HTTPFound(h.route_path('my_account_emails'))
291 return HTTPFound(h.route_path('my_account_emails'))
290
292
291 @LoginRequired()
293 @LoginRequired()
292 @NotAnonymous()
294 @NotAnonymous()
293 @CSRFRequired()
295 @CSRFRequired()
294 @view_config(
296 @view_config(
295 route_name='my_account_notifications_test_channelstream',
297 route_name='my_account_notifications_test_channelstream',
296 request_method='POST', renderer='json_ext')
298 request_method='POST', renderer='json_ext')
297 def my_account_notifications_test_channelstream(self):
299 def my_account_notifications_test_channelstream(self):
298 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
300 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
299 self._rhodecode_user.username, datetime.datetime.now())
301 self._rhodecode_user.username, datetime.datetime.now())
300 payload = {
302 payload = {
301 # 'channel': 'broadcast',
303 # 'channel': 'broadcast',
302 'type': 'message',
304 'type': 'message',
303 'timestamp': datetime.datetime.utcnow(),
305 'timestamp': datetime.datetime.utcnow(),
304 'user': 'system',
306 'user': 'system',
305 'pm_users': [self._rhodecode_user.username],
307 'pm_users': [self._rhodecode_user.username],
306 'message': {
308 'message': {
307 'message': message,
309 'message': message,
308 'level': 'info',
310 'level': 'info',
309 'topic': '/notifications'
311 'topic': '/notifications'
310 }
312 }
311 }
313 }
312
314
313 registry = self.request.registry
315 registry = self.request.registry
314 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
316 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
315 channelstream_config = rhodecode_plugins.get('channelstream', {})
317 channelstream_config = rhodecode_plugins.get('channelstream', {})
316
318
317 try:
319 try:
318 channelstream_request(channelstream_config, [payload], '/message')
320 channelstream_request(channelstream_config, [payload], '/message')
319 except ChannelstreamException as e:
321 except ChannelstreamException as e:
320 log.exception('Failed to send channelstream data')
322 log.exception('Failed to send channelstream data')
321 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
323 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
322 return {"response": 'Channelstream data sent. '
324 return {"response": 'Channelstream data sent. '
323 'You should see a new live message now.'}
325 'You should see a new live message now.'}
324
326
325 def _load_my_repos_data(self, watched=False):
327 def _load_my_repos_data(self, watched=False):
326 if watched:
328 if watched:
327 admin = False
329 admin = False
328 follows_repos = Session().query(UserFollowing)\
330 follows_repos = Session().query(UserFollowing)\
329 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
331 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
330 .options(joinedload(UserFollowing.follows_repository))\
332 .options(joinedload(UserFollowing.follows_repository))\
331 .all()
333 .all()
332 repo_list = [x.follows_repository for x in follows_repos]
334 repo_list = [x.follows_repository for x in follows_repos]
333 else:
335 else:
334 admin = True
336 admin = True
335 repo_list = Repository.get_all_repos(
337 repo_list = Repository.get_all_repos(
336 user_id=self._rhodecode_user.user_id)
338 user_id=self._rhodecode_user.user_id)
337 repo_list = RepoList(repo_list, perm_set=[
339 repo_list = RepoList(repo_list, perm_set=[
338 'repository.read', 'repository.write', 'repository.admin'])
340 'repository.read', 'repository.write', 'repository.admin'])
339
341
340 repos_data = RepoModel().get_repos_as_dict(
342 repos_data = RepoModel().get_repos_as_dict(
341 repo_list=repo_list, admin=admin)
343 repo_list=repo_list, admin=admin)
342 # json used to render the grid
344 # json used to render the grid
343 return json.dumps(repos_data)
345 return json.dumps(repos_data)
344
346
345 @LoginRequired()
347 @LoginRequired()
346 @NotAnonymous()
348 @NotAnonymous()
347 @view_config(
349 @view_config(
348 route_name='my_account_repos', request_method='GET',
350 route_name='my_account_repos', request_method='GET',
349 renderer='rhodecode:templates/admin/my_account/my_account.mako')
351 renderer='rhodecode:templates/admin/my_account/my_account.mako')
350 def my_account_repos(self):
352 def my_account_repos(self):
351 c = self.load_default_context()
353 c = self.load_default_context()
352 c.active = 'repos'
354 c.active = 'repos'
353
355
354 # json used to render the grid
356 # json used to render the grid
355 c.data = self._load_my_repos_data()
357 c.data = self._load_my_repos_data()
356 return self._get_template_context(c)
358 return self._get_template_context(c)
357
359
358 @LoginRequired()
360 @LoginRequired()
359 @NotAnonymous()
361 @NotAnonymous()
360 @view_config(
362 @view_config(
361 route_name='my_account_watched', request_method='GET',
363 route_name='my_account_watched', request_method='GET',
362 renderer='rhodecode:templates/admin/my_account/my_account.mako')
364 renderer='rhodecode:templates/admin/my_account/my_account.mako')
363 def my_account_watched(self):
365 def my_account_watched(self):
364 c = self.load_default_context()
366 c = self.load_default_context()
365 c.active = 'watched'
367 c.active = 'watched'
366
368
367 # json used to render the grid
369 # json used to render the grid
368 c.data = self._load_my_repos_data(watched=True)
370 c.data = self._load_my_repos_data(watched=True)
369 return self._get_template_context(c)
371 return self._get_template_context(c)
370
372
371 @LoginRequired()
373 @LoginRequired()
372 @NotAnonymous()
374 @NotAnonymous()
373 @view_config(
375 @view_config(
374 route_name='my_account_perms', request_method='GET',
376 route_name='my_account_perms', request_method='GET',
375 renderer='rhodecode:templates/admin/my_account/my_account.mako')
377 renderer='rhodecode:templates/admin/my_account/my_account.mako')
376 def my_account_perms(self):
378 def my_account_perms(self):
377 c = self.load_default_context()
379 c = self.load_default_context()
378 c.active = 'perms'
380 c.active = 'perms'
379
381
380 c.perm_user = c.auth_user
382 c.perm_user = c.auth_user
381 return self._get_template_context(c)
383 return self._get_template_context(c)
382
384
383 @LoginRequired()
385 @LoginRequired()
384 @NotAnonymous()
386 @NotAnonymous()
385 @view_config(
387 @view_config(
386 route_name='my_account_notifications', request_method='GET',
388 route_name='my_account_notifications', request_method='GET',
387 renderer='rhodecode:templates/admin/my_account/my_account.mako')
389 renderer='rhodecode:templates/admin/my_account/my_account.mako')
388 def my_notifications(self):
390 def my_notifications(self):
389 c = self.load_default_context()
391 c = self.load_default_context()
390 c.active = 'notifications'
392 c.active = 'notifications'
391
393
392 return self._get_template_context(c)
394 return self._get_template_context(c)
393
395
394 @LoginRequired()
396 @LoginRequired()
395 @NotAnonymous()
397 @NotAnonymous()
396 @CSRFRequired()
398 @CSRFRequired()
397 @view_config(
399 @view_config(
398 route_name='my_account_notifications_toggle_visibility',
400 route_name='my_account_notifications_toggle_visibility',
399 request_method='POST', renderer='json_ext')
401 request_method='POST', renderer='json_ext')
400 def my_notifications_toggle_visibility(self):
402 def my_notifications_toggle_visibility(self):
401 user = self._rhodecode_db_user
403 user = self._rhodecode_db_user
402 new_status = not user.user_data.get('notification_status', True)
404 new_status = not user.user_data.get('notification_status', True)
403 user.update_userdata(notification_status=new_status)
405 user.update_userdata(notification_status=new_status)
404 Session().commit()
406 Session().commit()
405 return user.user_data['notification_status']
407 return user.user_data['notification_status']
406
408
407 @LoginRequired()
409 @LoginRequired()
408 @NotAnonymous()
410 @NotAnonymous()
409 @view_config(
411 @view_config(
410 route_name='my_account_edit',
412 route_name='my_account_edit',
411 request_method='GET',
413 request_method='GET',
412 renderer='rhodecode:templates/admin/my_account/my_account.mako')
414 renderer='rhodecode:templates/admin/my_account/my_account.mako')
413 def my_account_edit(self):
415 def my_account_edit(self):
414 c = self.load_default_context()
416 c = self.load_default_context()
415 c.active = 'profile_edit'
417 c.active = 'profile_edit'
416
418
417 c.perm_user = c.auth_user
419 c.perm_user = c.auth_user
418 c.extern_type = c.user.extern_type
420 c.extern_type = c.user.extern_type
419 c.extern_name = c.user.extern_name
421 c.extern_name = c.user.extern_name
420
422
421 defaults = c.user.get_dict()
423 defaults = c.user.get_dict()
422
424
423 data = render('rhodecode:templates/admin/my_account/my_account.mako',
425 data = render('rhodecode:templates/admin/my_account/my_account.mako',
424 self._get_template_context(c), self.request)
426 self._get_template_context(c), self.request)
425 html = formencode.htmlfill.render(
427 html = formencode.htmlfill.render(
426 data,
428 data,
427 defaults=defaults,
429 defaults=defaults,
428 encoding="UTF-8",
430 encoding="UTF-8",
429 force_defaults=False
431 force_defaults=False
430 )
432 )
431 return Response(html)
433 return Response(html)
432
434
433 @LoginRequired()
435 @LoginRequired()
434 @NotAnonymous()
436 @NotAnonymous()
435 @CSRFRequired()
437 @CSRFRequired()
436 @view_config(
438 @view_config(
437 route_name='my_account_update',
439 route_name='my_account_update',
438 request_method='POST',
440 request_method='POST',
439 renderer='rhodecode:templates/admin/my_account/my_account.mako')
441 renderer='rhodecode:templates/admin/my_account/my_account.mako')
440 def my_account_update(self):
442 def my_account_update(self):
441 _ = self.request.translate
443 _ = self.request.translate
442 c = self.load_default_context()
444 c = self.load_default_context()
443 c.active = 'profile_edit'
445 c.active = 'profile_edit'
444
446
445 c.perm_user = c.auth_user
447 c.perm_user = c.auth_user
446 c.extern_type = c.user.extern_type
448 c.extern_type = c.user.extern_type
447 c.extern_name = c.user.extern_name
449 c.extern_name = c.user.extern_name
448
450
449 _form = UserForm(edit=True,
451 _form = UserForm(edit=True,
450 old_data={'user_id': self._rhodecode_user.user_id,
452 old_data={'user_id': self._rhodecode_user.user_id,
451 'email': self._rhodecode_user.email})()
453 'email': self._rhodecode_user.email})()
452 form_result = {}
454 form_result = {}
453 try:
455 try:
454 post_data = dict(self.request.POST)
456 post_data = dict(self.request.POST)
455 post_data['new_password'] = ''
457 post_data['new_password'] = ''
456 post_data['password_confirmation'] = ''
458 post_data['password_confirmation'] = ''
457 form_result = _form.to_python(post_data)
459 form_result = _form.to_python(post_data)
458 # skip updating those attrs for my account
460 # skip updating those attrs for my account
459 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
461 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
460 'new_password', 'password_confirmation']
462 'new_password', 'password_confirmation']
461 # TODO: plugin should define if username can be updated
463 # TODO: plugin should define if username can be updated
462 if c.extern_type != "rhodecode":
464 if c.extern_type != "rhodecode":
463 # forbid updating username for external accounts
465 # forbid updating username for external accounts
464 skip_attrs.append('username')
466 skip_attrs.append('username')
465
467
466 UserModel().update_user(
468 UserModel().update_user(
467 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
469 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
468 **form_result)
470 **form_result)
469 h.flash(_('Your account was updated successfully'),
471 h.flash(_('Your account was updated successfully'),
470 category='success')
472 category='success')
471 Session().commit()
473 Session().commit()
472
474
473 except formencode.Invalid as errors:
475 except formencode.Invalid as errors:
474 data = render(
476 data = render(
475 'rhodecode:templates/admin/my_account/my_account.mako',
477 'rhodecode:templates/admin/my_account/my_account.mako',
476 self._get_template_context(c), self.request)
478 self._get_template_context(c), self.request)
477
479
478 html = formencode.htmlfill.render(
480 html = formencode.htmlfill.render(
479 data,
481 data,
480 defaults=errors.value,
482 defaults=errors.value,
481 errors=errors.error_dict or {},
483 errors=errors.error_dict or {},
482 prefix_error=False,
484 prefix_error=False,
483 encoding="UTF-8",
485 encoding="UTF-8",
484 force_defaults=False)
486 force_defaults=False)
485 return Response(html)
487 return Response(html)
486
488
487 except Exception:
489 except Exception:
488 log.exception("Exception updating user")
490 log.exception("Exception updating user")
489 h.flash(_('Error occurred during update of user %s')
491 h.flash(_('Error occurred during update of user %s')
490 % form_result.get('username'), category='error')
492 % form_result.get('username'), category='error')
491 raise HTTPFound(h.route_path('my_account_profile'))
493 raise HTTPFound(h.route_path('my_account_profile'))
492
494
493 raise HTTPFound(h.route_path('my_account_profile'))
495 raise HTTPFound(h.route_path('my_account_profile'))
494
496
495 def _get_pull_requests_list(self, statuses):
497 def _get_pull_requests_list(self, statuses):
496 draw, start, limit = self._extract_chunk(self.request)
498 draw, start, limit = self._extract_chunk(self.request)
497 search_q, order_by, order_dir = self._extract_ordering(self.request)
499 search_q, order_by, order_dir = self._extract_ordering(self.request)
498 _render = self.request.get_partial_renderer(
500 _render = self.request.get_partial_renderer(
499 'data_table/_dt_elements.mako')
501 'data_table/_dt_elements.mako')
500
502
501 pull_requests = PullRequestModel().get_im_participating_in(
503 pull_requests = PullRequestModel().get_im_participating_in(
502 user_id=self._rhodecode_user.user_id,
504 user_id=self._rhodecode_user.user_id,
503 statuses=statuses,
505 statuses=statuses,
504 offset=start, length=limit, order_by=order_by,
506 offset=start, length=limit, order_by=order_by,
505 order_dir=order_dir)
507 order_dir=order_dir)
506
508
507 pull_requests_total_count = PullRequestModel().count_im_participating_in(
509 pull_requests_total_count = PullRequestModel().count_im_participating_in(
508 user_id=self._rhodecode_user.user_id, statuses=statuses)
510 user_id=self._rhodecode_user.user_id, statuses=statuses)
509
511
510 data = []
512 data = []
511 comments_model = CommentsModel()
513 comments_model = CommentsModel()
512 for pr in pull_requests:
514 for pr in pull_requests:
513 repo_id = pr.target_repo_id
515 repo_id = pr.target_repo_id
514 comments = comments_model.get_all_comments(
516 comments = comments_model.get_all_comments(
515 repo_id, pull_request=pr)
517 repo_id, pull_request=pr)
516 owned = pr.user_id == self._rhodecode_user.user_id
518 owned = pr.user_id == self._rhodecode_user.user_id
517
519
518 data.append({
520 data.append({
519 'target_repo': _render('pullrequest_target_repo',
521 'target_repo': _render('pullrequest_target_repo',
520 pr.target_repo.repo_name),
522 pr.target_repo.repo_name),
521 'name': _render('pullrequest_name',
523 'name': _render('pullrequest_name',
522 pr.pull_request_id, pr.target_repo.repo_name,
524 pr.pull_request_id, pr.target_repo.repo_name,
523 short=True),
525 short=True),
524 'name_raw': pr.pull_request_id,
526 'name_raw': pr.pull_request_id,
525 'status': _render('pullrequest_status',
527 'status': _render('pullrequest_status',
526 pr.calculated_review_status()),
528 pr.calculated_review_status()),
527 'title': _render(
529 'title': _render(
528 'pullrequest_title', pr.title, pr.description),
530 'pullrequest_title', pr.title, pr.description),
529 'description': h.escape(pr.description),
531 'description': h.escape(pr.description),
530 'updated_on': _render('pullrequest_updated_on',
532 'updated_on': _render('pullrequest_updated_on',
531 h.datetime_to_time(pr.updated_on)),
533 h.datetime_to_time(pr.updated_on)),
532 'updated_on_raw': h.datetime_to_time(pr.updated_on),
534 'updated_on_raw': h.datetime_to_time(pr.updated_on),
533 'created_on': _render('pullrequest_updated_on',
535 'created_on': _render('pullrequest_updated_on',
534 h.datetime_to_time(pr.created_on)),
536 h.datetime_to_time(pr.created_on)),
535 'created_on_raw': h.datetime_to_time(pr.created_on),
537 'created_on_raw': h.datetime_to_time(pr.created_on),
536 'author': _render('pullrequest_author',
538 'author': _render('pullrequest_author',
537 pr.author.full_contact, ),
539 pr.author.full_contact, ),
538 'author_raw': pr.author.full_name,
540 'author_raw': pr.author.full_name,
539 'comments': _render('pullrequest_comments', len(comments)),
541 'comments': _render('pullrequest_comments', len(comments)),
540 'comments_raw': len(comments),
542 'comments_raw': len(comments),
541 'closed': pr.is_closed(),
543 'closed': pr.is_closed(),
542 'owned': owned
544 'owned': owned
543 })
545 })
544
546
545 # json used to render the grid
547 # json used to render the grid
546 data = ({
548 data = ({
547 'draw': draw,
549 'draw': draw,
548 'data': data,
550 'data': data,
549 'recordsTotal': pull_requests_total_count,
551 'recordsTotal': pull_requests_total_count,
550 'recordsFiltered': pull_requests_total_count,
552 'recordsFiltered': pull_requests_total_count,
551 })
553 })
552 return data
554 return data
553
555
554 @LoginRequired()
556 @LoginRequired()
555 @NotAnonymous()
557 @NotAnonymous()
556 @view_config(
558 @view_config(
557 route_name='my_account_pullrequests',
559 route_name='my_account_pullrequests',
558 request_method='GET',
560 request_method='GET',
559 renderer='rhodecode:templates/admin/my_account/my_account.mako')
561 renderer='rhodecode:templates/admin/my_account/my_account.mako')
560 def my_account_pullrequests(self):
562 def my_account_pullrequests(self):
561 c = self.load_default_context()
563 c = self.load_default_context()
562 c.active = 'pullrequests'
564 c.active = 'pullrequests'
563 req_get = self.request.GET
565 req_get = self.request.GET
564
566
565 c.closed = str2bool(req_get.get('pr_show_closed'))
567 c.closed = str2bool(req_get.get('pr_show_closed'))
566
568
567 return self._get_template_context(c)
569 return self._get_template_context(c)
568
570
569 @LoginRequired()
571 @LoginRequired()
570 @NotAnonymous()
572 @NotAnonymous()
571 @view_config(
573 @view_config(
572 route_name='my_account_pullrequests_data',
574 route_name='my_account_pullrequests_data',
573 request_method='GET', renderer='json_ext')
575 request_method='GET', renderer='json_ext')
574 def my_account_pullrequests_data(self):
576 def my_account_pullrequests_data(self):
575 req_get = self.request.GET
577 req_get = self.request.GET
576 closed = str2bool(req_get.get('closed'))
578 closed = str2bool(req_get.get('closed'))
577
579
578 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
580 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
579 if closed:
581 if closed:
580 statuses += [PullRequest.STATUS_CLOSED]
582 statuses += [PullRequest.STATUS_CLOSED]
581
583
582 data = self._get_pull_requests_list(statuses=statuses)
584 data = self._get_pull_requests_list(statuses=statuses)
583 return data
585 return data
584
586
@@ -1,367 +1,370 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 logging
21 import logging
22 import string
22 import string
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from beaker.cache import cache_region
26 from beaker.cache import cache_region
27
27
28
28
29 from rhodecode.controllers import utils
29 from rhodecode.controllers import utils
30
30
31 from rhodecode.apps._base import RepoAppView
31 from rhodecode.apps._base import RepoAppView
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
33 from rhodecode.lib import caches, helpers as h
33 from rhodecode.lib import caches, helpers as h
34 from rhodecode.lib.helpers import RepoPage
34 from rhodecode.lib.helpers import RepoPage
35 from rhodecode.lib.utils2 import safe_str, safe_int
35 from rhodecode.lib.utils2 import safe_str, safe_int
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
37 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.vcs.backends.base import EmptyCommit
39 from rhodecode.lib.vcs.backends.base import EmptyCommit
40 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
40 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
41 from rhodecode.model.db import Statistics, CacheKey, User
41 from rhodecode.model.db import Statistics, CacheKey, User
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43 from rhodecode.model.repo import ReadmeFinder
43 from rhodecode.model.repo import ReadmeFinder
44 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class RepoSummaryView(RepoAppView):
49 class RepoSummaryView(RepoAppView):
50
50
51 def load_default_context(self):
51 def load_default_context(self):
52 c = self._get_local_tmpl_context(include_app_defaults=True)
52 c = self._get_local_tmpl_context(include_app_defaults=True)
53
53
54 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
54 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
55 c.repo_info = self.db_repo
55 c.repo_info = self.db_repo
56 c.rhodecode_repo = None
56 c.rhodecode_repo = None
57 if not c.repository_requirements_missing:
57 if not c.repository_requirements_missing:
58 c.rhodecode_repo = self.rhodecode_vcs_repo
58 c.rhodecode_repo = self.rhodecode_vcs_repo
59
59
60 self._register_global_c(c)
60 self._register_global_c(c)
61 return c
61 return c
62
62
63 def _get_readme_data(self, db_repo, default_renderer):
63 def _get_readme_data(self, db_repo, default_renderer):
64 repo_name = db_repo.repo_name
64 repo_name = db_repo.repo_name
65 log.debug('Looking for README file')
65 log.debug('Looking for README file')
66
66
67 @cache_region('long_term')
67 @cache_region('long_term')
68 def _generate_readme(cache_key):
68 def _generate_readme(cache_key):
69 readme_data = None
69 readme_data = None
70 readme_node = None
70 readme_node = None
71 readme_filename = None
71 readme_filename = None
72 commit = self._get_landing_commit_or_none(db_repo)
72 commit = self._get_landing_commit_or_none(db_repo)
73 if commit:
73 if commit:
74 log.debug("Searching for a README file.")
74 log.debug("Searching for a README file.")
75 readme_node = ReadmeFinder(default_renderer).search(commit)
75 readme_node = ReadmeFinder(default_renderer).search(commit)
76 if readme_node:
76 if readme_node:
77 relative_url = h.route_path(
77 relative_url = h.route_path(
78 'repo_file_raw', repo_name=repo_name,
78 'repo_file_raw', repo_name=repo_name,
79 commit_id=commit.raw_id, f_path=readme_node.path)
79 commit_id=commit.raw_id, f_path=readme_node.path)
80 readme_data = self._render_readme_or_none(
80 readme_data = self._render_readme_or_none(
81 commit, readme_node, relative_url)
81 commit, readme_node, relative_url)
82 readme_filename = readme_node.path
82 readme_filename = readme_node.path
83 return readme_data, readme_filename
83 return readme_data, readme_filename
84
84
85 invalidator_context = CacheKey.repo_context_cache(
85 invalidator_context = CacheKey.repo_context_cache(
86 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
86 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
87
87
88 with invalidator_context as context:
88 with invalidator_context as context:
89 context.invalidate()
89 context.invalidate()
90 computed = context.compute()
90 computed = context.compute()
91
91
92 return computed
92 return computed
93
93
94 def _get_landing_commit_or_none(self, db_repo):
94 def _get_landing_commit_or_none(self, db_repo):
95 log.debug("Getting the landing commit.")
95 log.debug("Getting the landing commit.")
96 try:
96 try:
97 commit = db_repo.get_landing_commit()
97 commit = db_repo.get_landing_commit()
98 if not isinstance(commit, EmptyCommit):
98 if not isinstance(commit, EmptyCommit):
99 return commit
99 return commit
100 else:
100 else:
101 log.debug("Repository is empty, no README to render.")
101 log.debug("Repository is empty, no README to render.")
102 except CommitError:
102 except CommitError:
103 log.exception(
103 log.exception(
104 "Problem getting commit when trying to render the README.")
104 "Problem getting commit when trying to render the README.")
105
105
106 def _render_readme_or_none(self, commit, readme_node, relative_url):
106 def _render_readme_or_none(self, commit, readme_node, relative_url):
107 log.debug(
107 log.debug(
108 'Found README file `%s` rendering...', readme_node.path)
108 'Found README file `%s` rendering...', readme_node.path)
109 renderer = MarkupRenderer()
109 renderer = MarkupRenderer()
110 try:
110 try:
111 html_source = renderer.render(
111 html_source = renderer.render(
112 readme_node.content, filename=readme_node.path)
112 readme_node.content, filename=readme_node.path)
113 if relative_url:
113 if relative_url:
114 return relative_links(html_source, relative_url)
114 return relative_links(html_source, relative_url)
115 return html_source
115 return html_source
116 except Exception:
116 except Exception:
117 log.exception(
117 log.exception(
118 "Exception while trying to render the README")
118 "Exception while trying to render the README")
119
119
120 def _load_commits_context(self, c):
120 def _load_commits_context(self, c):
121 p = safe_int(self.request.GET.get('page'), 1)
121 p = safe_int(self.request.GET.get('page'), 1)
122 size = safe_int(self.request.GET.get('size'), 10)
122 size = safe_int(self.request.GET.get('size'), 10)
123
123
124 def url_generator(**kw):
124 def url_generator(**kw):
125 query_params = {
125 query_params = {
126 'size': size
126 'size': size
127 }
127 }
128 query_params.update(kw)
128 query_params.update(kw)
129 return h.route_path(
129 return h.route_path(
130 'repo_summary_commits',
130 'repo_summary_commits',
131 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
131 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
132
132
133 pre_load = ['author', 'branch', 'date', 'message']
133 pre_load = ['author', 'branch', 'date', 'message']
134 try:
134 try:
135 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
135 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
136 except EmptyRepositoryError:
136 except EmptyRepositoryError:
137 collection = self.rhodecode_vcs_repo
137 collection = self.rhodecode_vcs_repo
138
138
139 c.repo_commits = RepoPage(
139 c.repo_commits = RepoPage(
140 collection, page=p, items_per_page=size, url=url_generator)
140 collection, page=p, items_per_page=size, url=url_generator)
141 page_ids = [x.raw_id for x in c.repo_commits]
141 page_ids = [x.raw_id for x in c.repo_commits]
142 c.comments = self.db_repo.get_comments(page_ids)
142 c.comments = self.db_repo.get_comments(page_ids)
143 c.statuses = self.db_repo.statuses(page_ids)
143 c.statuses = self.db_repo.statuses(page_ids)
144
144
145 @LoginRequired()
145 @LoginRequired()
146 @HasRepoPermissionAnyDecorator(
146 @HasRepoPermissionAnyDecorator(
147 'repository.read', 'repository.write', 'repository.admin')
147 'repository.read', 'repository.write', 'repository.admin')
148 @view_config(
148 @view_config(
149 route_name='repo_summary_commits', request_method='GET',
149 route_name='repo_summary_commits', request_method='GET',
150 renderer='rhodecode:templates/summary/summary_commits.mako')
150 renderer='rhodecode:templates/summary/summary_commits.mako')
151 def summary_commits(self):
151 def summary_commits(self):
152 c = self.load_default_context()
152 c = self.load_default_context()
153 self._load_commits_context(c)
153 self._load_commits_context(c)
154 return self._get_template_context(c)
154 return self._get_template_context(c)
155
155
156 @LoginRequired()
156 @LoginRequired()
157 @HasRepoPermissionAnyDecorator(
157 @HasRepoPermissionAnyDecorator(
158 'repository.read', 'repository.write', 'repository.admin')
158 'repository.read', 'repository.write', 'repository.admin')
159 @view_config(
159 @view_config(
160 route_name='repo_summary', request_method='GET',
160 route_name='repo_summary', request_method='GET',
161 renderer='rhodecode:templates/summary/summary.mako')
161 renderer='rhodecode:templates/summary/summary.mako')
162 @view_config(
162 @view_config(
163 route_name='repo_summary_slash', request_method='GET',
163 route_name='repo_summary_slash', request_method='GET',
164 renderer='rhodecode:templates/summary/summary.mako')
164 renderer='rhodecode:templates/summary/summary.mako')
165 @view_config(
166 route_name='repo_summary_explicit', request_method='GET',
167 renderer='rhodecode:templates/summary/summary.mako')
165 def summary(self):
168 def summary(self):
166 c = self.load_default_context()
169 c = self.load_default_context()
167
170
168 # Prepare the clone URL
171 # Prepare the clone URL
169 username = ''
172 username = ''
170 if self._rhodecode_user.username != User.DEFAULT_USER:
173 if self._rhodecode_user.username != User.DEFAULT_USER:
171 username = safe_str(self._rhodecode_user.username)
174 username = safe_str(self._rhodecode_user.username)
172
175
173 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
176 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
174 if '{repo}' in _def_clone_uri:
177 if '{repo}' in _def_clone_uri:
175 _def_clone_uri_by_id = _def_clone_uri.replace(
178 _def_clone_uri_by_id = _def_clone_uri.replace(
176 '{repo}', '_{repoid}')
179 '{repo}', '_{repoid}')
177 elif '{repoid}' in _def_clone_uri:
180 elif '{repoid}' in _def_clone_uri:
178 _def_clone_uri_by_id = _def_clone_uri.replace(
181 _def_clone_uri_by_id = _def_clone_uri.replace(
179 '_{repoid}', '{repo}')
182 '_{repoid}', '{repo}')
180
183
181 c.clone_repo_url = self.db_repo.clone_url(
184 c.clone_repo_url = self.db_repo.clone_url(
182 user=username, uri_tmpl=_def_clone_uri)
185 user=username, uri_tmpl=_def_clone_uri)
183 c.clone_repo_url_id = self.db_repo.clone_url(
186 c.clone_repo_url_id = self.db_repo.clone_url(
184 user=username, uri_tmpl=_def_clone_uri_by_id)
187 user=username, uri_tmpl=_def_clone_uri_by_id)
185
188
186 # If enabled, get statistics data
189 # If enabled, get statistics data
187
190
188 c.show_stats = bool(self.db_repo.enable_statistics)
191 c.show_stats = bool(self.db_repo.enable_statistics)
189
192
190 stats = Session().query(Statistics) \
193 stats = Session().query(Statistics) \
191 .filter(Statistics.repository == self.db_repo) \
194 .filter(Statistics.repository == self.db_repo) \
192 .scalar()
195 .scalar()
193
196
194 c.stats_percentage = 0
197 c.stats_percentage = 0
195
198
196 if stats and stats.languages:
199 if stats and stats.languages:
197 c.no_data = False is self.db_repo.enable_statistics
200 c.no_data = False is self.db_repo.enable_statistics
198 lang_stats_d = json.loads(stats.languages)
201 lang_stats_d = json.loads(stats.languages)
199
202
200 # Sort first by decreasing count and second by the file extension,
203 # Sort first by decreasing count and second by the file extension,
201 # so we have a consistent output.
204 # so we have a consistent output.
202 lang_stats_items = sorted(lang_stats_d.iteritems(),
205 lang_stats_items = sorted(lang_stats_d.iteritems(),
203 key=lambda k: (-k[1], k[0]))[:10]
206 key=lambda k: (-k[1], k[0]))[:10]
204 lang_stats = [(x, {"count": y,
207 lang_stats = [(x, {"count": y,
205 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
208 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
206 for x, y in lang_stats_items]
209 for x, y in lang_stats_items]
207
210
208 c.trending_languages = json.dumps(lang_stats)
211 c.trending_languages = json.dumps(lang_stats)
209 else:
212 else:
210 c.no_data = True
213 c.no_data = True
211 c.trending_languages = json.dumps({})
214 c.trending_languages = json.dumps({})
212
215
213 scm_model = ScmModel()
216 scm_model = ScmModel()
214 c.enable_downloads = self.db_repo.enable_downloads
217 c.enable_downloads = self.db_repo.enable_downloads
215 c.repository_followers = scm_model.get_followers(self.db_repo)
218 c.repository_followers = scm_model.get_followers(self.db_repo)
216 c.repository_forks = scm_model.get_forks(self.db_repo)
219 c.repository_forks = scm_model.get_forks(self.db_repo)
217 c.repository_is_user_following = scm_model.is_following_repo(
220 c.repository_is_user_following = scm_model.is_following_repo(
218 self.db_repo_name, self._rhodecode_user.user_id)
221 self.db_repo_name, self._rhodecode_user.user_id)
219
222
220 # first interaction with the VCS instance after here...
223 # first interaction with the VCS instance after here...
221 if c.repository_requirements_missing:
224 if c.repository_requirements_missing:
222 self.request.override_renderer = \
225 self.request.override_renderer = \
223 'rhodecode:templates/summary/missing_requirements.mako'
226 'rhodecode:templates/summary/missing_requirements.mako'
224 return self._get_template_context(c)
227 return self._get_template_context(c)
225
228
226 c.readme_data, c.readme_file = \
229 c.readme_data, c.readme_file = \
227 self._get_readme_data(self.db_repo, c.visual.default_renderer)
230 self._get_readme_data(self.db_repo, c.visual.default_renderer)
228
231
229 # loads the summary commits template context
232 # loads the summary commits template context
230 self._load_commits_context(c)
233 self._load_commits_context(c)
231
234
232 return self._get_template_context(c)
235 return self._get_template_context(c)
233
236
234 def get_request_commit_id(self):
237 def get_request_commit_id(self):
235 return self.request.matchdict['commit_id']
238 return self.request.matchdict['commit_id']
236
239
237 @LoginRequired()
240 @LoginRequired()
238 @HasRepoPermissionAnyDecorator(
241 @HasRepoPermissionAnyDecorator(
239 'repository.read', 'repository.write', 'repository.admin')
242 'repository.read', 'repository.write', 'repository.admin')
240 @view_config(
243 @view_config(
241 route_name='repo_stats', request_method='GET',
244 route_name='repo_stats', request_method='GET',
242 renderer='json_ext')
245 renderer='json_ext')
243 def repo_stats(self):
246 def repo_stats(self):
244 commit_id = self.get_request_commit_id()
247 commit_id = self.get_request_commit_id()
245
248
246 _namespace = caches.get_repo_namespace_key(
249 _namespace = caches.get_repo_namespace_key(
247 caches.SUMMARY_STATS, self.db_repo_name)
250 caches.SUMMARY_STATS, self.db_repo_name)
248 show_stats = bool(self.db_repo.enable_statistics)
251 show_stats = bool(self.db_repo.enable_statistics)
249 cache_manager = caches.get_cache_manager(
252 cache_manager = caches.get_cache_manager(
250 'repo_cache_long', _namespace)
253 'repo_cache_long', _namespace)
251 _cache_key = caches.compute_key_from_params(
254 _cache_key = caches.compute_key_from_params(
252 self.db_repo_name, commit_id, show_stats)
255 self.db_repo_name, commit_id, show_stats)
253
256
254 def compute_stats():
257 def compute_stats():
255 code_stats = {}
258 code_stats = {}
256 size = 0
259 size = 0
257 try:
260 try:
258 scm_instance = self.db_repo.scm_instance()
261 scm_instance = self.db_repo.scm_instance()
259 commit = scm_instance.get_commit(commit_id)
262 commit = scm_instance.get_commit(commit_id)
260
263
261 for node in commit.get_filenodes_generator():
264 for node in commit.get_filenodes_generator():
262 size += node.size
265 size += node.size
263 if not show_stats:
266 if not show_stats:
264 continue
267 continue
265 ext = string.lower(node.extension)
268 ext = string.lower(node.extension)
266 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
269 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
267 if ext_info:
270 if ext_info:
268 if ext in code_stats:
271 if ext in code_stats:
269 code_stats[ext]['count'] += 1
272 code_stats[ext]['count'] += 1
270 else:
273 else:
271 code_stats[ext] = {"count": 1, "desc": ext_info}
274 code_stats[ext] = {"count": 1, "desc": ext_info}
272 except EmptyRepositoryError:
275 except EmptyRepositoryError:
273 pass
276 pass
274 return {'size': h.format_byte_size_binary(size),
277 return {'size': h.format_byte_size_binary(size),
275 'code_stats': code_stats}
278 'code_stats': code_stats}
276
279
277 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
280 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
278 return stats
281 return stats
279
282
280 @LoginRequired()
283 @LoginRequired()
281 @HasRepoPermissionAnyDecorator(
284 @HasRepoPermissionAnyDecorator(
282 'repository.read', 'repository.write', 'repository.admin')
285 'repository.read', 'repository.write', 'repository.admin')
283 @view_config(
286 @view_config(
284 route_name='repo_refs_data', request_method='GET',
287 route_name='repo_refs_data', request_method='GET',
285 renderer='json_ext')
288 renderer='json_ext')
286 def repo_refs_data(self):
289 def repo_refs_data(self):
287 _ = self.request.translate
290 _ = self.request.translate
288 self.load_default_context()
291 self.load_default_context()
289
292
290 repo = self.rhodecode_vcs_repo
293 repo = self.rhodecode_vcs_repo
291 refs_to_create = [
294 refs_to_create = [
292 (_("Branch"), repo.branches, 'branch'),
295 (_("Branch"), repo.branches, 'branch'),
293 (_("Tag"), repo.tags, 'tag'),
296 (_("Tag"), repo.tags, 'tag'),
294 (_("Bookmark"), repo.bookmarks, 'book'),
297 (_("Bookmark"), repo.bookmarks, 'book'),
295 ]
298 ]
296 res = self._create_reference_data(
299 res = self._create_reference_data(
297 repo, self.db_repo_name, refs_to_create)
300 repo, self.db_repo_name, refs_to_create)
298 data = {
301 data = {
299 'more': False,
302 'more': False,
300 'results': res
303 'results': res
301 }
304 }
302 return data
305 return data
303
306
304 @LoginRequired()
307 @LoginRequired()
305 @HasRepoPermissionAnyDecorator(
308 @HasRepoPermissionAnyDecorator(
306 'repository.read', 'repository.write', 'repository.admin')
309 'repository.read', 'repository.write', 'repository.admin')
307 @view_config(
310 @view_config(
308 route_name='repo_refs_changelog_data', request_method='GET',
311 route_name='repo_refs_changelog_data', request_method='GET',
309 renderer='json_ext')
312 renderer='json_ext')
310 def repo_refs_changelog_data(self):
313 def repo_refs_changelog_data(self):
311 _ = self.request.translate
314 _ = self.request.translate
312 self.load_default_context()
315 self.load_default_context()
313
316
314 repo = self.rhodecode_vcs_repo
317 repo = self.rhodecode_vcs_repo
315
318
316 refs_to_create = [
319 refs_to_create = [
317 (_("Branches"), repo.branches, 'branch'),
320 (_("Branches"), repo.branches, 'branch'),
318 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
321 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
319 # TODO: enable when vcs can handle bookmarks filters
322 # TODO: enable when vcs can handle bookmarks filters
320 # (_("Bookmarks"), repo.bookmarks, "book"),
323 # (_("Bookmarks"), repo.bookmarks, "book"),
321 ]
324 ]
322 res = self._create_reference_data(
325 res = self._create_reference_data(
323 repo, self.db_repo_name, refs_to_create)
326 repo, self.db_repo_name, refs_to_create)
324 data = {
327 data = {
325 'more': False,
328 'more': False,
326 'results': res
329 'results': res
327 }
330 }
328 return data
331 return data
329
332
330 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
333 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
331 format_ref_id = utils.get_format_ref_id(repo)
334 format_ref_id = utils.get_format_ref_id(repo)
332
335
333 result = []
336 result = []
334 for title, refs, ref_type in refs_to_create:
337 for title, refs, ref_type in refs_to_create:
335 if refs:
338 if refs:
336 result.append({
339 result.append({
337 'text': title,
340 'text': title,
338 'children': self._create_reference_items(
341 'children': self._create_reference_items(
339 repo, full_repo_name, refs, ref_type,
342 repo, full_repo_name, refs, ref_type,
340 format_ref_id),
343 format_ref_id),
341 })
344 })
342 return result
345 return result
343
346
344 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
347 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
345 format_ref_id):
348 format_ref_id):
346 result = []
349 result = []
347 is_svn = h.is_svn(repo)
350 is_svn = h.is_svn(repo)
348 for ref_name, raw_id in refs.iteritems():
351 for ref_name, raw_id in refs.iteritems():
349 files_url = self._create_files_url(
352 files_url = self._create_files_url(
350 repo, full_repo_name, ref_name, raw_id, is_svn)
353 repo, full_repo_name, ref_name, raw_id, is_svn)
351 result.append({
354 result.append({
352 'text': ref_name,
355 'text': ref_name,
353 'id': format_ref_id(ref_name, raw_id),
356 'id': format_ref_id(ref_name, raw_id),
354 'raw_id': raw_id,
357 'raw_id': raw_id,
355 'type': ref_type,
358 'type': ref_type,
356 'files_url': files_url,
359 'files_url': files_url,
357 })
360 })
358 return result
361 return result
359
362
360 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
363 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
361 use_commit_id = '/' in ref_name or is_svn
364 use_commit_id = '/' in ref_name or is_svn
362 return h.route_path(
365 return h.route_path(
363 'repo_files',
366 'repo_files',
364 repo_name=full_repo_name,
367 repo_name=full_repo_name,
365 f_path=ref_name if is_svn else '',
368 f_path=ref_name if is_svn else '',
366 commit_id=raw_id if use_commit_id else ref_name,
369 commit_id=raw_id if use_commit_id else ref_name,
367 _query=dict(at=ref_name))
370 _query=dict(at=ref_name))
@@ -1,53 +1,53 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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 logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPNotFound
23 from pyramid.httpexceptions import HTTPNotFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import LoginRequired, NotAnonymous
27 from rhodecode.lib.auth import LoginRequired, NotAnonymous
28
28
29 from rhodecode.model.db import User
29 from rhodecode.model.db import User
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class UserProfileView(BaseAppView):
35 class UserProfileView(BaseAppView):
36
36
37 @LoginRequired()
37 @LoginRequired()
38 @NotAnonymous()
38 @NotAnonymous()
39 @view_config(
39 @view_config(
40 route_name='user_profile', request_method='GET',
40 route_name='user_profile', request_method='GET',
41 renderer='rhodecode:templates/users/user.mako')
41 renderer='rhodecode:templates/users/user.mako')
42 def login(self):
42 def user_profile(self):
43 # register local template context
43 # register local template context
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45 c.active = 'user_profile'
45 c.active = 'user_profile'
46
46
47 username = self.request.matchdict.get('username')
47 username = self.request.matchdict.get('username')
48
48
49 c.user = UserModel().get_by_username(username)
49 c.user = UserModel().get_by_username(username)
50 if not c.user or c.user.username == User.DEFAULT_USER:
50 if not c.user or c.user.username == User.DEFAULT_USER:
51 raise HTTPNotFound()
51 raise HTTPNotFound()
52
52
53 return self._get_template_context(c)
53 return self._get_template_context(c)
General Comments 0
You need to be logged in to leave comments. Login now