##// END OF EJS Templates
ssh: admin management of ssh keys...
Christian Oyarzun -
r7678:3e84ac8e default
parent child Browse files
Show More
@@ -0,0 +1,63 b''
1 <table class="table">
2 %if c.user_ssh_keys:
3 <tr>
4 <th>${_('Fingerprint')}</th>
5 <th>${_('Description')}</th>
6 <th>${_('Action')}</th>
7 </tr>
8 %for ssh_key in c.user_ssh_keys:
9 <tr>
10 <td>
11 ${ssh_key.fingerprint}
12 </td>
13 <td>
14 ${ssh_key.description}
15 </td>
16 <td>
17 ${h.form(url('edit_user_ssh_keys_delete', id=c.user.user_id))}
18 ${h.hidden('del_public_key', ssh_key.public_key)}
19 <button class="btn btn-danger btn-xs" type="submit"
20 onclick="return confirm('${_('Confirm to remove this SSH key: %s') % ssh_key.fingerprint}');">
21 <i class="icon-trashcan"></i>
22 ${_('Remove')}
23 </button>
24 ${h.end_form()}
25 </td>
26 </tr>
27 %endfor
28 %else:
29 <tr>
30 <td>
31 <div class="ip">${_('No SSH keys have been added')}</div>
32 </td>
33 </tr>
34 %endif
35 </table>
36
37 <div>
38 ${h.form(url('edit_user_ssh_keys', id=c.user.user_id))}
39 <div class="form">
40 <div class="form-group">
41 <label class="control-label">${_('New SSH key')}</label>
42 </div>
43 <div class="form-group">
44 <label class="control-label" for="public_key">${_('Public key')}:</label>
45 <div>
46 ${h.textarea('public_key', '', class_='form-control', placeholder=_('Public key (contents of e.g. ~/.ssh/id_rsa.pub)'), cols=80, rows=5)}
47 </div>
48 </div>
49 <div class="form-group">
50 <label class="control-label" for="description">${_('Description')}:</label>
51 <div>
52 ${h.text('description', class_='form-control', placeholder=_('Description'))}
53 </div>
54 </div>
55 <div class="form-group">
56 <div class="buttons">
57 ${h.submit('save', _('Add'), class_="btn btn-default")}
58 ${h.reset('reset', _('Reset'), class_="btn btn-default")}
59 </div>
60 </div>
61 </div>
62 ${h.end_form()}
63 </div>
@@ -1,146 +1,147 b''
1 List of contributors to Kallithea project:
1 List of contributors to Kallithea project:
2
2
3 Andrej Shadura <andrew@shadura.me> 2012 2014-2017 2019
3 Andrej Shadura <andrew@shadura.me> 2012 2014-2017 2019
4 Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2019
4 Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2019
5 Étienne Gilli <etienne.gilli@gmail.com> 2015-2017 2019
5 Étienne Gilli <etienne.gilli@gmail.com> 2015-2017 2019
6 Mads Kiilerich <mads@kiilerich.com> 2016-2019
6 Mads Kiilerich <mads@kiilerich.com> 2016-2019
7 Allan Nordhøy <epost@anotheragency.no> 2017-2019
7 Allan Nordhøy <epost@anotheragency.no> 2017-2019
8 ssantos <ssantos@web.de> 2018-2019
8 ssantos <ssantos@web.de> 2018-2019
9 Danni Randeris <danniranderis@gmail.com> 2019
9 Danni Randeris <danniranderis@gmail.com> 2019
10 Edmund Wong <ewong@crazy-cat.org> 2019
10 Edmund Wong <ewong@crazy-cat.org> 2019
11 Manuel Jacob <me@manueljacob.de> 2019
11 Manuel Jacob <me@manueljacob.de> 2019
12 Wolfgang Scherer <wolfgang.scherer@gmx.de> 2019
12 Wolfgang Scherer <wolfgang.scherer@gmx.de> 2019
13 Dominik Ruf <dominikruf@gmail.com> 2012 2014-2018
13 Dominik Ruf <dominikruf@gmail.com> 2012 2014-2018
14 Michal Čihař <michal@cihar.com> 2014-2015 2018
14 Michal Čihař <michal@cihar.com> 2014-2015 2018
15 Branko Majic <branko@majic.rs> 2015 2018
15 Branko Majic <branko@majic.rs> 2015 2018
16 Chris Rule <crule@aegistg.com> 2018
16 Chris Rule <crule@aegistg.com> 2018
17 Jesús Sánchez <jsanchezfdz95@gmail.com> 2018
17 Jesús Sánchez <jsanchezfdz95@gmail.com> 2018
18 Patrick Vane <patrick_vane@lowentry.com> 2018
18 Patrick Vane <patrick_vane@lowentry.com> 2018
19 Pheng Heong Tan <phtan90@gmail.com> 2018
19 Pheng Heong Tan <phtan90@gmail.com> 2018
20 Максим Якимчук <xpinovo@gmail.com> 2018
20 Максим Якимчук <xpinovo@gmail.com> 2018
21 Марс Ямбар <mjambarmeta@gmail.com> 2018
21 Марс Ямбар <mjambarmeta@gmail.com> 2018
22 Mads Kiilerich <madski@unity3d.com> 2012-2017
22 Mads Kiilerich <madski@unity3d.com> 2012-2017
23 Unity Technologies 2012-2017
23 Unity Technologies 2012-2017
24 Søren Løvborg <sorenl@unity3d.com> 2015-2017
24 Søren Løvborg <sorenl@unity3d.com> 2015-2017
25 Sam Jaques <sam.jaques@me.com> 2015 2017
25 Sam Jaques <sam.jaques@me.com> 2015 2017
26 Asterios Dimitriou <steve@pci.gr> 2016-2017
26 Asterios Dimitriou <steve@pci.gr> 2016-2017
27 Alessandro Molina <alessandro.molina@axant.it> 2017
27 Alessandro Molina <alessandro.molina@axant.it> 2017
28 Anton Schur <tonich.sh@gmail.com> 2017
28 Anton Schur <tonich.sh@gmail.com> 2017
29 Ching-Chen Mao <mao@lins.fju.edu.tw> 2017
29 Ching-Chen Mao <mao@lins.fju.edu.tw> 2017
30 Eivind Tagseth <eivindt@gmail.com> 2017
30 Eivind Tagseth <eivindt@gmail.com> 2017
31 FUJIWARA Katsunori <foozy@lares.dti.ne.jp> 2017
31 FUJIWARA Katsunori <foozy@lares.dti.ne.jp> 2017
32 Holger Schramm <info@schramm.by> 2017
32 Holger Schramm <info@schramm.by> 2017
33 Karl Goetz <karl@kgoetz.id.au> 2017
33 Karl Goetz <karl@kgoetz.id.au> 2017
34 Lars Kruse <devel@sumpfralle.de> 2017
34 Lars Kruse <devel@sumpfralle.de> 2017
35 Marko Semet <markosemet@googlemail.com> 2017
35 Marko Semet <markosemet@googlemail.com> 2017
36 Viktar Vauchkevich <victorenator@gmail.com> 2017
36 Viktar Vauchkevich <victorenator@gmail.com> 2017
37 Takumi IINO <trot.thunder@gmail.com> 2012-2016
37 Takumi IINO <trot.thunder@gmail.com> 2012-2016
38 Jan Heylen <heyleke@gmail.com> 2015-2016
38 Jan Heylen <heyleke@gmail.com> 2015-2016
39 Robert Martinez <ntttq@inboxen.org> 2015-2016
39 Robert Martinez <ntttq@inboxen.org> 2015-2016
40 Robert Rauch <mail@robertrauch.de> 2015-2016
40 Robert Rauch <mail@robertrauch.de> 2015-2016
41 Angel Ezquerra <angel.ezquerra@gmail.com> 2016
41 Angel Ezquerra <angel.ezquerra@gmail.com> 2016
42 Anton Shestakov <av6@dwimlabs.net> 2016
42 Anton Shestakov <av6@dwimlabs.net> 2016
43 Brandon Jones <bjones14@gmail.com> 2016
43 Brandon Jones <bjones14@gmail.com> 2016
44 Kateryna Musina <kateryna@unity3d.com> 2016
44 Kateryna Musina <kateryna@unity3d.com> 2016
45 Konstantin Veretennicov <kveretennicov@gmail.com> 2016
45 Konstantin Veretennicov <kveretennicov@gmail.com> 2016
46 Oscar Curero <oscar@naiandei.net> 2016
46 Oscar Curero <oscar@naiandei.net> 2016
47 Robert James Dennington <tinytimrob@googlemail.com> 2016
47 Robert James Dennington <tinytimrob@googlemail.com> 2016
48 timeless@gmail.com 2016
48 timeless@gmail.com 2016
49 YFdyh000 <yfdyh000@gmail.com> 2016
49 YFdyh000 <yfdyh000@gmail.com> 2016
50 Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
50 Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
51 Sean Farley <sean.michael.farley@gmail.com> 2013-2015
51 Sean Farley <sean.michael.farley@gmail.com> 2013-2015
52 Christian Oyarzun <oyarzun@gmail.com> 2014-2015
52 Christian Oyarzun <oyarzun@gmail.com> 2014-2015
53 Joseph Rivera <rivera.d.joseph@gmail.com> 2014-2015
53 Joseph Rivera <rivera.d.joseph@gmail.com> 2014-2015
54 Anatoly Bubenkov <bubenkoff@gmail.com> 2015
54 Anatoly Bubenkov <bubenkoff@gmail.com> 2015
55 Andrew Bartlett <abartlet@catalyst.net.nz> 2015
55 Andrew Bartlett <abartlet@catalyst.net.nz> 2015
56 Balázs Úr <urbalazs@gmail.com> 2015
56 Balázs Úr <urbalazs@gmail.com> 2015
57 Ben Finney <ben@benfinney.id.au> 2015
57 Ben Finney <ben@benfinney.id.au> 2015
58 Daniel Hobley <danielh@unity3d.com> 2015
58 Daniel Hobley <danielh@unity3d.com> 2015
59 David Avigni <david.avigni@ankapi.com> 2015
59 David Avigni <david.avigni@ankapi.com> 2015
60 Denis Blanchette <dblanchette@coveo.com> 2015
60 Denis Blanchette <dblanchette@coveo.com> 2015
61 duanhongyi <duanhongyi@doopai.com> 2015
61 duanhongyi <duanhongyi@doopai.com> 2015
62 EriCSN Chang <ericsning@gmail.com> 2015
62 EriCSN Chang <ericsning@gmail.com> 2015
63 Grzegorz Krason <grzegorz.krason@gmail.com> 2015
63 Grzegorz Krason <grzegorz.krason@gmail.com> 2015
64 Jiří Suchan <yed@vanyli.net> 2015
64 Jiří Suchan <yed@vanyli.net> 2015
65 Kazunari Kobayashi <kobanari@nifty.com> 2015
65 Kazunari Kobayashi <kobanari@nifty.com> 2015
66 Kevin Bullock <kbullock@ringworld.org> 2015
66 Kevin Bullock <kbullock@ringworld.org> 2015
67 kobanari <kobanari@nifty.com> 2015
67 kobanari <kobanari@nifty.com> 2015
68 Marc Abramowitz <marc@marc-abramowitz.com> 2015
68 Marc Abramowitz <marc@marc-abramowitz.com> 2015
69 Marc Villetard <marc.villetard@gmail.com> 2015
69 Marc Villetard <marc.villetard@gmail.com> 2015
70 Matthias Zilk <matthias.zilk@gmail.com> 2015
70 Matthias Zilk <matthias.zilk@gmail.com> 2015
71 Michael Pohl <michael@mipapo.de> 2015
71 Michael Pohl <michael@mipapo.de> 2015
72 Michael V. DePalatis <mike@depalatis.net> 2015
72 Michael V. DePalatis <mike@depalatis.net> 2015
73 Morten Skaaning <mortens@unity3d.com> 2015
73 Morten Skaaning <mortens@unity3d.com> 2015
74 Nick High <nick@silverchip.org> 2015
74 Nick High <nick@silverchip.org> 2015
75 Niemand Jedermann <predatorix@web.de> 2015
75 Niemand Jedermann <predatorix@web.de> 2015
76 Peter Vitt <petervitt@web.de> 2015
76 Peter Vitt <petervitt@web.de> 2015
77 Ronny Pfannschmidt <opensource@ronnypfannschmidt.de> 2015
77 Ronny Pfannschmidt <opensource@ronnypfannschmidt.de> 2015
78 Tuux <tuxa@galaxie.eu.org> 2015
78 Tuux <tuxa@galaxie.eu.org> 2015
79 Viktar Palstsiuk <vipals@gmail.com> 2015
79 Viktar Palstsiuk <vipals@gmail.com> 2015
80 Ante Ilic <ante@unity3d.com> 2014
80 Ante Ilic <ante@unity3d.com> 2014
81 Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014
81 Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014
82 Calinou <calinou@opmbx.org> 2014
82 Calinou <calinou@opmbx.org> 2014
83 Daniel Anderson <daniel@dattrix.com> 2014
83 Daniel Anderson <daniel@dattrix.com> 2014
84 Henrik Stuart <hg@hstuart.dk> 2014
84 Henrik Stuart <hg@hstuart.dk> 2014
85 Ingo von Borstel <kallithea@planetmaker.de> 2014
85 Ingo von Borstel <kallithea@planetmaker.de> 2014
86 Jelmer Vernooij <jelmer@samba.org> 2014
86 Jelmer Vernooij <jelmer@samba.org> 2014
87 Jim Hague <jim.hague@acm.org> 2014
87 Jim Hague <jim.hague@acm.org> 2014
88 Matt Fellows <kallithea@matt-fellows.me.uk> 2014
88 Matt Fellows <kallithea@matt-fellows.me.uk> 2014
89 Max Roman <max@choloclos.se> 2014
89 Max Roman <max@choloclos.se> 2014
90 Na'Tosha Bard <natosha@unity3d.com> 2014
90 Na'Tosha Bard <natosha@unity3d.com> 2014
91 Rasmus Selsmark <rasmuss@unity3d.com> 2014
91 Rasmus Selsmark <rasmuss@unity3d.com> 2014
92 Tim Freund <tim@freunds.net> 2014
92 Tim Freund <tim@freunds.net> 2014
93 Travis Burtrum <android@moparisthebest.com> 2014
93 Travis Burtrum <android@moparisthebest.com> 2014
94 Zoltan Gyarmati <mr.zoltan.gyarmati@gmail.com> 2014
94 Zoltan Gyarmati <mr.zoltan.gyarmati@gmail.com> 2014
95 Marcin Kuźmiński <marcin@python-works.com> 2010-2013
95 Marcin Kuźmiński <marcin@python-works.com> 2010-2013
96 xpol <xpolife@gmail.com> 2012-2013
96 xpol <xpolife@gmail.com> 2012-2013
97 Aparkar <aparkar@icloud.com> 2013
97 Aparkar <aparkar@icloud.com> 2013
98 Dennis Brakhane <brakhane@googlemail.com> 2013
98 Dennis Brakhane <brakhane@googlemail.com> 2013
99 Grzegorz Rożniecki <xaerxess@gmail.com> 2013
99 Grzegorz Rożniecki <xaerxess@gmail.com> 2013
100 Ilya Beda <ir4y.ix@gmail.com> 2013
100 Jonathan Sternberg <jonathansternberg@gmail.com> 2013
101 Jonathan Sternberg <jonathansternberg@gmail.com> 2013
101 Leonardo Carneiro <leonardo@unity3d.com> 2013
102 Leonardo Carneiro <leonardo@unity3d.com> 2013
102 Magnus Ericmats <magnus.ericmats@gmail.com> 2013
103 Magnus Ericmats <magnus.ericmats@gmail.com> 2013
103 Martin Vium <martinv@unity3d.com> 2013
104 Martin Vium <martinv@unity3d.com> 2013
104 Simon Lopez <simon.lopez@slopez.org> 2013
105 Simon Lopez <simon.lopez@slopez.org> 2013
105 Ton Plomp <tcplomp@gmail.com> 2013
106 Ton Plomp <tcplomp@gmail.com> 2013
106 Augusto Herrmann <augusto.herrmann@planejamento.gov.br> 2011-2012
107 Augusto Herrmann <augusto.herrmann@planejamento.gov.br> 2011-2012
107 Dan Sheridan <djs@adelard.com> 2012
108 Dan Sheridan <djs@adelard.com> 2012
108 Dies Koper <diesk@fast.au.fujitsu.com> 2012
109 Dies Koper <diesk@fast.au.fujitsu.com> 2012
109 Erwin Kroon <e.kroon@smartmetersolutions.nl> 2012
110 Erwin Kroon <e.kroon@smartmetersolutions.nl> 2012
110 H Waldo G <gwaldo@gmail.com> 2012
111 H Waldo G <gwaldo@gmail.com> 2012
111 hppj <hppj@postmage.biz> 2012
112 hppj <hppj@postmage.biz> 2012
112 Indra Talip <indra.talip@gmail.com> 2012
113 Indra Talip <indra.talip@gmail.com> 2012
113 mikespook 2012
114 mikespook 2012
114 nansenat16 <nansenat16@null.tw> 2012
115 nansenat16 <nansenat16@null.tw> 2012
115 Philip Jameson <philip.j@hostdime.com> 2012
116 Philip Jameson <philip.j@hostdime.com> 2012
116 Raoul Thill <raoul.thill@gmail.com> 2012
117 Raoul Thill <raoul.thill@gmail.com> 2012
117 Stefan Engel <mail@engel-stefan.de> 2012
118 Stefan Engel <mail@engel-stefan.de> 2012
118 Tony Bussieres <t.bussieres@gmail.com> 2012
119 Tony Bussieres <t.bussieres@gmail.com> 2012
119 Vincent Caron <vcaron@bearstech.com> 2012
120 Vincent Caron <vcaron@bearstech.com> 2012
120 Vincent Duvert <vincent@duvert.net> 2012
121 Vincent Duvert <vincent@duvert.net> 2012
121 Vladislav Poluhin <nuklea@gmail.com> 2012
122 Vladislav Poluhin <nuklea@gmail.com> 2012
122 Zachary Auclair <zach101@gmail.com> 2012
123 Zachary Auclair <zach101@gmail.com> 2012
123 Ankit Solanki <ankit.solanki@gmail.com> 2011
124 Ankit Solanki <ankit.solanki@gmail.com> 2011
124 Dmitri Kuznetsov 2011
125 Dmitri Kuznetsov 2011
125 Jared Bunting <jared.bunting@peachjean.com> 2011
126 Jared Bunting <jared.bunting@peachjean.com> 2011
126 Jason Harris <jason@jasonfharris.com> 2011
127 Jason Harris <jason@jasonfharris.com> 2011
127 Les Peabody <lpeabody@gmail.com> 2011
128 Les Peabody <lpeabody@gmail.com> 2011
128 Liad Shani <liadff@gmail.com> 2011
129 Liad Shani <liadff@gmail.com> 2011
129 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it> 2011
130 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it> 2011
130 Matt Zuba <matt.zuba@goodwillaz.org> 2011
131 Matt Zuba <matt.zuba@goodwillaz.org> 2011
131 Nicolas VINOT <aeris@imirhil.fr> 2011
132 Nicolas VINOT <aeris@imirhil.fr> 2011
132 Shawn K. O'Shea <shawn@eth0.net> 2011
133 Shawn K. O'Shea <shawn@eth0.net> 2011
133 Thayne Harbaugh <thayne@fusionio.com> 2011
134 Thayne Harbaugh <thayne@fusionio.com> 2011
134 Łukasz Balcerzak <lukaszbalcerzak@gmail.com> 2010
135 Łukasz Balcerzak <lukaszbalcerzak@gmail.com> 2010
135 Andrew Kesterson <andrew@aklabs.net>
136 Andrew Kesterson <andrew@aklabs.net>
136 cejones
137 cejones
137 David A. Sjøen <david.sjoen@westcon.no>
138 David A. Sjøen <david.sjoen@westcon.no>
138 James Rhodes <jrhodes@redpointsoftware.com.au>
139 James Rhodes <jrhodes@redpointsoftware.com.au>
139 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
140 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
140 larikale
141 larikale
141 RhodeCode GmbH
142 RhodeCode GmbH
142 Sebastian Kreutzberger <sebastian@rhodecode.com>
143 Sebastian Kreutzberger <sebastian@rhodecode.com>
143 Steve Romanow <slestak989@gmail.com>
144 Steve Romanow <slestak989@gmail.com>
144 SteveCohen
145 SteveCohen
145 Thomas <thomas@rhodecode.com>
146 Thomas <thomas@rhodecode.com>
146 Thomas Waldmann <tw-public@gmx.de>
147 Thomas Waldmann <tw-public@gmx.de>
@@ -1,778 +1,785 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 Routes configuration
15 Routes configuration
16
16
17 The more specific and detailed routes should be defined first so they
17 The more specific and detailed routes should be defined first so they
18 may take precedent over the more generic routes. For more information
18 may take precedent over the more generic routes. For more information
19 refer to the routes manual at http://routes.groovie.org/docs/
19 refer to the routes manual at http://routes.groovie.org/docs/
20 """
20 """
21
21
22 from tg import request
22 from tg import request
23 from routes import Mapper
23 from routes import Mapper
24
24
25 # prefix for non repository related links needs to be prefixed with `/`
25 # prefix for non repository related links needs to be prefixed with `/`
26 ADMIN_PREFIX = '/_admin'
26 ADMIN_PREFIX = '/_admin'
27
27
28
28
29 def make_map(config):
29 def make_map(config):
30 """Create, configure and return the routes Mapper"""
30 """Create, configure and return the routes Mapper"""
31 rmap = Mapper(directory=config['paths']['controllers'],
31 rmap = Mapper(directory=config['paths']['controllers'],
32 always_scan=config['debug'])
32 always_scan=config['debug'])
33 rmap.minimization = False
33 rmap.minimization = False
34 rmap.explicit = False
34 rmap.explicit = False
35
35
36 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
36 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
37
37
38 def check_repo(environ, match_dict):
38 def check_repo(environ, match_dict):
39 """
39 """
40 Check for valid repository for proper 404 handling.
40 Check for valid repository for proper 404 handling.
41 Also, a bit of side effect modifying match_dict ...
41 Also, a bit of side effect modifying match_dict ...
42 """
42 """
43 if match_dict.get('f_path'):
43 if match_dict.get('f_path'):
44 # fix for multiple initial slashes that causes errors
44 # fix for multiple initial slashes that causes errors
45 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
45 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
46
46
47 return is_valid_repo(match_dict['repo_name'], config['base_path'])
47 return is_valid_repo(match_dict['repo_name'], config['base_path'])
48
48
49 def check_group(environ, match_dict):
49 def check_group(environ, match_dict):
50 """
50 """
51 check for valid repository group for proper 404 handling
51 check for valid repository group for proper 404 handling
52
52
53 :param environ:
53 :param environ:
54 :param match_dict:
54 :param match_dict:
55 """
55 """
56 repo_group_name = match_dict.get('group_name')
56 repo_group_name = match_dict.get('group_name')
57 return is_valid_repo_group(repo_group_name, config['base_path'])
57 return is_valid_repo_group(repo_group_name, config['base_path'])
58
58
59 def check_group_skip_path(environ, match_dict):
59 def check_group_skip_path(environ, match_dict):
60 """
60 """
61 check for valid repository group for proper 404 handling, but skips
61 check for valid repository group for proper 404 handling, but skips
62 verification of existing path
62 verification of existing path
63
63
64 :param environ:
64 :param environ:
65 :param match_dict:
65 :param match_dict:
66 """
66 """
67 repo_group_name = match_dict.get('group_name')
67 repo_group_name = match_dict.get('group_name')
68 return is_valid_repo_group(repo_group_name, config['base_path'],
68 return is_valid_repo_group(repo_group_name, config['base_path'],
69 skip_path_check=True)
69 skip_path_check=True)
70
70
71 def check_user_group(environ, match_dict):
71 def check_user_group(environ, match_dict):
72 """
72 """
73 check for valid user group for proper 404 handling
73 check for valid user group for proper 404 handling
74
74
75 :param environ:
75 :param environ:
76 :param match_dict:
76 :param match_dict:
77 """
77 """
78 return True
78 return True
79
79
80 def check_int(environ, match_dict):
80 def check_int(environ, match_dict):
81 return match_dict.get('id').isdigit()
81 return match_dict.get('id').isdigit()
82
82
83 #==========================================================================
83 #==========================================================================
84 # CUSTOM ROUTES HERE
84 # CUSTOM ROUTES HERE
85 #==========================================================================
85 #==========================================================================
86
86
87 # MAIN PAGE
87 # MAIN PAGE
88 rmap.connect('home', '/', controller='home', action='index')
88 rmap.connect('home', '/', controller='home', action='index')
89 rmap.connect('about', '/about', controller='home', action='about')
89 rmap.connect('about', '/about', controller='home', action='about')
90 rmap.redirect('/favicon.ico', '/images/favicon.ico')
90 rmap.redirect('/favicon.ico', '/images/favicon.ico')
91 rmap.connect('repo_switcher_data', '/_repos', controller='home',
91 rmap.connect('repo_switcher_data', '/_repos', controller='home',
92 action='repo_switcher_data')
92 action='repo_switcher_data')
93 rmap.connect('users_and_groups_data', '/_users_and_groups', controller='home',
93 rmap.connect('users_and_groups_data', '/_users_and_groups', controller='home',
94 action='users_and_groups_data')
94 action='users_and_groups_data')
95
95
96 rmap.connect('rst_help',
96 rmap.connect('rst_help',
97 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
97 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
98 _static=True)
98 _static=True)
99 rmap.connect('kallithea_project_url', "https://kallithea-scm.org/", _static=True)
99 rmap.connect('kallithea_project_url', "https://kallithea-scm.org/", _static=True)
100 rmap.connect('issues_url', 'https://bitbucket.org/conservancy/kallithea/issues', _static=True)
100 rmap.connect('issues_url', 'https://bitbucket.org/conservancy/kallithea/issues', _static=True)
101
101
102 # ADMIN REPOSITORY ROUTES
102 # ADMIN REPOSITORY ROUTES
103 with rmap.submapper(path_prefix=ADMIN_PREFIX,
103 with rmap.submapper(path_prefix=ADMIN_PREFIX,
104 controller='admin/repos') as m:
104 controller='admin/repos') as m:
105 m.connect("repos", "/repos",
105 m.connect("repos", "/repos",
106 action="create", conditions=dict(method=["POST"]))
106 action="create", conditions=dict(method=["POST"]))
107 m.connect("repos", "/repos",
107 m.connect("repos", "/repos",
108 action="index", conditions=dict(method=["GET"]))
108 action="index", conditions=dict(method=["GET"]))
109 m.connect("new_repo", "/create_repository",
109 m.connect("new_repo", "/create_repository",
110 action="create_repository", conditions=dict(method=["GET"]))
110 action="create_repository", conditions=dict(method=["GET"]))
111 m.connect("update_repo", "/repos/{repo_name:.*?}",
111 m.connect("update_repo", "/repos/{repo_name:.*?}",
112 action="update", conditions=dict(method=["POST"],
112 action="update", conditions=dict(method=["POST"],
113 function=check_repo))
113 function=check_repo))
114 m.connect("delete_repo", "/repos/{repo_name:.*?}/delete",
114 m.connect("delete_repo", "/repos/{repo_name:.*?}/delete",
115 action="delete", conditions=dict(method=["POST"]))
115 action="delete", conditions=dict(method=["POST"]))
116
116
117 # ADMIN REPOSITORY GROUPS ROUTES
117 # ADMIN REPOSITORY GROUPS ROUTES
118 with rmap.submapper(path_prefix=ADMIN_PREFIX,
118 with rmap.submapper(path_prefix=ADMIN_PREFIX,
119 controller='admin/repo_groups') as m:
119 controller='admin/repo_groups') as m:
120 m.connect("repos_groups", "/repo_groups",
120 m.connect("repos_groups", "/repo_groups",
121 action="create", conditions=dict(method=["POST"]))
121 action="create", conditions=dict(method=["POST"]))
122 m.connect("repos_groups", "/repo_groups",
122 m.connect("repos_groups", "/repo_groups",
123 action="index", conditions=dict(method=["GET"]))
123 action="index", conditions=dict(method=["GET"]))
124 m.connect("new_repos_group", "/repo_groups/new",
124 m.connect("new_repos_group", "/repo_groups/new",
125 action="new", conditions=dict(method=["GET"]))
125 action="new", conditions=dict(method=["GET"]))
126 m.connect("update_repos_group", "/repo_groups/{group_name:.*?}",
126 m.connect("update_repos_group", "/repo_groups/{group_name:.*?}",
127 action="update", conditions=dict(method=["POST"],
127 action="update", conditions=dict(method=["POST"],
128 function=check_group))
128 function=check_group))
129
129
130 m.connect("repos_group", "/repo_groups/{group_name:.*?}",
130 m.connect("repos_group", "/repo_groups/{group_name:.*?}",
131 action="show", conditions=dict(method=["GET"],
131 action="show", conditions=dict(method=["GET"],
132 function=check_group))
132 function=check_group))
133
133
134 # EXTRAS REPO GROUP ROUTES
134 # EXTRAS REPO GROUP ROUTES
135 m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit",
135 m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit",
136 action="edit",
136 action="edit",
137 conditions=dict(method=["GET"], function=check_group))
137 conditions=dict(method=["GET"], function=check_group))
138
138
139 m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced",
139 m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced",
140 action="edit_repo_group_advanced",
140 action="edit_repo_group_advanced",
141 conditions=dict(method=["GET"], function=check_group))
141 conditions=dict(method=["GET"], function=check_group))
142
142
143 m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
143 m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
144 action="edit_repo_group_perms",
144 action="edit_repo_group_perms",
145 conditions=dict(method=["GET"], function=check_group))
145 conditions=dict(method=["GET"], function=check_group))
146 m.connect("edit_repo_group_perms_update", "/repo_groups/{group_name:.*?}/edit/permissions",
146 m.connect("edit_repo_group_perms_update", "/repo_groups/{group_name:.*?}/edit/permissions",
147 action="update_perms",
147 action="update_perms",
148 conditions=dict(method=["POST"], function=check_group))
148 conditions=dict(method=["POST"], function=check_group))
149 m.connect("edit_repo_group_perms_delete", "/repo_groups/{group_name:.*?}/edit/permissions/delete",
149 m.connect("edit_repo_group_perms_delete", "/repo_groups/{group_name:.*?}/edit/permissions/delete",
150 action="delete_perms",
150 action="delete_perms",
151 conditions=dict(method=["POST"], function=check_group))
151 conditions=dict(method=["POST"], function=check_group))
152
152
153 m.connect("delete_repo_group", "/repo_groups/{group_name:.*?}/delete",
153 m.connect("delete_repo_group", "/repo_groups/{group_name:.*?}/delete",
154 action="delete", conditions=dict(method=["POST"],
154 action="delete", conditions=dict(method=["POST"],
155 function=check_group_skip_path))
155 function=check_group_skip_path))
156
156
157 # ADMIN USER ROUTES
157 # ADMIN USER ROUTES
158 with rmap.submapper(path_prefix=ADMIN_PREFIX,
158 with rmap.submapper(path_prefix=ADMIN_PREFIX,
159 controller='admin/users') as m:
159 controller='admin/users') as m:
160 m.connect("new_user", "/users/new",
160 m.connect("new_user", "/users/new",
161 action="create", conditions=dict(method=["POST"]))
161 action="create", conditions=dict(method=["POST"]))
162 m.connect("users", "/users",
162 m.connect("users", "/users",
163 action="index", conditions=dict(method=["GET"]))
163 action="index", conditions=dict(method=["GET"]))
164 m.connect("formatted_users", "/users.{format}",
164 m.connect("formatted_users", "/users.{format}",
165 action="index", conditions=dict(method=["GET"]))
165 action="index", conditions=dict(method=["GET"]))
166 m.connect("new_user", "/users/new",
166 m.connect("new_user", "/users/new",
167 action="new", conditions=dict(method=["GET"]))
167 action="new", conditions=dict(method=["GET"]))
168 m.connect("update_user", "/users/{id}",
168 m.connect("update_user", "/users/{id}",
169 action="update", conditions=dict(method=["POST"]))
169 action="update", conditions=dict(method=["POST"]))
170 m.connect("delete_user", "/users/{id}/delete",
170 m.connect("delete_user", "/users/{id}/delete",
171 action="delete", conditions=dict(method=["POST"]))
171 action="delete", conditions=dict(method=["POST"]))
172 m.connect("edit_user", "/users/{id}/edit",
172 m.connect("edit_user", "/users/{id}/edit",
173 action="edit", conditions=dict(method=["GET"]))
173 action="edit", conditions=dict(method=["GET"]))
174
174
175 # EXTRAS USER ROUTES
175 # EXTRAS USER ROUTES
176 m.connect("edit_user_advanced", "/users/{id}/edit/advanced",
176 m.connect("edit_user_advanced", "/users/{id}/edit/advanced",
177 action="edit_advanced", conditions=dict(method=["GET"]))
177 action="edit_advanced", conditions=dict(method=["GET"]))
178
178
179 m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
179 m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
180 action="edit_api_keys", conditions=dict(method=["GET"]))
180 action="edit_api_keys", conditions=dict(method=["GET"]))
181 m.connect("edit_user_api_keys_update", "/users/{id}/edit/api_keys",
181 m.connect("edit_user_api_keys_update", "/users/{id}/edit/api_keys",
182 action="add_api_key", conditions=dict(method=["POST"]))
182 action="add_api_key", conditions=dict(method=["POST"]))
183 m.connect("edit_user_api_keys_delete", "/users/{id}/edit/api_keys/delete",
183 m.connect("edit_user_api_keys_delete", "/users/{id}/edit/api_keys/delete",
184 action="delete_api_key", conditions=dict(method=["POST"]))
184 action="delete_api_key", conditions=dict(method=["POST"]))
185
185
186 m.connect("edit_user_ssh_keys", "/users/{id}/edit/ssh_keys",
187 action="edit_ssh_keys", conditions=dict(method=["GET"]))
188 m.connect("edit_user_ssh_keys", "/users/{id}/edit/ssh_keys",
189 action="ssh_keys_add", conditions=dict(method=["POST"]))
190 m.connect("edit_user_ssh_keys_delete", "/users/{id}/edit/ssh_keys/delete",
191 action="ssh_keys_delete", conditions=dict(method=["POST"]))
192
186 m.connect("edit_user_perms", "/users/{id}/edit/permissions",
193 m.connect("edit_user_perms", "/users/{id}/edit/permissions",
187 action="edit_perms", conditions=dict(method=["GET"]))
194 action="edit_perms", conditions=dict(method=["GET"]))
188 m.connect("edit_user_perms_update", "/users/{id}/edit/permissions",
195 m.connect("edit_user_perms_update", "/users/{id}/edit/permissions",
189 action="update_perms", conditions=dict(method=["POST"]))
196 action="update_perms", conditions=dict(method=["POST"]))
190
197
191 m.connect("edit_user_emails", "/users/{id}/edit/emails",
198 m.connect("edit_user_emails", "/users/{id}/edit/emails",
192 action="edit_emails", conditions=dict(method=["GET"]))
199 action="edit_emails", conditions=dict(method=["GET"]))
193 m.connect("edit_user_emails_update", "/users/{id}/edit/emails",
200 m.connect("edit_user_emails_update", "/users/{id}/edit/emails",
194 action="add_email", conditions=dict(method=["POST"]))
201 action="add_email", conditions=dict(method=["POST"]))
195 m.connect("edit_user_emails_delete", "/users/{id}/edit/emails/delete",
202 m.connect("edit_user_emails_delete", "/users/{id}/edit/emails/delete",
196 action="delete_email", conditions=dict(method=["POST"]))
203 action="delete_email", conditions=dict(method=["POST"]))
197
204
198 m.connect("edit_user_ips", "/users/{id}/edit/ips",
205 m.connect("edit_user_ips", "/users/{id}/edit/ips",
199 action="edit_ips", conditions=dict(method=["GET"]))
206 action="edit_ips", conditions=dict(method=["GET"]))
200 m.connect("edit_user_ips_update", "/users/{id}/edit/ips",
207 m.connect("edit_user_ips_update", "/users/{id}/edit/ips",
201 action="add_ip", conditions=dict(method=["POST"]))
208 action="add_ip", conditions=dict(method=["POST"]))
202 m.connect("edit_user_ips_delete", "/users/{id}/edit/ips/delete",
209 m.connect("edit_user_ips_delete", "/users/{id}/edit/ips/delete",
203 action="delete_ip", conditions=dict(method=["POST"]))
210 action="delete_ip", conditions=dict(method=["POST"]))
204
211
205 # ADMIN USER GROUPS REST ROUTES
212 # ADMIN USER GROUPS REST ROUTES
206 with rmap.submapper(path_prefix=ADMIN_PREFIX,
213 with rmap.submapper(path_prefix=ADMIN_PREFIX,
207 controller='admin/user_groups') as m:
214 controller='admin/user_groups') as m:
208 m.connect("users_groups", "/user_groups",
215 m.connect("users_groups", "/user_groups",
209 action="create", conditions=dict(method=["POST"]))
216 action="create", conditions=dict(method=["POST"]))
210 m.connect("users_groups", "/user_groups",
217 m.connect("users_groups", "/user_groups",
211 action="index", conditions=dict(method=["GET"]))
218 action="index", conditions=dict(method=["GET"]))
212 m.connect("new_users_group", "/user_groups/new",
219 m.connect("new_users_group", "/user_groups/new",
213 action="new", conditions=dict(method=["GET"]))
220 action="new", conditions=dict(method=["GET"]))
214 m.connect("update_users_group", "/user_groups/{id}",
221 m.connect("update_users_group", "/user_groups/{id}",
215 action="update", conditions=dict(method=["POST"]))
222 action="update", conditions=dict(method=["POST"]))
216 m.connect("delete_users_group", "/user_groups/{id}/delete",
223 m.connect("delete_users_group", "/user_groups/{id}/delete",
217 action="delete", conditions=dict(method=["POST"]))
224 action="delete", conditions=dict(method=["POST"]))
218 m.connect("edit_users_group", "/user_groups/{id}/edit",
225 m.connect("edit_users_group", "/user_groups/{id}/edit",
219 action="edit", conditions=dict(method=["GET"]),
226 action="edit", conditions=dict(method=["GET"]),
220 function=check_user_group)
227 function=check_user_group)
221
228
222 # EXTRAS USER GROUP ROUTES
229 # EXTRAS USER GROUP ROUTES
223 m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms",
230 m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms",
224 action="edit_default_perms", conditions=dict(method=["GET"]))
231 action="edit_default_perms", conditions=dict(method=["GET"]))
225 m.connect("edit_user_group_default_perms_update", "/user_groups/{id}/edit/default_perms",
232 m.connect("edit_user_group_default_perms_update", "/user_groups/{id}/edit/default_perms",
226 action="update_default_perms", conditions=dict(method=["POST"]))
233 action="update_default_perms", conditions=dict(method=["POST"]))
227
234
228 m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
235 m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
229 action="edit_perms", conditions=dict(method=["GET"]))
236 action="edit_perms", conditions=dict(method=["GET"]))
230 m.connect("edit_user_group_perms_update", "/user_groups/{id}/edit/perms",
237 m.connect("edit_user_group_perms_update", "/user_groups/{id}/edit/perms",
231 action="update_perms", conditions=dict(method=["POST"]))
238 action="update_perms", conditions=dict(method=["POST"]))
232 m.connect("edit_user_group_perms_delete", "/user_groups/{id}/edit/perms/delete",
239 m.connect("edit_user_group_perms_delete", "/user_groups/{id}/edit/perms/delete",
233 action="delete_perms", conditions=dict(method=["POST"]))
240 action="delete_perms", conditions=dict(method=["POST"]))
234
241
235 m.connect("edit_user_group_advanced", "/user_groups/{id}/edit/advanced",
242 m.connect("edit_user_group_advanced", "/user_groups/{id}/edit/advanced",
236 action="edit_advanced", conditions=dict(method=["GET"]))
243 action="edit_advanced", conditions=dict(method=["GET"]))
237
244
238 m.connect("edit_user_group_members", "/user_groups/{id}/edit/members",
245 m.connect("edit_user_group_members", "/user_groups/{id}/edit/members",
239 action="edit_members", conditions=dict(method=["GET"]))
246 action="edit_members", conditions=dict(method=["GET"]))
240
247
241 # ADMIN PERMISSIONS ROUTES
248 # ADMIN PERMISSIONS ROUTES
242 with rmap.submapper(path_prefix=ADMIN_PREFIX,
249 with rmap.submapper(path_prefix=ADMIN_PREFIX,
243 controller='admin/permissions') as m:
250 controller='admin/permissions') as m:
244 m.connect("admin_permissions", "/permissions",
251 m.connect("admin_permissions", "/permissions",
245 action="permission_globals", conditions=dict(method=["POST"]))
252 action="permission_globals", conditions=dict(method=["POST"]))
246 m.connect("admin_permissions", "/permissions",
253 m.connect("admin_permissions", "/permissions",
247 action="permission_globals", conditions=dict(method=["GET"]))
254 action="permission_globals", conditions=dict(method=["GET"]))
248
255
249 m.connect("admin_permissions_ips", "/permissions/ips",
256 m.connect("admin_permissions_ips", "/permissions/ips",
250 action="permission_ips", conditions=dict(method=["GET"]))
257 action="permission_ips", conditions=dict(method=["GET"]))
251
258
252 m.connect("admin_permissions_perms", "/permissions/perms",
259 m.connect("admin_permissions_perms", "/permissions/perms",
253 action="permission_perms", conditions=dict(method=["GET"]))
260 action="permission_perms", conditions=dict(method=["GET"]))
254
261
255 # ADMIN DEFAULTS ROUTES
262 # ADMIN DEFAULTS ROUTES
256 with rmap.submapper(path_prefix=ADMIN_PREFIX,
263 with rmap.submapper(path_prefix=ADMIN_PREFIX,
257 controller='admin/defaults') as m:
264 controller='admin/defaults') as m:
258 m.connect('defaults', 'defaults',
265 m.connect('defaults', 'defaults',
259 action="index")
266 action="index")
260 m.connect('defaults_update', 'defaults/{id}/update',
267 m.connect('defaults_update', 'defaults/{id}/update',
261 action="update", conditions=dict(method=["POST"]))
268 action="update", conditions=dict(method=["POST"]))
262
269
263 # ADMIN AUTH SETTINGS
270 # ADMIN AUTH SETTINGS
264 rmap.connect('auth_settings', '%s/auth' % ADMIN_PREFIX,
271 rmap.connect('auth_settings', '%s/auth' % ADMIN_PREFIX,
265 controller='admin/auth_settings', action='auth_settings',
272 controller='admin/auth_settings', action='auth_settings',
266 conditions=dict(method=["POST"]))
273 conditions=dict(method=["POST"]))
267 rmap.connect('auth_home', '%s/auth' % ADMIN_PREFIX,
274 rmap.connect('auth_home', '%s/auth' % ADMIN_PREFIX,
268 controller='admin/auth_settings')
275 controller='admin/auth_settings')
269
276
270 # ADMIN SETTINGS ROUTES
277 # ADMIN SETTINGS ROUTES
271 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
272 controller='admin/settings') as m:
279 controller='admin/settings') as m:
273 m.connect("admin_settings", "/settings",
280 m.connect("admin_settings", "/settings",
274 action="settings_vcs", conditions=dict(method=["POST"]))
281 action="settings_vcs", conditions=dict(method=["POST"]))
275 m.connect("admin_settings", "/settings",
282 m.connect("admin_settings", "/settings",
276 action="settings_vcs", conditions=dict(method=["GET"]))
283 action="settings_vcs", conditions=dict(method=["GET"]))
277
284
278 m.connect("admin_settings_mapping", "/settings/mapping",
285 m.connect("admin_settings_mapping", "/settings/mapping",
279 action="settings_mapping", conditions=dict(method=["POST"]))
286 action="settings_mapping", conditions=dict(method=["POST"]))
280 m.connect("admin_settings_mapping", "/settings/mapping",
287 m.connect("admin_settings_mapping", "/settings/mapping",
281 action="settings_mapping", conditions=dict(method=["GET"]))
288 action="settings_mapping", conditions=dict(method=["GET"]))
282
289
283 m.connect("admin_settings_global", "/settings/global",
290 m.connect("admin_settings_global", "/settings/global",
284 action="settings_global", conditions=dict(method=["POST"]))
291 action="settings_global", conditions=dict(method=["POST"]))
285 m.connect("admin_settings_global", "/settings/global",
292 m.connect("admin_settings_global", "/settings/global",
286 action="settings_global", conditions=dict(method=["GET"]))
293 action="settings_global", conditions=dict(method=["GET"]))
287
294
288 m.connect("admin_settings_visual", "/settings/visual",
295 m.connect("admin_settings_visual", "/settings/visual",
289 action="settings_visual", conditions=dict(method=["POST"]))
296 action="settings_visual", conditions=dict(method=["POST"]))
290 m.connect("admin_settings_visual", "/settings/visual",
297 m.connect("admin_settings_visual", "/settings/visual",
291 action="settings_visual", conditions=dict(method=["GET"]))
298 action="settings_visual", conditions=dict(method=["GET"]))
292
299
293 m.connect("admin_settings_email", "/settings/email",
300 m.connect("admin_settings_email", "/settings/email",
294 action="settings_email", conditions=dict(method=["POST"]))
301 action="settings_email", conditions=dict(method=["POST"]))
295 m.connect("admin_settings_email", "/settings/email",
302 m.connect("admin_settings_email", "/settings/email",
296 action="settings_email", conditions=dict(method=["GET"]))
303 action="settings_email", conditions=dict(method=["GET"]))
297
304
298 m.connect("admin_settings_hooks", "/settings/hooks",
305 m.connect("admin_settings_hooks", "/settings/hooks",
299 action="settings_hooks", conditions=dict(method=["POST"]))
306 action="settings_hooks", conditions=dict(method=["POST"]))
300 m.connect("admin_settings_hooks_delete", "/settings/hooks/delete",
307 m.connect("admin_settings_hooks_delete", "/settings/hooks/delete",
301 action="settings_hooks", conditions=dict(method=["POST"]))
308 action="settings_hooks", conditions=dict(method=["POST"]))
302 m.connect("admin_settings_hooks", "/settings/hooks",
309 m.connect("admin_settings_hooks", "/settings/hooks",
303 action="settings_hooks", conditions=dict(method=["GET"]))
310 action="settings_hooks", conditions=dict(method=["GET"]))
304
311
305 m.connect("admin_settings_search", "/settings/search",
312 m.connect("admin_settings_search", "/settings/search",
306 action="settings_search", conditions=dict(method=["POST"]))
313 action="settings_search", conditions=dict(method=["POST"]))
307 m.connect("admin_settings_search", "/settings/search",
314 m.connect("admin_settings_search", "/settings/search",
308 action="settings_search", conditions=dict(method=["GET"]))
315 action="settings_search", conditions=dict(method=["GET"]))
309
316
310 m.connect("admin_settings_system", "/settings/system",
317 m.connect("admin_settings_system", "/settings/system",
311 action="settings_system", conditions=dict(method=["POST"]))
318 action="settings_system", conditions=dict(method=["POST"]))
312 m.connect("admin_settings_system", "/settings/system",
319 m.connect("admin_settings_system", "/settings/system",
313 action="settings_system", conditions=dict(method=["GET"]))
320 action="settings_system", conditions=dict(method=["GET"]))
314 m.connect("admin_settings_system_update", "/settings/system/updates",
321 m.connect("admin_settings_system_update", "/settings/system/updates",
315 action="settings_system_update", conditions=dict(method=["GET"]))
322 action="settings_system_update", conditions=dict(method=["GET"]))
316
323
317 # ADMIN MY ACCOUNT
324 # ADMIN MY ACCOUNT
318 with rmap.submapper(path_prefix=ADMIN_PREFIX,
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
319 controller='admin/my_account') as m:
326 controller='admin/my_account') as m:
320
327
321 m.connect("my_account", "/my_account",
328 m.connect("my_account", "/my_account",
322 action="my_account", conditions=dict(method=["GET"]))
329 action="my_account", conditions=dict(method=["GET"]))
323 m.connect("my_account", "/my_account",
330 m.connect("my_account", "/my_account",
324 action="my_account", conditions=dict(method=["POST"]))
331 action="my_account", conditions=dict(method=["POST"]))
325
332
326 m.connect("my_account_password", "/my_account/password",
333 m.connect("my_account_password", "/my_account/password",
327 action="my_account_password", conditions=dict(method=["GET"]))
334 action="my_account_password", conditions=dict(method=["GET"]))
328 m.connect("my_account_password", "/my_account/password",
335 m.connect("my_account_password", "/my_account/password",
329 action="my_account_password", conditions=dict(method=["POST"]))
336 action="my_account_password", conditions=dict(method=["POST"]))
330
337
331 m.connect("my_account_repos", "/my_account/repos",
338 m.connect("my_account_repos", "/my_account/repos",
332 action="my_account_repos", conditions=dict(method=["GET"]))
339 action="my_account_repos", conditions=dict(method=["GET"]))
333
340
334 m.connect("my_account_watched", "/my_account/watched",
341 m.connect("my_account_watched", "/my_account/watched",
335 action="my_account_watched", conditions=dict(method=["GET"]))
342 action="my_account_watched", conditions=dict(method=["GET"]))
336
343
337 m.connect("my_account_perms", "/my_account/perms",
344 m.connect("my_account_perms", "/my_account/perms",
338 action="my_account_perms", conditions=dict(method=["GET"]))
345 action="my_account_perms", conditions=dict(method=["GET"]))
339
346
340 m.connect("my_account_emails", "/my_account/emails",
347 m.connect("my_account_emails", "/my_account/emails",
341 action="my_account_emails", conditions=dict(method=["GET"]))
348 action="my_account_emails", conditions=dict(method=["GET"]))
342 m.connect("my_account_emails", "/my_account/emails",
349 m.connect("my_account_emails", "/my_account/emails",
343 action="my_account_emails_add", conditions=dict(method=["POST"]))
350 action="my_account_emails_add", conditions=dict(method=["POST"]))
344 m.connect("my_account_emails_delete", "/my_account/emails/delete",
351 m.connect("my_account_emails_delete", "/my_account/emails/delete",
345 action="my_account_emails_delete", conditions=dict(method=["POST"]))
352 action="my_account_emails_delete", conditions=dict(method=["POST"]))
346
353
347 m.connect("my_account_api_keys", "/my_account/api_keys",
354 m.connect("my_account_api_keys", "/my_account/api_keys",
348 action="my_account_api_keys", conditions=dict(method=["GET"]))
355 action="my_account_api_keys", conditions=dict(method=["GET"]))
349 m.connect("my_account_api_keys", "/my_account/api_keys",
356 m.connect("my_account_api_keys", "/my_account/api_keys",
350 action="my_account_api_keys_add", conditions=dict(method=["POST"]))
357 action="my_account_api_keys_add", conditions=dict(method=["POST"]))
351 m.connect("my_account_api_keys_delete", "/my_account/api_keys/delete",
358 m.connect("my_account_api_keys_delete", "/my_account/api_keys/delete",
352 action="my_account_api_keys_delete", conditions=dict(method=["POST"]))
359 action="my_account_api_keys_delete", conditions=dict(method=["POST"]))
353
360
354 # ADMIN GIST
361 # ADMIN GIST
355 with rmap.submapper(path_prefix=ADMIN_PREFIX,
362 with rmap.submapper(path_prefix=ADMIN_PREFIX,
356 controller='admin/gists') as m:
363 controller='admin/gists') as m:
357 m.connect("gists", "/gists",
364 m.connect("gists", "/gists",
358 action="create", conditions=dict(method=["POST"]))
365 action="create", conditions=dict(method=["POST"]))
359 m.connect("gists", "/gists",
366 m.connect("gists", "/gists",
360 action="index", conditions=dict(method=["GET"]))
367 action="index", conditions=dict(method=["GET"]))
361 m.connect("new_gist", "/gists/new",
368 m.connect("new_gist", "/gists/new",
362 action="new", conditions=dict(method=["GET"]))
369 action="new", conditions=dict(method=["GET"]))
363
370
364 m.connect("gist_delete", "/gists/{gist_id}/delete",
371 m.connect("gist_delete", "/gists/{gist_id}/delete",
365 action="delete", conditions=dict(method=["POST"]))
372 action="delete", conditions=dict(method=["POST"]))
366 m.connect("edit_gist", "/gists/{gist_id}/edit",
373 m.connect("edit_gist", "/gists/{gist_id}/edit",
367 action="edit", conditions=dict(method=["GET", "POST"]))
374 action="edit", conditions=dict(method=["GET", "POST"]))
368 m.connect("edit_gist_check_revision", "/gists/{gist_id}/edit/check_revision",
375 m.connect("edit_gist_check_revision", "/gists/{gist_id}/edit/check_revision",
369 action="check_revision", conditions=dict(method=["POST"]))
376 action="check_revision", conditions=dict(method=["POST"]))
370
377
371 m.connect("gist", "/gists/{gist_id}",
378 m.connect("gist", "/gists/{gist_id}",
372 action="show", conditions=dict(method=["GET"]))
379 action="show", conditions=dict(method=["GET"]))
373 m.connect("gist_rev", "/gists/{gist_id}/{revision}",
380 m.connect("gist_rev", "/gists/{gist_id}/{revision}",
374 revision="tip",
381 revision="tip",
375 action="show", conditions=dict(method=["GET"]))
382 action="show", conditions=dict(method=["GET"]))
376 m.connect("formatted_gist", "/gists/{gist_id}/{revision}/{format}",
383 m.connect("formatted_gist", "/gists/{gist_id}/{revision}/{format}",
377 revision="tip",
384 revision="tip",
378 action="show", conditions=dict(method=["GET"]))
385 action="show", conditions=dict(method=["GET"]))
379 m.connect("formatted_gist_file", "/gists/{gist_id}/{revision}/{format}/{f_path:.*}",
386 m.connect("formatted_gist_file", "/gists/{gist_id}/{revision}/{format}/{f_path:.*}",
380 revision='tip',
387 revision='tip',
381 action="show", conditions=dict(method=["GET"]))
388 action="show", conditions=dict(method=["GET"]))
382
389
383 # ADMIN MAIN PAGES
390 # ADMIN MAIN PAGES
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
391 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 controller='admin/admin') as m:
392 controller='admin/admin') as m:
386 m.connect('admin_home', '', action='index')
393 m.connect('admin_home', '', action='index')
387 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
394 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
388 action='add_repo')
395 action='add_repo')
389 #==========================================================================
396 #==========================================================================
390 # API V2
397 # API V2
391 #==========================================================================
398 #==========================================================================
392 with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='api/api',
399 with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='api/api',
393 action='_dispatch') as m:
400 action='_dispatch') as m:
394 m.connect('api', '/api')
401 m.connect('api', '/api')
395
402
396 # USER JOURNAL
403 # USER JOURNAL
397 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
404 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
398 controller='journal', action='index')
405 controller='journal', action='index')
399 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
406 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
400 controller='journal', action='journal_rss')
407 controller='journal', action='journal_rss')
401 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
408 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
402 controller='journal', action='journal_atom')
409 controller='journal', action='journal_atom')
403
410
404 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
411 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
405 controller='journal', action="public_journal")
412 controller='journal', action="public_journal")
406
413
407 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
414 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
408 controller='journal', action="public_journal_rss")
415 controller='journal', action="public_journal_rss")
409
416
410 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
417 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
411 controller='journal', action="public_journal_rss")
418 controller='journal', action="public_journal_rss")
412
419
413 rmap.connect('public_journal_atom',
420 rmap.connect('public_journal_atom',
414 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
421 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
415 action="public_journal_atom")
422 action="public_journal_atom")
416
423
417 rmap.connect('public_journal_atom_old',
424 rmap.connect('public_journal_atom_old',
418 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
425 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
419 action="public_journal_atom")
426 action="public_journal_atom")
420
427
421 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
428 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
422 controller='journal', action='toggle_following',
429 controller='journal', action='toggle_following',
423 conditions=dict(method=["POST"]))
430 conditions=dict(method=["POST"]))
424
431
425 # SEARCH
432 # SEARCH
426 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
433 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
427 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
434 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
428 controller='search',
435 controller='search',
429 conditions=dict(function=check_repo))
436 conditions=dict(function=check_repo))
430 rmap.connect('search_repo', '/{repo_name:.*?}/search',
437 rmap.connect('search_repo', '/{repo_name:.*?}/search',
431 controller='search',
438 controller='search',
432 conditions=dict(function=check_repo),
439 conditions=dict(function=check_repo),
433 )
440 )
434
441
435 # LOGIN/LOGOUT/REGISTER/SIGN IN
442 # LOGIN/LOGOUT/REGISTER/SIGN IN
436 rmap.connect('authentication_token', '%s/authentication_token' % ADMIN_PREFIX, controller='login', action='authentication_token')
443 rmap.connect('authentication_token', '%s/authentication_token' % ADMIN_PREFIX, controller='login', action='authentication_token')
437 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
444 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
438 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
445 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
439 action='logout')
446 action='logout')
440
447
441 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
448 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
442 action='register')
449 action='register')
443
450
444 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
451 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
445 controller='login', action='password_reset')
452 controller='login', action='password_reset')
446
453
447 rmap.connect('reset_password_confirmation',
454 rmap.connect('reset_password_confirmation',
448 '%s/password_reset_confirmation' % ADMIN_PREFIX,
455 '%s/password_reset_confirmation' % ADMIN_PREFIX,
449 controller='login', action='password_reset_confirmation')
456 controller='login', action='password_reset_confirmation')
450
457
451 # FEEDS
458 # FEEDS
452 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
459 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
453 controller='feed', action='rss',
460 controller='feed', action='rss',
454 conditions=dict(function=check_repo))
461 conditions=dict(function=check_repo))
455
462
456 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
463 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
457 controller='feed', action='atom',
464 controller='feed', action='atom',
458 conditions=dict(function=check_repo))
465 conditions=dict(function=check_repo))
459
466
460 #==========================================================================
467 #==========================================================================
461 # REPOSITORY ROUTES
468 # REPOSITORY ROUTES
462 #==========================================================================
469 #==========================================================================
463 rmap.connect('repo_creating_home', '/{repo_name:.*?}/repo_creating',
470 rmap.connect('repo_creating_home', '/{repo_name:.*?}/repo_creating',
464 controller='admin/repos', action='repo_creating')
471 controller='admin/repos', action='repo_creating')
465 rmap.connect('repo_check_home', '/{repo_name:.*?}/crepo_check',
472 rmap.connect('repo_check_home', '/{repo_name:.*?}/crepo_check',
466 controller='admin/repos', action='repo_check')
473 controller='admin/repos', action='repo_check')
467
474
468 rmap.connect('summary_home', '/{repo_name:.*?}',
475 rmap.connect('summary_home', '/{repo_name:.*?}',
469 controller='summary',
476 controller='summary',
470 conditions=dict(function=check_repo))
477 conditions=dict(function=check_repo))
471
478
472 # must be here for proper group/repo catching
479 # must be here for proper group/repo catching
473 rmap.connect('repos_group_home', '/{group_name:.*}',
480 rmap.connect('repos_group_home', '/{group_name:.*}',
474 controller='admin/repo_groups', action="show_by_name",
481 controller='admin/repo_groups', action="show_by_name",
475 conditions=dict(function=check_group))
482 conditions=dict(function=check_group))
476 rmap.connect('repo_stats_home', '/{repo_name:.*?}/statistics',
483 rmap.connect('repo_stats_home', '/{repo_name:.*?}/statistics',
477 controller='summary', action='statistics',
484 controller='summary', action='statistics',
478 conditions=dict(function=check_repo))
485 conditions=dict(function=check_repo))
479
486
480 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
487 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
481 controller='summary', action='repo_size',
488 controller='summary', action='repo_size',
482 conditions=dict(function=check_repo))
489 conditions=dict(function=check_repo))
483
490
484 rmap.connect('repo_refs_data', '/{repo_name:.*?}/refs-data',
491 rmap.connect('repo_refs_data', '/{repo_name:.*?}/refs-data',
485 controller='home', action='repo_refs_data')
492 controller='home', action='repo_refs_data')
486
493
487 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision:.*}',
494 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision:.*}',
488 controller='changeset', revision='tip',
495 controller='changeset', revision='tip',
489 conditions=dict(function=check_repo))
496 conditions=dict(function=check_repo))
490 rmap.connect('changeset_children', '/{repo_name:.*?}/changeset_children/{revision}',
497 rmap.connect('changeset_children', '/{repo_name:.*?}/changeset_children/{revision}',
491 controller='changeset', revision='tip', action="changeset_children",
498 controller='changeset', revision='tip', action="changeset_children",
492 conditions=dict(function=check_repo))
499 conditions=dict(function=check_repo))
493 rmap.connect('changeset_parents', '/{repo_name:.*?}/changeset_parents/{revision}',
500 rmap.connect('changeset_parents', '/{repo_name:.*?}/changeset_parents/{revision}',
494 controller='changeset', revision='tip', action="changeset_parents",
501 controller='changeset', revision='tip', action="changeset_parents",
495 conditions=dict(function=check_repo))
502 conditions=dict(function=check_repo))
496
503
497 # repo edit options
504 # repo edit options
498 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
505 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
499 controller='admin/repos', action="edit",
506 controller='admin/repos', action="edit",
500 conditions=dict(method=["GET"], function=check_repo))
507 conditions=dict(method=["GET"], function=check_repo))
501
508
502 rmap.connect("edit_repo_perms", "/{repo_name:.*?}/settings/permissions",
509 rmap.connect("edit_repo_perms", "/{repo_name:.*?}/settings/permissions",
503 controller='admin/repos', action="edit_permissions",
510 controller='admin/repos', action="edit_permissions",
504 conditions=dict(method=["GET"], function=check_repo))
511 conditions=dict(method=["GET"], function=check_repo))
505 rmap.connect("edit_repo_perms_update", "/{repo_name:.*?}/settings/permissions",
512 rmap.connect("edit_repo_perms_update", "/{repo_name:.*?}/settings/permissions",
506 controller='admin/repos', action="edit_permissions_update",
513 controller='admin/repos', action="edit_permissions_update",
507 conditions=dict(method=["POST"], function=check_repo))
514 conditions=dict(method=["POST"], function=check_repo))
508 rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions/delete",
515 rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions/delete",
509 controller='admin/repos', action="edit_permissions_revoke",
516 controller='admin/repos', action="edit_permissions_revoke",
510 conditions=dict(method=["POST"], function=check_repo))
517 conditions=dict(method=["POST"], function=check_repo))
511
518
512 rmap.connect("edit_repo_fields", "/{repo_name:.*?}/settings/fields",
519 rmap.connect("edit_repo_fields", "/{repo_name:.*?}/settings/fields",
513 controller='admin/repos', action="edit_fields",
520 controller='admin/repos', action="edit_fields",
514 conditions=dict(method=["GET"], function=check_repo))
521 conditions=dict(method=["GET"], function=check_repo))
515 rmap.connect('create_repo_fields', "/{repo_name:.*?}/settings/fields/new",
522 rmap.connect('create_repo_fields', "/{repo_name:.*?}/settings/fields/new",
516 controller='admin/repos', action="create_repo_field",
523 controller='admin/repos', action="create_repo_field",
517 conditions=dict(method=["POST"], function=check_repo))
524 conditions=dict(method=["POST"], function=check_repo))
518 rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}/delete",
525 rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}/delete",
519 controller='admin/repos', action="delete_repo_field",
526 controller='admin/repos', action="delete_repo_field",
520 conditions=dict(method=["POST"], function=check_repo))
527 conditions=dict(method=["POST"], function=check_repo))
521
528
522 rmap.connect("edit_repo_advanced", "/{repo_name:.*?}/settings/advanced",
529 rmap.connect("edit_repo_advanced", "/{repo_name:.*?}/settings/advanced",
523 controller='admin/repos', action="edit_advanced",
530 controller='admin/repos', action="edit_advanced",
524 conditions=dict(method=["GET"], function=check_repo))
531 conditions=dict(method=["GET"], function=check_repo))
525
532
526 rmap.connect("edit_repo_advanced_journal", "/{repo_name:.*?}/settings/advanced/journal",
533 rmap.connect("edit_repo_advanced_journal", "/{repo_name:.*?}/settings/advanced/journal",
527 controller='admin/repos', action="edit_advanced_journal",
534 controller='admin/repos', action="edit_advanced_journal",
528 conditions=dict(method=["POST"], function=check_repo))
535 conditions=dict(method=["POST"], function=check_repo))
529
536
530 rmap.connect("edit_repo_advanced_fork", "/{repo_name:.*?}/settings/advanced/fork",
537 rmap.connect("edit_repo_advanced_fork", "/{repo_name:.*?}/settings/advanced/fork",
531 controller='admin/repos', action="edit_advanced_fork",
538 controller='admin/repos', action="edit_advanced_fork",
532 conditions=dict(method=["POST"], function=check_repo))
539 conditions=dict(method=["POST"], function=check_repo))
533
540
534 rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches",
541 rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches",
535 controller='admin/repos', action="edit_caches",
542 controller='admin/repos', action="edit_caches",
536 conditions=dict(method=["GET"], function=check_repo))
543 conditions=dict(method=["GET"], function=check_repo))
537 rmap.connect("update_repo_caches", "/{repo_name:.*?}/settings/caches",
544 rmap.connect("update_repo_caches", "/{repo_name:.*?}/settings/caches",
538 controller='admin/repos', action="edit_caches",
545 controller='admin/repos', action="edit_caches",
539 conditions=dict(method=["POST"], function=check_repo))
546 conditions=dict(method=["POST"], function=check_repo))
540
547
541 rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote",
548 rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote",
542 controller='admin/repos', action="edit_remote",
549 controller='admin/repos', action="edit_remote",
543 conditions=dict(method=["GET"], function=check_repo))
550 conditions=dict(method=["GET"], function=check_repo))
544 rmap.connect("edit_repo_remote_update", "/{repo_name:.*?}/settings/remote",
551 rmap.connect("edit_repo_remote_update", "/{repo_name:.*?}/settings/remote",
545 controller='admin/repos', action="edit_remote",
552 controller='admin/repos', action="edit_remote",
546 conditions=dict(method=["POST"], function=check_repo))
553 conditions=dict(method=["POST"], function=check_repo))
547
554
548 rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics",
555 rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics",
549 controller='admin/repos', action="edit_statistics",
556 controller='admin/repos', action="edit_statistics",
550 conditions=dict(method=["GET"], function=check_repo))
557 conditions=dict(method=["GET"], function=check_repo))
551 rmap.connect("edit_repo_statistics_update", "/{repo_name:.*?}/settings/statistics",
558 rmap.connect("edit_repo_statistics_update", "/{repo_name:.*?}/settings/statistics",
552 controller='admin/repos', action="edit_statistics",
559 controller='admin/repos', action="edit_statistics",
553 conditions=dict(method=["POST"], function=check_repo))
560 conditions=dict(method=["POST"], function=check_repo))
554
561
555 # still working url for backward compat.
562 # still working url for backward compat.
556 rmap.connect('raw_changeset_home_depraced',
563 rmap.connect('raw_changeset_home_depraced',
557 '/{repo_name:.*?}/raw-changeset/{revision}',
564 '/{repo_name:.*?}/raw-changeset/{revision}',
558 controller='changeset', action='changeset_raw',
565 controller='changeset', action='changeset_raw',
559 revision='tip', conditions=dict(function=check_repo))
566 revision='tip', conditions=dict(function=check_repo))
560
567
561 ## new URLs
568 ## new URLs
562 rmap.connect('changeset_raw_home',
569 rmap.connect('changeset_raw_home',
563 '/{repo_name:.*?}/changeset-diff/{revision}',
570 '/{repo_name:.*?}/changeset-diff/{revision}',
564 controller='changeset', action='changeset_raw',
571 controller='changeset', action='changeset_raw',
565 revision='tip', conditions=dict(function=check_repo))
572 revision='tip', conditions=dict(function=check_repo))
566
573
567 rmap.connect('changeset_patch_home',
574 rmap.connect('changeset_patch_home',
568 '/{repo_name:.*?}/changeset-patch/{revision}',
575 '/{repo_name:.*?}/changeset-patch/{revision}',
569 controller='changeset', action='changeset_patch',
576 controller='changeset', action='changeset_patch',
570 revision='tip', conditions=dict(function=check_repo))
577 revision='tip', conditions=dict(function=check_repo))
571
578
572 rmap.connect('changeset_download_home',
579 rmap.connect('changeset_download_home',
573 '/{repo_name:.*?}/changeset-download/{revision}',
580 '/{repo_name:.*?}/changeset-download/{revision}',
574 controller='changeset', action='changeset_download',
581 controller='changeset', action='changeset_download',
575 revision='tip', conditions=dict(function=check_repo))
582 revision='tip', conditions=dict(function=check_repo))
576
583
577 rmap.connect('changeset_comment',
584 rmap.connect('changeset_comment',
578 '/{repo_name:.*?}/changeset-comment/{revision}',
585 '/{repo_name:.*?}/changeset-comment/{revision}',
579 controller='changeset', revision='tip', action='comment',
586 controller='changeset', revision='tip', action='comment',
580 conditions=dict(function=check_repo))
587 conditions=dict(function=check_repo))
581
588
582 rmap.connect('changeset_comment_delete',
589 rmap.connect('changeset_comment_delete',
583 '/{repo_name:.*?}/changeset-comment/{comment_id}/delete',
590 '/{repo_name:.*?}/changeset-comment/{comment_id}/delete',
584 controller='changeset', action='delete_comment',
591 controller='changeset', action='delete_comment',
585 conditions=dict(function=check_repo, method=["POST"]))
592 conditions=dict(function=check_repo, method=["POST"]))
586
593
587 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
594 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
588 controller='changeset', action='changeset_info')
595 controller='changeset', action='changeset_info')
589
596
590 rmap.connect('compare_home',
597 rmap.connect('compare_home',
591 '/{repo_name:.*?}/compare',
598 '/{repo_name:.*?}/compare',
592 controller='compare', action='index',
599 controller='compare', action='index',
593 conditions=dict(function=check_repo))
600 conditions=dict(function=check_repo))
594
601
595 rmap.connect('compare_url',
602 rmap.connect('compare_url',
596 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref_name:.*?}...{other_ref_type}@{other_ref_name:.*?}',
603 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref_name:.*?}...{other_ref_type}@{other_ref_name:.*?}',
597 controller='compare', action='compare',
604 controller='compare', action='compare',
598 conditions=dict(function=check_repo),
605 conditions=dict(function=check_repo),
599 requirements=dict(
606 requirements=dict(
600 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
607 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
601 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
608 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
602 )
609 )
603
610
604 rmap.connect('pullrequest_home',
611 rmap.connect('pullrequest_home',
605 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
612 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
606 action='index', conditions=dict(function=check_repo,
613 action='index', conditions=dict(function=check_repo,
607 method=["GET"]))
614 method=["GET"]))
608
615
609 rmap.connect('pullrequest_repo_info',
616 rmap.connect('pullrequest_repo_info',
610 '/{repo_name:.*?}/pull-request-repo-info',
617 '/{repo_name:.*?}/pull-request-repo-info',
611 controller='pullrequests', action='repo_info',
618 controller='pullrequests', action='repo_info',
612 conditions=dict(function=check_repo, method=["GET"]))
619 conditions=dict(function=check_repo, method=["GET"]))
613
620
614 rmap.connect('pullrequest',
621 rmap.connect('pullrequest',
615 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
622 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
616 action='create', conditions=dict(function=check_repo,
623 action='create', conditions=dict(function=check_repo,
617 method=["POST"]))
624 method=["POST"]))
618
625
619 rmap.connect('pullrequest_show',
626 rmap.connect('pullrequest_show',
620 '/{repo_name:.*?}/pull-request/{pull_request_id:\\d+}{extra:(/.*)?}', extra='',
627 '/{repo_name:.*?}/pull-request/{pull_request_id:\\d+}{extra:(/.*)?}', extra='',
621 controller='pullrequests',
628 controller='pullrequests',
622 action='show', conditions=dict(function=check_repo,
629 action='show', conditions=dict(function=check_repo,
623 method=["GET"]))
630 method=["GET"]))
624 rmap.connect('pullrequest_post',
631 rmap.connect('pullrequest_post',
625 '/{repo_name:.*?}/pull-request/{pull_request_id}',
632 '/{repo_name:.*?}/pull-request/{pull_request_id}',
626 controller='pullrequests',
633 controller='pullrequests',
627 action='post', conditions=dict(function=check_repo,
634 action='post', conditions=dict(function=check_repo,
628 method=["POST"]))
635 method=["POST"]))
629 rmap.connect('pullrequest_delete',
636 rmap.connect('pullrequest_delete',
630 '/{repo_name:.*?}/pull-request/{pull_request_id}/delete',
637 '/{repo_name:.*?}/pull-request/{pull_request_id}/delete',
631 controller='pullrequests',
638 controller='pullrequests',
632 action='delete', conditions=dict(function=check_repo,
639 action='delete', conditions=dict(function=check_repo,
633 method=["POST"]))
640 method=["POST"]))
634
641
635 rmap.connect('pullrequest_show_all',
642 rmap.connect('pullrequest_show_all',
636 '/{repo_name:.*?}/pull-request',
643 '/{repo_name:.*?}/pull-request',
637 controller='pullrequests',
644 controller='pullrequests',
638 action='show_all', conditions=dict(function=check_repo,
645 action='show_all', conditions=dict(function=check_repo,
639 method=["GET"]))
646 method=["GET"]))
640
647
641 rmap.connect('my_pullrequests',
648 rmap.connect('my_pullrequests',
642 '/my_pullrequests',
649 '/my_pullrequests',
643 controller='pullrequests',
650 controller='pullrequests',
644 action='show_my', conditions=dict(method=["GET"]))
651 action='show_my', conditions=dict(method=["GET"]))
645
652
646 rmap.connect('pullrequest_comment',
653 rmap.connect('pullrequest_comment',
647 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
654 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
648 controller='pullrequests',
655 controller='pullrequests',
649 action='comment', conditions=dict(function=check_repo,
656 action='comment', conditions=dict(function=check_repo,
650 method=["POST"]))
657 method=["POST"]))
651
658
652 rmap.connect('pullrequest_comment_delete',
659 rmap.connect('pullrequest_comment_delete',
653 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
660 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
654 controller='pullrequests', action='delete_comment',
661 controller='pullrequests', action='delete_comment',
655 conditions=dict(function=check_repo, method=["POST"]))
662 conditions=dict(function=check_repo, method=["POST"]))
656
663
657 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
664 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
658 controller='summary', conditions=dict(function=check_repo))
665 controller='summary', conditions=dict(function=check_repo))
659
666
660 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
667 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
661 controller='changelog', conditions=dict(function=check_repo))
668 controller='changelog', conditions=dict(function=check_repo))
662
669
663 rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
670 rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
664 controller='changelog', f_path=None,
671 controller='changelog', f_path=None,
665 conditions=dict(function=check_repo))
672 conditions=dict(function=check_repo))
666
673
667 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
674 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
668 controller='changelog', action='changelog_details',
675 controller='changelog', action='changelog_details',
669 conditions=dict(function=check_repo))
676 conditions=dict(function=check_repo))
670
677
671 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
678 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
672 controller='files', revision='tip', f_path='',
679 controller='files', revision='tip', f_path='',
673 conditions=dict(function=check_repo))
680 conditions=dict(function=check_repo))
674
681
675 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
682 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
676 controller='files', revision='tip', f_path='',
683 controller='files', revision='tip', f_path='',
677 conditions=dict(function=check_repo))
684 conditions=dict(function=check_repo))
678
685
679 rmap.connect('files_history_home',
686 rmap.connect('files_history_home',
680 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
687 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
681 controller='files', action='history', revision='tip', f_path='',
688 controller='files', action='history', revision='tip', f_path='',
682 conditions=dict(function=check_repo))
689 conditions=dict(function=check_repo))
683
690
684 rmap.connect('files_authors_home',
691 rmap.connect('files_authors_home',
685 '/{repo_name:.*?}/authors/{revision}/{f_path:.*}',
692 '/{repo_name:.*?}/authors/{revision}/{f_path:.*}',
686 controller='files', action='authors', revision='tip', f_path='',
693 controller='files', action='authors', revision='tip', f_path='',
687 conditions=dict(function=check_repo))
694 conditions=dict(function=check_repo))
688
695
689 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
696 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
690 controller='files', action='diff', revision='tip', f_path='',
697 controller='files', action='diff', revision='tip', f_path='',
691 conditions=dict(function=check_repo))
698 conditions=dict(function=check_repo))
692
699
693 rmap.connect('files_diff_2way_home', '/{repo_name:.*?}/diff-2way/{f_path:.+}',
700 rmap.connect('files_diff_2way_home', '/{repo_name:.*?}/diff-2way/{f_path:.+}',
694 controller='files', action='diff_2way', revision='tip', f_path='',
701 controller='files', action='diff_2way', revision='tip', f_path='',
695 conditions=dict(function=check_repo))
702 conditions=dict(function=check_repo))
696
703
697 rmap.connect('files_rawfile_home',
704 rmap.connect('files_rawfile_home',
698 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
705 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
699 controller='files', action='rawfile', revision='tip',
706 controller='files', action='rawfile', revision='tip',
700 f_path='', conditions=dict(function=check_repo))
707 f_path='', conditions=dict(function=check_repo))
701
708
702 rmap.connect('files_raw_home',
709 rmap.connect('files_raw_home',
703 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
710 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
704 controller='files', action='raw', revision='tip', f_path='',
711 controller='files', action='raw', revision='tip', f_path='',
705 conditions=dict(function=check_repo))
712 conditions=dict(function=check_repo))
706
713
707 rmap.connect('files_annotate_home',
714 rmap.connect('files_annotate_home',
708 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
715 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
709 controller='files', action='index', revision='tip',
716 controller='files', action='index', revision='tip',
710 f_path='', annotate=True, conditions=dict(function=check_repo))
717 f_path='', annotate=True, conditions=dict(function=check_repo))
711
718
712 rmap.connect('files_edit_home',
719 rmap.connect('files_edit_home',
713 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
720 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
714 controller='files', action='edit', revision='tip',
721 controller='files', action='edit', revision='tip',
715 f_path='', conditions=dict(function=check_repo))
722 f_path='', conditions=dict(function=check_repo))
716
723
717 rmap.connect('files_add_home',
724 rmap.connect('files_add_home',
718 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
725 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
719 controller='files', action='add', revision='tip',
726 controller='files', action='add', revision='tip',
720 f_path='', conditions=dict(function=check_repo))
727 f_path='', conditions=dict(function=check_repo))
721
728
722 rmap.connect('files_delete_home',
729 rmap.connect('files_delete_home',
723 '/{repo_name:.*?}/delete/{revision}/{f_path:.*}',
730 '/{repo_name:.*?}/delete/{revision}/{f_path:.*}',
724 controller='files', action='delete', revision='tip',
731 controller='files', action='delete', revision='tip',
725 f_path='', conditions=dict(function=check_repo))
732 f_path='', conditions=dict(function=check_repo))
726
733
727 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
734 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
728 controller='files', action='archivefile',
735 controller='files', action='archivefile',
729 conditions=dict(function=check_repo))
736 conditions=dict(function=check_repo))
730
737
731 rmap.connect('files_nodelist_home',
738 rmap.connect('files_nodelist_home',
732 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
739 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
733 controller='files', action='nodelist',
740 controller='files', action='nodelist',
734 conditions=dict(function=check_repo))
741 conditions=dict(function=check_repo))
735
742
736 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
743 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
737 controller='forks', action='fork_create',
744 controller='forks', action='fork_create',
738 conditions=dict(function=check_repo, method=["POST"]))
745 conditions=dict(function=check_repo, method=["POST"]))
739
746
740 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
747 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
741 controller='forks', action='fork',
748 controller='forks', action='fork',
742 conditions=dict(function=check_repo))
749 conditions=dict(function=check_repo))
743
750
744 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
751 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
745 controller='forks', action='forks',
752 controller='forks', action='forks',
746 conditions=dict(function=check_repo))
753 conditions=dict(function=check_repo))
747
754
748 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
755 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
749 controller='followers', action='followers',
756 controller='followers', action='followers',
750 conditions=dict(function=check_repo))
757 conditions=dict(function=check_repo))
751
758
752 return rmap
759 return rmap
753
760
754
761
755 class UrlGenerator(object):
762 class UrlGenerator(object):
756 """Emulate pylons.url in providing a wrapper around routes.url
763 """Emulate pylons.url in providing a wrapper around routes.url
757
764
758 This code was added during migration from Pylons to Turbogears2. Pylons
765 This code was added during migration from Pylons to Turbogears2. Pylons
759 already provided a wrapper like this, but Turbogears2 does not.
766 already provided a wrapper like this, but Turbogears2 does not.
760
767
761 When the routing of Kallithea is changed to use less Routes and more
768 When the routing of Kallithea is changed to use less Routes and more
762 Turbogears2-style routing, this class may disappear or change.
769 Turbogears2-style routing, this class may disappear or change.
763
770
764 url() (the __call__ method) returns the URL based on a route name and
771 url() (the __call__ method) returns the URL based on a route name and
765 arguments.
772 arguments.
766 url.current() returns the URL of the current page with arguments applied.
773 url.current() returns the URL of the current page with arguments applied.
767
774
768 Refer to documentation of Routes for details:
775 Refer to documentation of Routes for details:
769 https://routes.readthedocs.io/en/latest/generating.html#generation
776 https://routes.readthedocs.io/en/latest/generating.html#generation
770 """
777 """
771 def __call__(self, *args, **kwargs):
778 def __call__(self, *args, **kwargs):
772 return request.environ['routes.url'](*args, **kwargs)
779 return request.environ['routes.url'](*args, **kwargs)
773
780
774 def current(self, *args, **kwargs):
781 def current(self, *args, **kwargs):
775 return request.environ['routes.url'].current(*args, **kwargs)
782 return request.environ['routes.url'].current(*args, **kwargs)
776
783
777
784
778 url = UrlGenerator()
785 url = UrlGenerator()
@@ -1,431 +1,465 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.admin.users
15 kallithea.controllers.admin.users
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Users crud controller
18 Users crud controller
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 4, 2010
22 :created_on: Apr 4, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import formencode
30 import formencode
31
31
32 from formencode import htmlfill
32 from formencode import htmlfill
33 from tg import request, tmpl_context as c, config, app_globals
33 from tg import request, tmpl_context as c, config, app_globals
34 from tg.i18n import ugettext as _
34 from tg.i18n import ugettext as _
35 from sqlalchemy.sql.expression import func
35 from sqlalchemy.sql.expression import func
36 from webob.exc import HTTPFound, HTTPNotFound
36 from webob.exc import HTTPFound, HTTPNotFound
37
37
38 import kallithea
38 import kallithea
39 from kallithea.config.routing import url
39 from kallithea.config.routing import url
40 from kallithea.lib.exceptions import DefaultUserException, \
40 from kallithea.lib.exceptions import DefaultUserException, \
41 UserOwnsReposException, UserCreationError
41 UserOwnsReposException, UserCreationError
42 from kallithea.lib import helpers as h
42 from kallithea.lib import helpers as h
43 from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator, \
43 from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator, \
44 AuthUser
44 AuthUser
45 from kallithea.lib import auth_modules
45 from kallithea.lib import auth_modules
46 from kallithea.lib.base import BaseController, render
46 from kallithea.lib.base import BaseController, render, IfSshEnabled
47 from kallithea.model.api_key import ApiKeyModel
47 from kallithea.model.api_key import ApiKeyModel
48
48 from kallithea.model.ssh_key import SshKeyModel
49 from kallithea.model.db import User, UserEmailMap, UserIpMap, UserToPerm
49 from kallithea.model.db import User, UserEmailMap, UserIpMap, UserToPerm
50 from kallithea.model.forms import UserForm, CustomDefaultPermissionsForm
50 from kallithea.model.forms import UserForm, CustomDefaultPermissionsForm
51 from kallithea.model.user import UserModel
51 from kallithea.model.user import UserModel
52 from kallithea.model.meta import Session
52 from kallithea.model.meta import Session
53 from kallithea.lib.utils import action_logger
53 from kallithea.lib.utils import action_logger
54 from kallithea.lib.utils2 import datetime_to_time, safe_int, generate_api_key
54 from kallithea.lib.utils2 import datetime_to_time, safe_int, generate_api_key
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class UsersController(BaseController):
59 class UsersController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
60 """REST Controller styled on the Atom Publishing Protocol"""
61
61
62 @LoginRequired()
62 @LoginRequired()
63 @HasPermissionAnyDecorator('hg.admin')
63 @HasPermissionAnyDecorator('hg.admin')
64 def _before(self, *args, **kwargs):
64 def _before(self, *args, **kwargs):
65 super(UsersController, self)._before(*args, **kwargs)
65 super(UsersController, self)._before(*args, **kwargs)
66 c.available_permissions = config['available_permissions']
66 c.available_permissions = config['available_permissions']
67
67
68 def index(self, format='html'):
68 def index(self, format='html'):
69 c.users_list = User.query().order_by(User.username) \
69 c.users_list = User.query().order_by(User.username) \
70 .filter_by(is_default_user=False) \
70 .filter_by(is_default_user=False) \
71 .order_by(func.lower(User.username)) \
71 .order_by(func.lower(User.username)) \
72 .all()
72 .all()
73
73
74 users_data = []
74 users_data = []
75 total_records = len(c.users_list)
75 total_records = len(c.users_list)
76 _tmpl_lookup = app_globals.mako_lookup
76 _tmpl_lookup = app_globals.mako_lookup
77 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
77 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
78
78
79 grav_tmpl = '<div class="gravatar">%s</div>'
79 grav_tmpl = '<div class="gravatar">%s</div>'
80
80
81 username = lambda user_id, username: (
81 username = lambda user_id, username: (
82 template.get_def("user_name")
82 template.get_def("user_name")
83 .render(user_id, username, _=_, h=h, c=c))
83 .render(user_id, username, _=_, h=h, c=c))
84
84
85 user_actions = lambda user_id, username: (
85 user_actions = lambda user_id, username: (
86 template.get_def("user_actions")
86 template.get_def("user_actions")
87 .render(user_id, username, _=_, h=h, c=c))
87 .render(user_id, username, _=_, h=h, c=c))
88
88
89 for user in c.users_list:
89 for user in c.users_list:
90 users_data.append({
90 users_data.append({
91 "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
91 "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
92 "raw_name": user.username,
92 "raw_name": user.username,
93 "username": username(user.user_id, user.username),
93 "username": username(user.user_id, user.username),
94 "firstname": h.escape(user.name),
94 "firstname": h.escape(user.name),
95 "lastname": h.escape(user.lastname),
95 "lastname": h.escape(user.lastname),
96 "last_login": h.fmt_date(user.last_login),
96 "last_login": h.fmt_date(user.last_login),
97 "last_login_raw": datetime_to_time(user.last_login),
97 "last_login_raw": datetime_to_time(user.last_login),
98 "active": h.boolicon(user.active),
98 "active": h.boolicon(user.active),
99 "admin": h.boolicon(user.admin),
99 "admin": h.boolicon(user.admin),
100 "extern_type": user.extern_type,
100 "extern_type": user.extern_type,
101 "extern_name": user.extern_name,
101 "extern_name": user.extern_name,
102 "action": user_actions(user.user_id, user.username),
102 "action": user_actions(user.user_id, user.username),
103 })
103 })
104
104
105 c.data = {
105 c.data = {
106 "sort": None,
106 "sort": None,
107 "dir": "asc",
107 "dir": "asc",
108 "records": users_data
108 "records": users_data
109 }
109 }
110
110
111 return render('admin/users/users.html')
111 return render('admin/users/users.html')
112
112
113 def create(self):
113 def create(self):
114 c.default_extern_type = User.DEFAULT_AUTH_TYPE
114 c.default_extern_type = User.DEFAULT_AUTH_TYPE
115 c.default_extern_name = ''
115 c.default_extern_name = ''
116 user_model = UserModel()
116 user_model = UserModel()
117 user_form = UserForm()()
117 user_form = UserForm()()
118 try:
118 try:
119 form_result = user_form.to_python(dict(request.POST))
119 form_result = user_form.to_python(dict(request.POST))
120 user = user_model.create(form_result)
120 user = user_model.create(form_result)
121 action_logger(request.authuser, 'admin_created_user:%s' % user.username,
121 action_logger(request.authuser, 'admin_created_user:%s' % user.username,
122 None, request.ip_addr)
122 None, request.ip_addr)
123 h.flash(_('Created user %s') % user.username,
123 h.flash(_('Created user %s') % user.username,
124 category='success')
124 category='success')
125 Session().commit()
125 Session().commit()
126 except formencode.Invalid as errors:
126 except formencode.Invalid as errors:
127 return htmlfill.render(
127 return htmlfill.render(
128 render('admin/users/user_add.html'),
128 render('admin/users/user_add.html'),
129 defaults=errors.value,
129 defaults=errors.value,
130 errors=errors.error_dict or {},
130 errors=errors.error_dict or {},
131 prefix_error=False,
131 prefix_error=False,
132 encoding="UTF-8",
132 encoding="UTF-8",
133 force_defaults=False)
133 force_defaults=False)
134 except UserCreationError as e:
134 except UserCreationError as e:
135 h.flash(e, 'error')
135 h.flash(e, 'error')
136 except Exception:
136 except Exception:
137 log.error(traceback.format_exc())
137 log.error(traceback.format_exc())
138 h.flash(_('Error occurred during creation of user %s')
138 h.flash(_('Error occurred during creation of user %s')
139 % request.POST.get('username'), category='error')
139 % request.POST.get('username'), category='error')
140 raise HTTPFound(location=url('edit_user', id=user.user_id))
140 raise HTTPFound(location=url('edit_user', id=user.user_id))
141
141
142 def new(self, format='html'):
142 def new(self, format='html'):
143 c.default_extern_type = User.DEFAULT_AUTH_TYPE
143 c.default_extern_type = User.DEFAULT_AUTH_TYPE
144 c.default_extern_name = ''
144 c.default_extern_name = ''
145 return render('admin/users/user_add.html')
145 return render('admin/users/user_add.html')
146
146
147 def update(self, id):
147 def update(self, id):
148 user_model = UserModel()
148 user_model = UserModel()
149 user = user_model.get(id)
149 user = user_model.get(id)
150 _form = UserForm(edit=True, old_data={'user_id': id,
150 _form = UserForm(edit=True, old_data={'user_id': id,
151 'email': user.email})()
151 'email': user.email})()
152 form_result = {}
152 form_result = {}
153 try:
153 try:
154 form_result = _form.to_python(dict(request.POST))
154 form_result = _form.to_python(dict(request.POST))
155 skip_attrs = ['extern_type', 'extern_name',
155 skip_attrs = ['extern_type', 'extern_name',
156 ] + auth_modules.get_managed_fields(user)
156 ] + auth_modules.get_managed_fields(user)
157
157
158 user_model.update(id, form_result, skip_attrs=skip_attrs)
158 user_model.update(id, form_result, skip_attrs=skip_attrs)
159 usr = form_result['username']
159 usr = form_result['username']
160 action_logger(request.authuser, 'admin_updated_user:%s' % usr,
160 action_logger(request.authuser, 'admin_updated_user:%s' % usr,
161 None, request.ip_addr)
161 None, request.ip_addr)
162 h.flash(_('User updated successfully'), category='success')
162 h.flash(_('User updated successfully'), category='success')
163 Session().commit()
163 Session().commit()
164 except formencode.Invalid as errors:
164 except formencode.Invalid as errors:
165 defaults = errors.value
165 defaults = errors.value
166 e = errors.error_dict or {}
166 e = errors.error_dict or {}
167 defaults.update({
167 defaults.update({
168 'create_repo_perm': user_model.has_perm(id,
168 'create_repo_perm': user_model.has_perm(id,
169 'hg.create.repository'),
169 'hg.create.repository'),
170 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
170 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
171 })
171 })
172 return htmlfill.render(
172 return htmlfill.render(
173 self._render_edit_profile(user),
173 self._render_edit_profile(user),
174 defaults=defaults,
174 defaults=defaults,
175 errors=e,
175 errors=e,
176 prefix_error=False,
176 prefix_error=False,
177 encoding="UTF-8",
177 encoding="UTF-8",
178 force_defaults=False)
178 force_defaults=False)
179 except Exception:
179 except Exception:
180 log.error(traceback.format_exc())
180 log.error(traceback.format_exc())
181 h.flash(_('Error occurred during update of user %s')
181 h.flash(_('Error occurred during update of user %s')
182 % form_result.get('username'), category='error')
182 % form_result.get('username'), category='error')
183 raise HTTPFound(location=url('edit_user', id=id))
183 raise HTTPFound(location=url('edit_user', id=id))
184
184
185 def delete(self, id):
185 def delete(self, id):
186 usr = User.get_or_404(id)
186 usr = User.get_or_404(id)
187 try:
187 try:
188 UserModel().delete(usr)
188 UserModel().delete(usr)
189 Session().commit()
189 Session().commit()
190 h.flash(_('Successfully deleted user'), category='success')
190 h.flash(_('Successfully deleted user'), category='success')
191 except (UserOwnsReposException, DefaultUserException) as e:
191 except (UserOwnsReposException, DefaultUserException) as e:
192 h.flash(e, category='warning')
192 h.flash(e, category='warning')
193 except Exception:
193 except Exception:
194 log.error(traceback.format_exc())
194 log.error(traceback.format_exc())
195 h.flash(_('An error occurred during deletion of user'),
195 h.flash(_('An error occurred during deletion of user'),
196 category='error')
196 category='error')
197 raise HTTPFound(location=url('users'))
197 raise HTTPFound(location=url('users'))
198
198
199 def _get_user_or_raise_if_default(self, id):
199 def _get_user_or_raise_if_default(self, id):
200 try:
200 try:
201 return User.get_or_404(id, allow_default=False)
201 return User.get_or_404(id, allow_default=False)
202 except DefaultUserException:
202 except DefaultUserException:
203 h.flash(_("The default user cannot be edited"), category='warning')
203 h.flash(_("The default user cannot be edited"), category='warning')
204 raise HTTPNotFound
204 raise HTTPNotFound
205
205
206 def _render_edit_profile(self, user):
206 def _render_edit_profile(self, user):
207 c.user = user
207 c.user = user
208 c.active = 'profile'
208 c.active = 'profile'
209 c.perm_user = AuthUser(dbuser=user)
209 c.perm_user = AuthUser(dbuser=user)
210 managed_fields = auth_modules.get_managed_fields(user)
210 managed_fields = auth_modules.get_managed_fields(user)
211 c.readonly = lambda n: 'readonly' if n in managed_fields else None
211 c.readonly = lambda n: 'readonly' if n in managed_fields else None
212 return render('admin/users/user_edit.html')
212 return render('admin/users/user_edit.html')
213
213
214 def edit(self, id, format='html'):
214 def edit(self, id, format='html'):
215 user = self._get_user_or_raise_if_default(id)
215 user = self._get_user_or_raise_if_default(id)
216 defaults = user.get_dict()
216 defaults = user.get_dict()
217
217
218 return htmlfill.render(
218 return htmlfill.render(
219 self._render_edit_profile(user),
219 self._render_edit_profile(user),
220 defaults=defaults,
220 defaults=defaults,
221 encoding="UTF-8",
221 encoding="UTF-8",
222 force_defaults=False)
222 force_defaults=False)
223
223
224 def edit_advanced(self, id):
224 def edit_advanced(self, id):
225 c.user = self._get_user_or_raise_if_default(id)
225 c.user = self._get_user_or_raise_if_default(id)
226 c.active = 'advanced'
226 c.active = 'advanced'
227 c.perm_user = AuthUser(dbuser=c.user)
227 c.perm_user = AuthUser(dbuser=c.user)
228
228
229 umodel = UserModel()
229 umodel = UserModel()
230 defaults = c.user.get_dict()
230 defaults = c.user.get_dict()
231 defaults.update({
231 defaults.update({
232 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
232 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
233 'create_user_group_perm': umodel.has_perm(c.user,
233 'create_user_group_perm': umodel.has_perm(c.user,
234 'hg.usergroup.create.true'),
234 'hg.usergroup.create.true'),
235 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
235 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
236 })
236 })
237 return htmlfill.render(
237 return htmlfill.render(
238 render('admin/users/user_edit.html'),
238 render('admin/users/user_edit.html'),
239 defaults=defaults,
239 defaults=defaults,
240 encoding="UTF-8",
240 encoding="UTF-8",
241 force_defaults=False)
241 force_defaults=False)
242
242
243 def edit_api_keys(self, id):
243 def edit_api_keys(self, id):
244 c.user = self._get_user_or_raise_if_default(id)
244 c.user = self._get_user_or_raise_if_default(id)
245 c.active = 'api_keys'
245 c.active = 'api_keys'
246 show_expired = True
246 show_expired = True
247 c.lifetime_values = [
247 c.lifetime_values = [
248 (str(-1), _('Forever')),
248 (str(-1), _('Forever')),
249 (str(5), _('5 minutes')),
249 (str(5), _('5 minutes')),
250 (str(60), _('1 hour')),
250 (str(60), _('1 hour')),
251 (str(60 * 24), _('1 day')),
251 (str(60 * 24), _('1 day')),
252 (str(60 * 24 * 30), _('1 month')),
252 (str(60 * 24 * 30), _('1 month')),
253 ]
253 ]
254 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
254 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
255 c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
255 c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
256 show_expired=show_expired)
256 show_expired=show_expired)
257 defaults = c.user.get_dict()
257 defaults = c.user.get_dict()
258 return htmlfill.render(
258 return htmlfill.render(
259 render('admin/users/user_edit.html'),
259 render('admin/users/user_edit.html'),
260 defaults=defaults,
260 defaults=defaults,
261 encoding="UTF-8",
261 encoding="UTF-8",
262 force_defaults=False)
262 force_defaults=False)
263
263
264 def add_api_key(self, id):
264 def add_api_key(self, id):
265 c.user = self._get_user_or_raise_if_default(id)
265 c.user = self._get_user_or_raise_if_default(id)
266
266
267 lifetime = safe_int(request.POST.get('lifetime'), -1)
267 lifetime = safe_int(request.POST.get('lifetime'), -1)
268 description = request.POST.get('description')
268 description = request.POST.get('description')
269 ApiKeyModel().create(c.user.user_id, description, lifetime)
269 ApiKeyModel().create(c.user.user_id, description, lifetime)
270 Session().commit()
270 Session().commit()
271 h.flash(_("API key successfully created"), category='success')
271 h.flash(_("API key successfully created"), category='success')
272 raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
272 raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
273
273
274 def delete_api_key(self, id):
274 def delete_api_key(self, id):
275 c.user = self._get_user_or_raise_if_default(id)
275 c.user = self._get_user_or_raise_if_default(id)
276
276
277 api_key = request.POST.get('del_api_key')
277 api_key = request.POST.get('del_api_key')
278 if request.POST.get('del_api_key_builtin'):
278 if request.POST.get('del_api_key_builtin'):
279 c.user.api_key = generate_api_key()
279 c.user.api_key = generate_api_key()
280 Session().commit()
280 Session().commit()
281 h.flash(_("API key successfully reset"), category='success')
281 h.flash(_("API key successfully reset"), category='success')
282 elif api_key:
282 elif api_key:
283 ApiKeyModel().delete(api_key, c.user.user_id)
283 ApiKeyModel().delete(api_key, c.user.user_id)
284 Session().commit()
284 Session().commit()
285 h.flash(_("API key successfully deleted"), category='success')
285 h.flash(_("API key successfully deleted"), category='success')
286
286
287 raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
287 raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
288
288
289 def update_account(self, id):
289 def update_account(self, id):
290 pass
290 pass
291
291
292 def edit_perms(self, id):
292 def edit_perms(self, id):
293 c.user = self._get_user_or_raise_if_default(id)
293 c.user = self._get_user_or_raise_if_default(id)
294 c.active = 'perms'
294 c.active = 'perms'
295 c.perm_user = AuthUser(dbuser=c.user)
295 c.perm_user = AuthUser(dbuser=c.user)
296
296
297 umodel = UserModel()
297 umodel = UserModel()
298 defaults = c.user.get_dict()
298 defaults = c.user.get_dict()
299 defaults.update({
299 defaults.update({
300 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
300 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
301 'create_user_group_perm': umodel.has_perm(c.user,
301 'create_user_group_perm': umodel.has_perm(c.user,
302 'hg.usergroup.create.true'),
302 'hg.usergroup.create.true'),
303 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
303 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
304 })
304 })
305 return htmlfill.render(
305 return htmlfill.render(
306 render('admin/users/user_edit.html'),
306 render('admin/users/user_edit.html'),
307 defaults=defaults,
307 defaults=defaults,
308 encoding="UTF-8",
308 encoding="UTF-8",
309 force_defaults=False)
309 force_defaults=False)
310
310
311 def update_perms(self, id):
311 def update_perms(self, id):
312 user = self._get_user_or_raise_if_default(id)
312 user = self._get_user_or_raise_if_default(id)
313
313
314 try:
314 try:
315 form = CustomDefaultPermissionsForm()()
315 form = CustomDefaultPermissionsForm()()
316 form_result = form.to_python(request.POST)
316 form_result = form.to_python(request.POST)
317
317
318 user_model = UserModel()
318 user_model = UserModel()
319
319
320 defs = UserToPerm.query() \
320 defs = UserToPerm.query() \
321 .filter(UserToPerm.user == user) \
321 .filter(UserToPerm.user == user) \
322 .all()
322 .all()
323 for ug in defs:
323 for ug in defs:
324 Session().delete(ug)
324 Session().delete(ug)
325
325
326 if form_result['create_repo_perm']:
326 if form_result['create_repo_perm']:
327 user_model.grant_perm(id, 'hg.create.repository')
327 user_model.grant_perm(id, 'hg.create.repository')
328 else:
328 else:
329 user_model.grant_perm(id, 'hg.create.none')
329 user_model.grant_perm(id, 'hg.create.none')
330 if form_result['create_user_group_perm']:
330 if form_result['create_user_group_perm']:
331 user_model.grant_perm(id, 'hg.usergroup.create.true')
331 user_model.grant_perm(id, 'hg.usergroup.create.true')
332 else:
332 else:
333 user_model.grant_perm(id, 'hg.usergroup.create.false')
333 user_model.grant_perm(id, 'hg.usergroup.create.false')
334 if form_result['fork_repo_perm']:
334 if form_result['fork_repo_perm']:
335 user_model.grant_perm(id, 'hg.fork.repository')
335 user_model.grant_perm(id, 'hg.fork.repository')
336 else:
336 else:
337 user_model.grant_perm(id, 'hg.fork.none')
337 user_model.grant_perm(id, 'hg.fork.none')
338 h.flash(_("Updated permissions"), category='success')
338 h.flash(_("Updated permissions"), category='success')
339 Session().commit()
339 Session().commit()
340 except Exception:
340 except Exception:
341 log.error(traceback.format_exc())
341 log.error(traceback.format_exc())
342 h.flash(_('An error occurred during permissions saving'),
342 h.flash(_('An error occurred during permissions saving'),
343 category='error')
343 category='error')
344 raise HTTPFound(location=url('edit_user_perms', id=id))
344 raise HTTPFound(location=url('edit_user_perms', id=id))
345
345
346 def edit_emails(self, id):
346 def edit_emails(self, id):
347 c.user = self._get_user_or_raise_if_default(id)
347 c.user = self._get_user_or_raise_if_default(id)
348 c.active = 'emails'
348 c.active = 'emails'
349 c.user_email_map = UserEmailMap.query() \
349 c.user_email_map = UserEmailMap.query() \
350 .filter(UserEmailMap.user == c.user).all()
350 .filter(UserEmailMap.user == c.user).all()
351
351
352 defaults = c.user.get_dict()
352 defaults = c.user.get_dict()
353 return htmlfill.render(
353 return htmlfill.render(
354 render('admin/users/user_edit.html'),
354 render('admin/users/user_edit.html'),
355 defaults=defaults,
355 defaults=defaults,
356 encoding="UTF-8",
356 encoding="UTF-8",
357 force_defaults=False)
357 force_defaults=False)
358
358
359 def add_email(self, id):
359 def add_email(self, id):
360 user = self._get_user_or_raise_if_default(id)
360 user = self._get_user_or_raise_if_default(id)
361 email = request.POST.get('new_email')
361 email = request.POST.get('new_email')
362 user_model = UserModel()
362 user_model = UserModel()
363
363
364 try:
364 try:
365 user_model.add_extra_email(id, email)
365 user_model.add_extra_email(id, email)
366 Session().commit()
366 Session().commit()
367 h.flash(_("Added email %s to user") % email, category='success')
367 h.flash(_("Added email %s to user") % email, category='success')
368 except formencode.Invalid as error:
368 except formencode.Invalid as error:
369 msg = error.error_dict['email']
369 msg = error.error_dict['email']
370 h.flash(msg, category='error')
370 h.flash(msg, category='error')
371 except Exception:
371 except Exception:
372 log.error(traceback.format_exc())
372 log.error(traceback.format_exc())
373 h.flash(_('An error occurred during email saving'),
373 h.flash(_('An error occurred during email saving'),
374 category='error')
374 category='error')
375 raise HTTPFound(location=url('edit_user_emails', id=id))
375 raise HTTPFound(location=url('edit_user_emails', id=id))
376
376
377 def delete_email(self, id):
377 def delete_email(self, id):
378 user = self._get_user_or_raise_if_default(id)
378 user = self._get_user_or_raise_if_default(id)
379 email_id = request.POST.get('del_email_id')
379 email_id = request.POST.get('del_email_id')
380 user_model = UserModel()
380 user_model = UserModel()
381 user_model.delete_extra_email(id, email_id)
381 user_model.delete_extra_email(id, email_id)
382 Session().commit()
382 Session().commit()
383 h.flash(_("Removed email from user"), category='success')
383 h.flash(_("Removed email from user"), category='success')
384 raise HTTPFound(location=url('edit_user_emails', id=id))
384 raise HTTPFound(location=url('edit_user_emails', id=id))
385
385
386 def edit_ips(self, id):
386 def edit_ips(self, id):
387 c.user = self._get_user_or_raise_if_default(id)
387 c.user = self._get_user_or_raise_if_default(id)
388 c.active = 'ips'
388 c.active = 'ips'
389 c.user_ip_map = UserIpMap.query() \
389 c.user_ip_map = UserIpMap.query() \
390 .filter(UserIpMap.user == c.user).all()
390 .filter(UserIpMap.user == c.user).all()
391
391
392 c.default_user_ip_map = UserIpMap.query() \
392 c.default_user_ip_map = UserIpMap.query() \
393 .filter(UserIpMap.user == User.get_default_user()).all()
393 .filter(UserIpMap.user == User.get_default_user()).all()
394
394
395 defaults = c.user.get_dict()
395 defaults = c.user.get_dict()
396 return htmlfill.render(
396 return htmlfill.render(
397 render('admin/users/user_edit.html'),
397 render('admin/users/user_edit.html'),
398 defaults=defaults,
398 defaults=defaults,
399 encoding="UTF-8",
399 encoding="UTF-8",
400 force_defaults=False)
400 force_defaults=False)
401
401
402 def add_ip(self, id):
402 def add_ip(self, id):
403 ip = request.POST.get('new_ip')
403 ip = request.POST.get('new_ip')
404 user_model = UserModel()
404 user_model = UserModel()
405
405
406 try:
406 try:
407 user_model.add_extra_ip(id, ip)
407 user_model.add_extra_ip(id, ip)
408 Session().commit()
408 Session().commit()
409 h.flash(_("Added IP address %s to user whitelist") % ip, category='success')
409 h.flash(_("Added IP address %s to user whitelist") % ip, category='success')
410 except formencode.Invalid as error:
410 except formencode.Invalid as error:
411 msg = error.error_dict['ip']
411 msg = error.error_dict['ip']
412 h.flash(msg, category='error')
412 h.flash(msg, category='error')
413 except Exception:
413 except Exception:
414 log.error(traceback.format_exc())
414 log.error(traceback.format_exc())
415 h.flash(_('An error occurred while adding IP address'),
415 h.flash(_('An error occurred while adding IP address'),
416 category='error')
416 category='error')
417
417
418 if 'default_user' in request.POST:
418 if 'default_user' in request.POST:
419 raise HTTPFound(location=url('admin_permissions_ips'))
419 raise HTTPFound(location=url('admin_permissions_ips'))
420 raise HTTPFound(location=url('edit_user_ips', id=id))
420 raise HTTPFound(location=url('edit_user_ips', id=id))
421
421
422 def delete_ip(self, id):
422 def delete_ip(self, id):
423 ip_id = request.POST.get('del_ip_id')
423 ip_id = request.POST.get('del_ip_id')
424 user_model = UserModel()
424 user_model = UserModel()
425 user_model.delete_extra_ip(id, ip_id)
425 user_model.delete_extra_ip(id, ip_id)
426 Session().commit()
426 Session().commit()
427 h.flash(_("Removed IP address from user whitelist"), category='success')
427 h.flash(_("Removed IP address from user whitelist"), category='success')
428
428
429 if 'default_user' in request.POST:
429 if 'default_user' in request.POST:
430 raise HTTPFound(location=url('admin_permissions_ips'))
430 raise HTTPFound(location=url('admin_permissions_ips'))
431 raise HTTPFound(location=url('edit_user_ips', id=id))
431 raise HTTPFound(location=url('edit_user_ips', id=id))
432
433 @IfSshEnabled
434 def edit_ssh_keys(self, id):
435 c.user = self._get_user_or_raise_if_default(id)
436 c.active = 'ssh_keys'
437 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
438 defaults = c.user.get_dict()
439 return htmlfill.render(
440 render('admin/users/user_edit.html'),
441 defaults=defaults,
442 encoding="UTF-8",
443 force_defaults=False)
444
445 @IfSshEnabled
446 def ssh_keys_add(self, id):
447 c.user = self._get_user_or_raise_if_default(id)
448
449 description = request.POST.get('description')
450 public_key = request.POST.get('public_key')
451 new_ssh_key = SshKeyModel().create(c.user.user_id,
452 description, public_key)
453 Session().commit()
454 h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
455 raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
456
457 @IfSshEnabled
458 def ssh_keys_delete(self, id):
459 c.user = self._get_user_or_raise_if_default(id)
460
461 public_key = request.POST.get('del_public_key')
462 SshKeyModel().delete(public_key, c.user.user_id)
463 Session().commit()
464 h.flash(_("SSH key successfully deleted"), category='success')
465 raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
@@ -1,185 +1,186 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3 <%block name="title">
3 <%block name="title">
4 ${_('About')}
4 ${_('About')}
5 </%block>
5 </%block>
6 <%block name="header_menu">
6 <%block name="header_menu">
7 ${self.menu('about')}
7 ${self.menu('about')}
8 </%block>
8 </%block>
9 <%def name="main()">
9 <%def name="main()">
10
10
11 <div class="panel panel-primary">
11 <div class="panel panel-primary">
12 <div class="panel-heading">
12 <div class="panel-heading">
13 <h5 class="panel-title">${_('About')} Kallithea</h5>
13 <h5 class="panel-title">${_('About')} Kallithea</h5>
14 </div>
14 </div>
15
15
16 <div class="panel-body panel-about">
16 <div class="panel-body panel-about">
17 <p><a href="https://kallithea-scm.org/">Kallithea</a> is a project of the
17 <p><a href="https://kallithea-scm.org/">Kallithea</a> is a project of the
18 <a href="http://sfconservancy.org/">Software Freedom Conservancy, Inc.</a>
18 <a href="http://sfconservancy.org/">Software Freedom Conservancy, Inc.</a>
19 and is released under the terms of the
19 and is released under the terms of the
20 <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License,
20 <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License,
21 v 3.0 (GPLv3)</a>.</p>
21 v 3.0 (GPLv3)</a>.</p>
22
22
23 <p>Kallithea is copyrighted by various authors, including but not
23 <p>Kallithea is copyrighted by various authors, including but not
24 necessarily limited to the following:</p>
24 necessarily limited to the following:</p>
25 <ul>
25 <ul>
26
26
27 <li>Copyright &copy; 2012&ndash;2019, Mads Kiilerich</li>
27 <li>Copyright &copy; 2012&ndash;2019, Mads Kiilerich</li>
28 <li>Copyright &copy; 2012, 2014&ndash;2017, 2019, Andrej Shadura</li>
28 <li>Copyright &copy; 2012, 2014&ndash;2017, 2019, Andrej Shadura</li>
29 <li>Copyright &copy; 2014&ndash;2019, Thomas De Schampheleire</li>
29 <li>Copyright &copy; 2014&ndash;2019, Thomas De Schampheleire</li>
30 <li>Copyright &copy; 2015&ndash;2017, 2019, Étienne Gilli</li>
30 <li>Copyright &copy; 2015&ndash;2017, 2019, Étienne Gilli</li>
31 <li>Copyright &copy; 2017&ndash;2019, Allan Nordhøy</li>
31 <li>Copyright &copy; 2017&ndash;2019, Allan Nordhøy</li>
32 <li>Copyright &copy; 2018&ndash;2019, ssantos</li>
32 <li>Copyright &copy; 2018&ndash;2019, ssantos</li>
33 <li>Copyright &copy; 2019, Danni Randeris</li>
33 <li>Copyright &copy; 2019, Danni Randeris</li>
34 <li>Copyright &copy; 2019, Edmund Wong</li>
34 <li>Copyright &copy; 2019, Edmund Wong</li>
35 <li>Copyright &copy; 2019, Manuel Jacob</li>
35 <li>Copyright &copy; 2019, Manuel Jacob</li>
36 <li>Copyright &copy; 2019, Wolfgang Scherer</li>
36 <li>Copyright &copy; 2019, Wolfgang Scherer</li>
37 <li>Copyright &copy; 2012, 2014&ndash;2018, Dominik Ruf</li>
37 <li>Copyright &copy; 2012, 2014&ndash;2018, Dominik Ruf</li>
38 <li>Copyright &copy; 2014&ndash;2015, 2018, Michal Čihař</li>
38 <li>Copyright &copy; 2014&ndash;2015, 2018, Michal Čihař</li>
39 <li>Copyright &copy; 2015, 2018, Branko Majic</li>
39 <li>Copyright &copy; 2015, 2018, Branko Majic</li>
40 <li>Copyright &copy; 2018, Chris Rule</li>
40 <li>Copyright &copy; 2018, Chris Rule</li>
41 <li>Copyright &copy; 2018, Jesús Sánchez</li>
41 <li>Copyright &copy; 2018, Jesús Sánchez</li>
42 <li>Copyright &copy; 2018, Patrick Vane</li>
42 <li>Copyright &copy; 2018, Patrick Vane</li>
43 <li>Copyright &copy; 2018, Pheng Heong Tan</li>
43 <li>Copyright &copy; 2018, Pheng Heong Tan</li>
44 <li>Copyright &copy; 2018, Максим Якимчук</li>
44 <li>Copyright &copy; 2018, Максим Якимчук</li>
45 <li>Copyright &copy; 2018, Марс Ямбар</li>
45 <li>Copyright &copy; 2018, Марс Ямбар</li>
46 <li>Copyright &copy; 2012&ndash;2017, Unity Technologies</li>
46 <li>Copyright &copy; 2012&ndash;2017, Unity Technologies</li>
47 <li>Copyright &copy; 2015&ndash;2017, Søren Løvborg</li>
47 <li>Copyright &copy; 2015&ndash;2017, Søren Løvborg</li>
48 <li>Copyright &copy; 2015, 2017, Sam Jaques</li>
48 <li>Copyright &copy; 2015, 2017, Sam Jaques</li>
49 <li>Copyright &copy; 2016&ndash;2017, Asterios Dimitriou</li>
49 <li>Copyright &copy; 2016&ndash;2017, Asterios Dimitriou</li>
50 <li>Copyright &copy; 2017, Alessandro Molina</li>
50 <li>Copyright &copy; 2017, Alessandro Molina</li>
51 <li>Copyright &copy; 2017, Anton Schur</li>
51 <li>Copyright &copy; 2017, Anton Schur</li>
52 <li>Copyright &copy; 2017, Ching-Chen Mao</li>
52 <li>Copyright &copy; 2017, Ching-Chen Mao</li>
53 <li>Copyright &copy; 2017, Eivind Tagseth</li>
53 <li>Copyright &copy; 2017, Eivind Tagseth</li>
54 <li>Copyright &copy; 2017, FUJIWARA Katsunori</li>
54 <li>Copyright &copy; 2017, FUJIWARA Katsunori</li>
55 <li>Copyright &copy; 2017, Holger Schramm</li>
55 <li>Copyright &copy; 2017, Holger Schramm</li>
56 <li>Copyright &copy; 2017, Karl Goetz</li>
56 <li>Copyright &copy; 2017, Karl Goetz</li>
57 <li>Copyright &copy; 2017, Lars Kruse</li>
57 <li>Copyright &copy; 2017, Lars Kruse</li>
58 <li>Copyright &copy; 2017, Marko Semet</li>
58 <li>Copyright &copy; 2017, Marko Semet</li>
59 <li>Copyright &copy; 2017, Viktar Vauchkevich</li>
59 <li>Copyright &copy; 2017, Viktar Vauchkevich</li>
60 <li>Copyright &copy; 2012&ndash;2016, Takumi IINO</li>
60 <li>Copyright &copy; 2012&ndash;2016, Takumi IINO</li>
61 <li>Copyright &copy; 2015&ndash;2016, Jan Heylen</li>
61 <li>Copyright &copy; 2015&ndash;2016, Jan Heylen</li>
62 <li>Copyright &copy; 2015&ndash;2016, Robert Martinez</li>
62 <li>Copyright &copy; 2015&ndash;2016, Robert Martinez</li>
63 <li>Copyright &copy; 2015&ndash;2016, Robert Rauch</li>
63 <li>Copyright &copy; 2015&ndash;2016, Robert Rauch</li>
64 <li>Copyright &copy; 2016, Angel Ezquerra</li>
64 <li>Copyright &copy; 2016, Angel Ezquerra</li>
65 <li>Copyright &copy; 2016, Anton Shestakov</li>
65 <li>Copyright &copy; 2016, Anton Shestakov</li>
66 <li>Copyright &copy; 2016, Brandon Jones</li>
66 <li>Copyright &copy; 2016, Brandon Jones</li>
67 <li>Copyright &copy; 2016, Kateryna Musina</li>
67 <li>Copyright &copy; 2016, Kateryna Musina</li>
68 <li>Copyright &copy; 2016, Konstantin Veretennicov</li>
68 <li>Copyright &copy; 2016, Konstantin Veretennicov</li>
69 <li>Copyright &copy; 2016, Oscar Curero</li>
69 <li>Copyright &copy; 2016, Oscar Curero</li>
70 <li>Copyright &copy; 2016, Robert James Dennington</li>
70 <li>Copyright &copy; 2016, Robert James Dennington</li>
71 <li>Copyright &copy; 2016, timeless@gmail.com</li>
71 <li>Copyright &copy; 2016, timeless@gmail.com</li>
72 <li>Copyright &copy; 2016, YFdyh000</li>
72 <li>Copyright &copy; 2016, YFdyh000</li>
73 <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
73 <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
74 <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
74 <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
75 <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
75 <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
76 <li>Copyright &copy; 2014&ndash;2015, Sean Farley</li>
76 <li>Copyright &copy; 2014&ndash;2015, Sean Farley</li>
77 <li>Copyright &copy; 2015, Anatoly Bubenkov</li>
77 <li>Copyright &copy; 2015, Anatoly Bubenkov</li>
78 <li>Copyright &copy; 2015, Andrew Bartlett</li>
78 <li>Copyright &copy; 2015, Andrew Bartlett</li>
79 <li>Copyright &copy; 2015, Balázs Úr</li>
79 <li>Copyright &copy; 2015, Balázs Úr</li>
80 <li>Copyright &copy; 2015, Ben Finney</li>
80 <li>Copyright &copy; 2015, Ben Finney</li>
81 <li>Copyright &copy; 2015, Daniel Hobley</li>
81 <li>Copyright &copy; 2015, Daniel Hobley</li>
82 <li>Copyright &copy; 2015, David Avigni</li>
82 <li>Copyright &copy; 2015, David Avigni</li>
83 <li>Copyright &copy; 2015, Denis Blanchette</li>
83 <li>Copyright &copy; 2015, Denis Blanchette</li>
84 <li>Copyright &copy; 2015, duanhongyi</li>
84 <li>Copyright &copy; 2015, duanhongyi</li>
85 <li>Copyright &copy; 2015, EriCSN Chang</li>
85 <li>Copyright &copy; 2015, EriCSN Chang</li>
86 <li>Copyright &copy; 2015, Grzegorz Krason</li>
86 <li>Copyright &copy; 2015, Grzegorz Krason</li>
87 <li>Copyright &copy; 2015, Jiří Suchan</li>
87 <li>Copyright &copy; 2015, Jiří Suchan</li>
88 <li>Copyright &copy; 2015, Kazunari Kobayashi</li>
88 <li>Copyright &copy; 2015, Kazunari Kobayashi</li>
89 <li>Copyright &copy; 2015, Kevin Bullock</li>
89 <li>Copyright &copy; 2015, Kevin Bullock</li>
90 <li>Copyright &copy; 2015, kobanari</li>
90 <li>Copyright &copy; 2015, kobanari</li>
91 <li>Copyright &copy; 2015, Marc Abramowitz</li>
91 <li>Copyright &copy; 2015, Marc Abramowitz</li>
92 <li>Copyright &copy; 2015, Marc Villetard</li>
92 <li>Copyright &copy; 2015, Marc Villetard</li>
93 <li>Copyright &copy; 2015, Matthias Zilk</li>
93 <li>Copyright &copy; 2015, Matthias Zilk</li>
94 <li>Copyright &copy; 2015, Michael Pohl</li>
94 <li>Copyright &copy; 2015, Michael Pohl</li>
95 <li>Copyright &copy; 2015, Michael V. DePalatis</li>
95 <li>Copyright &copy; 2015, Michael V. DePalatis</li>
96 <li>Copyright &copy; 2015, Morten Skaaning</li>
96 <li>Copyright &copy; 2015, Morten Skaaning</li>
97 <li>Copyright &copy; 2015, Nick High</li>
97 <li>Copyright &copy; 2015, Nick High</li>
98 <li>Copyright &copy; 2015, Niemand Jedermann</li>
98 <li>Copyright &copy; 2015, Niemand Jedermann</li>
99 <li>Copyright &copy; 2015, Peter Vitt</li>
99 <li>Copyright &copy; 2015, Peter Vitt</li>
100 <li>Copyright &copy; 2015, Ronny Pfannschmidt</li>
100 <li>Copyright &copy; 2015, Ronny Pfannschmidt</li>
101 <li>Copyright &copy; 2015, Tuux</li>
101 <li>Copyright &copy; 2015, Tuux</li>
102 <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
102 <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
103 <li>Copyright &copy; 2014, Ante Ilic</li>
103 <li>Copyright &copy; 2014, Ante Ilic</li>
104 <li>Copyright &copy; 2014, Bradley M. Kuhn</li>
104 <li>Copyright &copy; 2014, Bradley M. Kuhn</li>
105 <li>Copyright &copy; 2014, Calinou</li>
105 <li>Copyright &copy; 2014, Calinou</li>
106 <li>Copyright &copy; 2014, Daniel Anderson</li>
106 <li>Copyright &copy; 2014, Daniel Anderson</li>
107 <li>Copyright &copy; 2014, Henrik Stuart</li>
107 <li>Copyright &copy; 2014, Henrik Stuart</li>
108 <li>Copyright &copy; 2014, Ingo von Borstel</li>
108 <li>Copyright &copy; 2014, Ingo von Borstel</li>
109 <li>Copyright &copy; 2014, Jelmer Vernooij</li>
109 <li>Copyright &copy; 2014, Jelmer Vernooij</li>
110 <li>Copyright &copy; 2014, Jim Hague</li>
110 <li>Copyright &copy; 2014, Jim Hague</li>
111 <li>Copyright &copy; 2014, Matt Fellows</li>
111 <li>Copyright &copy; 2014, Matt Fellows</li>
112 <li>Copyright &copy; 2014, Max Roman</li>
112 <li>Copyright &copy; 2014, Max Roman</li>
113 <li>Copyright &copy; 2014, Na'Tosha Bard</li>
113 <li>Copyright &copy; 2014, Na'Tosha Bard</li>
114 <li>Copyright &copy; 2014, Rasmus Selsmark</li>
114 <li>Copyright &copy; 2014, Rasmus Selsmark</li>
115 <li>Copyright &copy; 2014, Tim Freund</li>
115 <li>Copyright &copy; 2014, Tim Freund</li>
116 <li>Copyright &copy; 2014, Travis Burtrum</li>
116 <li>Copyright &copy; 2014, Travis Burtrum</li>
117 <li>Copyright &copy; 2014, Zoltan Gyarmati</li>
117 <li>Copyright &copy; 2014, Zoltan Gyarmati</li>
118 <li>Copyright &copy; 2010&ndash;2013, Marcin Kuźmiński</li>
118 <li>Copyright &copy; 2010&ndash;2013, Marcin Kuźmiński</li>
119 <li>Copyright &copy; 2010&ndash;2013, RhodeCode GmbH</li>
119 <li>Copyright &copy; 2010&ndash;2013, RhodeCode GmbH</li>
120 <li>Copyright &copy; 2011, 2013, Aparkar</li>
120 <li>Copyright &copy; 2011, 2013, Aparkar</li>
121 <li>Copyright &copy; 2012&ndash;2013, xpol</li>
121 <li>Copyright &copy; 2012&ndash;2013, xpol</li>
122 <li>Copyright &copy; 2013, Dennis Brakhane</li>
122 <li>Copyright &copy; 2013, Dennis Brakhane</li>
123 <li>Copyright &copy; 2013, Grzegorz Rożniecki</li>
123 <li>Copyright &copy; 2013, Grzegorz Rożniecki</li>
124 <li>Copyright &copy; 2013, Ilya Beda</li>
124 <li>Copyright &copy; 2013, Jonathan Sternberg</li>
125 <li>Copyright &copy; 2013, Jonathan Sternberg</li>
125 <li>Copyright &copy; 2013, Leonardo Carneiro</li>
126 <li>Copyright &copy; 2013, Leonardo Carneiro</li>
126 <li>Copyright &copy; 2013, Magnus Ericmats</li>
127 <li>Copyright &copy; 2013, Magnus Ericmats</li>
127 <li>Copyright &copy; 2013, Martin Vium</li>
128 <li>Copyright &copy; 2013, Martin Vium</li>
128 <li>Copyright &copy; 2013, Simon Lopez</li>
129 <li>Copyright &copy; 2013, Simon Lopez</li>
129 <li>Copyright &copy; 2011&ndash;2012, Augusto Herrmann</li>
130 <li>Copyright &copy; 2011&ndash;2012, Augusto Herrmann</li>
130 <li>Copyright &copy; 2012, Dan Sheridan</li>
131 <li>Copyright &copy; 2012, Dan Sheridan</li>
131 <li>Copyright &copy; 2012, H Waldo G</li>
132 <li>Copyright &copy; 2012, H Waldo G</li>
132 <li>Copyright &copy; 2012, hppj</li>
133 <li>Copyright &copy; 2012, hppj</li>
133 <li>Copyright &copy; 2012, Indra Talip</li>
134 <li>Copyright &copy; 2012, Indra Talip</li>
134 <li>Copyright &copy; 2012, mikespook</li>
135 <li>Copyright &copy; 2012, mikespook</li>
135 <li>Copyright &copy; 2012, nansenat16</li>
136 <li>Copyright &copy; 2012, nansenat16</li>
136 <li>Copyright &copy; 2012, Philip Jameson</li>
137 <li>Copyright &copy; 2012, Philip Jameson</li>
137 <li>Copyright &copy; 2012, Raoul Thill</li>
138 <li>Copyright &copy; 2012, Raoul Thill</li>
138 <li>Copyright &copy; 2012, Tony Bussieres</li>
139 <li>Copyright &copy; 2012, Tony Bussieres</li>
139 <li>Copyright &copy; 2012, Vincent Duvert</li>
140 <li>Copyright &copy; 2012, Vincent Duvert</li>
140 <li>Copyright &copy; 2012, Vladislav Poluhin</li>
141 <li>Copyright &copy; 2012, Vladislav Poluhin</li>
141 <li>Copyright &copy; 2012, Zachary Auclair</li>
142 <li>Copyright &copy; 2012, Zachary Auclair</li>
142 <li>Copyright &copy; 2011, Ankit Solanki</li>
143 <li>Copyright &copy; 2011, Ankit Solanki</li>
143 <li>Copyright &copy; 2011, Dmitri Kuznetsov</li>
144 <li>Copyright &copy; 2011, Dmitri Kuznetsov</li>
144 <li>Copyright &copy; 2011, Jared Bunting</li>
145 <li>Copyright &copy; 2011, Jared Bunting</li>
145 <li>Copyright &copy; 2011, Jason Harris</li>
146 <li>Copyright &copy; 2011, Jason Harris</li>
146 <li>Copyright &copy; 2011, Les Peabody</li>
147 <li>Copyright &copy; 2011, Les Peabody</li>
147 <li>Copyright &copy; 2011, Liad Shani</li>
148 <li>Copyright &copy; 2011, Liad Shani</li>
148 <li>Copyright &copy; 2011, Lorenzo M. Catucci</li>
149 <li>Copyright &copy; 2011, Lorenzo M. Catucci</li>
149 <li>Copyright &copy; 2011, Matt Zuba</li>
150 <li>Copyright &copy; 2011, Matt Zuba</li>
150 <li>Copyright &copy; 2011, Nicolas VINOT</li>
151 <li>Copyright &copy; 2011, Nicolas VINOT</li>
151 <li>Copyright &copy; 2011, Shawn K. O'Shea</li>
152 <li>Copyright &copy; 2011, Shawn K. O'Shea</li>
152 <li>Copyright &copy; 2010, Łukasz Balcerzak</li>
153 <li>Copyright &copy; 2010, Łukasz Balcerzak</li>
153
154
154 ## We did not list the following copyright holders, given that they appeared
155 ## We did not list the following copyright holders, given that they appeared
155 ## to use for-profit company affiliations in their contribution in the
156 ## to use for-profit company affiliations in their contribution in the
156 ## Mercurial log and therefore I didn't know if copyright was theirs or
157 ## Mercurial log and therefore I didn't know if copyright was theirs or
157 ## their company's.
158 ## their company's.
158 ## Copyright &copy; 2011 Thayne Harbaugh <thayne@fusionio.com>
159 ## Copyright &copy; 2011 Thayne Harbaugh <thayne@fusionio.com>
159 ## Copyright &copy; 2012 Dies Koper <diesk@fast.au.fujitsu.com>
160 ## Copyright &copy; 2012 Dies Koper <diesk@fast.au.fujitsu.com>
160 ## Copyright &copy; 2012 Erwin Kroon <e.kroon@smartmetersolutions.nl>
161 ## Copyright &copy; 2012 Erwin Kroon <e.kroon@smartmetersolutions.nl>
161 ## Copyright &copy; 2012 Vincent Caron <vcaron@bearstech.com>
162 ## Copyright &copy; 2012 Vincent Caron <vcaron@bearstech.com>
162 ##
163 ##
163 ## These contributors' contributions may not be copyrightable:
164 ## These contributors' contributions may not be copyrightable:
164 ## philip.j@hostdime.com in 2012
165 ## philip.j@hostdime.com in 2012
165 ## Stefan Engel <mail@engel-stefan.de> in 2012
166 ## Stefan Engel <mail@engel-stefan.de> in 2012
166 ## Ton Plomp <tcplomp@gmail.com> in 2013
167 ## Ton Plomp <tcplomp@gmail.com> in 2013
167 ##
168 ##
168 </ul>
169 </ul>
169
170
170 <p>The above are the copyright holders who have submitted direct
171 <p>The above are the copyright holders who have submitted direct
171 contributions to the Kallithea repository.</p>
172 contributions to the Kallithea repository.</p>
172
173
173 <p>In the <a href="https://kallithea-scm.org/repos/kallithea">Kallithea
174 <p>In the <a href="https://kallithea-scm.org/repos/kallithea">Kallithea
174 source code</a>, there is a
175 source code</a>, there is a
175 <a href="https://kallithea-scm.org/repos/kallithea/files/tip/LICENSE.md">list
176 <a href="https://kallithea-scm.org/repos/kallithea/files/tip/LICENSE.md">list
176 of third-party libraries and code that Kallithea incorporates</a>.</p>
177 of third-party libraries and code that Kallithea incorporates</a>.</p>
177
178
178 <p>The front-end contains a <a href="${h.url('/LICENSES.txt')}">list of
179 <p>The front-end contains a <a href="${h.url('/LICENSES.txt')}">list of
179 software that is used to build the front-end</a> but isn't distributed as a
180 software that is used to build the front-end</a> but isn't distributed as a
180 part of Kallithea.</p>
181 part of Kallithea.</p>
181
182
182 </div>
183 </div>
183 </div>
184 </div>
184
185
185 </%def>
186 </%def>
@@ -1,43 +1,46 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%block name="title">
4 <%block name="title">
5 ${_('%s user settings') % c.user.username}
5 ${_('%s user settings') % c.user.username}
6 </%block>
6 </%block>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Users'),h.url('users'))}
11 ${h.link_to(_('Users'),h.url('users'))}
12 &raquo;
12 &raquo;
13 ${c.user.username}
13 ${c.user.username}
14 </%def>
14 </%def>
15
15
16 <%block name="header_menu">
16 <%block name="header_menu">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%block>
18 </%block>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="panel panel-primary">
21 <div class="panel panel-primary">
22 <div class="panel-heading clearfix">
22 <div class="panel-heading clearfix">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25
25
26 ##main
26 ##main
27 <div class="panel-body settings">
27 <div class="panel-body settings">
28 <ul class="nav nav-pills nav-stacked">
28 <ul class="nav nav-pills nav-stacked">
29 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', id=c.user.user_id)}">${_('Profile')}</a></li>
29 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', id=c.user.user_id)}">${_('Profile')}</a></li>
30 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', id=c.user.user_id)}">${_('Emails')}</a></li>
30 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', id=c.user.user_id)}">${_('Emails')}</a></li>
31 %if c.ssh_enabled:
32 <li class="${'active' if c.active=='ssh_keys' else ''}"><a href="${h.url('edit_user_ssh_keys', id=c.user.user_id)}">${_('SSH Keys')}</a></li>
33 %endif
31 <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('edit_user_api_keys', id=c.user.user_id)}">${_('API Keys')}</a></li>
34 <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('edit_user_api_keys', id=c.user.user_id)}">${_('API Keys')}</a></li>
32 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', id=c.user.user_id)}">${_('IP Whitelist')}</a></li>
35 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', id=c.user.user_id)}">${_('IP Whitelist')}</a></li>
33 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', id=c.user.user_id)}">${_('Advanced')}</a></li>
36 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', id=c.user.user_id)}">${_('Advanced')}</a></li>
34 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_perms', id=c.user.user_id)}">${_('Show Permissions')}</a></li>
37 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_perms', id=c.user.user_id)}">${_('Show Permissions')}</a></li>
35 </ul>
38 </ul>
36
39
37 <div>
40 <div>
38 <%include file="/admin/users/user_edit_${c.active}.html"/>
41 <%include file="/admin/users/user_edit_${c.active}.html"/>
39 </div>
42 </div>
40 </div>
43 </div>
41 </div>
44 </div>
42
45
43 </%def>
46 </%def>
@@ -1,605 +1,653 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
14
15 from sqlalchemy.orm.exc import NoResultFound, ObjectDeletedError
15 from sqlalchemy.orm.exc import NoResultFound, ObjectDeletedError
16
16
17 import pytest
17 import pytest
18 from kallithea.tests.base import *
18 from kallithea.tests.base import *
19 from kallithea.tests.fixture import Fixture
19 from kallithea.tests.fixture import Fixture
20 from kallithea.controllers.admin.users import UsersController
20 from kallithea.controllers.admin.users import UsersController
21 from kallithea.model.db import User, Permission, UserIpMap, UserApiKeys, RepoGroup
21 from kallithea.model.db import User, Permission, UserIpMap, UserApiKeys, RepoGroup, UserSshKeys
22 from kallithea.lib.auth import check_password
22 from kallithea.lib.auth import check_password
23 from kallithea.model.user import UserModel
23 from kallithea.model.user import UserModel
24 from kallithea.model import validators
24 from kallithea.model import validators
25 from kallithea.lib import helpers as h
25 from kallithea.lib import helpers as h
26 from kallithea.model.meta import Session
26 from kallithea.model.meta import Session
27 from webob.exc import HTTPNotFound
27 from webob.exc import HTTPNotFound
28
28
29 from tg.util.webtest import test_context
29 from tg.util.webtest import test_context
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34 @pytest.fixture
34 @pytest.fixture
35 def user_and_repo_group_fail():
35 def user_and_repo_group_fail():
36 username = 'repogrouperr'
36 username = 'repogrouperr'
37 groupname = u'repogroup_fail'
37 groupname = u'repogroup_fail'
38 user = fixture.create_user(name=username)
38 user = fixture.create_user(name=username)
39 repo_group = fixture.create_repo_group(name=groupname, cur_user=username)
39 repo_group = fixture.create_repo_group(name=groupname, cur_user=username)
40 yield user, repo_group
40 yield user, repo_group
41 # cleanup
41 # cleanup
42 if RepoGroup.get_by_group_name(groupname):
42 if RepoGroup.get_by_group_name(groupname):
43 fixture.destroy_repo_group(repo_group)
43 fixture.destroy_repo_group(repo_group)
44
44
45
45
46 class TestAdminUsersController(TestController):
46 class TestAdminUsersController(TestController):
47 test_user_1 = 'testme'
47 test_user_1 = 'testme'
48
48
49 @classmethod
49 @classmethod
50 def teardown_class(cls):
50 def teardown_class(cls):
51 if User.get_by_username(cls.test_user_1):
51 if User.get_by_username(cls.test_user_1):
52 UserModel().delete(cls.test_user_1)
52 UserModel().delete(cls.test_user_1)
53 Session().commit()
53 Session().commit()
54
54
55 def test_index(self):
55 def test_index(self):
56 self.log_user()
56 self.log_user()
57 response = self.app.get(url('users'))
57 response = self.app.get(url('users'))
58 # TODO: Test response...
58 # TODO: Test response...
59
59
60 def test_create(self):
60 def test_create(self):
61 self.log_user()
61 self.log_user()
62 username = 'newtestuser'
62 username = 'newtestuser'
63 password = 'test12'
63 password = 'test12'
64 password_confirmation = password
64 password_confirmation = password
65 name = u'name'
65 name = u'name'
66 lastname = u'lastname'
66 lastname = u'lastname'
67 email = 'mail@example.com'
67 email = 'mail@example.com'
68
68
69 response = self.app.post(url('new_user'),
69 response = self.app.post(url('new_user'),
70 {'username': username,
70 {'username': username,
71 'password': password,
71 'password': password,
72 'password_confirmation': password_confirmation,
72 'password_confirmation': password_confirmation,
73 'firstname': name,
73 'firstname': name,
74 'active': True,
74 'active': True,
75 'lastname': lastname,
75 'lastname': lastname,
76 'extern_name': 'internal',
76 'extern_name': 'internal',
77 'extern_type': 'internal',
77 'extern_type': 'internal',
78 'email': email,
78 'email': email,
79 '_authentication_token': self.authentication_token()})
79 '_authentication_token': self.authentication_token()})
80 # 302 Found
80 # 302 Found
81 # The resource was found at http://localhost/_admin/users/5/edit; you should be redirected automatically.
81 # The resource was found at http://localhost/_admin/users/5/edit; you should be redirected automatically.
82
82
83 self.checkSessionFlash(response, '''Created user %s''' % username)
83 self.checkSessionFlash(response, '''Created user %s''' % username)
84
84
85 response = response.follow()
85 response = response.follow()
86 response.mustcontain("""%s user settings""" % username) # in <title>
86 response.mustcontain("""%s user settings""" % username) # in <title>
87
87
88 new_user = Session().query(User). \
88 new_user = Session().query(User). \
89 filter(User.username == username).one()
89 filter(User.username == username).one()
90
90
91 assert new_user.username == username
91 assert new_user.username == username
92 assert check_password(password, new_user.password) == True
92 assert check_password(password, new_user.password) == True
93 assert new_user.name == name
93 assert new_user.name == name
94 assert new_user.lastname == lastname
94 assert new_user.lastname == lastname
95 assert new_user.email == email
95 assert new_user.email == email
96
96
97 def test_create_err(self):
97 def test_create_err(self):
98 self.log_user()
98 self.log_user()
99 username = 'new_user'
99 username = 'new_user'
100 password = ''
100 password = ''
101 name = u'name'
101 name = u'name'
102 lastname = u'lastname'
102 lastname = u'lastname'
103 email = 'errmail.example.com'
103 email = 'errmail.example.com'
104
104
105 response = self.app.post(url('new_user'),
105 response = self.app.post(url('new_user'),
106 {'username': username,
106 {'username': username,
107 'password': password,
107 'password': password,
108 'name': name,
108 'name': name,
109 'active': False,
109 'active': False,
110 'lastname': lastname,
110 'lastname': lastname,
111 'email': email,
111 'email': email,
112 '_authentication_token': self.authentication_token()})
112 '_authentication_token': self.authentication_token()})
113
113
114 with test_context(self.app):
114 with test_context(self.app):
115 msg = validators.ValidUsername(False, {})._messages['system_invalid_username']
115 msg = validators.ValidUsername(False, {})._messages['system_invalid_username']
116 msg = h.html_escape(msg % {'username': 'new_user'})
116 msg = h.html_escape(msg % {'username': 'new_user'})
117 response.mustcontain("""<span class="error-message">%s</span>""" % msg)
117 response.mustcontain("""<span class="error-message">%s</span>""" % msg)
118 response.mustcontain("""<span class="error-message">Please enter a value</span>""")
118 response.mustcontain("""<span class="error-message">Please enter a value</span>""")
119 response.mustcontain("""<span class="error-message">An email address must contain a single @</span>""")
119 response.mustcontain("""<span class="error-message">An email address must contain a single @</span>""")
120
120
121 def get_user():
121 def get_user():
122 Session().query(User).filter(User.username == username).one()
122 Session().query(User).filter(User.username == username).one()
123
123
124 with pytest.raises(NoResultFound):
124 with pytest.raises(NoResultFound):
125 get_user(), 'found user in database'
125 get_user(), 'found user in database'
126
126
127 def test_new(self):
127 def test_new(self):
128 self.log_user()
128 self.log_user()
129 response = self.app.get(url('new_user'))
129 response = self.app.get(url('new_user'))
130
130
131 @parametrize('name,attrs',
131 @parametrize('name,attrs',
132 [('firstname', {'firstname': 'new_username'}),
132 [('firstname', {'firstname': 'new_username'}),
133 ('lastname', {'lastname': 'new_username'}),
133 ('lastname', {'lastname': 'new_username'}),
134 ('admin', {'admin': True}),
134 ('admin', {'admin': True}),
135 ('admin', {'admin': False}),
135 ('admin', {'admin': False}),
136 ('extern_type', {'extern_type': 'ldap'}),
136 ('extern_type', {'extern_type': 'ldap'}),
137 ('extern_type', {'extern_type': None}),
137 ('extern_type', {'extern_type': None}),
138 ('extern_name', {'extern_name': 'test'}),
138 ('extern_name', {'extern_name': 'test'}),
139 ('extern_name', {'extern_name': None}),
139 ('extern_name', {'extern_name': None}),
140 ('active', {'active': False}),
140 ('active', {'active': False}),
141 ('active', {'active': True}),
141 ('active', {'active': True}),
142 ('email', {'email': 'someemail@example.com'}),
142 ('email', {'email': 'someemail@example.com'}),
143 # ('new_password', {'new_password': 'foobar123',
143 # ('new_password', {'new_password': 'foobar123',
144 # 'password_confirmation': 'foobar123'})
144 # 'password_confirmation': 'foobar123'})
145 ])
145 ])
146 def test_update(self, name, attrs):
146 def test_update(self, name, attrs):
147 self.log_user()
147 self.log_user()
148 usr = fixture.create_user(self.test_user_1, password='qweqwe',
148 usr = fixture.create_user(self.test_user_1, password='qweqwe',
149 email='testme@example.com',
149 email='testme@example.com',
150 extern_type='internal',
150 extern_type='internal',
151 extern_name=self.test_user_1,
151 extern_name=self.test_user_1,
152 skip_if_exists=True)
152 skip_if_exists=True)
153 Session().commit()
153 Session().commit()
154 params = usr.get_api_data(True)
154 params = usr.get_api_data(True)
155 params.update({'password_confirmation': ''})
155 params.update({'password_confirmation': ''})
156 params.update({'new_password': ''})
156 params.update({'new_password': ''})
157 params.update(attrs)
157 params.update(attrs)
158 if name == 'email':
158 if name == 'email':
159 params['emails'] = [attrs['email']]
159 params['emails'] = [attrs['email']]
160 if name == 'extern_type':
160 if name == 'extern_type':
161 # cannot update this via form, expected value is original one
161 # cannot update this via form, expected value is original one
162 params['extern_type'] = "internal"
162 params['extern_type'] = "internal"
163 if name == 'extern_name':
163 if name == 'extern_name':
164 # cannot update this via form, expected value is original one
164 # cannot update this via form, expected value is original one
165 params['extern_name'] = self.test_user_1
165 params['extern_name'] = self.test_user_1
166 # special case since this user is not logged in yet his data is
166 # special case since this user is not logged in yet his data is
167 # not filled so we use creation data
167 # not filled so we use creation data
168
168
169 params.update({'_authentication_token': self.authentication_token()})
169 params.update({'_authentication_token': self.authentication_token()})
170 response = self.app.post(url('update_user', id=usr.user_id), params)
170 response = self.app.post(url('update_user', id=usr.user_id), params)
171 self.checkSessionFlash(response, 'User updated successfully')
171 self.checkSessionFlash(response, 'User updated successfully')
172 params.pop('_authentication_token')
172 params.pop('_authentication_token')
173
173
174 updated_user = User.get_by_username(self.test_user_1)
174 updated_user = User.get_by_username(self.test_user_1)
175 updated_params = updated_user.get_api_data(True)
175 updated_params = updated_user.get_api_data(True)
176 updated_params.update({'password_confirmation': ''})
176 updated_params.update({'password_confirmation': ''})
177 updated_params.update({'new_password': ''})
177 updated_params.update({'new_password': ''})
178
178
179 assert params == updated_params
179 assert params == updated_params
180
180
181 def test_delete(self):
181 def test_delete(self):
182 self.log_user()
182 self.log_user()
183 username = 'newtestuserdeleteme'
183 username = 'newtestuserdeleteme'
184
184
185 fixture.create_user(name=username)
185 fixture.create_user(name=username)
186
186
187 new_user = Session().query(User) \
187 new_user = Session().query(User) \
188 .filter(User.username == username).one()
188 .filter(User.username == username).one()
189 response = self.app.post(url('delete_user', id=new_user.user_id),
189 response = self.app.post(url('delete_user', id=new_user.user_id),
190 params={'_authentication_token': self.authentication_token()})
190 params={'_authentication_token': self.authentication_token()})
191
191
192 self.checkSessionFlash(response, 'Successfully deleted user')
192 self.checkSessionFlash(response, 'Successfully deleted user')
193
193
194 def test_delete_repo_err(self):
194 def test_delete_repo_err(self):
195 self.log_user()
195 self.log_user()
196 username = 'repoerr'
196 username = 'repoerr'
197 reponame = u'repoerr_fail'
197 reponame = u'repoerr_fail'
198
198
199 fixture.create_user(name=username)
199 fixture.create_user(name=username)
200 fixture.create_repo(name=reponame, cur_user=username)
200 fixture.create_repo(name=reponame, cur_user=username)
201
201
202 new_user = Session().query(User) \
202 new_user = Session().query(User) \
203 .filter(User.username == username).one()
203 .filter(User.username == username).one()
204 response = self.app.post(url('delete_user', id=new_user.user_id),
204 response = self.app.post(url('delete_user', id=new_user.user_id),
205 params={'_authentication_token': self.authentication_token()})
205 params={'_authentication_token': self.authentication_token()})
206 self.checkSessionFlash(response, 'User "%s" still '
206 self.checkSessionFlash(response, 'User "%s" still '
207 'owns 1 repositories and cannot be removed. '
207 'owns 1 repositories and cannot be removed. '
208 'Switch owners or remove those repositories: '
208 'Switch owners or remove those repositories: '
209 '%s' % (username, reponame))
209 '%s' % (username, reponame))
210
210
211 response = self.app.post(url('delete_repo', repo_name=reponame),
211 response = self.app.post(url('delete_repo', repo_name=reponame),
212 params={'_authentication_token': self.authentication_token()})
212 params={'_authentication_token': self.authentication_token()})
213 self.checkSessionFlash(response, 'Deleted repository %s' % reponame)
213 self.checkSessionFlash(response, 'Deleted repository %s' % reponame)
214
214
215 response = self.app.post(url('delete_user', id=new_user.user_id),
215 response = self.app.post(url('delete_user', id=new_user.user_id),
216 params={'_authentication_token': self.authentication_token()})
216 params={'_authentication_token': self.authentication_token()})
217 self.checkSessionFlash(response, 'Successfully deleted user')
217 self.checkSessionFlash(response, 'Successfully deleted user')
218
218
219 def test_delete_repo_group_err(self, user_and_repo_group_fail):
219 def test_delete_repo_group_err(self, user_and_repo_group_fail):
220 new_user, repo_group = user_and_repo_group_fail
220 new_user, repo_group = user_and_repo_group_fail
221 username = new_user.username
221 username = new_user.username
222 groupname = repo_group.group_name
222 groupname = repo_group.group_name
223
223
224 self.log_user()
224 self.log_user()
225
225
226 response = self.app.post(url('delete_user', id=new_user.user_id),
226 response = self.app.post(url('delete_user', id=new_user.user_id),
227 params={'_authentication_token': self.authentication_token()})
227 params={'_authentication_token': self.authentication_token()})
228 self.checkSessionFlash(response, 'User "%s" still '
228 self.checkSessionFlash(response, 'User "%s" still '
229 'owns 1 repository groups and cannot be removed. '
229 'owns 1 repository groups and cannot be removed. '
230 'Switch owners or remove those repository groups: '
230 'Switch owners or remove those repository groups: '
231 '%s' % (username, groupname))
231 '%s' % (username, groupname))
232
232
233 # Relevant _if_ the user deletion succeeded to make sure we can render groups without owner
233 # Relevant _if_ the user deletion succeeded to make sure we can render groups without owner
234 # rg = RepoGroup.get_by_group_name(group_name=groupname)
234 # rg = RepoGroup.get_by_group_name(group_name=groupname)
235 # response = self.app.get(url('repos_groups', id=rg.group_id))
235 # response = self.app.get(url('repos_groups', id=rg.group_id))
236
236
237 response = self.app.post(url('delete_repo_group', group_name=groupname),
237 response = self.app.post(url('delete_repo_group', group_name=groupname),
238 params={'_authentication_token': self.authentication_token()})
238 params={'_authentication_token': self.authentication_token()})
239 self.checkSessionFlash(response, 'Removed repository group %s' % groupname)
239 self.checkSessionFlash(response, 'Removed repository group %s' % groupname)
240
240
241 response = self.app.post(url('delete_user', id=new_user.user_id),
241 response = self.app.post(url('delete_user', id=new_user.user_id),
242 params={'_authentication_token': self.authentication_token()})
242 params={'_authentication_token': self.authentication_token()})
243 self.checkSessionFlash(response, 'Successfully deleted user')
243 self.checkSessionFlash(response, 'Successfully deleted user')
244
244
245 def test_delete_user_group_err(self):
245 def test_delete_user_group_err(self):
246 self.log_user()
246 self.log_user()
247 username = 'usergrouperr'
247 username = 'usergrouperr'
248 groupname = u'usergroup_fail'
248 groupname = u'usergroup_fail'
249
249
250 fixture.create_user(name=username)
250 fixture.create_user(name=username)
251 ug = fixture.create_user_group(name=groupname, cur_user=username)
251 ug = fixture.create_user_group(name=groupname, cur_user=username)
252
252
253 new_user = Session().query(User) \
253 new_user = Session().query(User) \
254 .filter(User.username == username).one()
254 .filter(User.username == username).one()
255 response = self.app.post(url('delete_user', id=new_user.user_id),
255 response = self.app.post(url('delete_user', id=new_user.user_id),
256 params={'_authentication_token': self.authentication_token()})
256 params={'_authentication_token': self.authentication_token()})
257 self.checkSessionFlash(response, 'User "%s" still '
257 self.checkSessionFlash(response, 'User "%s" still '
258 'owns 1 user groups and cannot be removed. '
258 'owns 1 user groups and cannot be removed. '
259 'Switch owners or remove those user groups: '
259 'Switch owners or remove those user groups: '
260 '%s' % (username, groupname))
260 '%s' % (username, groupname))
261
261
262 # TODO: why do this fail?
262 # TODO: why do this fail?
263 #response = self.app.delete(url('delete_users_group', id=groupname))
263 #response = self.app.delete(url('delete_users_group', id=groupname))
264 #self.checkSessionFlash(response, 'Removed user group %s' % groupname)
264 #self.checkSessionFlash(response, 'Removed user group %s' % groupname)
265
265
266 fixture.destroy_user_group(ug.users_group_id)
266 fixture.destroy_user_group(ug.users_group_id)
267
267
268 response = self.app.post(url('delete_user', id=new_user.user_id),
268 response = self.app.post(url('delete_user', id=new_user.user_id),
269 params={'_authentication_token': self.authentication_token()})
269 params={'_authentication_token': self.authentication_token()})
270 self.checkSessionFlash(response, 'Successfully deleted user')
270 self.checkSessionFlash(response, 'Successfully deleted user')
271
271
272 def test_edit(self):
272 def test_edit(self):
273 self.log_user()
273 self.log_user()
274 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
274 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
275 response = self.app.get(url('edit_user', id=user.user_id))
275 response = self.app.get(url('edit_user', id=user.user_id))
276
276
277 def test_add_perm_create_repo(self):
277 def test_add_perm_create_repo(self):
278 self.log_user()
278 self.log_user()
279 perm_none = Permission.get_by_key('hg.create.none')
279 perm_none = Permission.get_by_key('hg.create.none')
280 perm_create = Permission.get_by_key('hg.create.repository')
280 perm_create = Permission.get_by_key('hg.create.repository')
281
281
282 user = UserModel().create_or_update(username='dummy', password='qwe',
282 user = UserModel().create_or_update(username='dummy', password='qwe',
283 email='dummy', firstname=u'a',
283 email='dummy', firstname=u'a',
284 lastname=u'b')
284 lastname=u'b')
285 Session().commit()
285 Session().commit()
286 uid = user.user_id
286 uid = user.user_id
287
287
288 try:
288 try:
289 # User should have None permission on creation repository
289 # User should have None permission on creation repository
290 assert UserModel().has_perm(user, perm_none) == False
290 assert UserModel().has_perm(user, perm_none) == False
291 assert UserModel().has_perm(user, perm_create) == False
291 assert UserModel().has_perm(user, perm_create) == False
292
292
293 response = self.app.post(url('edit_user_perms_update', id=uid),
293 response = self.app.post(url('edit_user_perms_update', id=uid),
294 params=dict(create_repo_perm=True,
294 params=dict(create_repo_perm=True,
295 _authentication_token=self.authentication_token()))
295 _authentication_token=self.authentication_token()))
296
296
297 perm_none = Permission.get_by_key('hg.create.none')
297 perm_none = Permission.get_by_key('hg.create.none')
298 perm_create = Permission.get_by_key('hg.create.repository')
298 perm_create = Permission.get_by_key('hg.create.repository')
299
299
300 # User should have None permission on creation repository
300 # User should have None permission on creation repository
301 assert UserModel().has_perm(uid, perm_none) == False
301 assert UserModel().has_perm(uid, perm_none) == False
302 assert UserModel().has_perm(uid, perm_create) == True
302 assert UserModel().has_perm(uid, perm_create) == True
303 finally:
303 finally:
304 UserModel().delete(uid)
304 UserModel().delete(uid)
305 Session().commit()
305 Session().commit()
306
306
307 def test_revoke_perm_create_repo(self):
307 def test_revoke_perm_create_repo(self):
308 self.log_user()
308 self.log_user()
309 perm_none = Permission.get_by_key('hg.create.none')
309 perm_none = Permission.get_by_key('hg.create.none')
310 perm_create = Permission.get_by_key('hg.create.repository')
310 perm_create = Permission.get_by_key('hg.create.repository')
311
311
312 user = UserModel().create_or_update(username='dummy', password='qwe',
312 user = UserModel().create_or_update(username='dummy', password='qwe',
313 email='dummy', firstname=u'a',
313 email='dummy', firstname=u'a',
314 lastname=u'b')
314 lastname=u'b')
315 Session().commit()
315 Session().commit()
316 uid = user.user_id
316 uid = user.user_id
317
317
318 try:
318 try:
319 # User should have None permission on creation repository
319 # User should have None permission on creation repository
320 assert UserModel().has_perm(user, perm_none) == False
320 assert UserModel().has_perm(user, perm_none) == False
321 assert UserModel().has_perm(user, perm_create) == False
321 assert UserModel().has_perm(user, perm_create) == False
322
322
323 response = self.app.post(url('edit_user_perms_update', id=uid),
323 response = self.app.post(url('edit_user_perms_update', id=uid),
324 params=dict(_authentication_token=self.authentication_token()))
324 params=dict(_authentication_token=self.authentication_token()))
325
325
326 perm_none = Permission.get_by_key('hg.create.none')
326 perm_none = Permission.get_by_key('hg.create.none')
327 perm_create = Permission.get_by_key('hg.create.repository')
327 perm_create = Permission.get_by_key('hg.create.repository')
328
328
329 # User should have None permission on creation repository
329 # User should have None permission on creation repository
330 assert UserModel().has_perm(uid, perm_none) == True
330 assert UserModel().has_perm(uid, perm_none) == True
331 assert UserModel().has_perm(uid, perm_create) == False
331 assert UserModel().has_perm(uid, perm_create) == False
332 finally:
332 finally:
333 UserModel().delete(uid)
333 UserModel().delete(uid)
334 Session().commit()
334 Session().commit()
335
335
336 def test_add_perm_fork_repo(self):
336 def test_add_perm_fork_repo(self):
337 self.log_user()
337 self.log_user()
338 perm_none = Permission.get_by_key('hg.fork.none')
338 perm_none = Permission.get_by_key('hg.fork.none')
339 perm_fork = Permission.get_by_key('hg.fork.repository')
339 perm_fork = Permission.get_by_key('hg.fork.repository')
340
340
341 user = UserModel().create_or_update(username='dummy', password='qwe',
341 user = UserModel().create_or_update(username='dummy', password='qwe',
342 email='dummy', firstname=u'a',
342 email='dummy', firstname=u'a',
343 lastname=u'b')
343 lastname=u'b')
344 Session().commit()
344 Session().commit()
345 uid = user.user_id
345 uid = user.user_id
346
346
347 try:
347 try:
348 # User should have None permission on creation repository
348 # User should have None permission on creation repository
349 assert UserModel().has_perm(user, perm_none) == False
349 assert UserModel().has_perm(user, perm_none) == False
350 assert UserModel().has_perm(user, perm_fork) == False
350 assert UserModel().has_perm(user, perm_fork) == False
351
351
352 response = self.app.post(url('edit_user_perms_update', id=uid),
352 response = self.app.post(url('edit_user_perms_update', id=uid),
353 params=dict(create_repo_perm=True,
353 params=dict(create_repo_perm=True,
354 _authentication_token=self.authentication_token()))
354 _authentication_token=self.authentication_token()))
355
355
356 perm_none = Permission.get_by_key('hg.create.none')
356 perm_none = Permission.get_by_key('hg.create.none')
357 perm_create = Permission.get_by_key('hg.create.repository')
357 perm_create = Permission.get_by_key('hg.create.repository')
358
358
359 # User should have None permission on creation repository
359 # User should have None permission on creation repository
360 assert UserModel().has_perm(uid, perm_none) == False
360 assert UserModel().has_perm(uid, perm_none) == False
361 assert UserModel().has_perm(uid, perm_create) == True
361 assert UserModel().has_perm(uid, perm_create) == True
362 finally:
362 finally:
363 UserModel().delete(uid)
363 UserModel().delete(uid)
364 Session().commit()
364 Session().commit()
365
365
366 def test_revoke_perm_fork_repo(self):
366 def test_revoke_perm_fork_repo(self):
367 self.log_user()
367 self.log_user()
368 perm_none = Permission.get_by_key('hg.fork.none')
368 perm_none = Permission.get_by_key('hg.fork.none')
369 perm_fork = Permission.get_by_key('hg.fork.repository')
369 perm_fork = Permission.get_by_key('hg.fork.repository')
370
370
371 user = UserModel().create_or_update(username='dummy', password='qwe',
371 user = UserModel().create_or_update(username='dummy', password='qwe',
372 email='dummy', firstname=u'a',
372 email='dummy', firstname=u'a',
373 lastname=u'b')
373 lastname=u'b')
374 Session().commit()
374 Session().commit()
375 uid = user.user_id
375 uid = user.user_id
376
376
377 try:
377 try:
378 # User should have None permission on creation repository
378 # User should have None permission on creation repository
379 assert UserModel().has_perm(user, perm_none) == False
379 assert UserModel().has_perm(user, perm_none) == False
380 assert UserModel().has_perm(user, perm_fork) == False
380 assert UserModel().has_perm(user, perm_fork) == False
381
381
382 response = self.app.post(url('edit_user_perms_update', id=uid),
382 response = self.app.post(url('edit_user_perms_update', id=uid),
383 params=dict(_authentication_token=self.authentication_token()))
383 params=dict(_authentication_token=self.authentication_token()))
384
384
385 perm_none = Permission.get_by_key('hg.create.none')
385 perm_none = Permission.get_by_key('hg.create.none')
386 perm_create = Permission.get_by_key('hg.create.repository')
386 perm_create = Permission.get_by_key('hg.create.repository')
387
387
388 # User should have None permission on creation repository
388 # User should have None permission on creation repository
389 assert UserModel().has_perm(uid, perm_none) == True
389 assert UserModel().has_perm(uid, perm_none) == True
390 assert UserModel().has_perm(uid, perm_create) == False
390 assert UserModel().has_perm(uid, perm_create) == False
391 finally:
391 finally:
392 UserModel().delete(uid)
392 UserModel().delete(uid)
393 Session().commit()
393 Session().commit()
394
394
395 def test_ips(self):
395 def test_ips(self):
396 self.log_user()
396 self.log_user()
397 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
397 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
398 response = self.app.get(url('edit_user_ips', id=user.user_id))
398 response = self.app.get(url('edit_user_ips', id=user.user_id))
399 response.mustcontain('All IP addresses are allowed')
399 response.mustcontain('All IP addresses are allowed')
400
400
401 @parametrize('test_name,ip,ip_range,failure', [
401 @parametrize('test_name,ip,ip_range,failure', [
402 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
402 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
403 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
403 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
404 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
404 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
405 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
405 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
406 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
406 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
407 ('127_bad_ip', 'foobar', 'foobar', True),
407 ('127_bad_ip', 'foobar', 'foobar', True),
408 ])
408 ])
409 def test_add_ip(self, test_name, ip, ip_range, failure, auto_clear_ip_permissions):
409 def test_add_ip(self, test_name, ip, ip_range, failure, auto_clear_ip_permissions):
410 self.log_user()
410 self.log_user()
411 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
411 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
412 user_id = user.user_id
412 user_id = user.user_id
413
413
414 response = self.app.post(url('edit_user_ips_update', id=user_id),
414 response = self.app.post(url('edit_user_ips_update', id=user_id),
415 params=dict(new_ip=ip, _authentication_token=self.authentication_token()))
415 params=dict(new_ip=ip, _authentication_token=self.authentication_token()))
416
416
417 if failure:
417 if failure:
418 self.checkSessionFlash(response, 'Please enter a valid IPv4 or IPv6 address')
418 self.checkSessionFlash(response, 'Please enter a valid IPv4 or IPv6 address')
419 response = self.app.get(url('edit_user_ips', id=user_id))
419 response = self.app.get(url('edit_user_ips', id=user_id))
420 response.mustcontain(no=[ip])
420 response.mustcontain(no=[ip])
421 response.mustcontain(no=[ip_range])
421 response.mustcontain(no=[ip_range])
422
422
423 else:
423 else:
424 response = self.app.get(url('edit_user_ips', id=user_id))
424 response = self.app.get(url('edit_user_ips', id=user_id))
425 response.mustcontain(ip)
425 response.mustcontain(ip)
426 response.mustcontain(ip_range)
426 response.mustcontain(ip_range)
427
427
428 def test_delete_ip(self, auto_clear_ip_permissions):
428 def test_delete_ip(self, auto_clear_ip_permissions):
429 self.log_user()
429 self.log_user()
430 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
430 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
431 user_id = user.user_id
431 user_id = user.user_id
432 ip = '127.0.0.1/32'
432 ip = '127.0.0.1/32'
433 ip_range = '127.0.0.1 - 127.0.0.1'
433 ip_range = '127.0.0.1 - 127.0.0.1'
434 with test_context(self.app):
434 with test_context(self.app):
435 new_ip = UserModel().add_extra_ip(user_id, ip)
435 new_ip = UserModel().add_extra_ip(user_id, ip)
436 Session().commit()
436 Session().commit()
437 new_ip_id = new_ip.ip_id
437 new_ip_id = new_ip.ip_id
438
438
439 response = self.app.get(url('edit_user_ips', id=user_id))
439 response = self.app.get(url('edit_user_ips', id=user_id))
440 response.mustcontain(ip)
440 response.mustcontain(ip)
441 response.mustcontain(ip_range)
441 response.mustcontain(ip_range)
442
442
443 self.app.post(url('edit_user_ips_delete', id=user_id),
443 self.app.post(url('edit_user_ips_delete', id=user_id),
444 params=dict(del_ip_id=new_ip_id, _authentication_token=self.authentication_token()))
444 params=dict(del_ip_id=new_ip_id, _authentication_token=self.authentication_token()))
445
445
446 response = self.app.get(url('edit_user_ips', id=user_id))
446 response = self.app.get(url('edit_user_ips', id=user_id))
447 response.mustcontain('All IP addresses are allowed')
447 response.mustcontain('All IP addresses are allowed')
448 response.mustcontain(no=[ip])
448 response.mustcontain(no=[ip])
449 response.mustcontain(no=[ip_range])
449 response.mustcontain(no=[ip_range])
450
450
451 def test_api_keys(self):
451 def test_api_keys(self):
452 self.log_user()
452 self.log_user()
453
453
454 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
454 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
455 response = self.app.get(url('edit_user_api_keys', id=user.user_id))
455 response = self.app.get(url('edit_user_api_keys', id=user.user_id))
456 response.mustcontain(user.api_key)
456 response.mustcontain(user.api_key)
457 response.mustcontain('Expires: Never')
457 response.mustcontain('Expires: Never')
458
458
459 @parametrize('desc,lifetime', [
459 @parametrize('desc,lifetime', [
460 ('forever', -1),
460 ('forever', -1),
461 ('5mins', 60*5),
461 ('5mins', 60*5),
462 ('30days', 60*60*24*30),
462 ('30days', 60*60*24*30),
463 ])
463 ])
464 def test_add_api_keys(self, desc, lifetime):
464 def test_add_api_keys(self, desc, lifetime):
465 self.log_user()
465 self.log_user()
466 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
466 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
467 user_id = user.user_id
467 user_id = user.user_id
468
468
469 response = self.app.post(url('edit_user_api_keys_update', id=user_id),
469 response = self.app.post(url('edit_user_api_keys_update', id=user_id),
470 {'description': desc, 'lifetime': lifetime, '_authentication_token': self.authentication_token()})
470 {'description': desc, 'lifetime': lifetime, '_authentication_token': self.authentication_token()})
471 self.checkSessionFlash(response, 'API key successfully created')
471 self.checkSessionFlash(response, 'API key successfully created')
472 try:
472 try:
473 response = response.follow()
473 response = response.follow()
474 user = User.get(user_id)
474 user = User.get(user_id)
475 for api_key in user.api_keys:
475 for api_key in user.api_keys:
476 response.mustcontain(api_key)
476 response.mustcontain(api_key)
477 finally:
477 finally:
478 for api_key in UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all():
478 for api_key in UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all():
479 Session().delete(api_key)
479 Session().delete(api_key)
480 Session().commit()
480 Session().commit()
481
481
482 def test_remove_api_key(self):
482 def test_remove_api_key(self):
483 self.log_user()
483 self.log_user()
484 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
484 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
485 user_id = user.user_id
485 user_id = user.user_id
486
486
487 response = self.app.post(url('edit_user_api_keys_update', id=user_id),
487 response = self.app.post(url('edit_user_api_keys_update', id=user_id),
488 {'description': 'desc', 'lifetime': -1, '_authentication_token': self.authentication_token()})
488 {'description': 'desc', 'lifetime': -1, '_authentication_token': self.authentication_token()})
489 self.checkSessionFlash(response, 'API key successfully created')
489 self.checkSessionFlash(response, 'API key successfully created')
490 response = response.follow()
490 response = response.follow()
491
491
492 # now delete our key
492 # now delete our key
493 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
493 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
494 assert 1 == len(keys)
494 assert 1 == len(keys)
495
495
496 response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
496 response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
497 {'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
497 {'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
498 self.checkSessionFlash(response, 'API key successfully deleted')
498 self.checkSessionFlash(response, 'API key successfully deleted')
499 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
499 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
500 assert 0 == len(keys)
500 assert 0 == len(keys)
501
501
502 def test_reset_main_api_key(self):
502 def test_reset_main_api_key(self):
503 self.log_user()
503 self.log_user()
504 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
504 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
505 user_id = user.user_id
505 user_id = user.user_id
506 api_key = user.api_key
506 api_key = user.api_key
507 response = self.app.get(url('edit_user_api_keys', id=user_id))
507 response = self.app.get(url('edit_user_api_keys', id=user_id))
508 response.mustcontain(api_key)
508 response.mustcontain(api_key)
509 response.mustcontain('Expires: Never')
509 response.mustcontain('Expires: Never')
510
510
511 response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
511 response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
512 {'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
512 {'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
513 self.checkSessionFlash(response, 'API key successfully reset')
513 self.checkSessionFlash(response, 'API key successfully reset')
514 response = response.follow()
514 response = response.follow()
515 response.mustcontain(no=[api_key])
515 response.mustcontain(no=[api_key])
516
516
517 def test_add_ssh_key(self):
518 description = u'something'
519 public_key = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== me@localhost'
520 fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8'
521
522 self.log_user()
523 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
524 user_id = user.user_id
525
526 response = self.app.post(url('edit_user_ssh_keys', id=user_id),
527 {'description': description,
528 'public_key': public_key,
529 '_authentication_token': self.authentication_token()})
530 self.checkSessionFlash(response, 'SSH key %s successfully added' % fingerprint)
531
532 response = response.follow()
533 response.mustcontain(fingerprint)
534 ssh_key = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).one()
535 assert ssh_key.fingerprint == fingerprint
536 assert ssh_key.description == description
537 Session().delete(ssh_key)
538 Session().commit()
539
540 def test_remove_ssh_key(self):
541 description = u''
542 public_key = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== me@localhost'
543 fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8'
544
545 self.log_user()
546 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
547 user_id = user.user_id
548
549 response = self.app.post(url('edit_user_ssh_keys', id=user_id),
550 {'description': description,
551 'public_key': public_key,
552 '_authentication_token': self.authentication_token()})
553 self.checkSessionFlash(response, 'SSH key %s successfully added' % fingerprint)
554 response.follow()
555 ssh_key = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).one()
556 assert ssh_key.description == description
557
558 response = self.app.post(url('edit_user_ssh_keys_delete', id=user_id),
559 {'del_public_key': ssh_key.public_key,
560 '_authentication_token': self.authentication_token()})
561 self.checkSessionFlash(response, 'SSH key successfully deleted')
562 keys = UserSshKeys.query().all()
563 assert 0 == len(keys)
564
517
565
518 class TestAdminUsersController_unittest(TestController):
566 class TestAdminUsersController_unittest(TestController):
519 """ Unit tests for the users controller """
567 """ Unit tests for the users controller """
520
568
521 def test_get_user_or_raise_if_default(self, monkeypatch, test_context_fixture):
569 def test_get_user_or_raise_if_default(self, monkeypatch, test_context_fixture):
522 # flash complains about an non-existing session
570 # flash complains about an non-existing session
523 def flash_mock(*args, **kwargs):
571 def flash_mock(*args, **kwargs):
524 pass
572 pass
525 monkeypatch.setattr(h, 'flash', flash_mock)
573 monkeypatch.setattr(h, 'flash', flash_mock)
526
574
527 u = UsersController()
575 u = UsersController()
528 # a regular user should work correctly
576 # a regular user should work correctly
529 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
577 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
530 assert u._get_user_or_raise_if_default(user.user_id) == user
578 assert u._get_user_or_raise_if_default(user.user_id) == user
531 # the default user should raise
579 # the default user should raise
532 with pytest.raises(HTTPNotFound):
580 with pytest.raises(HTTPNotFound):
533 u._get_user_or_raise_if_default(User.get_default_user().user_id)
581 u._get_user_or_raise_if_default(User.get_default_user().user_id)
534
582
535
583
536 class TestAdminUsersControllerForDefaultUser(TestController):
584 class TestAdminUsersControllerForDefaultUser(TestController):
537 """
585 """
538 Edit actions on the default user are not allowed.
586 Edit actions on the default user are not allowed.
539 Validate that they throw a 404 exception.
587 Validate that they throw a 404 exception.
540 """
588 """
541 def test_edit_default_user(self):
589 def test_edit_default_user(self):
542 self.log_user()
590 self.log_user()
543 user = User.get_default_user()
591 user = User.get_default_user()
544 response = self.app.get(url('edit_user', id=user.user_id), status=404)
592 response = self.app.get(url('edit_user', id=user.user_id), status=404)
545
593
546 def test_edit_advanced_default_user(self):
594 def test_edit_advanced_default_user(self):
547 self.log_user()
595 self.log_user()
548 user = User.get_default_user()
596 user = User.get_default_user()
549 response = self.app.get(url('edit_user_advanced', id=user.user_id), status=404)
597 response = self.app.get(url('edit_user_advanced', id=user.user_id), status=404)
550
598
551 # API keys
599 # API keys
552 def test_edit_api_keys_default_user(self):
600 def test_edit_api_keys_default_user(self):
553 self.log_user()
601 self.log_user()
554 user = User.get_default_user()
602 user = User.get_default_user()
555 response = self.app.get(url('edit_user_api_keys', id=user.user_id), status=404)
603 response = self.app.get(url('edit_user_api_keys', id=user.user_id), status=404)
556
604
557 def test_add_api_keys_default_user(self):
605 def test_add_api_keys_default_user(self):
558 self.log_user()
606 self.log_user()
559 user = User.get_default_user()
607 user = User.get_default_user()
560 response = self.app.post(url('edit_user_api_keys_update', id=user.user_id),
608 response = self.app.post(url('edit_user_api_keys_update', id=user.user_id),
561 {'_authentication_token': self.authentication_token()}, status=404)
609 {'_authentication_token': self.authentication_token()}, status=404)
562
610
563 def test_delete_api_keys_default_user(self):
611 def test_delete_api_keys_default_user(self):
564 self.log_user()
612 self.log_user()
565 user = User.get_default_user()
613 user = User.get_default_user()
566 response = self.app.post(url('edit_user_api_keys_delete', id=user.user_id),
614 response = self.app.post(url('edit_user_api_keys_delete', id=user.user_id),
567 {'_authentication_token': self.authentication_token()}, status=404)
615 {'_authentication_token': self.authentication_token()}, status=404)
568
616
569 # Permissions
617 # Permissions
570 def test_edit_perms_default_user(self):
618 def test_edit_perms_default_user(self):
571 self.log_user()
619 self.log_user()
572 user = User.get_default_user()
620 user = User.get_default_user()
573 response = self.app.get(url('edit_user_perms', id=user.user_id), status=404)
621 response = self.app.get(url('edit_user_perms', id=user.user_id), status=404)
574
622
575 def test_update_perms_default_user(self):
623 def test_update_perms_default_user(self):
576 self.log_user()
624 self.log_user()
577 user = User.get_default_user()
625 user = User.get_default_user()
578 response = self.app.post(url('edit_user_perms_update', id=user.user_id),
626 response = self.app.post(url('edit_user_perms_update', id=user.user_id),
579 {'_authentication_token': self.authentication_token()}, status=404)
627 {'_authentication_token': self.authentication_token()}, status=404)
580
628
581 # Emails
629 # Emails
582 def test_edit_emails_default_user(self):
630 def test_edit_emails_default_user(self):
583 self.log_user()
631 self.log_user()
584 user = User.get_default_user()
632 user = User.get_default_user()
585 response = self.app.get(url('edit_user_emails', id=user.user_id), status=404)
633 response = self.app.get(url('edit_user_emails', id=user.user_id), status=404)
586
634
587 def test_add_emails_default_user(self):
635 def test_add_emails_default_user(self):
588 self.log_user()
636 self.log_user()
589 user = User.get_default_user()
637 user = User.get_default_user()
590 response = self.app.post(url('edit_user_emails_update', id=user.user_id),
638 response = self.app.post(url('edit_user_emails_update', id=user.user_id),
591 {'_authentication_token': self.authentication_token()}, status=404)
639 {'_authentication_token': self.authentication_token()}, status=404)
592
640
593 def test_delete_emails_default_user(self):
641 def test_delete_emails_default_user(self):
594 self.log_user()
642 self.log_user()
595 user = User.get_default_user()
643 user = User.get_default_user()
596 response = self.app.post(url('edit_user_emails_delete', id=user.user_id),
644 response = self.app.post(url('edit_user_emails_delete', id=user.user_id),
597 {'_authentication_token': self.authentication_token()}, status=404)
645 {'_authentication_token': self.authentication_token()}, status=404)
598
646
599 # IP addresses
647 # IP addresses
600 # Add/delete of IP addresses for the default user is used to maintain
648 # Add/delete of IP addresses for the default user is used to maintain
601 # the global IP whitelist and thus allowed. Only 'edit' is forbidden.
649 # the global IP whitelist and thus allowed. Only 'edit' is forbidden.
602 def test_edit_ip_default_user(self):
650 def test_edit_ip_default_user(self):
603 self.log_user()
651 self.log_user()
604 user = User.get_default_user()
652 user = User.get_default_user()
605 response = self.app.get(url('edit_user_ips', id=user.user_id), status=404)
653 response = self.app.get(url('edit_user_ips', id=user.user_id), status=404)
@@ -1,96 +1,97 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Some committers are so wrong that it doesn't point at any contributor:
3 # Some committers are so wrong that it doesn't point at any contributor:
4 total_ignore = set()
4 total_ignore = set()
5 total_ignore.add('*** failed to import extension hggit: No module named hggit')
5 total_ignore.add('*** failed to import extension hggit: No module named hggit')
6 total_ignore.add('<>')
6 total_ignore.add('<>')
7
7
8 # Normalize some committer names where people have contributed under different
8 # Normalize some committer names where people have contributed under different
9 # names or email addresses:
9 # names or email addresses:
10 name_fixes = {}
10 name_fixes = {}
11 name_fixes['Andrew Shadura'] = "Andrej Shadura <andrew@shadura.me>"
11 name_fixes['Andrew Shadura'] = "Andrej Shadura <andrew@shadura.me>"
12 name_fixes['aparkar'] = "Aparkar <aparkar@icloud.com>"
12 name_fixes['aparkar'] = "Aparkar <aparkar@icloud.com>"
13 name_fixes['Aras Pranckevicius'] = "Aras Pranckevičius <aras@unity3d.com>"
13 name_fixes['Aras Pranckevicius'] = "Aras Pranckevičius <aras@unity3d.com>"
14 name_fixes['Augosto Hermann'] = "Augusto Herrmann <augusto.herrmann@planejamento.gov.br>"
14 name_fixes['Augosto Hermann'] = "Augusto Herrmann <augusto.herrmann@planejamento.gov.br>"
15 name_fixes['"Bradley M. Kuhn" <bkuhn@ebb.org>'] = "Bradley M. Kuhn <bkuhn@sfconservancy.org>"
15 name_fixes['"Bradley M. Kuhn" <bkuhn@ebb.org>'] = "Bradley M. Kuhn <bkuhn@sfconservancy.org>"
16 name_fixes['dmitri.kuznetsov'] = "Dmitri Kuznetsov"
16 name_fixes['dmitri.kuznetsov'] = "Dmitri Kuznetsov"
17 name_fixes['Dmitri Kuznetsov'] = "Dmitri Kuznetsov"
17 name_fixes['Dmitri Kuznetsov'] = "Dmitri Kuznetsov"
18 name_fixes['domruf'] = "Dominik Ruf <dominikruf@gmail.com>"
18 name_fixes['domruf'] = "Dominik Ruf <dominikruf@gmail.com>"
19 name_fixes['Ingo von borstel'] = "Ingo von Borstel <kallithea@planetmaker.de>"
19 name_fixes['Ingo von borstel'] = "Ingo von Borstel <kallithea@planetmaker.de>"
20 name_fixes['Jan Heylen'] = "Jan Heylen <heyleke@gmail.com>"
20 name_fixes['Jan Heylen'] = "Jan Heylen <heyleke@gmail.com>"
21 name_fixes['Jason F. Harris'] = "Jason Harris <jason@jasonfharris.com>"
21 name_fixes['Jason F. Harris'] = "Jason Harris <jason@jasonfharris.com>"
22 name_fixes['Jelmer Vernooij'] = "Jelmer Vernooij <jelmer@samba.org>"
22 name_fixes['Jelmer Vernooij'] = "Jelmer Vernooij <jelmer@samba.org>"
23 name_fixes['jfh <jason@jasonfharris.com>'] = "Jason Harris <jason@jasonfharris.com>"
23 name_fixes['jfh <jason@jasonfharris.com>'] = "Jason Harris <jason@jasonfharris.com>"
24 name_fixes['Leonardo Carneiro<leonardo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
24 name_fixes['Leonardo Carneiro<leonardo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
25 name_fixes['leonardo'] = "Leonardo Carneiro <leonardo@unity3d.com>"
25 name_fixes['leonardo'] = "Leonardo Carneiro <leonardo@unity3d.com>"
26 name_fixes['Leonardo <leo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
26 name_fixes['Leonardo <leo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
27 name_fixes['Les Peabody'] = "Les Peabody <lpeabody@gmail.com>"
27 name_fixes['Les Peabody'] = "Les Peabody <lpeabody@gmail.com>"
28 name_fixes['"Lorenzo M. Catucci" <lorenzo@sancho.ccd.uniroma2.it>'] = "Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>"
28 name_fixes['"Lorenzo M. Catucci" <lorenzo@sancho.ccd.uniroma2.it>'] = "Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>"
29 name_fixes['Lukasz Balcerzak'] = "Łukasz Balcerzak <lukaszbalcerzak@gmail.com>"
29 name_fixes['Lukasz Balcerzak'] = "Łukasz Balcerzak <lukaszbalcerzak@gmail.com>"
30 name_fixes['mao <mao@lins.fju.edu.tw>'] = "Ching-Chen Mao <mao@lins.fju.edu.tw>"
30 name_fixes['mao <mao@lins.fju.edu.tw>'] = "Ching-Chen Mao <mao@lins.fju.edu.tw>"
31 name_fixes['marcink'] = "Marcin Kuźmiński <marcin@python-works.com>"
31 name_fixes['marcink'] = "Marcin Kuźmiński <marcin@python-works.com>"
32 name_fixes['Marcin Kuzminski'] = "Marcin Kuźmiński <marcin@python-works.com>"
32 name_fixes['Marcin Kuzminski'] = "Marcin Kuźmiński <marcin@python-works.com>"
33 name_fixes['nansenat16@null.tw'] = "nansenat16 <nansenat16@null.tw>"
33 name_fixes['nansenat16@null.tw'] = "nansenat16 <nansenat16@null.tw>"
34 name_fixes['Peter Vitt'] = "Peter Vitt <petervitt@web.de>"
34 name_fixes['Peter Vitt'] = "Peter Vitt <petervitt@web.de>"
35 name_fixes['philip.j@hostdime.com'] = "Philip Jameson <philip.j@hostdime.com>"
35 name_fixes['philip.j@hostdime.com'] = "Philip Jameson <philip.j@hostdime.com>"
36 name_fixes['Søren Løvborg'] = "Søren Løvborg <sorenl@unity3d.com>"
36 name_fixes['Søren Løvborg'] = "Søren Løvborg <sorenl@unity3d.com>"
37 name_fixes['Thomas De Schampheleire'] = "Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>"
37 name_fixes['Thomas De Schampheleire'] = "Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>"
38 name_fixes['Hosted Weblate'] = "<>"
38 name_fixes['Hosted Weblate'] = "<>"
39 name_fixes['Weblate'] = "<>"
39 name_fixes['Weblate'] = "<>"
40 name_fixes['xpol'] = "xpol <xpolife@gmail.com>"
40 name_fixes['xpol'] = "xpol <xpolife@gmail.com>"
41 name_fixes['Lars <devel@sumpfralle.de>'] = "Lars Kruse <devel@sumpfralle.de>"
41 name_fixes['Lars <devel@sumpfralle.de>'] = "Lars Kruse <devel@sumpfralle.de>"
42
42
43 # Some committer email address domains that indicate that another entity might
43 # Some committer email address domains that indicate that another entity might
44 # hold some copyright too:
44 # hold some copyright too:
45 domain_extra = {}
45 domain_extra = {}
46 domain_extra['unity3d.com'] = "Unity Technologies"
46 domain_extra['unity3d.com'] = "Unity Technologies"
47 domain_extra['rhodecode.com'] = "RhodeCode GmbH"
47 domain_extra['rhodecode.com'] = "RhodeCode GmbH"
48
48
49 # Repository history show some old contributions that traditionally hasn't been
49 # Repository history show some old contributions that traditionally hasn't been
50 # listed in about.html - preserve that:
50 # listed in about.html - preserve that:
51 no_about = set(total_ignore)
51 no_about = set(total_ignore)
52 # The following contributors were traditionally not listed in about.html and it
52 # The following contributors were traditionally not listed in about.html and it
53 # seems unclear if the copyright is personal or belongs to a company.
53 # seems unclear if the copyright is personal or belongs to a company.
54 no_about.add(('Thayne Harbaugh <thayne@fusionio.com>', '2011'))
54 no_about.add(('Thayne Harbaugh <thayne@fusionio.com>', '2011'))
55 no_about.add(('Dies Koper <diesk@fast.au.fujitsu.com>', '2012'))
55 no_about.add(('Dies Koper <diesk@fast.au.fujitsu.com>', '2012'))
56 no_about.add(('Erwin Kroon <e.kroon@smartmetersolutions.nl>', '2012'))
56 no_about.add(('Erwin Kroon <e.kroon@smartmetersolutions.nl>', '2012'))
57 no_about.add(('Vincent Caron <vcaron@bearstech.com>', '2012'))
57 no_about.add(('Vincent Caron <vcaron@bearstech.com>', '2012'))
58 # These contributors' contributions might be too small to be copyrightable:
58 # These contributors' contributions might be too small to be copyrightable:
59 no_about.add(('philip.j@hostdime.com', '2012'))
59 no_about.add(('philip.j@hostdime.com', '2012'))
60 no_about.add(('Stefan Engel <mail@engel-stefan.de>', '2012'))
60 no_about.add(('Stefan Engel <mail@engel-stefan.de>', '2012'))
61 no_about.add(('Ton Plomp <tcplomp@gmail.com>', '2013'))
61 no_about.add(('Ton Plomp <tcplomp@gmail.com>', '2013'))
62 # Was reworked and contributed later and shadowed by other contributions:
62 # Was reworked and contributed later and shadowed by other contributions:
63 no_about.add(('Sean Farley <sean.michael.farley@gmail.com>', '2013'))
63 no_about.add(('Sean Farley <sean.michael.farley@gmail.com>', '2013'))
64
64
65 # Contributors in about.html and CONTRIBUTORS not appearing in repository
65 # Contributors in about.html and CONTRIBUTORS not appearing in repository
66 # history:
66 # history:
67 other = [
67 other = [
68 # Work folded into commits attributed to others:
68 # Work folded into commits attributed to others:
69 ('2013', 'Ilya Beda <ir4y.ix@gmail.com>'),
69 ]
70 ]
70
71
71 # Preserve contributors listed in about.html but not appearing in repository
72 # Preserve contributors listed in about.html but not appearing in repository
72 # history:
73 # history:
73 other_about = [
74 other_about = [
74 ("2011", "Aparkar <aparkar@icloud.com>"),
75 ("2011", "Aparkar <aparkar@icloud.com>"),
75 ("2010", "RhodeCode GmbH"),
76 ("2010", "RhodeCode GmbH"),
76 ("2011", "RhodeCode GmbH"),
77 ("2011", "RhodeCode GmbH"),
77 ("2012", "RhodeCode GmbH"),
78 ("2012", "RhodeCode GmbH"),
78 ("2013", "RhodeCode GmbH"),
79 ("2013", "RhodeCode GmbH"),
79 ]
80 ]
80
81
81 # Preserve contributors listed in CONTRIBUTORS but not appearing in repository
82 # Preserve contributors listed in CONTRIBUTORS but not appearing in repository
82 # history:
83 # history:
83 other_contributors = [
84 other_contributors = [
84 ("", "Andrew Kesterson <andrew@aklabs.net>"),
85 ("", "Andrew Kesterson <andrew@aklabs.net>"),
85 ("", "cejones"),
86 ("", "cejones"),
86 ("", "David A. Sjøen <david.sjoen@westcon.no>"),
87 ("", "David A. Sjøen <david.sjoen@westcon.no>"),
87 ("", "James Rhodes <jrhodes@redpointsoftware.com.au>"),
88 ("", "James Rhodes <jrhodes@redpointsoftware.com.au>"),
88 ("", "Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>"),
89 ("", "Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>"),
89 ("", "larikale"),
90 ("", "larikale"),
90 ("", "RhodeCode GmbH"),
91 ("", "RhodeCode GmbH"),
91 ("", "Sebastian Kreutzberger <sebastian@rhodecode.com>"),
92 ("", "Sebastian Kreutzberger <sebastian@rhodecode.com>"),
92 ("", "Steve Romanow <slestak989@gmail.com>"),
93 ("", "Steve Romanow <slestak989@gmail.com>"),
93 ("", "SteveCohen"),
94 ("", "SteveCohen"),
94 ("", "Thomas <thomas@rhodecode.com>"),
95 ("", "Thomas <thomas@rhodecode.com>"),
95 ("", "Thomas Waldmann <tw-public@gmx.de>"),
96 ("", "Thomas Waldmann <tw-public@gmx.de>"),
96 ]
97 ]
General Comments 0
You need to be logged in to leave comments. Login now