##// END OF EJS Templates
tests: password reset hidden/disabled options tests fix #3944
lisaq -
r1036:54572a14 default
parent child Browse files
Show More
@@ -1,653 +1,653 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26 <div class="main">
26 <div class="main">
27 ${next.main()}
27 ${next.main()}
28 </div>
28 </div>
29 </div>
29 </div>
30 <!-- END CONTENT -->
30 <!-- END CONTENT -->
31
31
32 </div>
32 </div>
33 <!-- FOOTER -->
33 <!-- FOOTER -->
34 <div id="footer">
34 <div id="footer">
35 <div id="footer-inner" class="title wrapper">
35 <div id="footer-inner" class="title wrapper">
36 <div>
36 <div>
37 <p class="footer-link-right">
37 <p class="footer-link-right">
38 % if c.visual.show_version:
38 % if c.visual.show_version:
39 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
39 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
40 % endif
40 % endif
41 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
41 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
42 % if c.visual.rhodecode_support_url:
42 % if c.visual.rhodecode_support_url:
43 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
43 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
44 % endif
44 % endif
45 </p>
45 </p>
46 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
46 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
47 <p class="server-instance" style="display:${sid}">
47 <p class="server-instance" style="display:${sid}">
48 ## display hidden instance ID if specially defined
48 ## display hidden instance ID if specially defined
49 % if c.rhodecode_instanceid:
49 % if c.rhodecode_instanceid:
50 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
50 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
51 % endif
51 % endif
52 </p>
52 </p>
53 </div>
53 </div>
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <!-- END FOOTER -->
57 <!-- END FOOTER -->
58
58
59 ### MAKO DEFS ###
59 ### MAKO DEFS ###
60
60
61 <%def name="menu_bar_subnav()">
61 <%def name="menu_bar_subnav()">
62 </%def>
62 </%def>
63
63
64 <%def name="breadcrumbs(class_='breadcrumbs')">
64 <%def name="breadcrumbs(class_='breadcrumbs')">
65 <div class="${class_}">
65 <div class="${class_}">
66 ${self.breadcrumbs_links()}
66 ${self.breadcrumbs_links()}
67 </div>
67 </div>
68 </%def>
68 </%def>
69
69
70 <%def name="admin_menu()">
70 <%def name="admin_menu()">
71 <ul class="admin_menu submenu">
71 <ul class="admin_menu submenu">
72 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
72 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
73 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
73 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
74 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
74 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
75 <li><a href="${h.url('users')}">${_('Users')}</a></li>
75 <li><a href="${h.url('users')}">${_('Users')}</a></li>
76 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
76 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
77 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
77 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
78 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
78 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
79 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
79 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
80 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
80 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
81 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
81 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
82 </ul>
82 </ul>
83 </%def>
83 </%def>
84
84
85
85
86 <%def name="dt_info_panel(elements)">
86 <%def name="dt_info_panel(elements)">
87 <dl class="dl-horizontal">
87 <dl class="dl-horizontal">
88 %for dt, dd, title, show_items in elements:
88 %for dt, dd, title, show_items in elements:
89 <dt>${dt}:</dt>
89 <dt>${dt}:</dt>
90 <dd title="${title}">
90 <dd title="${title}">
91 %if callable(dd):
91 %if callable(dd):
92 ## allow lazy evaluation of elements
92 ## allow lazy evaluation of elements
93 ${dd()}
93 ${dd()}
94 %else:
94 %else:
95 ${dd}
95 ${dd}
96 %endif
96 %endif
97 %if show_items:
97 %if show_items:
98 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
98 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
99 %endif
99 %endif
100 </dd>
100 </dd>
101
101
102 %if show_items:
102 %if show_items:
103 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
103 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
104 %for item in show_items:
104 %for item in show_items:
105 <dt></dt>
105 <dt></dt>
106 <dd>${item}</dd>
106 <dd>${item}</dd>
107 %endfor
107 %endfor
108 </div>
108 </div>
109 %endif
109 %endif
110
110
111 %endfor
111 %endfor
112 </dl>
112 </dl>
113 </%def>
113 </%def>
114
114
115
115
116 <%def name="gravatar(email, size=16)">
116 <%def name="gravatar(email, size=16)">
117 <%
117 <%
118 if (size > 16):
118 if (size > 16):
119 gravatar_class = 'gravatar gravatar-large'
119 gravatar_class = 'gravatar gravatar-large'
120 else:
120 else:
121 gravatar_class = 'gravatar'
121 gravatar_class = 'gravatar'
122 %>
122 %>
123 <%doc>
123 <%doc>
124 TODO: johbo: For now we serve double size images to make it smooth
124 TODO: johbo: For now we serve double size images to make it smooth
125 for retina. This is how it worked until now. Should be replaced
125 for retina. This is how it worked until now. Should be replaced
126 with a better solution at some point.
126 with a better solution at some point.
127 </%doc>
127 </%doc>
128 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
128 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
129 </%def>
129 </%def>
130
130
131
131
132 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
132 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
133 <% email = h.email_or_none(contact) %>
133 <% email = h.email_or_none(contact) %>
134 <div class="rc-user tooltip" title="${h.author_string(email)}">
134 <div class="rc-user tooltip" title="${h.author_string(email)}">
135 ${self.gravatar(email, size)}
135 ${self.gravatar(email, size)}
136 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
136 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
137 </div>
137 </div>
138 </%def>
138 </%def>
139
139
140
140
141 ## admin menu used for people that have some admin resources
141 ## admin menu used for people that have some admin resources
142 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
142 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
143 <ul class="submenu">
143 <ul class="submenu">
144 %if repositories:
144 %if repositories:
145 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
145 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
146 %endif
146 %endif
147 %if repository_groups:
147 %if repository_groups:
148 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
148 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
149 %endif
149 %endif
150 %if user_groups:
150 %if user_groups:
151 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
151 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
152 %endif
152 %endif
153 </ul>
153 </ul>
154 </%def>
154 </%def>
155
155
156 <%def name="repo_page_title(repo_instance)">
156 <%def name="repo_page_title(repo_instance)">
157 <div class="title-content">
157 <div class="title-content">
158 <div class="title-main">
158 <div class="title-main">
159 ## SVN/HG/GIT icons
159 ## SVN/HG/GIT icons
160 %if h.is_hg(repo_instance):
160 %if h.is_hg(repo_instance):
161 <i class="icon-hg"></i>
161 <i class="icon-hg"></i>
162 %endif
162 %endif
163 %if h.is_git(repo_instance):
163 %if h.is_git(repo_instance):
164 <i class="icon-git"></i>
164 <i class="icon-git"></i>
165 %endif
165 %endif
166 %if h.is_svn(repo_instance):
166 %if h.is_svn(repo_instance):
167 <i class="icon-svn"></i>
167 <i class="icon-svn"></i>
168 %endif
168 %endif
169
169
170 ## public/private
170 ## public/private
171 %if repo_instance.private:
171 %if repo_instance.private:
172 <i class="icon-repo-private"></i>
172 <i class="icon-repo-private"></i>
173 %else:
173 %else:
174 <i class="icon-repo-public"></i>
174 <i class="icon-repo-public"></i>
175 %endif
175 %endif
176
176
177 ## repo name with group name
177 ## repo name with group name
178 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
178 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
179
179
180 </div>
180 </div>
181
181
182 ## FORKED
182 ## FORKED
183 %if repo_instance.fork:
183 %if repo_instance.fork:
184 <p>
184 <p>
185 <i class="icon-code-fork"></i> ${_('Fork of')}
185 <i class="icon-code-fork"></i> ${_('Fork of')}
186 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
186 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
187 </p>
187 </p>
188 %endif
188 %endif
189
189
190 ## IMPORTED FROM REMOTE
190 ## IMPORTED FROM REMOTE
191 %if repo_instance.clone_uri:
191 %if repo_instance.clone_uri:
192 <p>
192 <p>
193 <i class="icon-code-fork"></i> ${_('Clone from')}
193 <i class="icon-code-fork"></i> ${_('Clone from')}
194 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
194 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
195 </p>
195 </p>
196 %endif
196 %endif
197
197
198 ## LOCKING STATUS
198 ## LOCKING STATUS
199 %if repo_instance.locked[0]:
199 %if repo_instance.locked[0]:
200 <p class="locking_locked">
200 <p class="locking_locked">
201 <i class="icon-repo-lock"></i>
201 <i class="icon-repo-lock"></i>
202 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
202 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
203 </p>
203 </p>
204 %elif repo_instance.enable_locking:
204 %elif repo_instance.enable_locking:
205 <p class="locking_unlocked">
205 <p class="locking_unlocked">
206 <i class="icon-repo-unlock"></i>
206 <i class="icon-repo-unlock"></i>
207 ${_('Repository not locked. Pull repository to lock it.')}
207 ${_('Repository not locked. Pull repository to lock it.')}
208 </p>
208 </p>
209 %endif
209 %endif
210
210
211 </div>
211 </div>
212 </%def>
212 </%def>
213
213
214 <%def name="repo_menu(active=None)">
214 <%def name="repo_menu(active=None)">
215 <%
215 <%
216 def is_active(selected):
216 def is_active(selected):
217 if selected == active:
217 if selected == active:
218 return "active"
218 return "active"
219 %>
219 %>
220
220
221 <!--- CONTEXT BAR -->
221 <!--- CONTEXT BAR -->
222 <div id="context-bar">
222 <div id="context-bar">
223 <div class="wrapper">
223 <div class="wrapper">
224 <ul id="context-pages" class="horizontal-list navigation">
224 <ul id="context-pages" class="horizontal-list navigation">
225 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
225 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
226 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
226 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
227 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
227 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
228 <li class="${is_active('compare')}">
228 <li class="${is_active('compare')}">
229 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
229 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
230 </li>
230 </li>
231 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
231 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
232 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
232 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
233 <li class="${is_active('showpullrequest')}">
233 <li class="${is_active('showpullrequest')}">
234 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
234 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
235 %if c.repository_pull_requests:
235 %if c.repository_pull_requests:
236 <span class="pr_notifications">${c.repository_pull_requests}</span>
236 <span class="pr_notifications">${c.repository_pull_requests}</span>
237 %endif
237 %endif
238 <div class="menulabel">${_('Pull Requests')}</div>
238 <div class="menulabel">${_('Pull Requests')}</div>
239 </a>
239 </a>
240 </li>
240 </li>
241 %endif
241 %endif
242 <li class="${is_active('options')}">
242 <li class="${is_active('options')}">
243 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
243 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
244 <ul class="submenu">
244 <ul class="submenu">
245 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
245 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
246 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
246 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
247 %endif
247 %endif
248 %if c.rhodecode_db_repo.fork:
248 %if c.rhodecode_db_repo.fork:
249 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
249 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
250 ${_('Compare fork')}</a></li>
250 ${_('Compare fork')}</a></li>
251 %endif
251 %endif
252
252
253 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
253 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
254
254
255 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
255 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
256 %if c.rhodecode_db_repo.locked[0]:
256 %if c.rhodecode_db_repo.locked[0]:
257 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
257 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
258 %else:
258 %else:
259 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
259 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
260 %endif
260 %endif
261 %endif
261 %endif
262 %if c.rhodecode_user.username != h.DEFAULT_USER:
262 %if c.rhodecode_user.username != h.DEFAULT_USER:
263 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
263 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
264 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
264 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
265 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
265 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
266 %endif
266 %endif
267 %endif
267 %endif
268 </ul>
268 </ul>
269 </li>
269 </li>
270 </ul>
270 </ul>
271 </div>
271 </div>
272 <div class="clear"></div>
272 <div class="clear"></div>
273 </div>
273 </div>
274 <!--- END CONTEXT BAR -->
274 <!--- END CONTEXT BAR -->
275
275
276 </%def>
276 </%def>
277
277
278 <%def name="usermenu()">
278 <%def name="usermenu()">
279 ## USER MENU
279 ## USER MENU
280 <li id="quick_login_li">
280 <li id="quick_login_li">
281 <a id="quick_login_link" class="menulink childs">
281 <a id="quick_login_link" class="menulink childs">
282 ${gravatar(c.rhodecode_user.email, 20)}
282 ${gravatar(c.rhodecode_user.email, 20)}
283 <span class="user">
283 <span class="user">
284 %if c.rhodecode_user.username != h.DEFAULT_USER:
284 %if c.rhodecode_user.username != h.DEFAULT_USER:
285 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
285 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
286 %else:
286 %else:
287 <span>${_('Sign in')}</span>
287 <span>${_('Sign in')}</span>
288 %endif
288 %endif
289 </span>
289 </span>
290 </a>
290 </a>
291
291
292 <div class="user-menu submenu">
292 <div class="user-menu submenu">
293 <div id="quick_login">
293 <div id="quick_login">
294 %if c.rhodecode_user.username == h.DEFAULT_USER:
294 %if c.rhodecode_user.username == h.DEFAULT_USER:
295 <h4>${_('Sign in to your account')}</h4>
295 <h4>${_('Sign in to your account')}</h4>
296 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
296 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
297 <div class="form form-vertical">
297 <div class="form form-vertical">
298 <div class="fields">
298 <div class="fields">
299 <div class="field">
299 <div class="field">
300 <div class="label">
300 <div class="label">
301 <label for="username">${_('Username')}:</label>
301 <label for="username">${_('Username')}:</label>
302 </div>
302 </div>
303 <div class="input">
303 <div class="input">
304 ${h.text('username',class_='focus',tabindex=1)}
304 ${h.text('username',class_='focus',tabindex=1)}
305 </div>
305 </div>
306
306
307 </div>
307 </div>
308 <div class="field">
308 <div class="field">
309 <div class="label">
309 <div class="label">
310 <label for="password">${_('Password')}:</label>
310 <label for="password">${_('Password')}:</label>
311 %if h.HasPermissionAny('hg.password_reset.enabled')():
311 %if h.HasPermissionAny('hg.password_reset.enabled')():
312 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
312 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
313 %endif
313 %endif
314 </div>
314 </div>
315 <div class="input">
315 <div class="input">
316 ${h.password('password',class_='focus',tabindex=2)}
316 ${h.password('password',class_='focus',tabindex=2)}
317 </div>
317 </div>
318 </div>
318 </div>
319 <div class="buttons">
319 <div class="buttons">
320 <div class="register">
320 <div class="register">
321 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
321 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
322 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
322 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
323 %endif
323 %endif
324 </div>
324 </div>
325 <div class="submit">
325 <div class="submit">
326 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
326 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
327 </div>
327 </div>
328 </div>
328 </div>
329 </div>
329 </div>
330 </div>
330 </div>
331 ${h.end_form()}
331 ${h.end_form()}
332 %else:
332 %else:
333 <div class="">
333 <div class="">
334 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
334 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
335 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
335 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
336 <div class="email">${c.rhodecode_user.email}</div>
336 <div class="email">${c.rhodecode_user.email}</div>
337 </div>
337 </div>
338 <div class="">
338 <div class="">
339 <ol class="links">
339 <ol class="links">
340 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
340 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
341 <li class="logout">
341 <li class="logout">
342 ${h.secure_form(h.route_path('logout'))}
342 ${h.secure_form(h.route_path('logout'))}
343 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
343 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
344 ${h.end_form()}
344 ${h.end_form()}
345 </li>
345 </li>
346 </ol>
346 </ol>
347 </div>
347 </div>
348 %endif
348 %endif
349 </div>
349 </div>
350 </div>
350 </div>
351 %if c.rhodecode_user.username != h.DEFAULT_USER:
351 %if c.rhodecode_user.username != h.DEFAULT_USER:
352 <div class="pill_container">
352 <div class="pill_container">
353 % if c.unread_notifications == 0:
353 % if c.unread_notifications == 0:
354 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
354 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
355 % else:
355 % else:
356 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
356 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
357 % endif
357 % endif
358 </div>
358 </div>
359 % endif
359 % endif
360 </li>
360 </li>
361 </%def>
361 </%def>
362
362
363 <%def name="menu_items(active=None)">
363 <%def name="menu_items(active=None)">
364 <%
364 <%
365 def is_active(selected):
365 def is_active(selected):
366 if selected == active:
366 if selected == active:
367 return "active"
367 return "active"
368 return ""
368 return ""
369 %>
369 %>
370 <ul id="quick" class="main_nav navigation horizontal-list">
370 <ul id="quick" class="main_nav navigation horizontal-list">
371 <!-- repo switcher -->
371 <!-- repo switcher -->
372 <li class="${is_active('repositories')} repo_switcher_li has_select2">
372 <li class="${is_active('repositories')} repo_switcher_li has_select2">
373 <input id="repo_switcher" name="repo_switcher" type="hidden">
373 <input id="repo_switcher" name="repo_switcher" type="hidden">
374 </li>
374 </li>
375
375
376 ## ROOT MENU
376 ## ROOT MENU
377 %if c.rhodecode_user.username != h.DEFAULT_USER:
377 %if c.rhodecode_user.username != h.DEFAULT_USER:
378 <li class="${is_active('journal')}">
378 <li class="${is_active('journal')}">
379 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
379 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
380 <div class="menulabel">${_('Journal')}</div>
380 <div class="menulabel">${_('Journal')}</div>
381 </a>
381 </a>
382 </li>
382 </li>
383 %else:
383 %else:
384 <li class="${is_active('journal')}">
384 <li class="${is_active('journal')}">
385 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
385 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
386 <div class="menulabel">${_('Public journal')}</div>
386 <div class="menulabel">${_('Public journal')}</div>
387 </a>
387 </a>
388 </li>
388 </li>
389 %endif
389 %endif
390 <li class="${is_active('gists')}">
390 <li class="${is_active('gists')}">
391 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
391 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
392 <div class="menulabel">${_('Gists')}</div>
392 <div class="menulabel">${_('Gists')}</div>
393 </a>
393 </a>
394 </li>
394 </li>
395 <li class="${is_active('search')}">
395 <li class="${is_active('search')}">
396 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
396 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
397 <div class="menulabel">${_('Search')}</div>
397 <div class="menulabel">${_('Search')}</div>
398 </a>
398 </a>
399 </li>
399 </li>
400 % if h.HasPermissionAll('hg.admin')('access admin main page'):
400 % if h.HasPermissionAll('hg.admin')('access admin main page'):
401 <li class="${is_active('admin')}">
401 <li class="${is_active('admin')}">
402 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
402 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
403 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
403 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
404 </a>
404 </a>
405 ${admin_menu()}
405 ${admin_menu()}
406 </li>
406 </li>
407 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
407 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
408 <li class="${is_active('admin')}">
408 <li class="${is_active('admin')}">
409 <a class="menulink childs" title="${_('Delegated Admin settings')}">
409 <a class="menulink childs" title="${_('Delegated Admin settings')}">
410 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
410 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
411 </a>
411 </a>
412 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
412 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
413 c.rhodecode_user.repository_groups_admin,
413 c.rhodecode_user.repository_groups_admin,
414 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
414 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
415 </li>
415 </li>
416 % endif
416 % endif
417 % if c.debug_style:
417 % if c.debug_style:
418 <li class="${is_active('debug_style')}">
418 <li class="${is_active('debug_style')}">
419 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
419 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
420 <div class="menulabel">${_('Style')}</div>
420 <div class="menulabel">${_('Style')}</div>
421 </a>
421 </a>
422 </li>
422 </li>
423 % endif
423 % endif
424 ## render extra user menu
424 ## render extra user menu
425 ${usermenu()}
425 ${usermenu()}
426 </ul>
426 </ul>
427
427
428 <script type="text/javascript">
428 <script type="text/javascript">
429 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
429 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
430
430
431 /*format the look of items in the list*/
431 /*format the look of items in the list*/
432 var format = function(state, escapeMarkup){
432 var format = function(state, escapeMarkup){
433 if (!state.id){
433 if (!state.id){
434 return state.text; // optgroup
434 return state.text; // optgroup
435 }
435 }
436 var obj_dict = state.obj;
436 var obj_dict = state.obj;
437 var tmpl = '';
437 var tmpl = '';
438
438
439 if(obj_dict && state.type == 'repo'){
439 if(obj_dict && state.type == 'repo'){
440 if(obj_dict['repo_type'] === 'hg'){
440 if(obj_dict['repo_type'] === 'hg'){
441 tmpl += '<i class="icon-hg"></i> ';
441 tmpl += '<i class="icon-hg"></i> ';
442 }
442 }
443 else if(obj_dict['repo_type'] === 'git'){
443 else if(obj_dict['repo_type'] === 'git'){
444 tmpl += '<i class="icon-git"></i> ';
444 tmpl += '<i class="icon-git"></i> ';
445 }
445 }
446 else if(obj_dict['repo_type'] === 'svn'){
446 else if(obj_dict['repo_type'] === 'svn'){
447 tmpl += '<i class="icon-svn"></i> ';
447 tmpl += '<i class="icon-svn"></i> ';
448 }
448 }
449 if(obj_dict['private']){
449 if(obj_dict['private']){
450 tmpl += '<i class="icon-lock" ></i> ';
450 tmpl += '<i class="icon-lock" ></i> ';
451 }
451 }
452 else if(visual_show_public_icon){
452 else if(visual_show_public_icon){
453 tmpl += '<i class="icon-unlock-alt"></i> ';
453 tmpl += '<i class="icon-unlock-alt"></i> ';
454 }
454 }
455 }
455 }
456 if(obj_dict && state.type == 'commit') {
456 if(obj_dict && state.type == 'commit') {
457 tmpl += '<i class="icon-tag"></i>';
457 tmpl += '<i class="icon-tag"></i>';
458 }
458 }
459 if(obj_dict && state.type == 'group'){
459 if(obj_dict && state.type == 'group'){
460 tmpl += '<i class="icon-folder-close"></i> ';
460 tmpl += '<i class="icon-folder-close"></i> ';
461 }
461 }
462 tmpl += escapeMarkup(state.text);
462 tmpl += escapeMarkup(state.text);
463 return tmpl;
463 return tmpl;
464 };
464 };
465
465
466 var formatResult = function(result, container, query, escapeMarkup) {
466 var formatResult = function(result, container, query, escapeMarkup) {
467 return format(result, escapeMarkup);
467 return format(result, escapeMarkup);
468 };
468 };
469
469
470 var formatSelection = function(data, container, escapeMarkup) {
470 var formatSelection = function(data, container, escapeMarkup) {
471 return format(data, escapeMarkup);
471 return format(data, escapeMarkup);
472 };
472 };
473
473
474 $("#repo_switcher").select2({
474 $("#repo_switcher").select2({
475 cachedDataSource: {},
475 cachedDataSource: {},
476 minimumInputLength: 2,
476 minimumInputLength: 2,
477 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
477 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
478 dropdownAutoWidth: true,
478 dropdownAutoWidth: true,
479 formatResult: formatResult,
479 formatResult: formatResult,
480 formatSelection: formatSelection,
480 formatSelection: formatSelection,
481 containerCssClass: "repo-switcher",
481 containerCssClass: "repo-switcher",
482 dropdownCssClass: "repo-switcher-dropdown",
482 dropdownCssClass: "repo-switcher-dropdown",
483 escapeMarkup: function(m){
483 escapeMarkup: function(m){
484 // don't escape our custom placeholder
484 // don't escape our custom placeholder
485 if(m.substr(0,23) == '<div class="menulabel">'){
485 if(m.substr(0,23) == '<div class="menulabel">'){
486 return m;
486 return m;
487 }
487 }
488
488
489 return Select2.util.escapeMarkup(m);
489 return Select2.util.escapeMarkup(m);
490 },
490 },
491 query: $.debounce(250, function(query){
491 query: $.debounce(250, function(query){
492 self = this;
492 self = this;
493 var cacheKey = query.term;
493 var cacheKey = query.term;
494 var cachedData = self.cachedDataSource[cacheKey];
494 var cachedData = self.cachedDataSource[cacheKey];
495
495
496 if (cachedData) {
496 if (cachedData) {
497 query.callback({results: cachedData.results});
497 query.callback({results: cachedData.results});
498 } else {
498 } else {
499 $.ajax({
499 $.ajax({
500 url: "${h.url('goto_switcher_data')}",
500 url: "${h.url('goto_switcher_data')}",
501 data: {'query': query.term},
501 data: {'query': query.term},
502 dataType: 'json',
502 dataType: 'json',
503 type: 'GET',
503 type: 'GET',
504 success: function(data) {
504 success: function(data) {
505 self.cachedDataSource[cacheKey] = data;
505 self.cachedDataSource[cacheKey] = data;
506 query.callback({results: data.results});
506 query.callback({results: data.results});
507 },
507 },
508 error: function(data, textStatus, errorThrown) {
508 error: function(data, textStatus, errorThrown) {
509 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
509 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
510 }
510 }
511 })
511 })
512 }
512 }
513 })
513 })
514 });
514 });
515
515
516 $("#repo_switcher").on('select2-selecting', function(e){
516 $("#repo_switcher").on('select2-selecting', function(e){
517 e.preventDefault();
517 e.preventDefault();
518 window.location = e.choice.url;
518 window.location = e.choice.url;
519 });
519 });
520
520
521 ## Global mouse bindings ##
521 ## Global mouse bindings ##
522
522
523 // general help "?"
523 // general help "?"
524 Mousetrap.bind(['?'], function(e) {
524 Mousetrap.bind(['?'], function(e) {
525 $('#help_kb').modal({})
525 $('#help_kb').modal({})
526 });
526 });
527
527
528 // / open the quick filter
528 // / open the quick filter
529 Mousetrap.bind(['/'], function(e) {
529 Mousetrap.bind(['/'], function(e) {
530 $("#repo_switcher").select2("open");
530 $("#repo_switcher").select2("open");
531
531
532 // return false to prevent default browser behavior
532 // return false to prevent default browser behavior
533 // and stop event from bubbling
533 // and stop event from bubbling
534 return false;
534 return false;
535 });
535 });
536
536
537 // general nav g + action
537 // general nav g + action
538 Mousetrap.bind(['g h'], function(e) {
538 Mousetrap.bind(['g h'], function(e) {
539 window.location = pyroutes.url('home');
539 window.location = pyroutes.url('home');
540 });
540 });
541 Mousetrap.bind(['g g'], function(e) {
541 Mousetrap.bind(['g g'], function(e) {
542 window.location = pyroutes.url('gists', {'private':1});
542 window.location = pyroutes.url('gists', {'private':1});
543 });
543 });
544 Mousetrap.bind(['g G'], function(e) {
544 Mousetrap.bind(['g G'], function(e) {
545 window.location = pyroutes.url('gists', {'public':1});
545 window.location = pyroutes.url('gists', {'public':1});
546 });
546 });
547 Mousetrap.bind(['n g'], function(e) {
547 Mousetrap.bind(['n g'], function(e) {
548 window.location = pyroutes.url('new_gist');
548 window.location = pyroutes.url('new_gist');
549 });
549 });
550 Mousetrap.bind(['n r'], function(e) {
550 Mousetrap.bind(['n r'], function(e) {
551 window.location = pyroutes.url('new_repo');
551 window.location = pyroutes.url('new_repo');
552 });
552 });
553
553
554 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
554 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
555 // nav in repo context
555 // nav in repo context
556 Mousetrap.bind(['g s'], function(e) {
556 Mousetrap.bind(['g s'], function(e) {
557 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
557 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
558 });
558 });
559 Mousetrap.bind(['g c'], function(e) {
559 Mousetrap.bind(['g c'], function(e) {
560 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
560 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
561 });
561 });
562 Mousetrap.bind(['g F'], function(e) {
562 Mousetrap.bind(['g F'], function(e) {
563 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
563 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
564 });
564 });
565 Mousetrap.bind(['g f'], function(e) {
565 Mousetrap.bind(['g f'], function(e) {
566 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
566 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
567 });
567 });
568 Mousetrap.bind(['g p'], function(e) {
568 Mousetrap.bind(['g p'], function(e) {
569 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
569 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
570 });
570 });
571 Mousetrap.bind(['g o'], function(e) {
571 Mousetrap.bind(['g o'], function(e) {
572 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
572 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
573 });
573 });
574 Mousetrap.bind(['g O'], function(e) {
574 Mousetrap.bind(['g O'], function(e) {
575 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
575 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
576 });
576 });
577 % endif
577 % endif
578
578
579 </script>
579 </script>
580 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
580 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
581 </%def>
581 </%def>
582
582
583 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
583 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
584 <div class="modal-dialog">
584 <div class="modal-dialog">
585 <div class="modal-content">
585 <div class="modal-content">
586 <div class="modal-header">
586 <div class="modal-header">
587 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
587 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
588 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
588 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
589 </div>
589 </div>
590 <div class="modal-body">
590 <div class="modal-body">
591 <div class="block-left">
591 <div class="block-left">
592 <table class="keyboard-mappings">
592 <table class="keyboard-mappings">
593 <tbody>
593 <tbody>
594 <tr>
594 <tr>
595 <th></th>
595 <th></th>
596 <th>${_('Site-wide shortcuts')}</th>
596 <th>${_('Site-wide shortcuts')}</th>
597 </tr>
597 </tr>
598 <%
598 <%
599 elems = [
599 elems = [
600 ('/', 'Open quick search box'),
600 ('/', 'Open quick search box'),
601 ('g h', 'Goto home page'),
601 ('g h', 'Goto home page'),
602 ('g g', 'Goto my private gists page'),
602 ('g g', 'Goto my private gists page'),
603 ('g G', 'Goto my public gists page'),
603 ('g G', 'Goto my public gists page'),
604 ('n r', 'New repository page'),
604 ('n r', 'New repository page'),
605 ('n g', 'New gist page'),
605 ('n g', 'New gist page'),
606 ]
606 ]
607 %>
607 %>
608 %for key, desc in elems:
608 %for key, desc in elems:
609 <tr>
609 <tr>
610 <td class="keys">
610 <td class="keys">
611 <span class="key tag">${key}</span>
611 <span class="key tag">${key}</span>
612 </td>
612 </td>
613 <td>${desc}</td>
613 <td>${desc}</td>
614 </tr>
614 </tr>
615 %endfor
615 %endfor
616 </tbody>
616 </tbody>
617 </table>
617 </table>
618 </div>
618 </div>
619 <div class="block-left">
619 <div class="block-left">
620 <table class="keyboard-mappings">
620 <table class="keyboard-mappings">
621 <tbody>
621 <tbody>
622 <tr>
622 <tr>
623 <th></th>
623 <th></th>
624 <th>${_('Repositories')}</th>
624 <th>${_('Repositories')}</th>
625 </tr>
625 </tr>
626 <%
626 <%
627 elems = [
627 elems = [
628 ('g s', 'Goto summary page'),
628 ('g s', 'Goto summary page'),
629 ('g c', 'Goto changelog page'),
629 ('g c', 'Goto changelog page'),
630 ('g f', 'Goto files page'),
630 ('g f', 'Goto files page'),
631 ('g F', 'Goto files page with file search activated'),
631 ('g F', 'Goto files page with file search activated'),
632 ('g p', 'Goto pull requests page'),
632 ('g p', 'Goto pull requests page'),
633 ('g o', 'Goto repository settings'),
633 ('g o', 'Goto repository settings'),
634 ('g O', 'Goto repository permissions settings'),
634 ('g O', 'Goto repository permissions settings'),
635 ]
635 ]
636 %>
636 %>
637 %for key, desc in elems:
637 %for key, desc in elems:
638 <tr>
638 <tr>
639 <td class="keys">
639 <td class="keys">
640 <span class="key tag">${key}</span>
640 <span class="key tag">${key}</span>
641 </td>
641 </td>
642 <td>${desc}</td>
642 <td>${desc}</td>
643 </tr>
643 </tr>
644 %endfor
644 %endfor
645 </tbody>
645 </tbody>
646 </table>
646 </table>
647 </div>
647 </div>
648 </div>
648 </div>
649 <div class="modal-footer">
649 <div class="modal-footer">
650 </div>
650 </div>
651 </div><!-- /.modal-content -->
651 </div><!-- /.modal-content -->
652 </div><!-- /.modal-dialog -->
652 </div><!-- /.modal-dialog -->
653 </div><!-- /.modal -->
653 </div><!-- /.modal -->
@@ -1,519 +1,588 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 urlparse
21 import urlparse
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
28 TestController, assert_session_flash, clear_all_caches, url,
29 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 from rhodecode.lib.auth import check_password, generate_auth_token
32 from rhodecode.lib.auth import check_password, generate_auth_token
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
33 from rhodecode.model.auth_token import AuthTokenModel
34 from rhodecode.model.auth_token import AuthTokenModel
34 from rhodecode.model import validators
35 from rhodecode.model import validators
35 from rhodecode.model.db import User, Notification
36 from rhodecode.model.db import User, Notification
36 from rhodecode.model.meta import Session
37 from rhodecode.model.meta import Session
37
38
38 fixture = Fixture()
39 fixture = Fixture()
39
40
40 # Hardcode URLs because we don't have a request object to use
41 # Hardcode URLs because we don't have a request object to use
41 # pyramids URL generation methods.
42 # pyramids URL generation methods.
43 index_url = '/'
42 login_url = ADMIN_PREFIX + '/login'
44 login_url = ADMIN_PREFIX + '/login'
43 logut_url = ADMIN_PREFIX + '/logout'
45 logut_url = ADMIN_PREFIX + '/logout'
44 register_url = ADMIN_PREFIX + '/register'
46 register_url = ADMIN_PREFIX + '/register'
45 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
47 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
46 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
48 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
47
49
48
50
49 @pytest.mark.usefixtures('app')
51 @pytest.mark.usefixtures('app')
50 class TestLoginController:
52 class TestLoginController:
51 destroy_users = set()
53 destroy_users = set()
52
54
53 @classmethod
55 @classmethod
54 def teardown_class(cls):
56 def teardown_class(cls):
55 fixture.destroy_users(cls.destroy_users)
57 fixture.destroy_users(cls.destroy_users)
56
58
57 def teardown_method(self, method):
59 def teardown_method(self, method):
58 for n in Notification.query().all():
60 for n in Notification.query().all():
59 Session().delete(n)
61 Session().delete(n)
60
62
61 Session().commit()
63 Session().commit()
62 assert Notification.query().all() == []
64 assert Notification.query().all() == []
63
65
64 def test_index(self):
66 def test_index(self):
65 response = self.app.get(login_url)
67 response = self.app.get(login_url)
66 assert response.status == '200 OK'
68 assert response.status == '200 OK'
67 # Test response...
69 # Test response...
68
70
69 def test_login_admin_ok(self):
71 def test_login_admin_ok(self):
70 response = self.app.post(login_url,
72 response = self.app.post(login_url,
71 {'username': 'test_admin',
73 {'username': 'test_admin',
72 'password': 'test12'})
74 'password': 'test12'})
73 assert response.status == '302 Found'
75 assert response.status == '302 Found'
74 session = get_session_from_response(response)
76 session = get_session_from_response(response)
75 username = session['rhodecode_user'].get('username')
77 username = session['rhodecode_user'].get('username')
76 assert username == 'test_admin'
78 assert username == 'test_admin'
77 response = response.follow()
79 response = response.follow()
78 response.mustcontain('/%s' % HG_REPO)
80 response.mustcontain('/%s' % HG_REPO)
79
81
80 def test_login_regular_ok(self):
82 def test_login_regular_ok(self):
81 response = self.app.post(login_url,
83 response = self.app.post(login_url,
82 {'username': 'test_regular',
84 {'username': 'test_regular',
83 'password': 'test12'})
85 'password': 'test12'})
84
86
85 assert response.status == '302 Found'
87 assert response.status == '302 Found'
86 session = get_session_from_response(response)
88 session = get_session_from_response(response)
87 username = session['rhodecode_user'].get('username')
89 username = session['rhodecode_user'].get('username')
88 assert username == 'test_regular'
90 assert username == 'test_regular'
89 response = response.follow()
91 response = response.follow()
90 response.mustcontain('/%s' % HG_REPO)
92 response.mustcontain('/%s' % HG_REPO)
91
93
92 def test_login_ok_came_from(self):
94 def test_login_ok_came_from(self):
93 test_came_from = '/_admin/users?branch=stable'
95 test_came_from = '/_admin/users?branch=stable'
94 _url = '{}?came_from={}'.format(login_url, test_came_from)
96 _url = '{}?came_from={}'.format(login_url, test_came_from)
95 response = self.app.post(
97 response = self.app.post(
96 _url, {'username': 'test_admin', 'password': 'test12'})
98 _url, {'username': 'test_admin', 'password': 'test12'})
97 assert response.status == '302 Found'
99 assert response.status == '302 Found'
98 assert 'branch=stable' in response.location
100 assert 'branch=stable' in response.location
99 response = response.follow()
101 response = response.follow()
100
102
101 assert response.status == '200 OK'
103 assert response.status == '200 OK'
102 response.mustcontain('Users administration')
104 response.mustcontain('Users administration')
103
105
104 def test_redirect_to_login_with_get_args(self):
106 def test_redirect_to_login_with_get_args(self):
105 with fixture.anon_access(False):
107 with fixture.anon_access(False):
106 kwargs = {'branch': 'stable'}
108 kwargs = {'branch': 'stable'}
107 response = self.app.get(
109 response = self.app.get(
108 url('summary_home', repo_name=HG_REPO, **kwargs))
110 url('summary_home', repo_name=HG_REPO, **kwargs))
109 assert response.status == '302 Found'
111 assert response.status == '302 Found'
110 response_query = urlparse.parse_qsl(response.location)
112 response_query = urlparse.parse_qsl(response.location)
111 assert 'branch=stable' in response_query[0][1]
113 assert 'branch=stable' in response_query[0][1]
112
114
113 def test_login_form_with_get_args(self):
115 def test_login_form_with_get_args(self):
114 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
116 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
115 response = self.app.get(_url)
117 response = self.app.get(_url)
116 assert 'branch%3Dstable' in response.form.action
118 assert 'branch%3Dstable' in response.form.action
117
119
118 @pytest.mark.parametrize("url_came_from", [
120 @pytest.mark.parametrize("url_came_from", [
119 'data:text/html,<script>window.alert("xss")</script>',
121 'data:text/html,<script>window.alert("xss")</script>',
120 'mailto:test@rhodecode.org',
122 'mailto:test@rhodecode.org',
121 'file:///etc/passwd',
123 'file:///etc/passwd',
122 'ftp://some.ftp.server',
124 'ftp://some.ftp.server',
123 'http://other.domain',
125 'http://other.domain',
124 '/\r\nX-Forwarded-Host: http://example.org',
126 '/\r\nX-Forwarded-Host: http://example.org',
125 ])
127 ])
126 def test_login_bad_came_froms(self, url_came_from):
128 def test_login_bad_came_froms(self, url_came_from):
127 _url = '{}?came_from={}'.format(login_url, url_came_from)
129 _url = '{}?came_from={}'.format(login_url, url_came_from)
128 response = self.app.post(
130 response = self.app.post(
129 _url,
131 _url,
130 {'username': 'test_admin', 'password': 'test12'})
132 {'username': 'test_admin', 'password': 'test12'})
131 assert response.status == '302 Found'
133 assert response.status == '302 Found'
132 response = response.follow()
134 response = response.follow()
133 assert response.status == '200 OK'
135 assert response.status == '200 OK'
134 assert response.request.path == '/'
136 assert response.request.path == '/'
135
137
136 def test_login_short_password(self):
138 def test_login_short_password(self):
137 response = self.app.post(login_url,
139 response = self.app.post(login_url,
138 {'username': 'test_admin',
140 {'username': 'test_admin',
139 'password': 'as'})
141 'password': 'as'})
140 assert response.status == '200 OK'
142 assert response.status == '200 OK'
141
143
142 response.mustcontain('Enter 3 characters or more')
144 response.mustcontain('Enter 3 characters or more')
143
145
144 def test_login_wrong_non_ascii_password(self, user_regular):
146 def test_login_wrong_non_ascii_password(self, user_regular):
145 response = self.app.post(
147 response = self.app.post(
146 login_url,
148 login_url,
147 {'username': user_regular.username,
149 {'username': user_regular.username,
148 'password': u'invalid-non-asci\xe4'.encode('utf8')})
150 'password': u'invalid-non-asci\xe4'.encode('utf8')})
149
151
150 response.mustcontain('invalid user name')
152 response.mustcontain('invalid user name')
151 response.mustcontain('invalid password')
153 response.mustcontain('invalid password')
152
154
153 def test_login_with_non_ascii_password(self, user_util):
155 def test_login_with_non_ascii_password(self, user_util):
154 password = u'valid-non-ascii\xe4'
156 password = u'valid-non-ascii\xe4'
155 user = user_util.create_user(password=password)
157 user = user_util.create_user(password=password)
156 response = self.app.post(
158 response = self.app.post(
157 login_url,
159 login_url,
158 {'username': user.username,
160 {'username': user.username,
159 'password': password.encode('utf-8')})
161 'password': password.encode('utf-8')})
160 assert response.status_code == 302
162 assert response.status_code == 302
161
163
162 def test_login_wrong_username_password(self):
164 def test_login_wrong_username_password(self):
163 response = self.app.post(login_url,
165 response = self.app.post(login_url,
164 {'username': 'error',
166 {'username': 'error',
165 'password': 'test12'})
167 'password': 'test12'})
166
168
167 response.mustcontain('invalid user name')
169 response.mustcontain('invalid user name')
168 response.mustcontain('invalid password')
170 response.mustcontain('invalid password')
169
171
170 def test_login_admin_ok_password_migration(self, real_crypto_backend):
172 def test_login_admin_ok_password_migration(self, real_crypto_backend):
171 from rhodecode.lib import auth
173 from rhodecode.lib import auth
172
174
173 # create new user, with sha256 password
175 # create new user, with sha256 password
174 temp_user = 'test_admin_sha256'
176 temp_user = 'test_admin_sha256'
175 user = fixture.create_user(temp_user)
177 user = fixture.create_user(temp_user)
176 user.password = auth._RhodeCodeCryptoSha256().hash_create(
178 user.password = auth._RhodeCodeCryptoSha256().hash_create(
177 b'test123')
179 b'test123')
178 Session().add(user)
180 Session().add(user)
179 Session().commit()
181 Session().commit()
180 self.destroy_users.add(temp_user)
182 self.destroy_users.add(temp_user)
181 response = self.app.post(login_url,
183 response = self.app.post(login_url,
182 {'username': temp_user,
184 {'username': temp_user,
183 'password': 'test123'})
185 'password': 'test123'})
184
186
185 assert response.status == '302 Found'
187 assert response.status == '302 Found'
186 session = get_session_from_response(response)
188 session = get_session_from_response(response)
187 username = session['rhodecode_user'].get('username')
189 username = session['rhodecode_user'].get('username')
188 assert username == temp_user
190 assert username == temp_user
189 response = response.follow()
191 response = response.follow()
190 response.mustcontain('/%s' % HG_REPO)
192 response.mustcontain('/%s' % HG_REPO)
191
193
192 # new password should be bcrypted, after log-in and transfer
194 # new password should be bcrypted, after log-in and transfer
193 user = User.get_by_username(temp_user)
195 user = User.get_by_username(temp_user)
194 assert user.password.startswith('$')
196 assert user.password.startswith('$')
195
197
196 # REGISTRATIONS
198 # REGISTRATIONS
197 def test_register(self):
199 def test_register(self):
198 response = self.app.get(register_url)
200 response = self.app.get(register_url)
199 response.mustcontain('Create an Account')
201 response.mustcontain('Create an Account')
200
202
201 def test_register_err_same_username(self):
203 def test_register_err_same_username(self):
202 uname = 'test_admin'
204 uname = 'test_admin'
203 response = self.app.post(
205 response = self.app.post(
204 register_url,
206 register_url,
205 {
207 {
206 'username': uname,
208 'username': uname,
207 'password': 'test12',
209 'password': 'test12',
208 'password_confirmation': 'test12',
210 'password_confirmation': 'test12',
209 'email': 'goodmail@domain.com',
211 'email': 'goodmail@domain.com',
210 'firstname': 'test',
212 'firstname': 'test',
211 'lastname': 'test'
213 'lastname': 'test'
212 }
214 }
213 )
215 )
214
216
215 assertr = AssertResponse(response)
217 assertr = AssertResponse(response)
216 msg = validators.ValidUsername()._messages['username_exists']
218 msg = validators.ValidUsername()._messages['username_exists']
217 msg = msg % {'username': uname}
219 msg = msg % {'username': uname}
218 assertr.element_contains('#username+.error-message', msg)
220 assertr.element_contains('#username+.error-message', msg)
219
221
220 def test_register_err_same_email(self):
222 def test_register_err_same_email(self):
221 response = self.app.post(
223 response = self.app.post(
222 register_url,
224 register_url,
223 {
225 {
224 'username': 'test_admin_0',
226 'username': 'test_admin_0',
225 'password': 'test12',
227 'password': 'test12',
226 'password_confirmation': 'test12',
228 'password_confirmation': 'test12',
227 'email': 'test_admin@mail.com',
229 'email': 'test_admin@mail.com',
228 'firstname': 'test',
230 'firstname': 'test',
229 'lastname': 'test'
231 'lastname': 'test'
230 }
232 }
231 )
233 )
232
234
233 assertr = AssertResponse(response)
235 assertr = AssertResponse(response)
234 msg = validators.UniqSystemEmail()()._messages['email_taken']
236 msg = validators.UniqSystemEmail()()._messages['email_taken']
235 assertr.element_contains('#email+.error-message', msg)
237 assertr.element_contains('#email+.error-message', msg)
236
238
237 def test_register_err_same_email_case_sensitive(self):
239 def test_register_err_same_email_case_sensitive(self):
238 response = self.app.post(
240 response = self.app.post(
239 register_url,
241 register_url,
240 {
242 {
241 'username': 'test_admin_1',
243 'username': 'test_admin_1',
242 'password': 'test12',
244 'password': 'test12',
243 'password_confirmation': 'test12',
245 'password_confirmation': 'test12',
244 'email': 'TesT_Admin@mail.COM',
246 'email': 'TesT_Admin@mail.COM',
245 'firstname': 'test',
247 'firstname': 'test',
246 'lastname': 'test'
248 'lastname': 'test'
247 }
249 }
248 )
250 )
249 assertr = AssertResponse(response)
251 assertr = AssertResponse(response)
250 msg = validators.UniqSystemEmail()()._messages['email_taken']
252 msg = validators.UniqSystemEmail()()._messages['email_taken']
251 assertr.element_contains('#email+.error-message', msg)
253 assertr.element_contains('#email+.error-message', msg)
252
254
253 def test_register_err_wrong_data(self):
255 def test_register_err_wrong_data(self):
254 response = self.app.post(
256 response = self.app.post(
255 register_url,
257 register_url,
256 {
258 {
257 'username': 'xs',
259 'username': 'xs',
258 'password': 'test',
260 'password': 'test',
259 'password_confirmation': 'test',
261 'password_confirmation': 'test',
260 'email': 'goodmailm',
262 'email': 'goodmailm',
261 'firstname': 'test',
263 'firstname': 'test',
262 'lastname': 'test'
264 'lastname': 'test'
263 }
265 }
264 )
266 )
265 assert response.status == '200 OK'
267 assert response.status == '200 OK'
266 response.mustcontain('An email address must contain a single @')
268 response.mustcontain('An email address must contain a single @')
267 response.mustcontain('Enter a value 6 characters long or more')
269 response.mustcontain('Enter a value 6 characters long or more')
268
270
269 def test_register_err_username(self):
271 def test_register_err_username(self):
270 response = self.app.post(
272 response = self.app.post(
271 register_url,
273 register_url,
272 {
274 {
273 'username': 'error user',
275 'username': 'error user',
274 'password': 'test12',
276 'password': 'test12',
275 'password_confirmation': 'test12',
277 'password_confirmation': 'test12',
276 'email': 'goodmailm',
278 'email': 'goodmailm',
277 'firstname': 'test',
279 'firstname': 'test',
278 'lastname': 'test'
280 'lastname': 'test'
279 }
281 }
280 )
282 )
281
283
282 response.mustcontain('An email address must contain a single @')
284 response.mustcontain('An email address must contain a single @')
283 response.mustcontain(
285 response.mustcontain(
284 'Username may only contain '
286 'Username may only contain '
285 'alphanumeric characters underscores, '
287 'alphanumeric characters underscores, '
286 'periods or dashes and must begin with '
288 'periods or dashes and must begin with '
287 'alphanumeric character')
289 'alphanumeric character')
288
290
289 def test_register_err_case_sensitive(self):
291 def test_register_err_case_sensitive(self):
290 usr = 'Test_Admin'
292 usr = 'Test_Admin'
291 response = self.app.post(
293 response = self.app.post(
292 register_url,
294 register_url,
293 {
295 {
294 'username': usr,
296 'username': usr,
295 'password': 'test12',
297 'password': 'test12',
296 'password_confirmation': 'test12',
298 'password_confirmation': 'test12',
297 'email': 'goodmailm',
299 'email': 'goodmailm',
298 'firstname': 'test',
300 'firstname': 'test',
299 'lastname': 'test'
301 'lastname': 'test'
300 }
302 }
301 )
303 )
302
304
303 assertr = AssertResponse(response)
305 assertr = AssertResponse(response)
304 msg = validators.ValidUsername()._messages['username_exists']
306 msg = validators.ValidUsername()._messages['username_exists']
305 msg = msg % {'username': usr}
307 msg = msg % {'username': usr}
306 assertr.element_contains('#username+.error-message', msg)
308 assertr.element_contains('#username+.error-message', msg)
307
309
308 def test_register_special_chars(self):
310 def test_register_special_chars(self):
309 response = self.app.post(
311 response = self.app.post(
310 register_url,
312 register_url,
311 {
313 {
312 'username': 'xxxaxn',
314 'username': 'xxxaxn',
313 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
315 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
314 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
316 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
315 'email': 'goodmailm@test.plx',
317 'email': 'goodmailm@test.plx',
316 'firstname': 'test',
318 'firstname': 'test',
317 'lastname': 'test'
319 'lastname': 'test'
318 }
320 }
319 )
321 )
320
322
321 msg = validators.ValidPassword()._messages['invalid_password']
323 msg = validators.ValidPassword()._messages['invalid_password']
322 response.mustcontain(msg)
324 response.mustcontain(msg)
323
325
324 def test_register_password_mismatch(self):
326 def test_register_password_mismatch(self):
325 response = self.app.post(
327 response = self.app.post(
326 register_url,
328 register_url,
327 {
329 {
328 'username': 'xs',
330 'username': 'xs',
329 'password': '123qwe',
331 'password': '123qwe',
330 'password_confirmation': 'qwe123',
332 'password_confirmation': 'qwe123',
331 'email': 'goodmailm@test.plxa',
333 'email': 'goodmailm@test.plxa',
332 'firstname': 'test',
334 'firstname': 'test',
333 'lastname': 'test'
335 'lastname': 'test'
334 }
336 }
335 )
337 )
336 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
338 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
337 response.mustcontain(msg)
339 response.mustcontain(msg)
338
340
339 def test_register_ok(self):
341 def test_register_ok(self):
340 username = 'test_regular4'
342 username = 'test_regular4'
341 password = 'qweqwe'
343 password = 'qweqwe'
342 email = 'marcin@test.com'
344 email = 'marcin@test.com'
343 name = 'testname'
345 name = 'testname'
344 lastname = 'testlastname'
346 lastname = 'testlastname'
345
347
346 response = self.app.post(
348 response = self.app.post(
347 register_url,
349 register_url,
348 {
350 {
349 'username': username,
351 'username': username,
350 'password': password,
352 'password': password,
351 'password_confirmation': password,
353 'password_confirmation': password,
352 'email': email,
354 'email': email,
353 'firstname': name,
355 'firstname': name,
354 'lastname': lastname,
356 'lastname': lastname,
355 'admin': True
357 'admin': True
356 }
358 }
357 ) # This should be overriden
359 ) # This should be overriden
358 assert response.status == '302 Found'
360 assert response.status == '302 Found'
359 assert_session_flash(
361 assert_session_flash(
360 response, 'You have successfully registered with RhodeCode')
362 response, 'You have successfully registered with RhodeCode')
361
363
362 ret = Session().query(User).filter(
364 ret = Session().query(User).filter(
363 User.username == 'test_regular4').one()
365 User.username == 'test_regular4').one()
364 assert ret.username == username
366 assert ret.username == username
365 assert check_password(password, ret.password)
367 assert check_password(password, ret.password)
366 assert ret.email == email
368 assert ret.email == email
367 assert ret.name == name
369 assert ret.name == name
368 assert ret.lastname == lastname
370 assert ret.lastname == lastname
369 assert ret.api_key is not None
371 assert ret.api_key is not None
370 assert not ret.admin
372 assert not ret.admin
371
373
372 def test_forgot_password_wrong_mail(self):
374 def test_forgot_password_wrong_mail(self):
373 bad_email = 'marcin@wrongmail.org'
375 bad_email = 'marcin@wrongmail.org'
374 response = self.app.post(
376 response = self.app.post(
375 pwd_reset_url,
377 pwd_reset_url,
376 {'email': bad_email, }
378 {'email': bad_email, }
377 )
379 )
378
380
379 msg = validators.ValidSystemEmail()._messages['non_existing_email']
381 msg = validators.ValidSystemEmail()._messages['non_existing_email']
380 msg = h.html_escape(msg % {'email': bad_email})
382 msg = h.html_escape(msg % {'email': bad_email})
381 response.mustcontain()
383 response.mustcontain()
382
384
383 def test_forgot_password(self):
385 def test_forgot_password(self):
384 response = self.app.get(pwd_reset_url)
386 response = self.app.get(pwd_reset_url)
385 assert response.status == '200 OK'
387 assert response.status == '200 OK'
386
388
387 username = 'test_password_reset_1'
389 username = 'test_password_reset_1'
388 password = 'qweqwe'
390 password = 'qweqwe'
389 email = 'marcin@python-works.com'
391 email = 'marcin@python-works.com'
390 name = 'passwd'
392 name = 'passwd'
391 lastname = 'reset'
393 lastname = 'reset'
392
394
393 new = User()
395 new = User()
394 new.username = username
396 new.username = username
395 new.password = password
397 new.password = password
396 new.email = email
398 new.email = email
397 new.name = name
399 new.name = name
398 new.lastname = lastname
400 new.lastname = lastname
399 new.api_key = generate_auth_token(username)
401 new.api_key = generate_auth_token(username)
400 Session().add(new)
402 Session().add(new)
401 Session().commit()
403 Session().commit()
402
404
403 response = self.app.post(pwd_reset_url,
405 response = self.app.post(pwd_reset_url,
404 {'email': email, })
406 {'email': email, })
405
407
406 assert_session_flash(
408 assert_session_flash(
407 response, 'Your password reset link was sent')
409 response, 'Your password reset link was sent')
408
410
409 response = response.follow()
411 response = response.follow()
410
412
411 # BAD KEY
413 # BAD KEY
412
414
413 key = "bad"
415 key = "bad"
414 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
416 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
415 response = self.app.get(confirm_url)
417 response = self.app.get(confirm_url)
416 assert response.status == '302 Found'
418 assert response.status == '302 Found'
417 assert response.location.endswith(pwd_reset_url)
419 assert response.location.endswith(pwd_reset_url)
418
420
419 # GOOD KEY
421 # GOOD KEY
420
422
421 key = User.get_by_username(username).api_key
423 key = User.get_by_username(username).api_key
422 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
424 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
423 response = self.app.get(confirm_url)
425 response = self.app.get(confirm_url)
424 assert response.status == '302 Found'
426 assert response.status == '302 Found'
425 assert response.location.endswith(login_url)
427 assert response.location.endswith(login_url)
426
428
427 assert_session_flash(
429 assert_session_flash(
428 response,
430 response,
429 'Your password reset was successful, '
431 'Your password reset was successful, '
430 'a new password has been sent to your email')
432 'a new password has been sent to your email')
431
433
432 response = response.follow()
434 response = response.follow()
433
435
434 def _get_api_whitelist(self, values=None):
436 def _get_api_whitelist(self, values=None):
435 config = {'api_access_controllers_whitelist': values or []}
437 config = {'api_access_controllers_whitelist': values or []}
436 return config
438 return config
437
439
438 @pytest.mark.parametrize("test_name, auth_token", [
440 @pytest.mark.parametrize("test_name, auth_token", [
439 ('none', None),
441 ('none', None),
440 ('empty_string', ''),
442 ('empty_string', ''),
441 ('fake_number', '123456'),
443 ('fake_number', '123456'),
442 ('proper_auth_token', None)
444 ('proper_auth_token', None)
443 ])
445 ])
444 def test_access_not_whitelisted_page_via_auth_token(self, test_name,
446 def test_access_not_whitelisted_page_via_auth_token(self, test_name,
445 auth_token):
447 auth_token):
446 whitelist = self._get_api_whitelist([])
448 whitelist = self._get_api_whitelist([])
447 with mock.patch.dict('rhodecode.CONFIG', whitelist):
449 with mock.patch.dict('rhodecode.CONFIG', whitelist):
448 assert [] == whitelist['api_access_controllers_whitelist']
450 assert [] == whitelist['api_access_controllers_whitelist']
449 if test_name == 'proper_auth_token':
451 if test_name == 'proper_auth_token':
450 # use builtin if api_key is None
452 # use builtin if api_key is None
451 auth_token = User.get_first_super_admin().api_key
453 auth_token = User.get_first_super_admin().api_key
452
454
453 with fixture.anon_access(False):
455 with fixture.anon_access(False):
454 self.app.get(url(controller='changeset',
456 self.app.get(url(controller='changeset',
455 action='changeset_raw',
457 action='changeset_raw',
456 repo_name=HG_REPO, revision='tip',
458 repo_name=HG_REPO, revision='tip',
457 api_key=auth_token),
459 api_key=auth_token),
458 status=302)
460 status=302)
459
461
460 @pytest.mark.parametrize("test_name, auth_token, code", [
462 @pytest.mark.parametrize("test_name, auth_token, code", [
461 ('none', None, 302),
463 ('none', None, 302),
462 ('empty_string', '', 302),
464 ('empty_string', '', 302),
463 ('fake_number', '123456', 302),
465 ('fake_number', '123456', 302),
464 ('proper_auth_token', None, 200)
466 ('proper_auth_token', None, 200)
465 ])
467 ])
466 def test_access_whitelisted_page_via_auth_token(self, test_name,
468 def test_access_whitelisted_page_via_auth_token(self, test_name,
467 auth_token, code):
469 auth_token, code):
468 whitelist = self._get_api_whitelist(
470 whitelist = self._get_api_whitelist(
469 ['ChangesetController:changeset_raw'])
471 ['ChangesetController:changeset_raw'])
470 with mock.patch.dict('rhodecode.CONFIG', whitelist):
472 with mock.patch.dict('rhodecode.CONFIG', whitelist):
471 assert ['ChangesetController:changeset_raw'] == \
473 assert ['ChangesetController:changeset_raw'] == \
472 whitelist['api_access_controllers_whitelist']
474 whitelist['api_access_controllers_whitelist']
473 if test_name == 'proper_auth_token':
475 if test_name == 'proper_auth_token':
474 auth_token = User.get_first_super_admin().api_key
476 auth_token = User.get_first_super_admin().api_key
475
477
476 with fixture.anon_access(False):
478 with fixture.anon_access(False):
477 self.app.get(url(controller='changeset',
479 self.app.get(url(controller='changeset',
478 action='changeset_raw',
480 action='changeset_raw',
479 repo_name=HG_REPO, revision='tip',
481 repo_name=HG_REPO, revision='tip',
480 api_key=auth_token),
482 api_key=auth_token),
481 status=code)
483 status=code)
482
484
483 def test_access_page_via_extra_auth_token(self):
485 def test_access_page_via_extra_auth_token(self):
484 whitelist = self._get_api_whitelist(
486 whitelist = self._get_api_whitelist(
485 ['ChangesetController:changeset_raw'])
487 ['ChangesetController:changeset_raw'])
486 with mock.patch.dict('rhodecode.CONFIG', whitelist):
488 with mock.patch.dict('rhodecode.CONFIG', whitelist):
487 assert ['ChangesetController:changeset_raw'] == \
489 assert ['ChangesetController:changeset_raw'] == \
488 whitelist['api_access_controllers_whitelist']
490 whitelist['api_access_controllers_whitelist']
489
491
490 new_auth_token = AuthTokenModel().create(
492 new_auth_token = AuthTokenModel().create(
491 TEST_USER_ADMIN_LOGIN, 'test')
493 TEST_USER_ADMIN_LOGIN, 'test')
492 Session().commit()
494 Session().commit()
493 with fixture.anon_access(False):
495 with fixture.anon_access(False):
494 self.app.get(url(controller='changeset',
496 self.app.get(url(controller='changeset',
495 action='changeset_raw',
497 action='changeset_raw',
496 repo_name=HG_REPO, revision='tip',
498 repo_name=HG_REPO, revision='tip',
497 api_key=new_auth_token.api_key),
499 api_key=new_auth_token.api_key),
498 status=200)
500 status=200)
499
501
500 def test_access_page_via_expired_auth_token(self):
502 def test_access_page_via_expired_auth_token(self):
501 whitelist = self._get_api_whitelist(
503 whitelist = self._get_api_whitelist(
502 ['ChangesetController:changeset_raw'])
504 ['ChangesetController:changeset_raw'])
503 with mock.patch.dict('rhodecode.CONFIG', whitelist):
505 with mock.patch.dict('rhodecode.CONFIG', whitelist):
504 assert ['ChangesetController:changeset_raw'] == \
506 assert ['ChangesetController:changeset_raw'] == \
505 whitelist['api_access_controllers_whitelist']
507 whitelist['api_access_controllers_whitelist']
506
508
507 new_auth_token = AuthTokenModel().create(
509 new_auth_token = AuthTokenModel().create(
508 TEST_USER_ADMIN_LOGIN, 'test')
510 TEST_USER_ADMIN_LOGIN, 'test')
509 Session().commit()
511 Session().commit()
510 # patch the api key and make it expired
512 # patch the api key and make it expired
511 new_auth_token.expires = 0
513 new_auth_token.expires = 0
512 Session().add(new_auth_token)
514 Session().add(new_auth_token)
513 Session().commit()
515 Session().commit()
514 with fixture.anon_access(False):
516 with fixture.anon_access(False):
515 self.app.get(url(controller='changeset',
517 self.app.get(url(controller='changeset',
516 action='changeset_raw',
518 action='changeset_raw',
517 repo_name=HG_REPO, revision='tip',
519 repo_name=HG_REPO, revision='tip',
518 api_key=new_auth_token.api_key),
520 api_key=new_auth_token.api_key),
519 status=302)
521 status=302)
522
523
524 class TestPasswordReset(TestController):
525
526 @pytest.mark.parametrize(
527 'pwd_reset_setting, show_link, show_reset', [
528 ('hg.password_reset.enabled', True, True),
529 ('hg.password_reset.hidden', False, True),
530 ('hg.password_reset.disabled', False, False),
531 ])
532 def test_password_reset_settings(
533 self, pwd_reset_setting, show_link, show_reset):
534 clear_all_caches()
535 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
536 params = {
537 'csrf_token': self.csrf_token,
538 'anonymous': 'True',
539 'default_register': 'hg.register.auto_activate',
540 'default_register_message': '',
541 'default_password_reset': pwd_reset_setting,
542 'default_extern_activate': 'hg.extern_activate.auto',
543 }
544 resp = self.app.post(url('admin_permissions_application'), params=params)
545 self.logout_user()
546
547 login_page = self.app.get(login_url)
548 asr_login = AssertResponse(login_page)
549 index_page = self.app.get(index_url)
550 asr_index = AssertResponse(index_page)
551
552 if show_link:
553 asr_login.one_element_exists('a.pwd_reset')
554 asr_index.one_element_exists('a.pwd_reset')
555 else:
556 asr_login.no_element_exists('a.pwd_reset')
557 asr_index.no_element_exists('a.pwd_reset')
558
559 pwdreset_page = self.app.get(pwd_reset_url)
560
561 asr_reset = AssertResponse(pwdreset_page)
562 if show_reset:
563 assert 'Send password reset email' in pwdreset_page
564 asr_reset.one_element_exists('#email')
565 asr_reset.one_element_exists('#send')
566 else:
567 assert 'Password reset has been disabled.' in pwdreset_page
568 asr_reset.no_element_exists('#email')
569 asr_reset.no_element_exists('#send')
570
571 def test_password_form_disabled(self):
572 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
573 params = {
574 'csrf_token': self.csrf_token,
575 'anonymous': 'True',
576 'default_register': 'hg.register.auto_activate',
577 'default_register_message': '',
578 'default_password_reset': 'hg.password_reset.disabled',
579 'default_extern_activate': 'hg.extern_activate.auto',
580 }
581 self.app.post(url('admin_permissions_application'), params=params)
582 self.logout_user()
583
584 pwdreset_page = self.app.post(
585 pwd_reset_url,
586 {'email': 'lisa@rhodecode.com',}
587 )
588 assert 'Password reset has been disabled.' in pwdreset_page
General Comments 0
You need to be logged in to leave comments. Login now